From d82bbb3bea1b3505201a4014c612be322bb82ff1 Mon Sep 17 00:00:00 2001 From: nikic <+@ni-po.com> Date: Fri, 3 Jun 2011 17:44:23 +0200 Subject: [PATCH] Throw ParseErrorException on error instead of error callback As long as the parser isn't reentrant having an error callback doesn't really make sense and only complicates everything. --- README.md | 34 ++++++--------- grammar/php.kmyacc | 37 +++++++++------- grammar/preprocessor.php | 8 +++- grammar/y.output | 72 +++++++++++++++---------------- grammar/zend_language_parser.phpy | 2 +- lib/Lexer.php | 26 ++++++++++- lib/Node/Stmt/Class.php | 17 +++----- lib/ParseErrorException.php | 70 ++++++++++++++++++++++++++++++ lib/Parser.php | 38 ++++++++-------- lib/ParserDebug.php | 38 ++++++++-------- test/testAgainstDirectory.php | 47 ++++++++------------ test/testExpressions.php | 13 +++--- 12 files changed, 246 insertions(+), 156 deletions(-) create mode 100644 lib/ParseErrorException.php diff --git a/README.md b/README.md index ba5e596..d940837 100644 --- a/README.md +++ b/README.md @@ -19,20 +19,17 @@ This package currently bundles several components: Parser and ParserDebug ---------------------- -Parsing is performed using `Parser->parse()`. This method accepts a `Lexer` as the first parameter -and a error callback, i.e. a function that will be passed a message in case of an error, as -second parameter. The parser returns an array of statement nodes. If an error occurs the parser -instead returns `false` and sends an error message to the error callback. +Parsing is performed using `Parser->parse()`. This method accepts a `Lexer` as the only parameter +and returns an array of statement nodes. If an error occurs it throws a ParseErrorException. $code = 'parse( - new Lexer($code), - function ($msg) { - echo $msg; - } - ); + try { + $parser = new Parser; + $stmts = $parser->parse(new Lexer($code)); + } catch (ParseErrorException $e) { + echo 'Parse Error: ', $e->getMessage(); + } The `ParserDebug` class also parses a PHP code, but outputs a debug trace while doing so. @@ -59,7 +56,7 @@ respective files. NodeDumper ---------- -Nodes can be dumped into a string representation using the `NodeDumper->dump` method: +Nodes can be dumped into a string representation using the `NodeDumper->dump()` method: $code = <<<'CODE' dump` me printLine('Hallo World!!!'); CODE; - $parser = new Parser; - $stmts = $parser->parse( - new Lexer($code), - function ($msg) { - echo $msg; - } - ); + try { + $parser = new Parser; + $stmts = $parser->parse(new Lexer($code)); - if (false !== $stmts) { $nodeDumper = new NodeDumper; echo '
' . htmlspecialchars($nodeDumper->dump($stmts)) . '
'; + } catch (ParseErrorException $e) { + echo 'Parse Error: ', $e->getMessage(); } This script will have an output similar to the following: diff --git a/grammar/php.kmyacc b/grammar/php.kmyacc index 24f0dd8..195bffc 100644 --- a/grammar/php.kmyacc +++ b/grammar/php.kmyacc @@ -142,12 +142,11 @@ class YYParser * Parses PHP code into a node tree and prints out debugging information. #endif * - * @param Lexer $lex A lexer - * @param callback $errorCallback Function to be passed a message in case of an error. + * @param Lexer $lex A lexer * * @return array Array of statements */ - public function parse(Lexer $lex, $errorCallback) { + public function parse(Lexer $lex) { $this->yyastk = array(); $yysstk = array(); $this->yysp = 0; @@ -201,8 +200,8 @@ class YYParser $this->yyastk[$this->yysp] = $yylval; $yychar = -1; - if ($yyerrflag > 0) - --$yyerrflag; + /*if ($yyerrflag > 0) + --$yyerrflag;*/ if ($yyn < self::YYNLSTATES) continue; @@ -229,7 +228,13 @@ class YYParser #if -t $this->YYTRACE_REDUCE($yyn); #endif - $this->{'yyn' . $yyn}(); + try { + $this->{'yyn' . $yyn}(); + } catch (ParseErrorException $e) { + $e->setRawLine($lex->getLine()); + + throw $e; + } /* Goto - shift nonterminal */ $this->yysp -= self::$yylen[$yyn]; @@ -248,18 +253,18 @@ class YYParser $this->yyastk[$this->yysp] = $this->yyval; } else { /* error */ - switch ($yyerrflag) { - case 0: - $errorCallback( - 'Parse error:' - . ' Unexpected token ' . self::$yyterminals[$yychar] - . ' on line ' . $lex->getLine() + /*switch ($yyerrflag) { + case 0:*/ +#endif + throw new ParseErrorException( + 'Unexpected token ' . self::$yyterminals[$yychar], + $lex->getLine() ); - case 1: + /*case 1: case 2: - $yyerrflag = 3; + $yyerrflag = 3;*/ /* Pop until error-expecting state uncovered */ - while (!(($yyn = self::$yybase[$yystate] + self::YYINTERRTOK) >= 0 + /*while (!(($yyn = self::$yybase[$yystate] + self::YYINTERRTOK) >= 0 && $yyn < self::YYLAST && self::$yycheck[$yyn] == self::YYINTERRTOK || ($yystate < self::YY2TBLSTATE @@ -290,7 +295,7 @@ class YYParser } $yychar = -1; break; - } + }*/ } if ($yystate < self::YYNLSTATES) diff --git a/grammar/preprocessor.php b/grammar/preprocessor.php index f38a200..67d417a 100644 --- a/grammar/preprocessor.php +++ b/grammar/preprocessor.php @@ -71,7 +71,7 @@ function resolveNodes($code) { function resolveMacros($code) { return preg_replace_callback( - '~(?init|push|pushNormalizing|toArray|parse(?:Var|Encapsed|LNumber|DNumber))' . ARGS . '~', + '~(?error|init|push|pushNormalizing|toArray|parse(?:Var|Encapsed|LNumber|DNumber))' . ARGS . '~', function($matches) { // recurse $matches['args'] = resolveMacros($matches['args']); @@ -82,6 +82,12 @@ function resolveMacros($code) { $matches['args'] ); + if ('error' == $name) { + assertArgs(1, $args, $name); + + return 'throw new ParseErrorException(' . $args[0] . ')'; + } + if ('init' == $name) { return '$$ = array(' . implode(', ', $args) . ')'; } diff --git a/grammar/y.output b/grammar/y.output index 46b1132..69c5714 100644 --- a/grammar/y.output +++ b/grammar/y.output @@ -924,8 +924,8 @@ state 9 . error state 10 - (24) inner_statement_list : inner_statement_list . inner_statement (233) expr : T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars '{' inner_statement_list . '}' + (24) inner_statement_list : inner_statement_list . inner_statement T_INCLUDE shift 56 T_INCLUDE_ONCE shift 57 @@ -1227,8 +1227,8 @@ state 12 . error state 13 - (24) inner_statement_list : inner_statement_list . inner_statement (136) method_body : '{' inner_statement_list . '}' + (24) inner_statement_list : inner_statement_list . inner_statement T_INCLUDE shift 56 T_INCLUDE_ONCE shift 57 @@ -10246,7 +10246,6 @@ state 134 . error state 135 - (92) case_list : case_list T_CASE expr . case_separator inner_statement_list (181) expr : expr . T_BOOLEAN_OR expr (182) expr : expr . T_BOOLEAN_AND expr (183) expr : expr . T_LOGICAL_OR expr @@ -10274,6 +10273,7 @@ state 135 (209) expr : expr . T_INSTANCEOF class_name_reference (211) expr : expr . '?' expr ':' expr (212) expr : expr . '?' ':' expr + (92) case_list : case_list T_CASE expr . case_separator inner_statement_list T_LOGICAL_OR shift 83 T_LOGICAL_XOR shift 84 @@ -10307,7 +10307,6 @@ state 135 . error state 136 - (47) statement : expr . ';' (181) expr : expr . T_BOOLEAN_OR expr (182) expr : expr . T_BOOLEAN_AND expr (183) expr : expr . T_LOGICAL_OR expr @@ -10335,6 +10334,7 @@ state 136 (209) expr : expr . T_INSTANCEOF class_name_reference (211) expr : expr . '?' expr ':' expr (212) expr : expr . '?' ':' expr + (47) statement : expr . ';' T_LOGICAL_OR shift 83 T_LOGICAL_XOR shift 84 @@ -10366,7 +10366,6 @@ state 136 . error state 137 - (38) statement : T_BREAK expr . ';' (181) expr : expr . T_BOOLEAN_OR expr (182) expr : expr . T_BOOLEAN_AND expr (183) expr : expr . T_LOGICAL_OR expr @@ -10394,6 +10393,7 @@ state 137 (209) expr : expr . T_INSTANCEOF class_name_reference (211) expr : expr . '?' expr ':' expr (212) expr : expr . '?' ':' expr + (38) statement : T_BREAK expr . ';' T_LOGICAL_OR shift 83 T_LOGICAL_XOR shift 84 @@ -10425,7 +10425,6 @@ state 137 . error state 138 - (40) statement : T_CONTINUE expr . ';' (181) expr : expr . T_BOOLEAN_OR expr (182) expr : expr . T_BOOLEAN_AND expr (183) expr : expr . T_LOGICAL_OR expr @@ -10453,6 +10452,7 @@ state 138 (209) expr : expr . T_INSTANCEOF class_name_reference (211) expr : expr . '?' expr ':' expr (212) expr : expr . '?' ':' expr + (40) statement : T_CONTINUE expr . ';' T_LOGICAL_OR shift 83 T_LOGICAL_XOR shift 84 @@ -10484,7 +10484,6 @@ state 138 . error state 139 - (42) statement : T_RETURN expr . ';' (181) expr : expr . T_BOOLEAN_OR expr (182) expr : expr . T_BOOLEAN_AND expr (183) expr : expr . T_LOGICAL_OR expr @@ -10512,6 +10511,7 @@ state 139 (209) expr : expr . T_INSTANCEOF class_name_reference (211) expr : expr . '?' expr ':' expr (212) expr : expr . '?' ':' expr + (42) statement : T_RETURN expr . ';' T_LOGICAL_OR shift 83 T_LOGICAL_XOR shift 84 @@ -10543,7 +10543,6 @@ state 139 . error state 140 - (55) statement : T_THROW expr . ';' (181) expr : expr . T_BOOLEAN_OR expr (182) expr : expr . T_BOOLEAN_AND expr (183) expr : expr . T_LOGICAL_OR expr @@ -10571,6 +10570,7 @@ state 140 (209) expr : expr . T_INSTANCEOF class_name_reference (211) expr : expr . '?' expr ':' expr (212) expr : expr . '?' ':' expr + (55) statement : T_THROW expr . ';' T_LOGICAL_OR shift 83 T_LOGICAL_XOR shift 84 @@ -10779,8 +10779,6 @@ state 143 . error state 144 - (31) statement : T_IF '(' expr . ')' statement elseif_list else_single - (32) statement : T_IF '(' expr . ')' ':' inner_statement_list new_elseif_list new_else_single T_ENDIF ';' (181) expr : expr . T_BOOLEAN_OR expr (182) expr : expr . T_BOOLEAN_AND expr (183) expr : expr . T_LOGICAL_OR expr @@ -10808,6 +10806,8 @@ state 144 (209) expr : expr . T_INSTANCEOF class_name_reference (211) expr : expr . '?' expr ':' expr (212) expr : expr . '?' ':' expr + (31) statement : T_IF '(' expr . ')' statement elseif_list else_single + (32) statement : T_IF '(' expr . ')' ':' inner_statement_list new_elseif_list new_else_single T_ENDIF ';' T_LOGICAL_OR shift 83 T_LOGICAL_XOR shift 84 @@ -10839,7 +10839,6 @@ state 144 . error state 145 - (33) statement : T_WHILE '(' expr . ')' while_statement (181) expr : expr . T_BOOLEAN_OR expr (182) expr : expr . T_BOOLEAN_AND expr (183) expr : expr . T_LOGICAL_OR expr @@ -10867,6 +10866,7 @@ state 145 (209) expr : expr . T_INSTANCEOF class_name_reference (211) expr : expr . '?' expr ':' expr (212) expr : expr . '?' ':' expr + (33) statement : T_WHILE '(' expr . ')' while_statement T_LOGICAL_OR shift 83 T_LOGICAL_XOR shift 84 @@ -10898,9 +10898,6 @@ state 145 . error state 146 - (49) statement : T_FOREACH '(' expr . T_AS variable ')' foreach_statement - (50) statement : T_FOREACH '(' expr . T_AS '&' variable ')' foreach_statement - (51) statement : T_FOREACH '(' expr . T_AS variable T_DOUBLE_ARROW optional_ref variable ')' foreach_statement (181) expr : expr . T_BOOLEAN_OR expr (182) expr : expr . T_BOOLEAN_AND expr (183) expr : expr . T_LOGICAL_OR expr @@ -10928,6 +10925,9 @@ state 146 (209) expr : expr . T_INSTANCEOF class_name_reference (211) expr : expr . '?' expr ':' expr (212) expr : expr . '?' ':' expr + (49) statement : T_FOREACH '(' expr . T_AS variable ')' foreach_statement + (50) statement : T_FOREACH '(' expr . T_AS '&' variable ')' foreach_statement + (51) statement : T_FOREACH '(' expr . T_AS variable T_DOUBLE_ARROW optional_ref variable ')' foreach_statement T_LOGICAL_OR shift 83 T_LOGICAL_XOR shift 84 @@ -10959,7 +10959,6 @@ state 146 . error state 147 - (36) statement : T_SWITCH '(' expr . ')' switch_case_list (181) expr : expr . T_BOOLEAN_OR expr (182) expr : expr . T_BOOLEAN_AND expr (183) expr : expr . T_LOGICAL_OR expr @@ -10987,6 +10986,7 @@ state 147 (209) expr : expr . T_INSTANCEOF class_name_reference (211) expr : expr . '?' expr ':' expr (212) expr : expr . '?' ':' expr + (36) statement : T_SWITCH '(' expr . ')' switch_case_list T_LOGICAL_OR shift 83 T_LOGICAL_XOR shift 84 @@ -11610,7 +11610,6 @@ state 157 . error state 158 - (34) statement : T_DO statement T_WHILE '(' expr . ')' ';' (181) expr : expr . T_BOOLEAN_OR expr (182) expr : expr . T_BOOLEAN_AND expr (183) expr : expr . T_LOGICAL_OR expr @@ -11638,6 +11637,7 @@ state 158 (209) expr : expr . T_INSTANCEOF class_name_reference (211) expr : expr . '?' expr ':' expr (212) expr : expr . '?' ':' expr + (34) statement : T_DO statement T_WHILE '(' expr . ')' ';' T_LOGICAL_OR shift 83 T_LOGICAL_XOR shift 84 @@ -11907,7 +11907,6 @@ state 162 . error state 163 - (99) elseif_list : elseif_list T_ELSEIF '(' expr . ')' statement (181) expr : expr . T_BOOLEAN_OR expr (182) expr : expr . T_BOOLEAN_AND expr (183) expr : expr . T_LOGICAL_OR expr @@ -11935,6 +11934,7 @@ state 163 (209) expr : expr . T_INSTANCEOF class_name_reference (211) expr : expr . '?' expr ':' expr (212) expr : expr . '?' ':' expr + (99) elseif_list : elseif_list T_ELSEIF '(' expr . ')' statement T_LOGICAL_OR shift 83 T_LOGICAL_XOR shift 84 @@ -11966,7 +11966,6 @@ state 163 . error state 164 - (101) new_elseif_list : new_elseif_list T_ELSEIF '(' expr . ')' ':' inner_statement_list (181) expr : expr . T_BOOLEAN_OR expr (182) expr : expr . T_BOOLEAN_AND expr (183) expr : expr . T_LOGICAL_OR expr @@ -11994,6 +11993,7 @@ state 164 (209) expr : expr . T_INSTANCEOF class_name_reference (211) expr : expr . '?' expr ':' expr (212) expr : expr . '?' ':' expr + (101) new_elseif_list : new_elseif_list T_ELSEIF '(' expr . ')' ':' inner_statement_list T_LOGICAL_OR shift 83 T_LOGICAL_XOR shift 84 @@ -14905,8 +14905,8 @@ state 226 . reduce (208) state 227 - (67) class_declaration_statement : T_INTERFACE T_STRING interface_extends_list '{' class_statement_list . '}' (130) class_statement_list : class_statement_list . class_statement + (67) class_declaration_statement : T_INTERFACE T_STRING interface_extends_list '{' class_statement_list . '}' (139) method_modifiers : . T_CONST shift 483 @@ -14927,8 +14927,8 @@ state 227 . reduce (139) state 228 - (66) class_declaration_statement : class_entry_type T_STRING extends_from implements_list '{' class_statement_list . '}' (130) class_statement_list : class_statement_list . class_statement + (66) class_declaration_statement : class_entry_type T_STRING extends_from implements_list '{' class_statement_list . '}' (139) method_modifiers : . T_CONST shift 483 @@ -15972,10 +15972,10 @@ state 277 . error state 278 + (246) name : T_NAMESPACE . T_NS_SEPARATOR namespace_name (11) top_statement : T_NAMESPACE . namespace_name ';' (12) top_statement : T_NAMESPACE . namespace_name '{' top_statement_list '}' (13) top_statement : T_NAMESPACE . '{' top_statement_list '}' - (246) name : T_NAMESPACE . T_NS_SEPARATOR namespace_name T_STRING shift 568 and reduce (5) T_NS_SEPARATOR shift 340 @@ -16164,10 +16164,10 @@ state 294 . error state 295 - (49) statement : T_FOREACH '(' expr T_AS variable . ')' foreach_statement - (51) statement : T_FOREACH '(' expr T_AS variable . T_DOUBLE_ARROW optional_ref variable ')' foreach_statement (302) object_access : variable . T_OBJECT_OPERATOR object_property '(' function_call_argument_list ')' (303) object_access_arrayable : variable . T_OBJECT_OPERATOR object_property + (49) statement : T_FOREACH '(' expr T_AS variable . ')' foreach_statement + (51) statement : T_FOREACH '(' expr T_AS variable . T_DOUBLE_ARROW optional_ref variable ')' foreach_statement T_OBJECT_OPERATOR shift 264 T_DOUBLE_ARROW shift 359 @@ -16325,16 +16325,16 @@ state 311 . reduce (311) state 312 - (45) statement : T_ECHO expr_list . ';' (155) expr_list : expr_list . ',' expr + (45) statement : T_ECHO expr_list . ';' ',' shift 122 ';' shift 605 and reduce (45) . error state 313 - (65) function_declaration_statement : T_FUNCTION optional_ref . T_STRING '(' parameter_list ')' '{' inner_statement_list '}' (233) expr : T_FUNCTION optional_ref . '(' parameter_list ')' lexical_vars '{' inner_statement_list '}' + (65) function_declaration_statement : T_FUNCTION optional_ref . T_STRING '(' parameter_list ')' '{' inner_statement_list '}' T_STRING shift 413 '(' shift 256 @@ -16349,17 +16349,17 @@ state 314 . error state 315 - (43) statement : T_GLOBAL global_var_list . ';' (121) global_var_list : global_var_list . ',' global_var + (43) statement : T_GLOBAL global_var_list . ';' ',' shift 303 ';' shift 612 and reduce (43) . error state 316 - (44) statement : T_STATIC static_var_list . ';' (126) static_var_list : static_var_list . ',' T_VARIABLE (127) static_var_list : static_var_list . ',' T_VARIABLE '=' static_scalar + (44) statement : T_STATIC static_var_list . ';' ',' shift 417 ';' shift 613 and reduce (44) @@ -16428,8 +16428,8 @@ state 323 . error state 324 - (62) variables_list : variables_list . ',' variable (213) expr : T_ISSET '(' variables_list . ')' + (62) variables_list : variables_list . ',' variable ',' shift 246 ')' shift 648 and reduce (213) @@ -16507,9 +16507,9 @@ state 332 . error state 333 - (50) statement : T_FOREACH '(' expr T_AS '&' variable . ')' foreach_statement (302) object_access : variable . T_OBJECT_OPERATOR object_property '(' function_call_argument_list ')' (303) object_access_arrayable : variable . T_OBJECT_OPERATOR object_property + (50) statement : T_FOREACH '(' expr T_AS '&' variable . ')' foreach_statement T_OBJECT_OPERATOR shift 264 ')' shift 22 @@ -16533,9 +16533,9 @@ state 335 . error state 336 - (51) statement : T_FOREACH '(' expr T_AS variable T_DOUBLE_ARROW optional_ref variable . ')' foreach_statement (302) object_access : variable . T_OBJECT_OPERATOR object_property '(' function_call_argument_list ')' (303) object_access_arrayable : variable . T_OBJECT_OPERATOR object_property + (51) statement : T_FOREACH '(' expr T_AS variable T_DOUBLE_ARROW optional_ref variable . ')' foreach_statement T_OBJECT_OPERATOR shift 264 ')' shift 24 @@ -16600,8 +16600,8 @@ state 343 . reduce (257) state 344 - (65) function_declaration_statement : T_FUNCTION . optional_ref T_STRING '(' parameter_list ')' '{' inner_statement_list '}' (233) expr : T_FUNCTION . optional_ref '(' parameter_list ')' lexical_vars '{' inner_statement_list '}' + (65) function_declaration_statement : T_FUNCTION . optional_ref T_STRING '(' parameter_list ')' '{' inner_statement_list '}' (63) optional_ref : . '&' shift 589 and reduce (64) @@ -16609,8 +16609,8 @@ state 344 . reduce (63) state 345 - (44) statement : T_STATIC . static_var_list ';' (243) class_name : T_STATIC . + (44) statement : T_STATIC . static_var_list ';' T_VARIABLE shift 402 static_var_list goto 316 @@ -17151,9 +17151,9 @@ state 417 . error state 418 - (61) variables_list : variable . (302) object_access : variable . T_OBJECT_OPERATOR object_property '(' function_call_argument_list ')' (303) object_access_arrayable : variable . T_OBJECT_OPERATOR object_property + (61) variables_list : variable . T_OBJECT_OPERATOR shift 264 . reduce (61) @@ -17423,9 +17423,9 @@ state 447 . error state 448 - (106) parameter_list : non_empty_parameter_list . (110) non_empty_parameter_list : non_empty_parameter_list . ',' optional_class_type optional_ref T_VARIABLE (111) non_empty_parameter_list : non_empty_parameter_list . ',' optional_class_type optional_ref T_VARIABLE '=' static_scalar + (106) parameter_list : non_empty_parameter_list . ',' shift 262 . reduce (106) @@ -17567,9 +17567,9 @@ state 469 . error state 470 - (62) variables_list : variables_list ',' variable . (302) object_access : variable . T_OBJECT_OPERATOR object_property '(' function_call_argument_list ')' (303) object_access_arrayable : variable . T_OBJECT_OPERATOR object_property + (62) variables_list : variables_list ',' variable . T_OBJECT_OPERATOR shift 264 . reduce (62) @@ -19468,4 +19468,4 @@ Statistics for zend_language_parser.phpy: 3821 items 1130 lookahead sets used 13448+800=14248 action entries - 228656 bytes used + 228688 bytes used diff --git a/grammar/zend_language_parser.phpy b/grammar/zend_language_parser.phpy index c27cc52..9ef6710 100644 --- a/grammar/zend_language_parser.phpy +++ b/grammar/zend_language_parser.phpy @@ -159,7 +159,7 @@ inner_statement: statement { $$ = $1; } | function_declaration_statement { $$ = $1; } | class_declaration_statement { $$ = $1; } - | T_HALT_COMPILER '(' ')' ';' { error('__halt_compiler() can only be used from the outermost scope'); } + | T_HALT_COMPILER '(' ')' ';' { throw new ParseErrorException('__halt_compiler() can only be used from the outermost scope'); } ; statement: diff --git a/lib/Lexer.php b/lib/Lexer.php index 209b6ca..2d223b7 100644 --- a/lib/Lexer.php +++ b/lib/Lexer.php @@ -18,8 +18,32 @@ class Lexer public function __construct($code) { self::initTokenMap(); - $this->tokens = token_get_all($code); + // Reset the error message in error_get_last() + // Still hoping for a better solution to be found. + @$errorGetLastResetUndefinedVariable; + + $this->tokens = @token_get_all($code); $this->pos = -1; + + $error = error_get_last(); + + if (preg_match( + '~^(Unterminated comment) starting line ([0-9]+)$~', + $error['message'], + $matches + ) + ) { + throw new ParseErrorException($matches[1], $matches[2]); + } + + if (preg_match( + '~^(Unexpected character in input:\s+\'(.)\' \(ASCII=[0-9]+\))~s', + $error['message'], + $matches + ) + ) { + throw new ParseErrorException($matches[1]); + } } /** diff --git a/lib/Node/Stmt/Class.php b/lib/Node/Stmt/Class.php index b0dc902..fe5e2fc 100644 --- a/lib/Node/Stmt/Class.php +++ b/lib/Node/Stmt/Class.php @@ -17,29 +17,24 @@ class Node_Stmt_Class extends Node_Stmt const MODIFIER_FINAL = 32; public static function verifyModifier($a, $b) { - // TODO: Actually throw errors - if ($a & 7 && $b & 7) { - ('Multiple access type modifiers are not allowed'); + throw new ParseErrorException('Multiple access type modifiers are not allowed'); } if ($a & self::MODIFIER_ABSTRACT && $b & self::MODIFIER_ABSTRACT) { - ('Multiple abstract modifiers are not allowed'); + throw new ParseErrorException('Multiple abstract modifiers are not allowed'); } if ($a & self::MODIFIER_STATIC && $b & self::MODIFIER_STATIC) { - ('Multiple static modifiers are not allowed'); + throw new ParseErrorException('Multiple static modifiers are not allowed'); } if ($a & self::MODIFIER_FINAL && $b & self::MODIFIER_FINAL) { - ('Multiple final modifiers are not allowed'); + throw new ParseErrorException('Multiple final modifiers are not allowed'); } - if (($a | $b) - & (self::MODIFIER_ABSTRACT | self::MODIFIER_FINAL) - == self::MODIFIER_ABSTRACT | self::MODIFIER_FINAL - ) { - ('Cannot use the final modifier on an abstract class member"'); + if ($a & 48 && $b & 48) { + throw new ParseErrorException('Cannot use the final modifier on an abstract class member"'); } } } \ No newline at end of file diff --git a/lib/ParseErrorException.php b/lib/ParseErrorException.php new file mode 100644 index 0000000..f6ca88c --- /dev/null +++ b/lib/ParseErrorException.php @@ -0,0 +1,70 @@ +rawMessage = (string) $message; + $this->rawLine = (int) $line; + $this->updateMessage(); + } + + /** + * Gets the error message + * + * @return string Error message + */ + public function getRawMessage() { + return $this->message; + } + + /** + * Sets the line of the PHP file the error occurred in. + * + * @param string $message Error message + */ + public function setRawMessage($message) { + $this->message = (string) $message; + $this->updateMessage(); + } + + /** + * Gets the error line in the PHP file. + * + * @return int Error line in the PHP file + */ + public function getRawLine() { + return $this->rawLine; + } + + /** + * Sets the line of the PHP file the error occurred in. + * + * @param int $line Error line in the PHP file + */ + public function setRawLine($line) { + $this->rawLine = (int) $line; + $this->updateMessage(); + } + + /** + * Updates the exception message after a change to rawMessage or rawLine. + */ + protected function updateMessage() { + $this->message = $this->rawMessage; + + if (-1 === $this->rawLine) { + $this->message .= ' on unknown line'; + } else { + $this->message .= ' on line ' . $this->rawLine; + } + } +} \ No newline at end of file diff --git a/lib/Parser.php b/lib/Parser.php index be22cb2..ecfdeb6 100644 --- a/lib/Parser.php +++ b/lib/Parser.php @@ -887,12 +887,11 @@ class Parser /** * Parses PHP code into a node tree. * - * @param Lexer $lex A lexer - * @param callback $errorCallback Function to be passed a message in case of an error. + * @param Lexer $lex A lexer * * @return array Array of statements */ - public function parse(Lexer $lex, $errorCallback) { + public function parse(Lexer $lex) { $this->yyastk = array(); $yysstk = array(); $this->yysp = 0; @@ -937,8 +936,8 @@ class Parser $this->yyastk[$this->yysp] = $yylval; $yychar = -1; - if ($yyerrflag > 0) - --$yyerrflag; + /*if ($yyerrflag > 0) + --$yyerrflag;*/ if ($yyn < self::YYNLSTATES) continue; @@ -959,7 +958,13 @@ class Parser return $this->yyval; } elseif ($yyn != self::YYUNEXPECTED) { /* reduce */ - $this->{'yyn' . $yyn}(); + try { + $this->{'yyn' . $yyn}(); + } catch (ParseErrorException $e) { + $e->setRawLine($lex->getLine()); + + throw $e; + } /* Goto - shift nonterminal */ $this->yysp -= self::$yylen[$yyn]; @@ -978,18 +983,17 @@ class Parser $this->yyastk[$this->yysp] = $this->yyval; } else { /* error */ - switch ($yyerrflag) { - case 0: - $errorCallback( - 'Parse error:' - . ' Unexpected token ' . self::$yyterminals[$yychar] - . ' on line ' . $lex->getLine() + /*switch ($yyerrflag) { + case 0:*/ + throw new ParseErrorException( + 'Unexpected token ' . self::$yyterminals[$yychar], + $lex->getLine() ); - case 1: + /*case 1: case 2: - $yyerrflag = 3; + $yyerrflag = 3;*/ /* Pop until error-expecting state uncovered */ - while (!(($yyn = self::$yybase[$yystate] + self::YYINTERRTOK) >= 0 + /*while (!(($yyn = self::$yybase[$yystate] + self::YYINTERRTOK) >= 0 && $yyn < self::YYLAST && self::$yycheck[$yyn] == self::YYINTERRTOK || ($yystate < self::YY2TBLSTATE @@ -1011,7 +1015,7 @@ class Parser } $yychar = -1; break; - } + }*/ } if ($yystate < self::YYNLSTATES) @@ -1139,7 +1143,7 @@ class Parser } private function yyn29() { - error('__halt_compiler() can only be used from the outermost scope'); + throw new ParseErrorException('__halt_compiler() can only be used from the outermost scope'); } private function yyn30() { diff --git a/lib/ParserDebug.php b/lib/ParserDebug.php index 8c238e4..c72f5cc 100644 --- a/lib/ParserDebug.php +++ b/lib/ParserDebug.php @@ -1281,12 +1281,11 @@ class ParserDebug /** * Parses PHP code into a node tree and prints out debugging information. * - * @param Lexer $lex A lexer - * @param callback $errorCallback Function to be passed a message in case of an error. + * @param Lexer $lex A lexer * * @return array Array of statements */ - public function parse(Lexer $lex, $errorCallback) { + public function parse(Lexer $lex) { $this->yyastk = array(); $yysstk = array(); $this->yysp = 0; @@ -1334,8 +1333,8 @@ class ParserDebug $this->yyastk[$this->yysp] = $yylval; $yychar = -1; - if ($yyerrflag > 0) - --$yyerrflag; + /*if ($yyerrflag > 0) + --$yyerrflag;*/ if ($yyn < self::YYNLSTATES) continue; @@ -1358,7 +1357,13 @@ class ParserDebug } elseif ($yyn != self::YYUNEXPECTED) { /* reduce */ $this->YYTRACE_REDUCE($yyn); - $this->{'yyn' . $yyn}(); + try { + $this->{'yyn' . $yyn}(); + } catch (ParseErrorException $e) { + $e->setRawLine($lex->getLine()); + + throw $e; + } /* Goto - shift nonterminal */ $this->yysp -= self::$yylen[$yyn]; @@ -1377,18 +1382,17 @@ class ParserDebug $this->yyastk[$this->yysp] = $this->yyval; } else { /* error */ - switch ($yyerrflag) { - case 0: - $errorCallback( - 'Parse error:' - . ' Unexpected token ' . self::$yyterminals[$yychar] - . ' on line ' . $lex->getLine() + /*switch ($yyerrflag) { + case 0:*/ + throw new ParseErrorException( + 'Unexpected token ' . self::$yyterminals[$yychar], + $lex->getLine() ); - case 1: + /*case 1: case 2: - $yyerrflag = 3; + $yyerrflag = 3;*/ /* Pop until error-expecting state uncovered */ - while (!(($yyn = self::$yybase[$yystate] + self::YYINTERRTOK) >= 0 + /*while (!(($yyn = self::$yybase[$yystate] + self::YYINTERRTOK) >= 0 && $yyn < self::YYLAST && self::$yycheck[$yyn] == self::YYINTERRTOK || ($yystate < self::YY2TBLSTATE @@ -1413,7 +1417,7 @@ class ParserDebug } $yychar = -1; break; - } + }*/ } if ($yystate < self::YYNLSTATES) @@ -1541,7 +1545,7 @@ class ParserDebug } private function yyn29() { - error('__halt_compiler() can only be used from the outermost scope'); + throw new ParseErrorException('__halt_compiler() can only be used from the outermost scope'); } private function yyn30() { diff --git a/test/testAgainstDirectory.php b/test/testAgainstDirectory.php index c29196e..0fba13a 100644 --- a/test/testAgainstDirectory.php +++ b/test/testAgainstDirectory.php @@ -22,9 +22,11 @@ echo ' $parseFail = $parseCount = $ppFail = $ppCount = $compareFail = $compareCount = 0; -$totalStartTime = microtime(true); $parseTime = $ppTime = $compareTime = 0; +$totalStartTime = microtime(true); + + foreach (new RecursiveIteratorIterator( new RecursiveDirectoryIterator($DIR), RecursiveIteratorIterator::LEAVES_ONLY) @@ -39,36 +41,24 @@ foreach (new RecursiveIteratorIterator( set_time_limit(10); - $errMsg = ''; + try { + ++$parseCount; + $startTime = microtime(true); + $stmts = $parser->parse(new Lexer(file_get_contents($file))); + $parseTime += microtime(true) - $startTime; - ++$parseCount; - $parseTime -= microtime(true); - $stmts = $parser->parse( - new Lexer(file_get_contents($file)), - function ($msg) use (&$errMsg) { - $errMsg = $msg; - } - ); - $parseTime += microtime(true); - - if (false !== $stmts) { ++$ppCount; - $ppTime -= microtime(true); + $startTime = microtime(true); $code = 'prettyPrint($stmts); - $ppTime += microtime(true); + $ppTime += microtime(true) - $startTime; - $ppStmts = $parser->parse( - new Lexer($code), - function ($msg) use (&$errMsg) { - $errMsg = $msg; - } - ); + try { + $ppStmts = $parser->parse(new Lexer($code)); - if (false !== $ppStmts) { ++$compareCount; - $compareTime -= microtime(true); + $startTime = microtime(true); $same = $nodeDumper->dump($stmts) == $nodeDumper->dump($ppStmts); - $compareTime += microtime(true); + $compareTime += microtime(true) - $startTime; if ($same) { echo ' @@ -85,22 +75,23 @@ foreach (new RecursiveIteratorIterator( ++$compareFail; } - } else { + } catch (ParseErrorException $e) { echo ' - '; + + '; ++$ppFail; } - } else { + } catch (ParseErrorException $e) { echo ' - '; + '; ++$parseFail; } diff --git a/test/testExpressions.php b/test/testExpressions.php index 78d52c5..9c4239b 100644 --- a/test/testExpressions.php +++ b/test/testExpressions.php @@ -31,16 +31,13 @@ foreach (explode("\n", $exprs) as $expr) { continue; } - if ($parser->parse( - new Lexer('parse(new Lexer(''; - } else { + } catch (ParseErrorException $e) { echo ''; + echo ''; } }
PASS FAIL
' . $e->getMessage() . '
FAIL
' . $errMsg . '
' . $e->getMessage() . '
' . $expr . 'PASS
' . $expr . 'FAIL
' . $e->getMessage() . '