00001 <?php
00002
00018 class ConfEditor {
00020 var $text;
00021
00023 var $tokens;
00024
00026 var $pos;
00027
00029 var $lineNum;
00030
00032 var $colNum;
00033
00035 var $byteNum;
00036
00038 var $currentToken;
00039
00041 var $prevToken;
00042
00047 var $stateStack;
00048
00049
00066 var $pathStack;
00067
00072 var $pathInfo;
00073
00077 var $serial;
00078
00083 var $edits;
00084
00088 static function test( $text ) {
00089 try {
00090 $ce = new self( $text );
00091 $ce->parse();
00092 } catch ( ConfEditorParseError $e ) {
00093 return $e->getMessage() . "\n" . $e->highlight( $text );
00094 }
00095 return "OK";
00096 }
00097
00101 public function __construct( $text ) {
00102 $this->text = $text;
00103 }
00104
00139 public function edit( $ops ) {
00140 $this->parse();
00141
00142 $this->edits = array(
00143 array( 'copy', 0, strlen( $this->text ) )
00144 );
00145 foreach ( $ops as $op ) {
00146 $type = $op['type'];
00147 $path = $op['path'];
00148 $value = isset( $op['value'] ) ? $op['value'] : null;
00149 $key = isset( $op['key'] ) ? $op['key'] : null;
00150
00151 switch ( $type ) {
00152 case 'delete':
00153 list( $start, $end ) = $this->findDeletionRegion( $path );
00154 $this->replaceSourceRegion( $start, $end, false );
00155 break;
00156 case 'set':
00157 if ( isset( $this->pathInfo[$path] ) ) {
00158 list( $start, $end ) = $this->findValueRegion( $path );
00159 $encValue = $value;
00160 $this->replaceSourceRegion( $start, $end, $encValue );
00161 break;
00162 }
00163
00164 $slashPos = strrpos( $path, '/' );
00165 $key = var_export( substr( $path, $slashPos + 1 ), true );
00166 $path = substr( $path, 0, $slashPos );
00167
00168 case 'append':
00169
00170 $lastEltPath = $this->findLastArrayElement( $path );
00171 if ( $lastEltPath === false ) {
00172 throw new MWException( "Can't find any element of array \"$path\"" );
00173 }
00174 $lastEltInfo = $this->pathInfo[$lastEltPath];
00175
00176
00177 if ( strpos( $lastEltPath, '@extra' ) === false && !$lastEltInfo['hasComma'] ) {
00178
00179 list( $start, $end ) = $this->findValueRegion( $lastEltPath );
00180 $this->replaceSourceRegion( $end - 1, $end - 1, ',' );
00181 }
00182
00183
00184 list( $start, $end ) = $this->findDeletionRegion( $lastEltPath );
00185
00186 if ( $key === null ) {
00187 list( $indent, $arrowIndent ) = $this->getIndent( $start );
00188 $textToInsert = "$indent$value,";
00189 } else {
00190 list( $indent, $arrowIndent ) =
00191 $this->getIndent( $start, $key, $lastEltInfo['arrowByte'] );
00192 $textToInsert = "$indent$key$arrowIndent=> $value,";
00193 }
00194 $textToInsert .= ( $indent === false ? ' ' : "\n" );
00195
00196
00197 $this->replaceSourceRegion( $end, $end, $textToInsert );
00198 break;
00199 case 'insert':
00200
00201 $firstEltPath = $this->findFirstArrayElement( $path );
00202 if ( $firstEltPath === false ) {
00203 throw new MWException( "Can't find array element of \"$path\"" );
00204 }
00205 list( $start, $end ) = $this->findDeletionRegion( $firstEltPath );
00206 $info = $this->pathInfo[$firstEltPath];
00207
00208
00209 if ( $key === null ) {
00210 list( $indent, $arrowIndent ) = $this->getIndent( $start );
00211 $textToInsert = "$indent$value,";
00212 } else {
00213 list( $indent, $arrowIndent ) =
00214 $this->getIndent( $start, $key, $info['arrowByte'] );
00215 $textToInsert = "$indent$key$arrowIndent=> $value,";
00216 }
00217 $textToInsert .= ( $indent === false ? ' ' : "\n" );
00218
00219
00220 $this->replaceSourceRegion( $start, $start, $textToInsert );
00221 break;
00222 default:
00223 throw new MWException( "Unrecognised operation: \"$type\"" );
00224 }
00225 }
00226
00227
00228 $out = '';
00229 foreach ( $this->edits as $edit ) {
00230 if ( $edit[0] == 'copy' ) {
00231 $out .= substr( $this->text, $edit[1], $edit[2] - $edit[1] );
00232 } else {
00233 $out .= $edit[1];
00234 }
00235 }
00236
00237
00238 $this->text = $out;
00239 try {
00240 $this->parse();
00241 } catch ( ConfEditorParseError $e ) {
00242 throw new MWException(
00243 "Sorry, ConfEditor broke the file during editing and it won't parse anymore: " .
00244 $e->getMessage() );
00245 }
00246 return $out;
00247 }
00248
00253 function getVars() {
00254 $vars = array();
00255 $this->parse();
00256 foreach( $this->pathInfo as $path => $data ) {
00257 if ( $path[0] != '$' )
00258 continue;
00259 $trimmedPath = substr( $path, 1 );
00260 $name = $data['name'];
00261 if ( $name[0] == '@' )
00262 continue;
00263 if ( $name[0] == '$' )
00264 $name = substr( $name, 1 );
00265 $parentPath = substr( $trimmedPath, 0,
00266 strlen( $trimmedPath ) - strlen( $name ) );
00267 if( substr( $parentPath, -1 ) == '/' )
00268 $parentPath = substr( $parentPath, 0, -1 );
00269
00270 $value = substr( $this->text, $data['valueStartByte'],
00271 $data['valueEndByte'] - $data['valueStartByte']
00272 );
00273 $this->setVar( $vars, $parentPath, $name,
00274 $this->parseScalar( $value ) );
00275 }
00276 return $vars;
00277 }
00278
00288 function setVar( &$array, $path, $key, $value ) {
00289 $pathArr = explode( '/', $path );
00290 $target =& $array;
00291 if ( $path !== '' ) {
00292 foreach ( $pathArr as $p ) {
00293 if( !isset( $target[$p] ) )
00294 $target[$p] = array();
00295 $target =& $target[$p];
00296 }
00297 }
00298 if ( !isset( $target[$key] ) )
00299 $target[$key] = $value;
00300 }
00301
00306 function parseScalar( $str ) {
00307 if ( $str !== '' && $str[0] == '\'' )
00308
00309
00310
00311
00312 return strtr( substr( trim( $str ), 1, -1 ),
00313 array( '\\\'' => '\'', '\\\\' => '\\' ) );
00314 if ( $str !== '' && @$str[0] == '"' )
00315
00316
00317
00318
00319 return stripcslashes( substr( trim( $str ), 1, -1 ) );
00320 if ( substr( $str, 0, 4 ) == 'true' )
00321 return true;
00322 if ( substr( $str, 0, 5 ) == 'false' )
00323 return false;
00324 if ( substr( $str, 0, 4 ) == 'null' )
00325 return null;
00326
00327
00328 return $str;
00329 }
00330
00335 function replaceSourceRegion( $start, $end, $newText = false ) {
00336
00337
00338 $newEdits = array();
00339 foreach ( $this->edits as $i => $edit ) {
00340 if ( $edit[0] !== 'copy' ) {
00341 $newEdits[] = $edit;
00342 continue;
00343 }
00344 $copyStart = $edit[1];
00345 $copyEnd = $edit[2];
00346 if ( $start >= $copyEnd || $end <= $copyStart ) {
00347
00348 $newEdits[] = $edit;
00349 continue;
00350 }
00351 if ( ( $start < $copyStart && $end > $copyStart )
00352 || ( $start < $copyEnd && $end > $copyEnd )
00353 ) {
00354 throw new MWException( "Overlapping regions found, can't do the edit" );
00355 }
00356
00357 $newEdits[] = array( 'copy', $copyStart, $start );
00358 if ( $newText !== false ) {
00359 $newEdits[] = array( 'insert', $newText );
00360 }
00361 $newEdits[] = array( 'copy', $end, $copyEnd );
00362 }
00363 $this->edits = $newEdits;
00364 }
00365
00371 function findDeletionRegion( $pathName ) {
00372 if ( !isset( $this->pathInfo[$pathName] ) ) {
00373 throw new MWException( "Can't find path \"$pathName\"" );
00374 }
00375 $path = $this->pathInfo[$pathName];
00376
00377 $this->firstToken();
00378 while ( $this->pos != $path['startToken'] ) {
00379 $this->nextToken();
00380 }
00381 $regionStart = $path['startByte'];
00382 for ( $offset = -1; $offset >= -$this->pos; $offset-- ) {
00383 $token = $this->getTokenAhead( $offset );
00384 if ( !$token->isSkip() ) {
00385
00386
00387 $regionStart = $path['startByte'];
00388 break;
00389 }
00390 $lfPos = strrpos( $token->text, "\n" );
00391 if ( $lfPos === false ) {
00392 $regionStart -= strlen( $token->text );
00393 } else {
00394
00395 $regionStart -= strlen( $token->text ) - $lfPos - 1;
00396 break;
00397 }
00398 }
00399
00400 while ( $this->pos != $path['endToken'] ) {
00401 $this->nextToken();
00402 }
00403 $regionEnd = $path['endByte'];
00404 for ( $offset = 0; $offset < count( $this->tokens ) - $this->pos; $offset++ ) {
00405 $token = $this->getTokenAhead( $offset );
00406 if ( !$token->isSkip() ) {
00407 break;
00408 }
00409 $lfPos = strpos( $token->text, "\n" );
00410 if ( $lfPos === false ) {
00411 $regionEnd += strlen( $token->text );
00412 } else {
00413
00414 $regionEnd += $lfPos + 1;
00415 break;
00416 }
00417 }
00418 return array( $regionStart, $regionEnd );
00419 }
00420
00428 function findValueRegion( $pathName ) {
00429 if ( !isset( $this->pathInfo[$pathName] ) ) {
00430 throw new MWEXception( "Can't find path \"$pathName\"" );
00431 }
00432 $path = $this->pathInfo[$pathName];
00433 if ( $path['valueStartByte'] === false || $path['valueEndByte'] === false ) {
00434 throw new MWException( "Can't find value region for path \"$pathName\"" );
00435 }
00436 return array( $path['valueStartByte'], $path['valueEndByte'] );
00437 }
00438
00444 function findLastArrayElement( $path ) {
00445
00446 $lastEltPath = false;
00447 foreach ( $this->pathInfo as $candidatePath => $info ) {
00448 $part1 = substr( $candidatePath, 0, strlen( $path ) + 1 );
00449 $part2 = substr( $candidatePath, strlen( $path ) + 1, 1 );
00450 if ( $part2 == '@' ) {
00451
00452 } elseif ( $part1 == "$path/" ) {
00453 $lastEltPath = $candidatePath;
00454 } elseif ( $lastEltPath !== false ) {
00455 break;
00456 }
00457 }
00458 if ( $lastEltPath !== false ) {
00459 return $lastEltPath;
00460 }
00461
00462
00463 $extraPath = false;
00464 foreach ( $this->pathInfo as $candidatePath => $info ) {
00465 $part1 = substr( $candidatePath, 0, strlen( $path ) + 1 );
00466 if ( $part1 == "$path/" ) {
00467 $extraPath = $candidatePath;
00468 } elseif ( $extraPath !== false ) {
00469 break;
00470 }
00471 }
00472 return $extraPath;
00473 }
00474
00475
00476
00477
00478
00479
00480 function findFirstArrayElement( $path ) {
00481
00482 foreach ( $this->pathInfo as $candidatePath => $info ) {
00483 $part1 = substr( $candidatePath, 0, strlen( $path ) + 1 );
00484 $part2 = substr( $candidatePath, strlen( $path ) + 1, 1 );
00485 if ( $part1 == "$path/" && $part2 != '@' ) {
00486 return $candidatePath;
00487 }
00488 }
00489
00490
00491 foreach ( $this->pathInfo as $candidatePath => $info ) {
00492 $part1 = substr( $candidatePath, 0, strlen( $path ) + 1 );
00493 if ( $part1 == "$path/" ) {
00494 return $candidatePath;
00495 }
00496 }
00497 return false;
00498 }
00499
00504 function getIndent( $pos, $key = false, $arrowPos = false ) {
00505 $arrowIndent = ' ';
00506 if ( $pos == 0 || $this->text[$pos-1] == "\n" ) {
00507 $indentLength = strspn( $this->text, " \t", $pos );
00508 $indent = substr( $this->text, $pos, $indentLength );
00509 } else {
00510 $indent = false;
00511 }
00512 if ( $indent !== false && $arrowPos !== false ) {
00513 $textToInsert = "$indent$key ";
00514 $arrowIndentLength = $arrowPos - $pos - $indentLength - strlen( $key );
00515 if ( $arrowIndentLength > 0 ) {
00516 $arrowIndent = str_repeat( ' ', $arrowIndentLength );
00517 }
00518 }
00519 return array( $indent, $arrowIndent );
00520 }
00521
00526 public function parse() {
00527 $this->initParse();
00528 $this->pushState( 'file' );
00529 $this->pushPath( '@extra-' . ($this->serial++) );
00530 $token = $this->firstToken();
00531
00532 while ( !$token->isEnd() ) {
00533 $state = $this->popState();
00534 if ( !$state ) {
00535 $this->error( 'internal error: empty state stack' );
00536 }
00537
00538 switch ( $state ) {
00539 case 'file':
00540 $token = $this->expect( T_OPEN_TAG );
00541 $token = $this->skipSpace();
00542 if ( $token->isEnd() ) {
00543 break 2;
00544 }
00545 $this->pushState( 'statement', 'file 2' );
00546 break;
00547 case 'file 2':
00548 $token = $this->skipSpace();
00549 if ( $token->isEnd() ) {
00550 break 2;
00551 }
00552 $this->pushState( 'statement', 'file 2' );
00553 break;
00554 case 'statement':
00555 $token = $this->skipSpace();
00556 if ( !$this->validatePath( $token->text ) ) {
00557 $this->error( "Invalid variable name \"{$token->text}\"" );
00558 }
00559 $this->nextPath( $token->text );
00560 $this->expect( T_VARIABLE );
00561 $this->skipSpace();
00562 $arrayAssign = false;
00563 if ( $this->currentToken()->type == '[' ) {
00564 $this->nextToken();
00565 $token = $this->skipSpace();
00566 if ( !$token->isScalar() ) {
00567 $this->error( "expected a string or number for the array key" );
00568 }
00569 if ( $token->type == T_CONSTANT_ENCAPSED_STRING ) {
00570 $text = $this->parseScalar( $token->text );
00571 } else {
00572 $text = $token->text;
00573 }
00574 if ( !$this->validatePath( $text ) ) {
00575 $this->error( "Invalid associative array name \"$text\"" );
00576 }
00577 $this->pushPath( $text );
00578 $this->nextToken();
00579 $this->skipSpace();
00580 $this->expect( ']' );
00581 $this->skipSpace();
00582 $arrayAssign = true;
00583 }
00584 $this->expect( '=' );
00585 $this->skipSpace();
00586 $this->startPathValue();
00587 if ( $arrayAssign )
00588 $this->pushState( 'expression', 'array assign end' );
00589 else
00590 $this->pushState( 'expression', 'statement end' );
00591 break;
00592 case 'array assign end':
00593 case 'statement end':
00594 $this->endPathValue();
00595 if ( $state == 'array assign end' )
00596 $this->popPath();
00597 $this->skipSpace();
00598 $this->expect( ';' );
00599 $this->nextPath( '@extra-' . ($this->serial++) );
00600 break;
00601 case 'expression':
00602 $token = $this->skipSpace();
00603 if ( $token->type == T_ARRAY ) {
00604 $this->pushState( 'array' );
00605 } elseif ( $token->isScalar() ) {
00606 $this->nextToken();
00607 } elseif ( $token->type == T_VARIABLE ) {
00608 $this->nextToken();
00609 } else {
00610 $this->error( "expected simple expression" );
00611 }
00612 break;
00613 case 'array':
00614 $this->skipSpace();
00615 $this->expect( T_ARRAY );
00616 $this->skipSpace();
00617 $this->expect( '(' );
00618 $this->skipSpace();
00619 $this->pushPath( '@extra-' . ($this->serial++) );
00620 if ( $this->isAhead( ')' ) ) {
00621
00622 $this->pushState( 'array end' );
00623 } else {
00624 $this->pushState( 'element', 'array end' );
00625 }
00626 break;
00627 case 'array end':
00628 $this->skipSpace();
00629 $this->popPath();
00630 $this->expect( ')' );
00631 break;
00632 case 'element':
00633 $token = $this->skipSpace();
00634
00635 if ( $token->isScalar() && $this->isAhead( T_DOUBLE_ARROW, 1 ) ) {
00636
00637 $this->pushState( 'assoc-element', 'element end' );
00638 } else {
00639
00640 $this->nextPath( '@next' );
00641 $this->startPathValue();
00642 $this->pushState( 'expression', 'element end' );
00643 }
00644 break;
00645 case 'element end':
00646 $token = $this->skipSpace();
00647 if ( $token->type == ',' ) {
00648 $this->endPathValue();
00649 $this->markComma();
00650 $this->nextToken();
00651 $this->nextPath( '@extra-' . ($this->serial++) );
00652
00653 if ( $this->isAhead( ")" ) ) {
00654
00655 $this->skipSpace();
00656 } else {
00657
00658 $this->pushState( 'element' );
00659 }
00660 } elseif ( $token->type == ')' ) {
00661
00662 $this->endPathValue();
00663 } else {
00664 $this->error( "expected the next array element or the end of the array" );
00665 }
00666 break;
00667 case 'assoc-element':
00668 $token = $this->skipSpace();
00669 if ( !$token->isScalar() ) {
00670 $this->error( "expected a string or number for the array key" );
00671 }
00672 if ( $token->type == T_CONSTANT_ENCAPSED_STRING ) {
00673 $text = $this->parseScalar( $token->text );
00674 } else {
00675 $text = $token->text;
00676 }
00677 if ( !$this->validatePath( $text ) ) {
00678 $this->error( "Invalid associative array name \"$text\"" );
00679 }
00680 $this->nextPath( $text );
00681 $this->nextToken();
00682 $this->skipSpace();
00683 $this->markArrow();
00684 $this->expect( T_DOUBLE_ARROW );
00685 $this->skipSpace();
00686 $this->startPathValue();
00687 $this->pushState( 'expression' );
00688 break;
00689 }
00690 }
00691 if ( count( $this->stateStack ) ) {
00692 $this->error( 'unexpected end of file' );
00693 }
00694 $this->popPath();
00695 }
00696
00700 protected function initParse() {
00701 $this->tokens = token_get_all( $this->text );
00702 $this->stateStack = array();
00703 $this->pathStack = array();
00704 $this->firstToken();
00705 $this->pathInfo = array();
00706 $this->serial = 1;
00707 }
00708
00713 protected function setPos( $pos ) {
00714 $this->pos = $pos;
00715 if ( $this->pos >= count( $this->tokens ) ) {
00716 $this->currentToken = ConfEditorToken::newEnd();
00717 } else {
00718 $this->currentToken = $this->newTokenObj( $this->tokens[$this->pos] );
00719 }
00720 return $this->currentToken;
00721 }
00722
00726 function newTokenObj( $internalToken ) {
00727 if ( is_array( $internalToken ) ) {
00728 return new ConfEditorToken( $internalToken[0], $internalToken[1] );
00729 } else {
00730 return new ConfEditorToken( $internalToken, $internalToken );
00731 }
00732 }
00733
00737 function firstToken() {
00738 $this->setPos( 0 );
00739 $this->prevToken = ConfEditorToken::newEnd();
00740 $this->lineNum = 1;
00741 $this->colNum = 1;
00742 $this->byteNum = 0;
00743 return $this->currentToken;
00744 }
00745
00749 function currentToken() {
00750 return $this->currentToken;
00751 }
00752
00756 function nextToken() {
00757 if ( $this->currentToken ) {
00758 $text = $this->currentToken->text;
00759 $lfCount = substr_count( $text, "\n" );
00760 if ( $lfCount ) {
00761 $this->lineNum += $lfCount;
00762 $this->colNum = strlen( $text ) - strrpos( $text, "\n" );
00763 } else {
00764 $this->colNum += strlen( $text );
00765 }
00766 $this->byteNum += strlen( $text );
00767 }
00768 $this->prevToken = $this->currentToken;
00769 $this->setPos( $this->pos + 1 );
00770 return $this->currentToken;
00771 }
00772
00777 function getTokenAhead( $offset ) {
00778 $pos = $this->pos + $offset;
00779 if ( $pos >= count( $this->tokens ) || $pos < 0 ) {
00780 return ConfEditorToken::newEnd();
00781 } else {
00782 return $this->newTokenObj( $this->tokens[$pos] );
00783 }
00784 }
00785
00789 function skipSpace() {
00790 while ( $this->currentToken && $this->currentToken->isSkip() ) {
00791 $this->nextToken();
00792 }
00793 return $this->currentToken;
00794 }
00795
00800 function expect( $type ) {
00801 if ( $this->currentToken && $this->currentToken->type == $type ) {
00802 return $this->nextToken();
00803 } else {
00804 $this->error( "expected " . $this->getTypeName( $type ) .
00805 ", got " . $this->getTypeName( $this->currentToken->type ) );
00806 }
00807 }
00808
00812 function pushState( $nextState, $stateAfterThat = null ) {
00813 if ( $stateAfterThat !== null ) {
00814 $this->stateStack[] = $stateAfterThat;
00815 }
00816 $this->stateStack[] = $nextState;
00817 }
00818
00822 function popState() {
00823 return array_pop( $this->stateStack );
00824 }
00825
00830 function validatePath( $path ) {
00831 return strpos( $path, '/' ) === false && substr( $path, 0, 1 ) != '@';
00832 }
00833
00838 function endPath() {
00839 $i = count( $this->pathStack ) - 1;
00840 $key = '';
00841 foreach ( $this->pathStack as $pathInfo ) {
00842 if ( $key !== '' ) {
00843 $key .= '/';
00844 }
00845 $key .= $pathInfo['name'];
00846 }
00847 $pathInfo['endByte'] = $this->byteNum;
00848 $pathInfo['endToken'] = $this->pos;
00849 $this->pathInfo[$key] = $pathInfo;
00850 }
00851
00855 function pushPath( $path ) {
00856 $this->pathStack[] = array(
00857 'name' => $path,
00858 'level' => count( $this->pathStack ) + 1,
00859 'startByte' => $this->byteNum,
00860 'startToken' => $this->pos,
00861 'valueStartToken' => false,
00862 'valueStartByte' => false,
00863 'valueEndToken' => false,
00864 'valueEndByte' => false,
00865 'nextArrayIndex' => 0,
00866 'hasComma' => false,
00867 'arrowByte' => false
00868 );
00869 }
00870
00874 function popPath() {
00875 $this->endPath();
00876 array_pop( $this->pathStack );
00877 }
00878
00884 function nextPath( $path ) {
00885 $this->endPath();
00886 $i = count( $this->pathStack ) - 1;
00887 if ( $path == '@next' ) {
00888 $nextArrayIndex =& $this->pathStack[$i]['nextArrayIndex'];
00889 $this->pathStack[$i]['name'] = $nextArrayIndex;
00890 $nextArrayIndex++;
00891 } else {
00892 $this->pathStack[$i]['name'] = $path;
00893 }
00894 $this->pathStack[$i] =
00895 array(
00896 'startByte' => $this->byteNum,
00897 'startToken' => $this->pos,
00898 'valueStartToken' => false,
00899 'valueStartByte' => false,
00900 'valueEndToken' => false,
00901 'valueEndByte' => false,
00902 'hasComma' => false,
00903 'arrowByte' => false,
00904 ) + $this->pathStack[$i];
00905 }
00906
00910 function startPathValue() {
00911 $path =& $this->pathStack[count( $this->pathStack ) - 1];
00912 $path['valueStartToken'] = $this->pos;
00913 $path['valueStartByte'] = $this->byteNum;
00914 }
00915
00919 function endPathValue() {
00920 $path =& $this->pathStack[count( $this->pathStack ) - 1];
00921 $path['valueEndToken'] = $this->pos;
00922 $path['valueEndByte'] = $this->byteNum;
00923 }
00924
00928 function markComma() {
00929 $path =& $this->pathStack[count( $this->pathStack ) - 1];
00930 $path['hasComma'] = true;
00931 }
00932
00936 function markArrow() {
00937 $path =& $this->pathStack[count( $this->pathStack ) - 1];
00938 $path['arrowByte'] = $this->byteNum;
00939 }
00940
00944 function error( $msg ) {
00945 throw new ConfEditorParseError( $this, $msg );
00946 }
00947
00951 function getTypeName( $type ) {
00952 if ( is_int( $type ) ) {
00953 return token_name( $type );
00954 } else {
00955 return "\"$type\"";
00956 }
00957 }
00958
00964 function isAhead( $type, $offset = 0 ) {
00965 $ahead = $offset;
00966 $token = $this->getTokenAhead( $offset );
00967 while ( !$token->isEnd() ) {
00968 if ( $token->isSkip() ) {
00969 $ahead++;
00970 $token = $this->getTokenAhead( $ahead );
00971 continue;
00972 } elseif ( $token->type == $type ) {
00973
00974 return true;
00975 } else {
00976
00977 return false;
00978 }
00979 }
00980 return false;
00981 }
00982
00986 function prevToken() {
00987 return $this->prevToken;
00988 }
00989
00993 function dumpTokens() {
00994 $out = '';
00995 foreach ( $this->tokens as $token ) {
00996 $obj = $this->newTokenObj( $token );
00997 $out .= sprintf( "%-28s %s\n",
00998 $this->getTypeName( $obj->type ),
00999 addcslashes( $obj->text, "\0..\37" ) );
01000 }
01001 echo "<pre>" . htmlspecialchars( $out ) . "</pre>";
01002 }
01003 }
01004
01008 class ConfEditorParseError extends MWException {
01009 var $lineNum, $colNum;
01010 function __construct( $editor, $msg ) {
01011 $this->lineNum = $editor->lineNum;
01012 $this->colNum = $editor->colNum;
01013 parent::__construct( "Parse error on line {$editor->lineNum} " .
01014 "col {$editor->colNum}: $msg" );
01015 }
01016
01017 function highlight( $text ) {
01018 $lines = StringUtils::explode( "\n", $text );
01019 foreach ( $lines as $lineNum => $line ) {
01020 if ( $lineNum == $this->lineNum - 1 ) {
01021 return "$line\n" .str_repeat( ' ', $this->colNum - 1 ) . "^\n";
01022 }
01023 }
01024 }
01025
01026 }
01027
01031 class ConfEditorToken {
01032 var $type, $text;
01033
01034 static $scalarTypes = array( T_LNUMBER, T_DNUMBER, T_STRING, T_CONSTANT_ENCAPSED_STRING );
01035 static $skipTypes = array( T_WHITESPACE, T_COMMENT, T_DOC_COMMENT );
01036
01037 static function newEnd() {
01038 return new self( 'END', '' );
01039 }
01040
01041 function __construct( $type, $text ) {
01042 $this->type = $type;
01043 $this->text = $text;
01044 }
01045
01046 function isSkip() {
01047 return in_array( $this->type, self::$skipTypes );
01048 }
01049
01050 function isScalar() {
01051 return in_array( $this->type, self::$scalarTypes );
01052 }
01053
01054 function isEnd() {
01055 return $this->type == 'END';
01056 }
01057 }
01058