113 $ce =
new self(
$text );
116 return $e->getMessage() .
"\n" .
$e->highlight(
$text );
169 array(
'copy', 0, strlen( $this->
text ) )
171 foreach ( $ops
as $op ) {
174 $value = isset( $op[
'value'] ) ? $op[
'value'] :
null;
175 $key = isset( $op[
'key'] ) ? $op[
'key'] :
null;
183 if ( isset( $this->pathInfo[
$path] ) ) {
190 $slashPos = strrpos(
$path,
'/' );
191 $key = var_export( substr(
$path, $slashPos + 1 ),
true );
197 if ( $lastEltPath ===
false ) {
198 throw new MWException(
"Can't find any element of array \"$path\"" );
200 $lastEltInfo = $this->pathInfo[$lastEltPath];
203 if ( strpos( $lastEltPath,
'@extra' ) ===
false && !$lastEltInfo[
'hasComma'] ) {
212 if ( $key ===
null ) {
214 $textToInsert =
"$indent$value,";
216 list( $indent, $arrowIndent ) =
217 $this->
getIndent( $start, $key, $lastEltInfo[
'arrowByte'] );
218 $textToInsert =
"$indent$key$arrowIndent=> $value,";
220 $textToInsert .= ( $indent ===
false ?
' ' :
"\n" );
228 if ( $firstEltPath ===
false ) {
229 throw new MWException(
"Can't find array element of \"$path\"" );
232 $info = $this->pathInfo[$firstEltPath];
235 if ( $key ===
null ) {
237 $textToInsert =
"$indent$value,";
239 list( $indent, $arrowIndent ) =
240 $this->
getIndent( $start, $key, $info[
'arrowByte'] );
241 $textToInsert =
"$indent$key$arrowIndent=> $value,";
243 $textToInsert .= ( $indent ===
false ?
' ' :
"\n" );
249 throw new MWException(
"Unrecognised operation: \"$type\"" );
255 foreach ( $this->
edits as $edit ) {
256 if ( $edit[0] ==
'copy' ) {
257 $out .= substr( $this->
text, $edit[1], $edit[2] - $edit[1] );
269 "Sorry, ConfEditor broke the file during editing and it won't parse anymore: " .
283 foreach ( $this->pathInfo
as $path => $data ) {
284 if (
$path[0] !=
'$' ) {
287 $trimmedPath = substr(
$path, 1 );
288 $name = $data[
'name'];
289 if (
$name[0] ==
'@' ) {
292 if (
$name[0] ==
'$' ) {
295 $parentPath = substr( $trimmedPath, 0,
296 strlen( $trimmedPath ) - strlen(
$name ) );
297 if ( substr( $parentPath, -1 ) ==
'/' ) {
298 $parentPath = substr( $parentPath, 0, -1 );
301 $value = substr( $this->
text, $data[
'valueStartByte'],
302 $data[
'valueEndByte'] - $data[
'valueStartByte']
321 $pathArr = explode(
'/',
$path );
323 if (
$path !==
'' ) {
324 foreach ( $pathArr
as $p ) {
325 if ( !isset( $target[$p] ) ) {
326 $target[$p] =
array();
328 $target =& $target[$p];
331 if ( !isset( $target[$key] ) ) {
341 if ( $str !==
'' && $str[0] ==
'\'' ) {
346 return strtr( substr( trim( $str ), 1, -1 ),
347 array(
'\\\'' =>
'\'',
'\\\\' =>
'\\' ) );
349 if ( $str !==
'' && $str[0] ==
'"' ) {
354 return stripcslashes( substr( trim( $str ), 1, -1 ) );
356 if ( substr( $str, 0, 4 ) ==
'true' ) {
359 if ( substr( $str, 0, 5 ) ==
'false' ) {
362 if ( substr( $str, 0, 4 ) ==
'null' ) {
379 foreach ( $this->
edits as $edit ) {
380 if ( $edit[0] !==
'copy' ) {
384 $copyStart = $edit[1];
386 if ( $start >= $copyEnd || $end <= $copyStart ) {
391 if ( ( $start < $copyStart && $end > $copyStart )
392 || ( $start < $copyEnd && $end > $copyEnd )
394 throw new MWException(
"Overlapping regions found, can't do the edit" );
397 $newEdits[] =
array(
'copy', $copyStart, $start );
398 if ( $newText !==
false ) {
399 $newEdits[] =
array(
'insert', $newText );
401 $newEdits[] =
array(
'copy', $end, $copyEnd );
403 $this->
edits = $newEdits;
415 if ( !isset( $this->pathInfo[$pathName] ) ) {
416 throw new MWException(
"Can't find path \"$pathName\"" );
418 $path = $this->pathInfo[$pathName];
421 while ( $this->pos !=
$path[
'startToken'] ) {
424 $regionStart =
$path[
'startByte'];
425 for ( $offset = -1; $offset >= -
$this->pos; $offset-- ) {
427 if ( !$token->isSkip() ) {
430 $regionStart =
$path[
'startByte'];
433 $lfPos = strrpos( $token->text,
"\n" );
434 if ( $lfPos ===
false ) {
435 $regionStart -= strlen( $token->text );
438 $regionStart -= strlen( $token->text ) - $lfPos - 1;
443 while ( $this->pos !=
$path[
'endToken'] ) {
446 $regionEnd =
$path[
'endByte'];
447 $count = count( $this->tokens );
450 if ( !$token->isSkip() ) {
453 $lfPos = strpos( $token->text,
"\n" );
454 if ( $lfPos ===
false ) {
455 $regionEnd += strlen( $token->text );
458 $regionEnd += $lfPos + 1;
463 return array( $regionStart, $regionEnd );
477 if ( !isset( $this->pathInfo[$pathName] ) ) {
478 throw new MWException(
"Can't find path \"$pathName\"" );
480 $path = $this->pathInfo[$pathName];
481 if (
$path[
'valueStartByte'] ===
false ||
$path[
'valueEndByte'] ===
false ) {
482 throw new MWException(
"Can't find value region for path \"$pathName\"" );
496 $lastEltPath =
false;
497 foreach ( $this->pathInfo
as $candidatePath => $info ) {
498 $part1 = substr( $candidatePath, 0, strlen(
$path ) + 1 );
499 $part2 = substr( $candidatePath, strlen(
$path ) + 1, 1 );
500 if ( $part2 ==
'@' ) {
502 } elseif ( $part1 ==
"$path/" ) {
503 $lastEltPath = $candidatePath;
504 } elseif ( $lastEltPath !==
false ) {
508 if ( $lastEltPath !==
false ) {
514 foreach ( $this->pathInfo
as $candidatePath => $info ) {
515 $part1 = substr( $candidatePath, 0, strlen(
$path ) + 1 );
516 if ( $part1 ==
"$path/" ) {
517 $extraPath = $candidatePath;
518 } elseif ( $extraPath !==
false ) {
534 foreach ( $this->pathInfo
as $candidatePath => $info ) {
535 $part1 = substr( $candidatePath, 0, strlen(
$path ) + 1 );
536 $part2 = substr( $candidatePath, strlen(
$path ) + 1, 1 );
537 if ( $part1 ==
"$path/" && $part2 !=
'@' ) {
538 return $candidatePath;
543 foreach ( $this->pathInfo
as $candidatePath => $info ) {
544 $part1 = substr( $candidatePath, 0, strlen(
$path ) + 1 );
545 if ( $part1 ==
"$path/" ) {
546 return $candidatePath;
560 if (
$pos == 0 || $this->
text[$pos - 1] ==
"\n" ) {
561 $indentLength = strspn( $this->
text,
" \t", $pos );
562 $indent = substr( $this->
text, $pos, $indentLength );
566 if ( $indent !==
false && $arrowPos !==
false ) {
567 $arrowIndentLength = $arrowPos -
$pos - $indentLength - strlen( $key );
568 if ( $arrowIndentLength > 0 ) {
569 $arrowIndent = str_repeat(
' ', $arrowIndentLength );
573 return array( $indent, $arrowIndent );
583 $this->
pushPath(
'@extra-' . ( $this->serial++ ) );
586 while ( !$token->isEnd() ) {
589 $this->
error(
'internal error: empty state stack' );
594 $this->
expect( T_OPEN_TAG );
596 if ( $token->isEnd() ) {
599 $this->
pushState(
'statement',
'file 2' );
603 if ( $token->isEnd() ) {
606 $this->
pushState(
'statement',
'file 2' );
611 $this->
error(
"Invalid variable name \"{$token->text}\"" );
614 $this->
expect( T_VARIABLE );
616 $arrayAssign =
false;
620 if ( !$token->isScalar() ) {
621 $this->
error(
"expected a string or number for the array key" );
623 if ( $token->type == T_CONSTANT_ENCAPSED_STRING ) {
626 $text = $token->text;
629 $this->
error(
"Invalid associative array name \"$text\"" );
641 if ( $arrayAssign ) {
642 $this->
pushState(
'expression',
'array assign end' );
644 $this->
pushState(
'expression',
'statement end' );
647 case 'array assign end':
648 case 'statement end':
650 if ( $state ==
'array assign end' ) {
655 $this->
nextPath(
'@extra-' . ( $this->serial++ ) );
659 if ( $token->type == T_ARRAY ) {
661 } elseif ( $token->isScalar() ) {
663 } elseif ( $token->type == T_VARIABLE ) {
666 $this->
error(
"expected simple expression" );
675 $this->
pushPath(
'@extra-' . ( $this->serial++ ) );
680 $this->
pushState(
'element',
'array end' );
691 if ( $token->isScalar() && $this->
isAhead( T_DOUBLE_ARROW, 1 ) ) {
693 $this->
pushState(
'assoc-element',
'element end' );
698 $this->
pushState(
'expression',
'element end' );
703 if ( $token->type ==
',' ) {
707 $this->
nextPath(
'@extra-' . ( $this->serial++ ) );
716 } elseif ( $token->type ==
')' ) {
720 $this->
error(
"expected the next array element or the end of the array" );
723 case 'assoc-element':
725 if ( !$token->isScalar() ) {
726 $this->
error(
"expected a string or number for the array key" );
728 if ( $token->type == T_CONSTANT_ENCAPSED_STRING ) {
731 $text = $token->text;
734 $this->
error(
"Invalid associative array name \"$text\"" );
740 $this->
expect( T_DOUBLE_ARROW );
747 if ( count( $this->stateStack ) ) {
748 $this->
error(
'unexpected end of file' );
757 $this->tokens = token_get_all( $this->
text );
758 $this->stateStack =
array();
759 $this->pathStack =
array();
761 $this->pathInfo =
array();
771 if ( $this->pos >= count( $this->tokens ) ) {
785 if ( is_array( $internalToken ) ) {
818 $lfCount = substr_count(
$text,
"\n" );
820 $this->lineNum += $lfCount;
821 $this->colNum = strlen(
$text ) - strrpos(
$text,
"\n" );
823 $this->colNum += strlen(
$text );
825 $this->byteNum += strlen(
$text );
828 $this->
setPos( $this->pos + 1 );
839 $pos = $this->pos + $offset;
840 if (
$pos >= count( $this->tokens ) ||
$pos < 0 ) {
874 function pushState( $nextState, $stateAfterThat =
null ) {
875 if ( $stateAfterThat !==
null ) {
876 $this->stateStack[] = $stateAfterThat;
878 $this->stateStack[] = $nextState;
886 return array_pop( $this->stateStack );
895 return strpos(
$path,
'/' ) ===
false && substr(
$path, 0, 1 ) !=
'@';
919 $this->pathStack[] =
array(
921 'level' => count( $this->pathStack ) + 1,
922 'startByte' => $this->byteNum,
923 'startToken' => $this->pos,
924 'valueStartToken' =>
false,
925 'valueStartByte' =>
false,
926 'valueEndToken' =>
false,
927 'valueEndByte' =>
false,
928 'nextArrayIndex' => 0,
939 array_pop( $this->pathStack );
949 $i = count( $this->pathStack ) - 1;
950 if (
$path ==
'@next' ) {
951 $nextArrayIndex =& $this->pathStack[$i][
'nextArrayIndex'];
952 $this->pathStack[$i][
'name'] = $nextArrayIndex;
955 $this->pathStack[$i][
'name'] =
$path;
957 $this->pathStack[$i] =
959 'startByte' => $this->byteNum,
960 'startToken' => $this->pos,
961 'valueStartToken' =>
false,
962 'valueStartByte' =>
false,
963 'valueEndToken' =>
false,
964 'valueEndByte' =>
false,
966 'arrowByte' =>
false,
967 ) + $this->pathStack[$i];
974 $path =& $this->pathStack[count( $this->pathStack ) - 1];
983 $path =& $this->pathStack[count( $this->pathStack ) - 1];
992 $path =& $this->pathStack[count( $this->pathStack ) - 1];
993 $path[
'hasComma'] =
true;
1000 $path =& $this->pathStack[count( $this->pathStack ) - 1];
1016 if ( is_int(
$type ) ) {
1017 return token_name(
$type );
1032 while ( !$token->isEnd() ) {
1033 if ( $token->isSkip() ) {
1037 } elseif ( $token->type ==
$type ) {
1061 foreach ( $this->tokens
as $token ) {
1063 $out .= sprintf(
"%-28s %s\n",
1065 addcslashes( $obj->text,
"\0..\37" ) );
1067 echo
"<pre>" . htmlspecialchars(
$out ) .
"</pre>";
1078 $this->lineNum =
$editor->lineNum;
1079 $this->colNum =
$editor->colNum;
1080 parent::__construct(
"Parse error on line {$editor->lineNum} " .
1081 "col {$editor->colNum}: $msg" );
1087 if (
$lineNum == $this->lineNum - 1 ) {
1088 return "$line\n" . str_repeat(
' ', $this->colNum - 1 ) .
"^\n";
1106 return new self(
'END',
'' );
1115 return in_array( $this->
type, self::$skipTypes );
1119 return in_array( $this->
type, self::$scalarTypes );
1123 return $this->
type ==
'END';