From 1a2546c2be839baa7d0a50dc056d4d6987d26852 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 13 May 2019 21:25:22 +0900 Subject: Backport racc-1.4.15 from upstream. --- test/racc/assets/cadenza.y | 170 + test/racc/assets/cast.y | 926 ++++ test/racc/assets/chk.y | 126 + test/racc/assets/conf.y | 16 + test/racc/assets/csspool.y | 729 ++++ test/racc/assets/digraph.y | 29 + test/racc/assets/echk.y | 118 + test/racc/assets/edtf.y | 583 +++ test/racc/assets/err.y | 60 + test/racc/assets/error_recovery.y | 35 + test/racc/assets/expect.y | 7 + test/racc/assets/firstline.y | 4 + test/racc/assets/huia.y | 318 ++ test/racc/assets/ichk.y | 102 + test/racc/assets/intp.y | 546 +++ test/racc/assets/journey.y | 47 + test/racc/assets/liquor.y | 313 ++ test/racc/assets/machete.y | 423 ++ test/racc/assets/macruby.y | 2197 ++++++++++ test/racc/assets/mailp.y | 437 ++ test/racc/assets/mediacloth.y | 599 +++ test/racc/assets/mof.y | 649 +++ test/racc/assets/namae.y | 302 ++ test/racc/assets/nasl.y | 626 +++ test/racc/assets/newsyn.y | 25 + test/racc/assets/noend.y | 4 + test/racc/assets/nokogiri-css.y | 255 ++ test/racc/assets/nonass.y | 41 + test/racc/assets/normal.y | 27 + test/racc/assets/norule.y | 4 + test/racc/assets/nullbug1.y | 25 + test/racc/assets/nullbug2.y | 15 + test/racc/assets/opal.y | 1807 ++++++++ test/racc/assets/opt.y | 123 + test/racc/assets/percent.y | 35 + test/racc/assets/php_serialization.y | 98 + test/racc/assets/recv.y | 97 + test/racc/assets/riml.y | 665 +++ test/racc/assets/rrconf.y | 14 + test/racc/assets/ruby18.y | 1943 +++++++++ test/racc/assets/ruby19.y | 2174 ++++++++++ test/racc/assets/ruby20.y | 2350 +++++++++++ test/racc/assets/ruby21.y | 2359 +++++++++++ test/racc/assets/ruby22.y | 2381 +++++++++++ test/racc/assets/scan.y | 72 + test/racc/assets/syntax.y | 50 + test/racc/assets/tp_plus.y | 622 +++ test/racc/assets/twowaysql.y | 278 ++ test/racc/assets/unterm.y | 5 + test/racc/assets/useless.y | 12 + test/racc/assets/yyerr.y | 46 + test/racc/bench.y | 36 + test/racc/helper.rb | 104 + test/racc/infini.y | 8 + test/racc/regress/README.txt | 7 + test/racc/regress/cadenza | 796 ++++ test/racc/regress/cast | 3425 +++++++++++++++ test/racc/regress/csspool | 2318 ++++++++++ test/racc/regress/edtf | 1794 ++++++++ test/racc/regress/huia | 1392 ++++++ test/racc/regress/journey | 222 + test/racc/regress/liquor | 885 ++++ test/racc/regress/machete | 833 ++++ test/racc/regress/mediacloth | 1463 +++++++ test/racc/regress/mof | 1368 ++++++ test/racc/regress/namae | 634 +++ test/racc/regress/nasl | 2058 +++++++++ test/racc/regress/nokogiri-css | 836 ++++ test/racc/regress/opal | 6429 ++++++++++++++++++++++++++++ test/racc/regress/php_serialization | 336 ++ test/racc/regress/riml | 3297 +++++++++++++++ test/racc/regress/ruby18 | 6351 ++++++++++++++++++++++++++++ test/racc/regress/ruby22 | 7456 +++++++++++++++++++++++++++++++++ test/racc/regress/tp_plus | 1933 +++++++++ test/racc/regress/twowaysql | 556 +++ test/racc/scandata/brace | 7 + test/racc/scandata/gvar | 1 + test/racc/scandata/normal | 4 + test/racc/scandata/percent | 18 + test/racc/scandata/slash | 10 + test/racc/src.intp | 34 + test/racc/start.y | 20 + test/racc/test_chk_y.rb | 51 + test/racc/test_grammar_file_parser.rb | 15 + test/racc/test_racc_command.rb | 322 ++ test/racc/test_scan_y.rb | 51 + test/racc/testscanner.rb | 51 + 87 files changed, 70010 insertions(+) create mode 100644 test/racc/assets/cadenza.y create mode 100644 test/racc/assets/cast.y create mode 100644 test/racc/assets/chk.y create mode 100644 test/racc/assets/conf.y create mode 100644 test/racc/assets/csspool.y create mode 100644 test/racc/assets/digraph.y create mode 100644 test/racc/assets/echk.y create mode 100644 test/racc/assets/edtf.y create mode 100644 test/racc/assets/err.y create mode 100644 test/racc/assets/error_recovery.y create mode 100644 test/racc/assets/expect.y create mode 100644 test/racc/assets/firstline.y create mode 100644 test/racc/assets/huia.y create mode 100644 test/racc/assets/ichk.y create mode 100644 test/racc/assets/intp.y create mode 100644 test/racc/assets/journey.y create mode 100644 test/racc/assets/liquor.y create mode 100644 test/racc/assets/machete.y create mode 100644 test/racc/assets/macruby.y create mode 100644 test/racc/assets/mailp.y create mode 100644 test/racc/assets/mediacloth.y create mode 100644 test/racc/assets/mof.y create mode 100644 test/racc/assets/namae.y create mode 100644 test/racc/assets/nasl.y create mode 100644 test/racc/assets/newsyn.y create mode 100644 test/racc/assets/noend.y create mode 100644 test/racc/assets/nokogiri-css.y create mode 100644 test/racc/assets/nonass.y create mode 100644 test/racc/assets/normal.y create mode 100644 test/racc/assets/norule.y create mode 100644 test/racc/assets/nullbug1.y create mode 100644 test/racc/assets/nullbug2.y create mode 100644 test/racc/assets/opal.y create mode 100644 test/racc/assets/opt.y create mode 100644 test/racc/assets/percent.y create mode 100644 test/racc/assets/php_serialization.y create mode 100644 test/racc/assets/recv.y create mode 100644 test/racc/assets/riml.y create mode 100644 test/racc/assets/rrconf.y create mode 100644 test/racc/assets/ruby18.y create mode 100644 test/racc/assets/ruby19.y create mode 100644 test/racc/assets/ruby20.y create mode 100644 test/racc/assets/ruby21.y create mode 100644 test/racc/assets/ruby22.y create mode 100644 test/racc/assets/scan.y create mode 100644 test/racc/assets/syntax.y create mode 100644 test/racc/assets/tp_plus.y create mode 100644 test/racc/assets/twowaysql.y create mode 100644 test/racc/assets/unterm.y create mode 100644 test/racc/assets/useless.y create mode 100644 test/racc/assets/yyerr.y create mode 100644 test/racc/bench.y create mode 100644 test/racc/helper.rb create mode 100644 test/racc/infini.y create mode 100644 test/racc/regress/README.txt create mode 100644 test/racc/regress/cadenza create mode 100644 test/racc/regress/cast create mode 100644 test/racc/regress/csspool create mode 100644 test/racc/regress/edtf create mode 100644 test/racc/regress/huia create mode 100644 test/racc/regress/journey create mode 100644 test/racc/regress/liquor create mode 100644 test/racc/regress/machete create mode 100644 test/racc/regress/mediacloth create mode 100644 test/racc/regress/mof create mode 100644 test/racc/regress/namae create mode 100644 test/racc/regress/nasl create mode 100644 test/racc/regress/nokogiri-css create mode 100644 test/racc/regress/opal create mode 100644 test/racc/regress/php_serialization create mode 100644 test/racc/regress/riml create mode 100644 test/racc/regress/ruby18 create mode 100644 test/racc/regress/ruby22 create mode 100644 test/racc/regress/tp_plus create mode 100644 test/racc/regress/twowaysql create mode 100644 test/racc/scandata/brace create mode 100644 test/racc/scandata/gvar create mode 100644 test/racc/scandata/normal create mode 100644 test/racc/scandata/percent create mode 100644 test/racc/scandata/slash create mode 100644 test/racc/src.intp create mode 100644 test/racc/start.y create mode 100644 test/racc/test_chk_y.rb create mode 100644 test/racc/test_grammar_file_parser.rb create mode 100644 test/racc/test_racc_command.rb create mode 100644 test/racc/test_scan_y.rb create mode 100644 test/racc/testscanner.rb (limited to 'test/racc') diff --git a/test/racc/assets/cadenza.y b/test/racc/assets/cadenza.y new file mode 100644 index 0000000000..1940ead225 --- /dev/null +++ b/test/racc/assets/cadenza.y @@ -0,0 +1,170 @@ +# This grammar is released under an MIT license +# Author: William Howard (http://github.com/whoward) +# Source: https://github.com/whoward/cadenza/blob/master/src/cadenza.y + +class Cadenza::RaccParser + +/* expect this many shift/reduce conflicts */ +expect 37 + +rule + target + : document + | /* none */ { result = nil } + ; + + parameter_list + : logical_expression { result = [val[0]] } + | parameter_list ',' logical_expression { result = val[0].push(val[2]) } + ; + + /* this has a shift/reduce conflict but since Racc will shift in this case it is the correct behavior */ + primary_expression + : IDENTIFIER { result = VariableNode.new(val[0].value) } + | IDENTIFIER parameter_list { result = VariableNode.new(val[0].value, val[1]) } + | INTEGER { result = ConstantNode.new(val[0].value) } + | REAL { result = ConstantNode.new(val[0].value) } + | STRING { result = ConstantNode.new(val[0].value) } + | '(' filtered_expression ')' { result = val[1] } + ; + + multiplicative_expression + : primary_expression + | multiplicative_expression '*' primary_expression { result = OperationNode.new(val[0], "*", val[2]) } + | multiplicative_expression '/' primary_expression { result = OperationNode.new(val[0], "/", val[2]) } + ; + + additive_expression + : multiplicative_expression + | additive_expression '+' multiplicative_expression { result = OperationNode.new(val[0], "+", val[2]) } + | additive_expression '-' multiplicative_expression { result = OperationNode.new(val[0], "-", val[2]) } + ; + + boolean_expression + : additive_expression + | boolean_expression OP_EQ additive_expression { result = OperationNode.new(val[0], "==", val[2]) } + | boolean_expression OP_NEQ additive_expression { result = OperationNode.new(val[0], "!=", val[2]) } + | boolean_expression OP_LEQ additive_expression { result = OperationNode.new(val[0], "<=", val[2]) } + | boolean_expression OP_GEQ additive_expression { result = OperationNode.new(val[0], ">=", val[2]) } + | boolean_expression '>' additive_expression { result = OperationNode.new(val[0], ">", val[2]) } + | boolean_expression '<' additive_expression { result = OperationNode.new(val[0], "<", val[2]) } + ; + + inverse_expression + : boolean_expression + | NOT boolean_expression { result = BooleanInverseNode.new(val[1]) } + ; + + logical_expression + : inverse_expression + | logical_expression AND inverse_expression { result = OperationNode.new(val[0], "and", val[2]) } + | logical_expression OR inverse_expression { result = OperationNode.new(val[0], "or", val[2]) } + ; + + filter + : IDENTIFIER { result = FilterNode.new(val[0].value) } + | IDENTIFIER ':' parameter_list { result = FilterNode.new(val[0].value, val[2]) } + ; + + filter_list + : filter { result = [val[0]] } + | filter_list '|' filter { result = val[0].push(val[2]) } + ; + + filtered_expression + : logical_expression + | logical_expression '|' filter_list { result = FilteredValueNode.new(val[0], val[2]) } + ; + + inject_statement + : VAR_OPEN filtered_expression VAR_CLOSE { result = val[1] } + ; + + if_tag + : STMT_OPEN IF logical_expression STMT_CLOSE { open_scope!; result = val[2] } + | STMT_OPEN UNLESS logical_expression STMT_CLOSE { open_scope!; result = BooleanInverseNode.new(val[2]) } + ; + + else_tag + : STMT_OPEN ELSE STMT_CLOSE { result = close_scope!; open_scope! } + ; + + end_if_tag + : STMT_OPEN ENDIF STMT_CLOSE { result = close_scope! } + | STMT_OPEN ENDUNLESS STMT_CLOSE { result = close_scope! } + ; + + if_block + : if_tag end_if_tag { result = IfNode.new(val[0], val[1]) } + | if_tag document end_if_tag { result = IfNode.new(val[0], val[2]) } + | if_tag else_tag document end_if_tag { result = IfNode.new(val[0], val[1], val[3]) } + | if_tag document else_tag end_if_tag { result = IfNode.new(val[0], val[2], val[3]) } + | if_tag document else_tag document end_if_tag { result = IfNode.new(val[0], val[2], val[4]) } + ; + + for_tag + : STMT_OPEN FOR IDENTIFIER IN filtered_expression STMT_CLOSE { open_scope!; result = [val[2].value, val[4]] } + ; + + end_for_tag + : STMT_OPEN ENDFOR STMT_CLOSE { result = close_scope! } + ; + + /* this has a shift/reduce conflict but since Racc will shift in this case it is the correct behavior */ + for_block + : for_tag end_for_tag { result = ForNode.new(VariableNode.new(val[0].first), val[0].last, val[1]) } + | for_tag document end_for_tag { result = ForNode.new(VariableNode.new(val[0].first), val[0].last, val[2]) } + ; + + block_tag + : STMT_OPEN BLOCK IDENTIFIER STMT_CLOSE { result = open_block_scope!(val[2].value) } + ; + + end_block_tag + : STMT_OPEN ENDBLOCK STMT_CLOSE { result = close_block_scope! } + ; + + /* this has a shift/reduce conflict but since Racc will shift in this case it is the correct behavior */ + block_block + : block_tag end_block_tag { result = BlockNode.new(val[0], val[1]) } + | block_tag document end_block_tag { result = BlockNode.new(val[0], val[2]) } + ; + + generic_block_tag + : STMT_OPEN IDENTIFIER STMT_CLOSE { open_scope!; result = [val[1].value, []] } + | STMT_OPEN IDENTIFIER parameter_list STMT_CLOSE { open_scope!; result = [val[1].value, val[2]] } + ; + + end_generic_block_tag + : STMT_OPEN END STMT_CLOSE { result = close_scope! } + ; + + generic_block + : generic_block_tag document end_generic_block_tag { result = GenericBlockNode.new(val[0].first, val[2], val[0].last) } + ; + + extends_statement + : STMT_OPEN EXTENDS STRING STMT_CLOSE { result = val[2].value } + | STMT_OPEN EXTENDS IDENTIFIER STMT_CLOSE { result = VariableNode.new(val[2].value) } + ; + + document_component + : TEXT_BLOCK { result = TextNode.new(val[0].value) } + | inject_statement + | if_block + | for_block + | generic_block + | block_block + ; + + document + : document_component { push val[0] } + | document document_component { push val[1] } + | extends_statement { document.extends = val[0] } + | document extends_statement { document.extends = val[1] } + ; + +---- header ---- +# racc_parser.rb : generated by racc + +---- inner ---- diff --git a/test/racc/assets/cast.y b/test/racc/assets/cast.y new file mode 100644 index 0000000000..d180c09e14 --- /dev/null +++ b/test/racc/assets/cast.y @@ -0,0 +1,926 @@ +# The MIT License +# +# Copyright (c) George Ogata +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +class C::Parser +# shift/reduce conflict on "if (c) if (c) ; else ; else ;" +expect 1 +rule + +# A.2.4 External definitions + +# Returns TranslationUnit +translation_unit + : external_declaration {result = TranslationUnit.new_at(val[0].pos, NodeChain[val[0]])} + | translation_unit external_declaration {result = val[0]; result.entities << val[1]} + +# Returns Declaration|FunctionDef +external_declaration + : function_definition {result = val[0]} + | declaration {result = val[0]} + +# Returns FunctionDef +function_definition + : declaration_specifiers declarator declaration_list compound_statement {result = make_function_def(val[0][0], val[0][1], val[1], val[2], val[3])} + | declaration_specifiers declarator compound_statement {result = make_function_def(val[0][0], val[0][1], val[1], nil , val[2])} + +# Returns [Declaration] +declaration_list + : declaration {result = [val[0]]} + | declaration_list declaration {result = val[0] << val[1]} + +# A.2.3 Statements + +# Returns Statement +statement + : labeled_statement {result = val[0]} + | compound_statement {result = val[0]} + | expression_statement {result = val[0]} + | selection_statement {result = val[0]} + | iteration_statement {result = val[0]} + | jump_statement {result = val[0]} + +# Returns Statement +labeled_statement + : identifier COLON statement {val[2].labels.unshift(PlainLabel.new_at(val[0].pos, val[0].val)); result = val[2]} + | CASE constant_expression COLON statement {val[3].labels.unshift(Case .new_at(val[0].pos, val[1] )); result = val[3]} + | DEFAULT COLON statement {val[2].labels.unshift(Default .new_at(val[0].pos )); result = val[2]} + # type names can also be used as labels + | typedef_name COLON statement {val[2].labels.unshift(PlainLabel.new_at(val[0].pos, val[0].name)); result = val[2]} + +# Returns Block +compound_statement + : LBRACE block_item_list RBRACE {result = Block.new_at(val[0].pos, val[1])} + | LBRACE RBRACE {result = Block.new_at(val[0].pos )} + +# Returns NodeChain[Declaration|Statement] +block_item_list + : block_item {result = NodeChain[val[0]]} + | block_item_list block_item {result = val[0] << val[1]} + +# Returns Declaration|Statement +block_item + : declaration {result = val[0]} + | statement {result = val[0]} + +# Returns ExpressionStatement +expression_statement + : expression SEMICOLON {result = ExpressionStatement.new_at(val[0].pos, val[0])} + | SEMICOLON {result = ExpressionStatement.new_at(val[0].pos )} + +# Returns Statement +selection_statement + : IF LPAREN expression RPAREN statement {result = If .new_at(val[0].pos, val[2], val[4] )} + | IF LPAREN expression RPAREN statement ELSE statement {result = If .new_at(val[0].pos, val[2], val[4], val[6])} + | SWITCH LPAREN expression RPAREN statement {result = Switch.new_at(val[0].pos, val[2], val[4] )} + +# Returns Statement +iteration_statement + : WHILE LPAREN expression RPAREN statement {result = While.new_at(val[0].pos, val[2], val[4] )} + | DO statement WHILE LPAREN expression RPAREN SEMICOLON {result = While.new_at(val[0].pos, val[4], val[1], :do => true )} + | FOR LPAREN expression SEMICOLON expression SEMICOLON expression RPAREN statement {result = For.new_at(val[0].pos, val[2], val[4], val[6], val[8])} + | FOR LPAREN expression SEMICOLON expression SEMICOLON RPAREN statement {result = For.new_at(val[0].pos, val[2], val[4], nil , val[7])} + | FOR LPAREN expression SEMICOLON SEMICOLON expression RPAREN statement {result = For.new_at(val[0].pos, val[2], nil , val[5], val[7])} + | FOR LPAREN expression SEMICOLON SEMICOLON RPAREN statement {result = For.new_at(val[0].pos, val[2], nil , nil , val[6])} + | FOR LPAREN SEMICOLON expression SEMICOLON expression RPAREN statement {result = For.new_at(val[0].pos, nil , val[3], val[5], val[7])} + | FOR LPAREN SEMICOLON expression SEMICOLON RPAREN statement {result = For.new_at(val[0].pos, nil , val[3], nil , val[6])} + | FOR LPAREN SEMICOLON SEMICOLON expression RPAREN statement {result = For.new_at(val[0].pos, nil , nil , val[4], val[6])} + | FOR LPAREN SEMICOLON SEMICOLON RPAREN statement {result = For.new_at(val[0].pos, nil , nil , nil , val[5])} + | FOR LPAREN declaration expression SEMICOLON expression RPAREN statement {result = For.new_at(val[0].pos, val[2], val[3], val[5], val[7])} + | FOR LPAREN declaration expression SEMICOLON RPAREN statement {result = For.new_at(val[0].pos, val[2], val[3], nil , val[6])} + | FOR LPAREN declaration SEMICOLON expression RPAREN statement {result = For.new_at(val[0].pos, val[2], nil , val[4], val[6])} + | FOR LPAREN declaration SEMICOLON RPAREN statement {result = For.new_at(val[0].pos, val[2], nil , nil , val[5])} + +# Returns Statement +jump_statement + : GOTO identifier SEMICOLON {result = Goto .new_at(val[0].pos, val[1].val)} + | CONTINUE SEMICOLON {result = Continue.new_at(val[0].pos )} + | BREAK SEMICOLON {result = Break .new_at(val[0].pos )} + | RETURN expression SEMICOLON {result = Return .new_at(val[0].pos, val[1] )} + | RETURN SEMICOLON {result = Return .new_at(val[0].pos )} + # type names can also be used as labels + | GOTO typedef_name SEMICOLON {result = Goto .new_at(val[0].pos, val[1].name)} + +# A.2.2 Declarations + +# Returns Declaration +declaration + : declaration_specifiers init_declarator_list SEMICOLON {result = make_declaration(val[0][0], val[0][1], val[1])} + | declaration_specifiers SEMICOLON {result = make_declaration(val[0][0], val[0][1], NodeArray[])} + +# Returns {Pos, [Symbol]} +declaration_specifiers + : storage_class_specifier declaration_specifiers {val[1][1] << val[0][1]; result = val[1]} + | storage_class_specifier {result = [val[0][0], [val[0][1]]]} + | type_specifier declaration_specifiers {val[1][1] << val[0][1]; result = val[1]} + | type_specifier {result = [val[0][0], [val[0][1]]]} + | type_qualifier declaration_specifiers {val[1][1] << val[0][1]; result = val[1]} + | type_qualifier {result = [val[0][0], [val[0][1]]]} + | function_specifier declaration_specifiers {val[1][1] << val[0][1]; result = val[1]} + | function_specifier {result = [val[0][0], [val[0][1]]]} + +# Returns NodeArray[Declarator] +init_declarator_list + : init_declarator {result = NodeArray[val[0]]} + | init_declarator_list COMMA init_declarator {result = val[0] << val[2]} + +# Returns Declarator +init_declarator + : declarator {result = val[0]} + | declarator EQ initializer {val[0].init = val[2]; result = val[0]} + +# Returns [Pos, Symbol] +storage_class_specifier + : TYPEDEF {result = [val[0].pos, :typedef ]} + | EXTERN {result = [val[0].pos, :extern ]} + | STATIC {result = [val[0].pos, :static ]} + | AUTO {result = [val[0].pos, :auto ]} + | REGISTER {result = [val[0].pos, :register]} + +# Returns [Pos, Type|Symbol] +type_specifier + : VOID {result = [val[0].pos, :void ]} + | CHAR {result = [val[0].pos, :char ]} + | SHORT {result = [val[0].pos, :short ]} + | INT {result = [val[0].pos, :int ]} + | LONG {result = [val[0].pos, :long ]} + | FLOAT {result = [val[0].pos, :float ]} + | DOUBLE {result = [val[0].pos, :double ]} + | SIGNED {result = [val[0].pos, :signed ]} + | UNSIGNED {result = [val[0].pos, :unsigned ]} + | BOOL {result = [val[0].pos, :_Bool ]} + | COMPLEX {result = [val[0].pos, :_Complex ]} + | IMAGINARY {result = [val[0].pos, :_Imaginary]} + | struct_or_union_specifier {result = [val[0].pos, val[0] ]} + | enum_specifier {result = [val[0].pos, val[0] ]} + | typedef_name {result = [val[0].pos, val[0] ]} + +# Returns Struct|Union +struct_or_union_specifier + : struct_or_union identifier LBRACE struct_declaration_list RBRACE {result = val[0][1].new_at(val[0][0], val[1].val, val[3])} + | struct_or_union LBRACE struct_declaration_list RBRACE {result = val[0][1].new_at(val[0][0], nil , val[2])} + | struct_or_union identifier {result = val[0][1].new_at(val[0][0], val[1].val, nil )} + # type names can also be used as struct identifiers + | struct_or_union typedef_name LBRACE struct_declaration_list RBRACE {result = val[0][1].new_at(val[0][0], val[1].name, val[3])} + | struct_or_union typedef_name {result = val[0][1].new_at(val[0][0], val[1].name, nil )} + +# Returns [Pos, Class] +struct_or_union + : STRUCT {result = [val[0].pos, Struct]} + | UNION {result = [val[0].pos, Union ]} + +# Returns NodeArray[Declaration] +struct_declaration_list + : struct_declaration {result = NodeArray[val[0]]} + | struct_declaration_list struct_declaration {val[0] << val[1]; result = val[0]} + +# Returns Declaration +struct_declaration + : specifier_qualifier_list struct_declarator_list SEMICOLON {result = make_declaration(val[0][0], val[0][1], val[1])} + +# Returns {Pos, [Symbol]} +specifier_qualifier_list + : type_specifier specifier_qualifier_list {val[1][1] << val[0][1]; result = val[1]} + | type_specifier {result = [val[0][0], [val[0][1]]]} + | type_qualifier specifier_qualifier_list {val[1][1] << val[0][1]; result = val[1]} + | type_qualifier {result = [val[0][0], [val[0][1]]]} + +# Returns NodeArray[Declarator] +struct_declarator_list + : struct_declarator {result = NodeArray[val[0]]} + | struct_declarator_list COMMA struct_declarator {result = val[0] << val[2]} + +# Returns Declarator +struct_declarator + : declarator {result = val[0]} + | declarator COLON constant_expression {result = val[0]; val[0].num_bits = val[2]} + | COLON constant_expression {result = Declarator.new_at(val[0].pos, :num_bits => val[1])} + +# Returns Enum +enum_specifier + : ENUM identifier LBRACE enumerator_list RBRACE {result = Enum.new_at(val[0].pos, val[1].val, val[3])} + | ENUM LBRACE enumerator_list RBRACE {result = Enum.new_at(val[0].pos, nil , val[2])} + | ENUM identifier LBRACE enumerator_list COMMA RBRACE {result = Enum.new_at(val[0].pos, val[1].val, val[3])} + | ENUM LBRACE enumerator_list COMMA RBRACE {result = Enum.new_at(val[0].pos, nil , val[2])} + | ENUM identifier {result = Enum.new_at(val[0].pos, val[1].val, nil )} + # type names can also be used as enum names + | ENUM typedef_name LBRACE enumerator_list RBRACE {result = Enum.new_at(val[0].pos, val[1].name, val[3])} + | ENUM typedef_name LBRACE enumerator_list COMMA RBRACE {result = Enum.new_at(val[0].pos, val[1].name, val[3])} + | ENUM typedef_name {result = Enum.new_at(val[0].pos, val[1].name, nil )} + +# Returns NodeArray[Enumerator] +enumerator_list + : enumerator {result = NodeArray[val[0]]} + | enumerator_list COMMA enumerator {result = val[0] << val[2]} + +# Returns Enumerator +enumerator + : enumeration_constant {result = Enumerator.new_at(val[0].pos, val[0].val, nil )} + | enumeration_constant EQ constant_expression {result = Enumerator.new_at(val[0].pos, val[0].val, val[2])} + +# Returns [Pos, Symbol] +type_qualifier + : CONST {result = [val[0].pos, :const ]} + | RESTRICT {result = [val[0].pos, :restrict]} + | VOLATILE {result = [val[0].pos, :volatile]} + +# Returns [Pos, Symbol] +function_specifier + : INLINE {result = [val[0].pos, :inline]} + +# Returns Declarator +declarator + : pointer direct_declarator {result = add_decl_type(val[1], val[0])} + | direct_declarator {result = val[0]} + +# Returns Declarator +direct_declarator + : identifier {result = Declarator.new_at(val[0].pos, nil, val[0].val)} + | LPAREN declarator RPAREN {result = val[1]} + | direct_declarator LBRACKET type_qualifier_list assignment_expression RBRACKET {result = add_decl_type(val[0], Array.new_at(val[0].pos ))} # TODO + | direct_declarator LBRACKET type_qualifier_list RBRACKET {result = add_decl_type(val[0], Array.new_at(val[0].pos ))} # TODO + | direct_declarator LBRACKET assignment_expression RBRACKET {result = add_decl_type(val[0], Array.new_at(val[0].pos, nil, val[2]))} + | direct_declarator LBRACKET RBRACKET {result = add_decl_type(val[0], Array.new_at(val[0].pos ))} + | direct_declarator LBRACKET STATIC type_qualifier_list assignment_expression RBRACKET {result = add_decl_type(val[0], Array.new_at(val[0].pos ))} # TODO + | direct_declarator LBRACKET STATIC assignment_expression RBRACKET {result = add_decl_type(val[0], Array.new_at(val[0].pos ))} # TODO + | direct_declarator LBRACKET type_qualifier_list STATIC assignment_expression RBRACKET {result = add_decl_type(val[0], Array.new_at(val[0].pos ))} # TODO + | direct_declarator LBRACKET type_qualifier_list MUL RBRACKET {result = add_decl_type(val[0], Array.new_at(val[0].pos ))} # TODO + | direct_declarator LBRACKET MUL RBRACKET {result = add_decl_type(val[0], Array.new_at(val[0].pos ))} # TODO + | direct_declarator LPAREN parameter_type_list RPAREN {result = add_decl_type(val[0], Function.new_at(val[0].pos, nil, param_list(*val[2]), :var_args => val[2][1]))} + | direct_declarator LPAREN identifier_list RPAREN {result = add_decl_type(val[0], Function.new_at(val[0].pos, nil, val[2]))} + | direct_declarator LPAREN RPAREN {result = add_decl_type(val[0], Function.new_at(val[0].pos ))} + +# Returns Pointer +pointer + : MUL type_qualifier_list {result = add_type_quals(Pointer.new_at(val[0].pos), val[1][1]) } + | MUL {result = Pointer.new_at(val[0].pos) } + | MUL type_qualifier_list pointer {p = add_type_quals(Pointer.new_at(val[0].pos), val[1][1]); val[2].direct_type = p; result = val[2]} + | MUL pointer {p = Pointer.new_at(val[0].pos) ; val[1].direct_type = p; result = val[1]} + +# Returns {Pos, [Symbol]} +type_qualifier_list + : type_qualifier {result = [val[0][0], [val[0][1]]]} + | type_qualifier_list type_qualifier {val[0][1] << val[1][1]; result = val[0]} + +# Returns [NodeArray[Parameter], var_args?] +parameter_type_list + : parameter_list {result = [val[0], false]} + | parameter_list COMMA ELLIPSIS {result = [val[0], true ]} + +# Returns NodeArray[Parameter] +parameter_list + : parameter_declaration {result = NodeArray[val[0]]} + | parameter_list COMMA parameter_declaration {result = val[0] << val[2]} + +# Returns Parameter +parameter_declaration + : declaration_specifiers declarator {ind_type = val[1].indirect_type and ind_type.detach + result = make_parameter(val[0][0], val[0][1], ind_type, val[1].name)} + | declaration_specifiers abstract_declarator {result = make_parameter(val[0][0], val[0][1], val[1] , nil )} + | declaration_specifiers {result = make_parameter(val[0][0], val[0][1], nil , nil )} + +# Returns NodeArray[Parameter] +identifier_list + : identifier {result = NodeArray[Parameter.new_at(val[0].pos, nil, val[0].val)]} + | identifier_list COMMA identifier {result = val[0] << Parameter.new_at(val[2].pos, nil, val[2].val)} + +# Returns Type +type_name + : specifier_qualifier_list abstract_declarator {val[1].direct_type = make_direct_type(val[0][0], val[0][1]); result = val[1]} + | specifier_qualifier_list {result = make_direct_type(val[0][0], val[0][1]) } + +# Returns Type +abstract_declarator + : pointer {result = val[0]} + | pointer direct_abstract_declarator {val[1].direct_type = val[0]; result = val[1]} + | direct_abstract_declarator {result = val[0]} + +# Returns Type +direct_abstract_declarator + : LPAREN abstract_declarator RPAREN {result = val[1]} + | direct_abstract_declarator LBRACKET assignment_expression RBRACKET {val[0].direct_type = Array.new_at(val[0].pos, nil, val[2]); result = val[0]} + | direct_abstract_declarator LBRACKET RBRACKET {val[0].direct_type = Array.new_at(val[0].pos, nil, nil ); result = val[0]} + | LBRACKET assignment_expression RBRACKET {result = Array.new_at(val[0].pos, nil, val[1])} + | LBRACKET RBRACKET {result = Array.new_at(val[0].pos )} + | direct_abstract_declarator LBRACKET MUL RBRACKET {val[0].direct_type = Array.new_at(val[0].pos); result = val[0]} # TODO + | LBRACKET MUL RBRACKET {result = Array.new_at(val[0].pos)} # TODO + | direct_abstract_declarator LPAREN parameter_type_list RPAREN {val[0].direct_type = Function.new_at(val[0].pos, nil, param_list(*val[2]), val[2][1]); result = val[0]} + | direct_abstract_declarator LPAREN RPAREN {val[0].direct_type = Function.new_at(val[0].pos ); result = val[0]} + | LPAREN parameter_type_list RPAREN {result = Function.new_at(val[0].pos, nil, param_list(*val[1]), val[1][1])} + | LPAREN RPAREN {result = Function.new_at(val[0].pos )} + +# Returns CustomType +typedef_name + #: identifier -- insufficient since we must distinguish between type + # names and var names (otherwise we have a conflict) + : TYPENAME {result = CustomType.new_at(val[0].pos, val[0].val)} + +# Returns Expression +initializer + : assignment_expression {result = val[0]} + | LBRACE initializer_list RBRACE {result = CompoundLiteral.new_at(val[0].pos, nil, val[1])} + | LBRACE initializer_list COMMA RBRACE {result = CompoundLiteral.new_at(val[0].pos, nil, val[1])} + +# Returns NodeArray[MemberInit] +initializer_list + : designation initializer {result = NodeArray[MemberInit.new_at(val[0][0] , val[0][1], val[1])]} + | initializer {result = NodeArray[MemberInit.new_at(val[0].pos, nil , val[0])]} + | initializer_list COMMA designation initializer {result = val[0] << MemberInit.new_at(val[2][0] , val[2][1], val[3])} + | initializer_list COMMA initializer {result = val[0] << MemberInit.new_at(val[2].pos, nil , val[2])} + +# Returns {Pos, NodeArray[Expression|Token]} +designation + : designator_list EQ {result = val[0]} + +# Returns {Pos, NodeArray[Expression|Token]} +designator_list + : designator {result = val[0]; val[0][1] = NodeArray[val[0][1]]} + | designator_list designator {result = val[0]; val[0][1] << val[1][1]} + +# Returns {Pos, Expression|Member} +designator + : LBRACKET constant_expression RBRACKET {result = [val[1].pos, val[1] ]} + | DOT identifier {result = [val[1].pos, Member.new_at(val[1].pos, val[1].val)]} + +# A.2.1 Expressions + +# Returns Expression +primary_expression + : identifier {result = Variable.new_at(val[0].pos, val[0].val)} + | constant {result = val[0]} + | string_literal {result = val[0]} + # GCC EXTENSION: allow a compound statement in parentheses as an expression + | LPAREN expression RPAREN {result = val[1]} + | LPAREN compound_statement RPAREN {block_expressions_enabled? or parse_error val[0].pos, "compound statement found where expression expected" + result = BlockExpression.new(val[1]); result.pos = val[0].pos} + +# Returns Expression +postfix_expression + : primary_expression {result = val[0]} + | postfix_expression LBRACKET expression RBRACKET {result = Index .new_at(val[0].pos, val[0], val[2])} + | postfix_expression LPAREN argument_expression_list RPAREN {result = Call .new_at(val[0].pos, val[0], val[2] )} + | postfix_expression LPAREN RPAREN {result = Call .new_at(val[0].pos, val[0], NodeArray[])} + | postfix_expression DOT identifier {result = Dot .new_at(val[0].pos, val[0], Member.new(val[2].val))} + | postfix_expression ARROW identifier {result = Arrow .new_at(val[0].pos, val[0], Member.new(val[2].val))} + | postfix_expression INC {result = PostInc .new_at(val[0].pos, val[0] )} + | postfix_expression DEC {result = PostDec .new_at(val[0].pos, val[0] )} + | LPAREN type_name RPAREN LBRACE initializer_list RBRACE {result = CompoundLiteral.new_at(val[0].pos, val[1], val[4])} + | LPAREN type_name RPAREN LBRACE initializer_list COMMA RBRACE {result = CompoundLiteral.new_at(val[0].pos, val[1], val[4])} + +# Returns [Expression|Type] +argument_expression_list + : argument_expression {result = NodeArray[val[0]]} + | argument_expression_list COMMA argument_expression {result = val[0] << val[2]} + +# Returns Expression|Type -- EXTENSION: allow type names here too, to support some standard library macros (e.g., va_arg [7.15.1.1]) +argument_expression + : assignment_expression {result = val[0]} + | type_name {result = val[0]} + +# Returns Expression +unary_expression + : postfix_expression {result = val[0]} + | INC unary_expression {result = PreInc.new_at(val[0].pos, val[1])} + | DEC unary_expression {result = PreDec.new_at(val[0].pos, val[1])} + | unary_operator cast_expression {result = val[0][0].new_at(val[0][1], val[1])} + | SIZEOF unary_expression {result = Sizeof.new_at(val[0].pos, val[1])} + | SIZEOF LPAREN type_name RPAREN {result = Sizeof.new_at(val[0].pos, val[2])} + +# Returns [Class, Pos] +unary_operator + : AND {result = [Address , val[0].pos]} + | MUL {result = [Dereference, val[0].pos]} + | ADD {result = [Positive , val[0].pos]} + | SUB {result = [Negative , val[0].pos]} + | NOT {result = [BitNot , val[0].pos]} + | BANG {result = [Not , val[0].pos]} + +# Returns Expression +cast_expression + : unary_expression {result = val[0]} + | LPAREN type_name RPAREN cast_expression {result = Cast.new_at(val[0].pos, val[1], val[3])} + +# Returns Expression +multiplicative_expression + : cast_expression {result = val[0]} + | multiplicative_expression MUL cast_expression {result = Multiply.new_at(val[0].pos, val[0], val[2])} + | multiplicative_expression DIV cast_expression {result = Divide .new_at(val[0].pos, val[0], val[2])} + | multiplicative_expression MOD cast_expression {result = Mod .new_at(val[0].pos, val[0], val[2])} + +# Returns Expression +additive_expression + : multiplicative_expression {result = val[0]} + | additive_expression ADD multiplicative_expression {result = Add .new_at(val[0].pos, val[0], val[2])} + | additive_expression SUB multiplicative_expression {result = Subtract.new_at(val[0].pos, val[0], val[2])} + +# Returns Expression +shift_expression + : additive_expression {result = val[0]} + | shift_expression LSHIFT additive_expression {result = ShiftLeft .new_at(val[0].pos, val[0], val[2])} + | shift_expression RSHIFT additive_expression {result = ShiftRight.new_at(val[0].pos, val[0], val[2])} + +# Returns Expression +relational_expression + : shift_expression {result = val[0]} + | relational_expression LT shift_expression {result = Less.new_at(val[0].pos, val[0], val[2])} + | relational_expression GT shift_expression {result = More.new_at(val[0].pos, val[0], val[2])} + | relational_expression LEQ shift_expression {result = LessOrEqual.new_at(val[0].pos, val[0], val[2])} + | relational_expression GEQ shift_expression {result = MoreOrEqual.new_at(val[0].pos, val[0], val[2])} + +# Returns Expression +equality_expression + : relational_expression {result = val[0]} + | equality_expression EQEQ relational_expression {result = Equal .new_at(val[0].pos, val[0], val[2])} + | equality_expression NEQ relational_expression {result = NotEqual.new_at(val[0].pos, val[0], val[2])} + +# Returns Expression +and_expression + : equality_expression {result = val[0]} + | and_expression AND equality_expression {result = BitAnd.new_at(val[0].pos, val[0], val[2])} + +# Returns Expression +exclusive_or_expression + : and_expression {result = val[0]} + | exclusive_or_expression XOR and_expression {result = BitXor.new_at(val[0].pos, val[0], val[2])} + +# Returns Expression +inclusive_or_expression + : exclusive_or_expression {result = val[0]} + | inclusive_or_expression OR exclusive_or_expression {result = BitOr.new_at(val[0].pos, val[0], val[2])} + +# Returns Expression +logical_and_expression + : inclusive_or_expression {result = val[0]} + | logical_and_expression ANDAND inclusive_or_expression {result = And.new_at(val[0].pos, val[0], val[2])} + +# Returns Expression +logical_or_expression + : logical_and_expression {result = val[0]} + | logical_or_expression OROR logical_and_expression {result = Or.new_at(val[0].pos, val[0], val[2])} + +# Returns Expression +conditional_expression + : logical_or_expression {result = val[0]} + | logical_or_expression QUESTION expression COLON conditional_expression {result = Conditional.new_at(val[0].pos, val[0], val[2], val[4])} + +# Returns Expression +assignment_expression + : conditional_expression {result = val[0]} + | unary_expression assignment_operator assignment_expression {result = val[1].new_at(val[0].pos, val[0], val[2])} + +# Returns Class +assignment_operator + : EQ {result = Assign} + | MULEQ {result = MultiplyAssign} + | DIVEQ {result = DivideAssign} + | MODEQ {result = ModAssign} + | ADDEQ {result = AddAssign} + | SUBEQ {result = SubtractAssign} + | LSHIFTEQ {result = ShiftLeftAssign} + | RSHIFTEQ {result = ShiftRightAssign} + | ANDEQ {result = BitAndAssign} + | XOREQ {result = BitXorAssign} + | OREQ {result = BitOrAssign} + +# Returns Expression +expression + : assignment_expression {result = val[0]} + | expression COMMA assignment_expression { + if val[0].is_a? Comma + if val[2].is_a? Comma + val[0].exprs.push(*val[2].exprs) + else + val[0].exprs << val[2] + end + result = val[0] + else + if val[2].is_a? Comma + val[2].exprs.unshift(val[0]) + val[2].pos = val[0].pos + result = val[2] + else + result = Comma.new_at(val[0].pos, NodeArray[val[0], val[2]]) + end + end + } + +# Returns Expression +constant_expression + : conditional_expression {result = val[0]} + +# A.1.1 -- Lexical elements +# +# token +# : keyword (raw string) +# | identifier expanded below +# | constant expanded below +# | string_literal expanded below +# | punctuator (raw string) +# +# preprocessing-token (skip) + +# Returns Token +identifier + : ID {result = val[0]} + +# Returns Literal +constant + : ICON {result = val[0].val; result.pos = val[0].pos} + | FCON {result = val[0].val; result.pos = val[0].pos} + #| enumeration_constant -- these are parsed as identifiers at all + # places the `constant' nonterminal appears + | CCON {result = val[0].val; result.pos = val[0].pos} + +# Returns Token +enumeration_constant + : ID {result = val[0]} + +# Returns StringLiteral +# Also handles string literal concatenation (6.4.5.4) +string_literal + : string_literal SCON {val[0].val << val[1].val.val; result = val[0]} + | SCON { result = val[0].val; result.pos = val[0].pos } + +---- inner + # A.1.9 -- Preprocessing numbers -- skip + # A.1.8 -- Header names -- skip + + # A.1.7 -- Puncuators -- we don't bother with {##,#,%:,%:%:} since + # we don't do preprocessing + @@punctuators = %r'\+\+|-[->]|&&|\|\||\.\.\.|(?:<<|>>|[<>=!*/%+\-&^|])=?|[\[\](){}.~?:;,]' + @@digraphs = %r'<[:%]|[:%]>' + + # A.1.6 -- String Literals -- simple for us because we don't decode + # the string (and indeed accept some illegal strings) + @@string_literal = %r'L?"(?:[^\\]|\\.)*?"'m + + # A.1.5 -- Constants + @@decimal_floating_constant = %r'(?:(?:\d*\.\d+|\d+\.)(?:e[-+]?\d+)?|\d+e[-+]?\d+)[fl]?'i + @@hexadecimal_floating_constant = %r'0x(?:(?:[0-9a-f]*\.[0-9a-f]+|[0-9a-f]+\.)|[0-9a-f]+)p[-+]?\d+[fl]?'i + + @@integer_constant = %r'(?:[1-9][0-9]*|0x[0-9a-f]+|0[0-7]*)(?:ul?l?|ll?u?)?'i + @@floating_constant = %r'#{@@decimal_floating_constant}|#{@@hexadecimal_floating_constant}' + @@enumeration_constant = %r'[a-zA-Z_\\][a-zA-Z_\\0-9]*' + @@character_constant = %r"L?'(?:[^\\]|\\.)+?'" + # (note that as with string-literals, we accept some illegal + # character-constants) + + # A.1.4 -- Universal character names -- skip + + # A.1.3 -- Identifiers -- skip, since an identifier is lexically + # identical to an enumeration constant + + # A.1.2 Keywords + keywords = %w'auto break case char const continue default do +double else enum extern float for goto if inline int long register +restrict return short signed sizeof static struct switch typedef union + unsigned void volatile while _Bool _Complex _Imaginary' + @@keywords = %r"#{keywords.join('|')}" + + def initialize + @type_names = ::Set.new + + @warning_proc = lambda{} + @pos = C::Node::Pos.new(nil, 1, 0) + end + def initialize_copy(x) + @pos = x.pos.dup + @type_names = x.type_names.dup + end + attr_accessor :pos, :type_names + + def parse(str) + if str.respond_to? :read + str = str.read + end + @str = str + begin + prepare_lexer(str) + return do_parse + rescue ParseError => e + e.set_backtrace(caller) + raise + end + end + + # + # Error handler, as used by racc. + # + def on_error(error_token_id, error_value, value_stack) + if error_value == '$' + parse_error @pos, "unexpected EOF" + else + parse_error(error_value.pos, + "parse error on #{token_to_str(error_token_id)} (#{error_value.val})") + end + end + + def self.feature(name) + attr_writer "#{name}_enabled" + class_eval <<-EOS + def enable_#{name} + @#{name}_enabled = true + end + def #{name}_enabled? + @#{name}_enabled + end + EOS + end + private_class_method :feature + + # + # Allow blocks in parentheses as expressions, as per the gcc + # extension. [http://rubyurl.com/iB7] + # + feature :block_expressions + + private # --------------------------------------------------------- + + class Token + attr_accessor :pos, :val + def initialize(pos, val) + @pos = pos + @val = val + end + end + def eat(str) + lines = str.split(/\r\n|[\r\n]/, -1) + if lines.length == 1 + @pos.col_num += lines[0].length + else + @pos.line_num += lines.length - 1 + @pos.col_num = lines[-1].length + end + end + + # + # Make a Declaration from the given specs and declarators. + # + def make_declaration(pos, specs, declarators) + specs.all?{|x| x.is_a?(Symbol) || x.is_a?(Type)} or raise specs.map{|x| x.class}.inspect + decl = Declaration.new_at(pos, nil, declarators) + + # set storage class + storage_classes = specs.find_all do |x| + [:typedef, :extern, :static, :auto, :register].include? x + end + # 6.7.1p2: at most, one storage-class specifier may be given in + # the declaration specifiers in a declaration + storage_classes.length <= 1 or + begin + if declarators.length == 0 + for_name = '' + else + for_name = "for `#{declarators[0].name}'" + end + parse_error pos, "multiple or duplicate storage classes given #{for_name}'" + end + decl.storage = storage_classes[0] + + # set type (specifiers, qualifiers) + decl.type = make_direct_type(pos, specs) + + # set function specifiers + decl.inline = specs.include?(:inline) + + # look for new type names + if decl.typedef? + decl.declarators.each do |d| + if d.name + @type_names << d.name + end + end + end + + return decl + end + + def make_function_def(pos, specs, func_declarator, decl_list, defn) + add_decl_type(func_declarator, make_direct_type(pos, specs)) + + # get types from decl_list if necessary + function = func_declarator.indirect_type + function.is_a? Function or + parse_error pos, "non function type for function `#{func_declarator.name}'" + params = function.params + if decl_list + params.all?{|p| p.type.nil?} or + parse_error pos, "both prototype and declaration list given for `#{func_declarator.name}'" + decl_list.each do |declaration| + declaration.declarators.each do |declarator| + param = params.find{|p| p.name == declarator.name} or + parse_error pos, "no parameter named #{declarator.name}" + if declarator.indirect_type + param.type = declarator.indirect_type + param.type.direct_type = declaration.type.dup + else + param.type = declaration.type.dup + end + end + end + params.all?{|p| p.type} or + begin + s = params.find_all{|p| p.type.nil?}.map{|p| "`#{p.name}'"}.join(' and ') + parse_error pos, "types missing for parameters #{s}" + end + end + + fd = FunctionDef.new_at(pos, + function.detach, + func_declarator.name, + defn, + :no_prototype => !decl_list.nil?) + + # set storage class + # 6.9.1p4: only extern or static allowed + specs.each do |s| + [:typedef, :auto, :register].include?(s) and + "`#{s}' illegal for function" + end + storage_classes = specs.find_all do |s| + s == :extern || s == :static + end + # 6.7.1p2: at most, one storage-class specifier may be given in + # the declaration specifiers in a declaration + storage_classes.length <= 1 or + "multiple or duplicate storage classes given for `#{func_declarator.name}'" + fd.storage = storage_classes[0] if storage_classes[0] + + # set function specifiers + # 6.7.4p5 'inline' can be repeated + fd.inline = specs.include?(:inline) + + return fd + end + + # + # Make a direct type from the list of type specifiers and type + # qualifiers. + # + def make_direct_type(pos, specs) + specs_order = [:signed, :unsigned, :short, :long, :double, :void, + :char, :int, :float, :_Bool, :_Complex, :_Imaginary] + + type_specs = specs.find_all do |x| + specs_order.include?(x) || !x.is_a?(Symbol) + end + type_specs.sort! do |a, b| + (specs_order.index(a)||100) <=> (specs_order.index(b)||100) + end + + # set type specifiers + # 6.7.2p2: the specifier list should be one of these + type = + case type_specs + when [:void] + Void.new + when [:char] + Char.new + when [:signed, :char] + Char.new :signed => true + when [:unsigned, :char] + Char.new :signed => false + when [:short], [:signed, :short], [:short, :int], + [:signed, :short, :int] + Int.new :longness => -1 + when [:unsigned, :short], [:unsigned, :short, :int] + Int.new :unsigned => true, :longness => -1 + when [:int], [:signed], [:signed, :int] + Int.new + when [:unsigned], [:unsigned, :int] + Int.new :unsigned => true + when [:long], [:signed, :long], [:long, :int], + [:signed, :long, :int] + Int.new :longness => 1 + when [:unsigned, :long], [:unsigned, :long, :int] + Int.new :longness => 1, :unsigned => true + when [:long, :long], [:signed, :long, :long], + [:long, :long, :int], [:signed, :long, :long, :int] + Int.new :longness => 2 + when [:unsigned, :long, :long], [:unsigned, :long, :long, :int] + Int.new :longness => 2, :unsigned => true + when [:float] + Float.new + when [:double] + Float.new :longness => 1 + when [:long, :double] + Float.new :longness => 2 + when [:_Bool] + Bool.new + when [:float, :_Complex] + Complex.new + when [:double, :_Complex] + Complex.new :longness => 1 + when [:long, :double, :_Complex] + Complex.new :longness => 2 + when [:float, :_Imaginary] + Imaginary.new + when [:double, :_Imaginary] + Imaginary.new :longness => 1 + when [:long, :double, :_Imaginary] + Imaginary.new :longness => 2 + else + if type_specs.length == 1 && + [CustomType, Struct, Union, Enum].any?{|c| type_specs[0].is_a? c} + type_specs[0] + else + if type_specs == [] + parse_error pos, "no type specifiers given" + else + parse_error pos, "invalid type specifier combination: #{type_specs.join(' ')}" + end + end + end + type.pos ||= pos + + # set type qualifiers + # 6.7.3p4: type qualifiers can be repeated + type.const = specs.any?{|x| x.equal? :const } + type.restrict = specs.any?{|x| x.equal? :restrict} + type.volatile = specs.any?{|x| x.equal? :volatile} + + return type + end + + def make_parameter(pos, specs, indirect_type, name) + type = indirect_type + if type + type.direct_type = make_direct_type(pos, specs) + else + type = make_direct_type(pos, specs) + end + [:typedef, :extern, :static, :auto, :inline].each do |sym| + specs.include? sym and + parse_error pos, "parameter `#{declarator.name}' declared `#{sym}'" + end + return Parameter.new_at(pos, type, name, + :register => specs.include?(:register)) + end + + def add_type_quals(type, quals) + type.const = quals.include?(:const ) + type.restrict = quals.include?(:restrict) + type.volatile = quals.include?(:volatile) + return type + end + + # + # Add te given type as the "most direct" type to the given + # declarator. Return the declarator. + # + def add_decl_type(declarator, type) + if declarator.indirect_type + declarator.indirect_type.direct_type = type + else + declarator.indirect_type = type + end + return declarator + end + + def param_list(params, var_args) + if params.length == 1 && + params[0].type.is_a?(Void) && + params[0].name.nil? + return NodeArray[] + elsif params.empty? + return nil + else + return params + end + end + + def parse_error(pos, str) + raise ParseError, "#{pos}: #{str}" + end + +---- header + +require 'set' + +# Error classes +module C + class ParseError < StandardError; end +end + +# Local variables: +# mode: ruby +# end: diff --git a/test/racc/assets/chk.y b/test/racc/assets/chk.y new file mode 100644 index 0000000000..7e0ee20f1e --- /dev/null +++ b/test/racc/assets/chk.y @@ -0,0 +1,126 @@ +# +# racc tester +# + +class Calcp + + prechigh + left '*' '/' + left '+' '-' + preclow + + convert + NUMBER 'Number' + end + +rule + + target : exp | /* none */ { result = 0 } ; + + exp : exp '+' exp { result += val[2]; @plus = 'plus' } + | exp '-' exp { result -= val[2]; @str = "string test" } + | exp '*' exp { result *= val[2] } + | exp '/' exp { result /= val[2] } + | '(' { $emb = true } exp ')' + { + raise 'must not happen' unless $emb + result = val[2] + } + | '-' NUMBER { result = -val[1] } + | NUMBER + ; + +end + +----header + +class Number; end + +----inner + + def parse( src ) + $emb = false + @plus = nil + @str = nil + @src = src + result = do_parse + if @plus + raise 'string parse failed' unless @plus == 'plus' + end + if @str + raise 'string parse failed' unless @str == 'string test' + end + result + end + + def next_token + @src.shift + end + + def initialize + @yydebug = true + end + +----footer + +$parser = Calcp.new +$test_number = 1 + +def chk( src, ans ) + result = $parser.parse(src) + raise "test #{$test_number} fail" unless result == ans + $test_number += 1 +end + +chk( + [ [Number, 9], + [false, false], + [false, false] ], 9 +) + +chk( + [ [Number, 5], + ['*', nil], + [Number, 1], + ['-', nil], + [Number, 1], + ['*', nil], + [Number, 8], + [false, false], + [false, false] ], -3 +) + +chk( + [ [Number, 5], + ['+', nil], + [Number, 2], + ['-', nil], + [Number, 5], + ['+', nil], + [Number, 2], + ['-', nil], + [Number, 5], + [false, false], + [false, false] ], -1 +) + +chk( + [ ['-', nil], + [Number, 4], + [false, false], + [false, false] ], -4 +) + +chk( + [ [Number, 7], + ['*', nil], + ['(', nil], + [Number, 4], + ['+', nil], + [Number, 3], + [')', nil], + ['-', nil], + [Number, 9], + [false, false], + [false, false] ], 40 +) diff --git a/test/racc/assets/conf.y b/test/racc/assets/conf.y new file mode 100644 index 0000000000..de9de71d28 --- /dev/null +++ b/test/racc/assets/conf.y @@ -0,0 +1,16 @@ + +class A +rule + +a: A c C expr; + +b: A B; # useless + +c: A; +c: A; + +expr: expr '+' expr +expr: expr '-' expr +expr: NUMBER + +end diff --git a/test/racc/assets/csspool.y b/test/racc/assets/csspool.y new file mode 100644 index 0000000000..3d6af25d85 --- /dev/null +++ b/test/racc/assets/csspool.y @@ -0,0 +1,729 @@ +class CSSPool::CSS::Parser + +token CHARSET_SYM IMPORT_SYM STRING SEMI IDENT S COMMA LBRACE RBRACE STAR HASH +token LSQUARE RSQUARE EQUAL INCLUDES DASHMATCH LPAREN RPAREN FUNCTION GREATER PLUS +token SLASH NUMBER MINUS LENGTH PERCENTAGE ANGLE TIME FREQ URI +token IMPORTANT_SYM MEDIA_SYM NOT ONLY AND NTH_PSEUDO_CLASS +token DOCUMENT_QUERY_SYM FUNCTION_NO_QUOTE +token TILDE +token PREFIXMATCH SUFFIXMATCH SUBSTRINGMATCH +token NOT_PSEUDO_CLASS +token KEYFRAMES_SYM +token MATCHES_PSEUDO_CLASS +token NAMESPACE_SYM +token MOZ_PSEUDO_ELEMENT +token RESOLUTION +token COLON +token SUPPORTS_SYM +token OR +token VARIABLE_NAME +token CALC_SYM +token FONTFACE_SYM +token UNICODE_RANGE +token RATIO + +rule + document + : { @handler.start_document } + stylesheet + { @handler.end_document } + ; + stylesheet + : charset stylesheet + | import stylesheet + | namespace stylesheet + | charset + | import + | namespace + | body + | + ; + charset + : CHARSET_SYM STRING SEMI { @handler.charset interpret_string(val[1]), {} } + ; + import + : IMPORT_SYM import_location medium SEMI { + @handler.import_style val[2], val[1] + } + | IMPORT_SYM import_location SEMI { + @handler.import_style [], val[1] + } + ; + import_location + : import_location S + | STRING { result = Terms::String.new interpret_string val.first } + | URI { result = Terms::URI.new interpret_uri val.first } + ; + namespace + : NAMESPACE_SYM ident import_location SEMI { + @handler.namespace val[1], val[2] + } + | NAMESPACE_SYM import_location SEMI { + @handler.namespace nil, val[1] + } + ; + medium + : medium COMMA IDENT { + result = val[0] << MediaType.new(val[2]) + } + | IDENT { + result = [MediaType.new(val[0])] + } + ; + media_query_list + : media_query { result = MediaQueryList.new([ val[0] ]) } + | media_query_list COMMA media_query { result = val[0] << val[2] } + | { result = MediaQueryList.new } + ; + media_query + : optional_only_or_not media_type optional_and_exprs { result = MediaQuery.new(val[0], val[1], val[2]) } + | media_expr optional_and_exprs { result = MediaQuery.new(nil, val[0], val[1]) } + ; + optional_only_or_not + : ONLY { result = :only } + | NOT { result = :not } + | { result = nil } + ; + media_type + : IDENT { result = MediaType.new(val[0]) } + ; + media_expr + : LPAREN optional_space IDENT optional_space RPAREN { result = MediaType.new(val[2]) } + | LPAREN optional_space IDENT optional_space COLON optional_space expr RPAREN { result = MediaFeature.new(val[2], val[6][0]) } + ; + optional_space + : S { result = val[0] } + | { result = nil } + ; + optional_and_exprs + : optional_and_exprs AND media_expr { result = val[0] << val[2] } + | { result = [] } + ; + resolution + : RESOLUTION { + unit = val.first.gsub(/[\s\d.]/, '') + number = numeric(val.first) + result = Terms::Resolution.new(number, unit) + } + ; + body + : ruleset body + | conditional_rule body + | keyframes_rule body + | fontface_rule body + | ruleset + | conditional_rule + | keyframes_rule + | fontface_rule + ; + conditional_rule + : media + | document_query + | supports + ; + body_in_media + : body + | empty_ruleset + ; + media + : start_media body_in_media RBRACE { @handler.end_media val.first } + ; + start_media + : MEDIA_SYM media_query_list LBRACE { + result = val[1] + @handler.start_media result + } + ; + document_query + : start_document_query body RBRACE { @handler.end_document_query(before_pos(val), after_pos(val)) } + | start_document_query RBRACE { @handler.end_document_query(before_pos(val), after_pos(val)) } + ; + start_document_query + : start_document_query_pos url_match_fns LBRACE { + @handler.start_document_query(val[1], after_pos(val)) + } + ; + start_document_query_pos + : DOCUMENT_QUERY_SYM { + @handler.node_start_pos = before_pos(val) + } + ; + url_match_fns + : url_match_fn COMMA url_match_fns { + result = [val[0], val[2]].flatten + } + | url_match_fn { + result = val + } + ; + url_match_fn + : function_no_quote + | function + | uri + ; + supports + : start_supports body RBRACE { @handler.end_supports } + | start_supports RBRACE { @handler.end_supports } + ; + start_supports + : SUPPORTS_SYM supports_condition_root LBRACE { + @handler.start_supports val[1] + } + ; + supports_condition_root + : supports_negation { result = val.join('') } + | supports_conjunction_or_disjunction { result = val.join('') } + | supports_condition_in_parens { result = val.join('') } + ; + supports_condition + : supports_negation { result = val.join('') } + | supports_conjunction_or_disjunction { result = val.join('') } + | supports_condition_in_parens { result = val.join('') } + ; + supports_condition_in_parens + : LPAREN supports_condition RPAREN { result = val.join('') } + | supports_declaration_condition { result = val.join('') } + ; + supports_negation + : NOT supports_condition_in_parens { result = val.join('') } + ; + supports_conjunction_or_disjunction + : supports_conjunction + | supports_disjunction + ; + supports_conjunction + : supports_condition_in_parens AND supports_condition_in_parens { result = val.join('') } + | supports_conjunction_or_disjunction AND supports_condition_in_parens { result = val.join('') } + ; + supports_disjunction + : supports_condition_in_parens OR supports_condition_in_parens { result = val.join('') } + | supports_conjunction_or_disjunction OR supports_condition_in_parens { result = val.join('') } + ; + supports_declaration_condition + : LPAREN declaration_internal RPAREN { result = val.join('') } + | LPAREN S declaration_internal RPAREN { result = val.join('') } + ; + keyframes_rule + : start_keyframes_rule keyframes_blocks RBRACE + | start_keyframes_rule RBRACE + ; + start_keyframes_rule + : KEYFRAMES_SYM IDENT LBRACE { + @handler.start_keyframes_rule val[1] + } + ; + keyframes_blocks + : keyframes_block keyframes_blocks + | keyframes_block + ; + keyframes_block + : start_keyframes_block declarations RBRACE { @handler.end_keyframes_block } + | start_keyframes_block RBRACE { @handler.end_keyframes_block } + ; + start_keyframes_block + : keyframes_selectors LBRACE { + @handler.start_keyframes_block val[0] + } + ; + keyframes_selectors + | keyframes_selector COMMA keyframes_selectors { + result = val[0] + ', ' + val[2] + } + | keyframes_selector + ; + keyframes_selector + : IDENT + | PERCENTAGE { result = val[0].strip } + ; + fontface_rule + : start_fontface_rule declarations RBRACE { @handler.end_fontface_rule } + | start_fontface_rule RBRACE { @handler.end_fontface_rule } + ; + start_fontface_rule + : FONTFACE_SYM LBRACE { + @handler.start_fontface_rule + } + ; + ruleset + : start_selector declarations RBRACE { + @handler.end_selector val.first + } + | start_selector RBRACE { + @handler.end_selector val.first + } + ; + empty_ruleset + : optional_space { + start = @handler.start_selector([]) + @handler.end_selector(start) + } + ; + start_selector + : S start_selector { result = val.last } + | selectors LBRACE { + @handler.start_selector val.first + } + ; + selectors + : selector COMMA selectors + { + sel = Selector.new(val.first, {}) + result = [sel].concat(val[2]) + } + | selector + { + result = [Selector.new(val.first, {})] + } + ; + selector + : simple_selector combinator selector + { + val.flatten! + val[2].combinator = val.delete_at 1 + result = val + } + | simple_selector + ; + combinator + : S { result = :s } + | GREATER { result = :> } + | PLUS { result = :+ } + | TILDE { result = :~ } + ; + simple_selector + : element_name hcap { + selector = val.first + selector.additional_selectors = val.last + result = [selector] + } + | element_name { result = val } + | hcap + { + ss = Selectors::Simple.new nil, nil + ss.additional_selectors = val.flatten + result = [ss] + } + ; + simple_selectors + : simple_selector COMMA simple_selectors { result = [val[0], val[2]].flatten } + | simple_selector + ; + ident_with_namespace + : IDENT { result = [interpret_identifier(val[0]), nil] } + | IDENT '|' IDENT { result = [interpret_identifier(val[2]), interpret_identifier(val[0])] } + | '|' IDENT { result = [interpret_identifier(val[1]), nil] } + | STAR '|' IDENT { result = [interpret_identifier(val[2]), '*'] } + ; + element_name + : ident_with_namespace { result = Selectors::Type.new val.first[0], nil, val.first[1] } + | STAR { result = Selectors::Universal.new val.first } + | '|' STAR { result = Selectors::Universal.new val[1] } + | STAR '|' STAR { result = Selectors::Universal.new val[2], nil, val[0] } + | IDENT '|' STAR { result = Selectors::Universal.new val[2], nil, interpret_identifier(val[0]) } + ; + hcap + : hash { result = val } + | class { result = val } + | attrib { result = val } + | pseudo { result = val } + | hash hcap { result = val.flatten } + | class hcap { result = val.flatten } + | attrib hcap { result = val.flatten } + | pseudo hcap { result = val.flatten } + ; + hash + : HASH { + result = Selectors::Id.new interpret_identifier val.first.sub(/^#/, '') + } + class + : '.' IDENT { + result = Selectors::Class.new interpret_identifier val.last + } + ; + attrib + : LSQUARE ident_with_namespace EQUAL IDENT RSQUARE { + result = Selectors::Attribute.new( + val[1][0], + interpret_identifier(val[3]), + Selectors::Attribute::EQUALS, + val[1][1] + ) + } + | LSQUARE ident_with_namespace EQUAL STRING RSQUARE { + result = Selectors::Attribute.new( + val[1][0], + interpret_string(val[3]), + Selectors::Attribute::EQUALS, + val[1][1] + ) + } + | LSQUARE ident_with_namespace INCLUDES STRING RSQUARE { + result = Selectors::Attribute.new( + val[1][0], + interpret_string(val[3]), + Selectors::Attribute::INCLUDES, + val[1][1] + ) + } + | LSQUARE ident_with_namespace INCLUDES IDENT RSQUARE { + result = Selectors::Attribute.new( + val[1][0], + interpret_identifier(val[3]), + Selectors::Attribute::INCLUDES, + val[1][1] + ) + } + | LSQUARE ident_with_namespace DASHMATCH IDENT RSQUARE { + result = Selectors::Attribute.new( + val[1][0], + interpret_identifier(val[3]), + Selectors::Attribute::DASHMATCH, + val[1][1] + ) + } + | LSQUARE ident_with_namespace DASHMATCH STRING RSQUARE { + result = Selectors::Attribute.new( + val[1][0], + interpret_string(val[3]), + Selectors::Attribute::DASHMATCH, + val[1][1] + ) + } + | LSQUARE ident_with_namespace PREFIXMATCH IDENT RSQUARE { + result = Selectors::Attribute.new( + val[1][0], + interpret_identifier(val[3]), + Selectors::Attribute::PREFIXMATCH, + val[1][1] + ) + } + | LSQUARE ident_with_namespace PREFIXMATCH STRING RSQUARE { + result = Selectors::Attribute.new( + val[1][0], + interpret_string(val[3]), + Selectors::Attribute::PREFIXMATCH, + val[1][1] + ) + } + | LSQUARE ident_with_namespace SUFFIXMATCH IDENT RSQUARE { + result = Selectors::Attribute.new( + val[1][0], + interpret_identifier(val[3]), + Selectors::Attribute::SUFFIXMATCH, + val[1][1] + ) + } + | LSQUARE ident_with_namespace SUFFIXMATCH STRING RSQUARE { + result = Selectors::Attribute.new( + val[1][0], + interpret_string(val[3]), + Selectors::Attribute::SUFFIXMATCH, + val[1][1] + ) + } + | LSQUARE ident_with_namespace SUBSTRINGMATCH IDENT RSQUARE { + result = Selectors::Attribute.new( + val[1][0], + interpret_identifier(val[3]), + Selectors::Attribute::SUBSTRINGMATCH, + val[1][1] + ) + } + | LSQUARE ident_with_namespace SUBSTRINGMATCH STRING RSQUARE { + result = Selectors::Attribute.new( + val[1][0], + interpret_string(val[3]), + Selectors::Attribute::SUBSTRINGMATCH, + val[1][1] + ) + } + | LSQUARE ident_with_namespace RSQUARE { + result = Selectors::Attribute.new( + val[1][0], + nil, + Selectors::Attribute::SET, + val[1][1] + ) + } + ; + pseudo + : COLON IDENT { + result = Selectors::pseudo interpret_identifier(val[1]) + } + | COLON COLON IDENT { + result = Selectors::PseudoElement.new( + interpret_identifier(val[2]) + ) + } + | COLON FUNCTION RPAREN { + result = Selectors::PseudoClass.new( + interpret_identifier(val[1].sub(/\($/, '')), + '' + ) + } + | COLON FUNCTION IDENT RPAREN { + result = Selectors::PseudoClass.new( + interpret_identifier(val[1].sub(/\($/, '')), + interpret_identifier(val[2]) + ) + } + | COLON NOT_PSEUDO_CLASS simple_selector RPAREN { + result = Selectors::PseudoClass.new( + 'not', + val[2].first.to_s + ) + } + | COLON NTH_PSEUDO_CLASS { + result = Selectors::PseudoClass.new( + interpret_identifier(val[1].sub(/\(.*/, '')), + interpret_identifier(val[1].sub(/.*\(/, '').sub(/\).*/, '')) + ) + } + | COLON MATCHES_PSEUDO_CLASS simple_selectors RPAREN { + result = Selectors::PseudoClass.new( + val[1].split('(').first.strip, + val[2].join(', ') + ) + } + | COLON MOZ_PSEUDO_ELEMENT optional_space any_number_of_idents optional_space RPAREN { + result = Selectors::PseudoElement.new( + interpret_identifier(val[1].sub(/\($/, '')) + ) + } + | COLON COLON MOZ_PSEUDO_ELEMENT optional_space any_number_of_idents optional_space RPAREN { + result = Selectors::PseudoElement.new( + interpret_identifier(val[2].sub(/\($/, '')) + ) + } + ; + any_number_of_idents + : + | multiple_idents + ; + multiple_idents + : IDENT + | IDENT COMMA multiple_idents + ; + # declarations can be separated by one *or more* semicolons. semi-colons at the start or end of a ruleset are also allowed + one_or_more_semis + : SEMI + | SEMI one_or_more_semis + ; + declarations + : declaration one_or_more_semis declarations + | one_or_more_semis declarations + | declaration one_or_more_semis + | declaration + | one_or_more_semis + ; + declaration + : declaration_internal { @handler.property val.first } + ; + declaration_internal + : property COLON expr prio + { result = Declaration.new(val.first, val[2], val[3]) } + | property COLON S expr prio + { result = Declaration.new(val.first, val[3], val[4]) } + | property S COLON expr prio + { result = Declaration.new(val.first, val[3], val[4]) } + | property S COLON S expr prio + { result = Declaration.new(val.first, val[4], val[5]) } + ; + prio + : IMPORTANT_SYM { result = true } + | { result = false } + ; + property + : IDENT { result = interpret_identifier val[0] } + | STAR IDENT { result = interpret_identifier val.join } + | VARIABLE_NAME { result = interpret_identifier val[0] } + ; + operator + : COMMA + | SLASH + | EQUAL + ; + expr + : term operator expr { + result = [val.first, val.last].flatten + val.last.first.operator = val[1] + } + | term expr { result = val.flatten } + | term { result = val } + ; + term + : ident + | ratio + | numeric + | string + | uri + | hexcolor + | calc + | function + | resolution + | VARIABLE_NAME + | uranges + ; + function + : function S { result = val.first } + | FUNCTION expr RPAREN { + name = interpret_identifier val.first.sub(/\($/, '') + if name == 'rgb' + result = Terms::Rgb.new(*val[1]) + else + result = Terms::Function.new name, val[1] + end + } + | FUNCTION RPAREN { + name = interpret_identifier val.first.sub(/\($/, '') + result = Terms::Function.new name + } + ; + function_no_quote + : function_no_quote S { result = val.first } + | FUNCTION_NO_QUOTE { + parts = val.first.split('(') + name = interpret_identifier parts.first + result = Terms::Function.new(name, [Terms::String.new(interpret_string_no_quote(parts.last))]) + } + ; + uranges + : UNICODE_RANGE COMMA uranges + | UNICODE_RANGE + ; + calc + : CALC_SYM calc_sum RPAREN optional_space { + result = Terms::Math.new(val.first.split('(').first, val[1]) + } + ; + # plus and minus are supposed to have whitespace around them, per http://dev.w3.org/csswg/css-values/#calc-syntax, but the numbers are eating trailing whitespace, so we inject it back in + calc_sum + : calc_product + | calc_product PLUS calc_sum { val.insert(1, ' '); result = val.join('') } + | calc_product MINUS calc_sum { val.insert(1, ' '); result = val.join('') } + ; + calc_product + : calc_value + | calc_value optional_space STAR calc_value { result = val.join('') } + | calc_value optional_space SLASH calc_value { result = val.join('') } + ; + calc_value + : numeric { result = val.join('') } + | function { result = val.join('') } # for var() variable references + | LPAREN calc_sum RPAREN { result = val.join('') } + ; + hexcolor + : hexcolor S { result = val.first } + | HASH { result = Terms::Hash.new val.first.sub(/^#/, '') } + ; + uri + : uri S { result = val.first } + | URI { result = Terms::URI.new interpret_uri val.first } + ; + string + : string S { result = val.first } + | STRING { result = Terms::String.new interpret_string val.first } + ; + numeric + : unary_operator numeric { + result = val[1] + val[1].unary_operator = val.first + } + | NUMBER { + result = Terms::Number.new numeric val.first + } + | PERCENTAGE { + result = Terms::Number.new numeric(val.first), nil, '%' + } + | LENGTH { + unit = val.first.gsub(/[\s\d.]/, '') + result = Terms::Number.new numeric(val.first), nil, unit + } + | ANGLE { + unit = val.first.gsub(/[\s\d.]/, '') + result = Terms::Number.new numeric(val.first), nil, unit + } + | TIME { + unit = val.first.gsub(/[\s\d.]/, '') + result = Terms::Number.new numeric(val.first), nil, unit + } + | FREQ { + unit = val.first.gsub(/[\s\d.]/, '') + result = Terms::Number.new numeric(val.first), nil, unit + } + ; + ratio + : RATIO { + result = Terms::Ratio.new(val[0], val[1]) + } + ; + unary_operator + : MINUS { result = :minus } + | PLUS { result = :plus } + ; + ident + : ident S { result = val.first } + | IDENT { result = Terms::Ident.new interpret_identifier val.first } + ; + +---- inner + +def numeric thing + thing = thing.gsub(/[^\d.]/, '') + Integer(thing) rescue Float(thing) +end + +def interpret_identifier s + interpret_escapes s +end + +def interpret_uri s + interpret_escapes s.match(/^url\((.*)\)$/mui)[1].strip.match(/^(['"]?)((?:\\.|.)*)\1$/mu)[2] +end + +def interpret_string_no_quote s + interpret_escapes s.match(/^(.*)\)$/mu)[1].strip.match(/^(['"]?)((?:\\.|.)*)\1$/mu)[2] +end + +def interpret_string s + interpret_escapes s.match(/^(['"])((?:\\.|.)*)\1$/mu)[2] +end + +def interpret_escapes s + token_exp = /\\(?:([0-9a-fA-F]{1,6}(?:\r\n|\s)?)|(.))/mu + return s.gsub(token_exp) do |escape_sequence| + if !$1.nil? + code = $1.chomp.to_i 16 + code = 0xFFFD if code > 0x10FFFF + next [code].pack('U') + end + next '' if $2 == "\n" + next $2 + end +end + +# override racc's on_error so we can have context in our error messages +def on_error(t, val, vstack) + errcontext = (@ss.pre_match[-10..-1] || @ss.pre_match) + + @ss.matched + @ss.post_match[0..9] + line_number = @ss.pre_match.lines.count + raise ParseError, sprintf("parse error on value %s (%s) " + + "on line %s around \"%s\"", + val.inspect, token_to_str(t) || '?', + line_number, errcontext) +end + +def before_pos(val) + # don't include leading whitespace + return current_pos - val.last.length + val.last[/\A\s*/].size +end + +def after_pos(val) + # don't include trailing whitespace + return current_pos - val.last[/\s*\z/].size +end + +# charpos will work with multibyte strings but is not available until ruby 2 +def current_pos + @ss.respond_to?('charpos') ? @ss.charpos : @ss.pos +end diff --git a/test/racc/assets/digraph.y b/test/racc/assets/digraph.y new file mode 100644 index 0000000000..17a034ee54 --- /dev/null +++ b/test/racc/assets/digraph.y @@ -0,0 +1,29 @@ +# ? detect digraph bug + +class P + token A B C D +rule + target : a b c d + a : A + | + b : B + | + c : C + | + d : D + | +end + +---- inner + + def parse + do_parse + end + + def next_token + [false, '$'] + end + +---- footer + +P.new.parse diff --git a/test/racc/assets/echk.y b/test/racc/assets/echk.y new file mode 100644 index 0000000000..0fda2685aa --- /dev/null +++ b/test/racc/assets/echk.y @@ -0,0 +1,118 @@ +# +# racc tester +# + +class Calcp + + prechigh + left '*' '/' + left '+' '-' + preclow + + convert + NUMBER 'Number' + end + +rule + + target : exp | /* none */ { result = 0 } ; + + exp : exp '+' exp { result += val[2]; a = 'plus' } + | exp '-' exp { result -= val[2]; "string test" } + | exp '*' exp { result *= val[2] } + | exp '/' exp { result /= val[2] } + | '(' { $emb = true } exp ')' + { + raise 'must not happen' unless $emb + result = val[2] + } + | '-' NUMBER { result = -val[1] } + | NUMBER + ; + +end + +----header + +class Number ; end + +----inner + + def parse( src ) + @src = src + do_parse + end + + def next_token + @src.shift + end + + def initialize + @yydebug = true + end + +----footer + +$parser = Calcp.new +$tidx = 1 + +def chk( src, ans ) + ret = $parser.parse( src ) + unless ret == ans then + bug! "test #{$tidx} fail" + end + $tidx += 1 +end + +chk( + [ [Number, 9], + [false, false], + [false, false] ], 9 +) + +chk( + [ [Number, 5], + ['*', nil], + [Number, 1], + ['-', nil], + [Number, 1], + ['*', nil], + [Number, 8], + [false, false], + [false, false] ], -3 +) + +chk( + [ [Number, 5], + ['+', nil], + [Number, 2], + ['-', nil], + [Number, 5], + ['+', nil], + [Number, 2], + ['-', nil], + [Number, 5], + [false, false], + [false, false] ], -1 +) + +chk( + [ ['-', nil], + [Number, 4], + [false, false], + [false, false] ], -4 +) + +chk( + [ [Number, 7], + ['*', nil], + ['(', nil], + [Number, 4], + ['+', nil], + [Number, 3], + [')', nil], + ['-', nil], + [Number, 9], + [false, false], + [false, false] ], 40 +) diff --git a/test/racc/assets/edtf.y b/test/racc/assets/edtf.y new file mode 100644 index 0000000000..4f5f6bb4fd --- /dev/null +++ b/test/racc/assets/edtf.y @@ -0,0 +1,583 @@ +# -*- racc -*- + +# Copyright 2011 Sylvester Keil. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +# EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# The views and conclusions contained in the software and documentation are +# those of the authors and should not be interpreted as representing official +# policies, either expressed or implied, of the copyright holder. + +class EDTF::Parser + +token T Z E X U UNKNOWN OPEN LONGYEAR UNMATCHED DOTS UA PUA + +expect 0 + +rule + + edtf : level_0_expression + | level_1_expression + | level_2_expression + ; + + # ---- Level 0 / ISO 8601 Rules ---- + + # NB: level 0 intervals are covered by the level 1 interval rules + level_0_expression : date + | date_time + ; + + date : positive_date + | negative_date + ; + + positive_date : + year { result = Date.new(val[0]).year_precision! } + | year_month { result = Date.new(*val.flatten).month_precision! } + | year_month_day { result = Date.new(*val.flatten).day_precision! } + ; + + negative_date : '-' positive_date { result = -val[1] } + + + date_time : date T time { + result = DateTime.new(val[0].year, val[0].month, val[0].day, *val[2]) + result.skip_timezone = (val[2].length == 3) + } + + time : base_time + | base_time zone_offset { result = val.flatten } + + base_time : hour ':' minute ':' second { result = val.values_at(0, 2, 4) } + | midnight + + midnight : '2' '4' ':' '0' '0' ':' '0' '0' { result = [24, 0, 0] } + + zone_offset : Z { result = 0 } + | '-' zone_offset_hour { result = -1 * val[1] } + | '+' positive_zone_offset { result = val[1] } + ; + + positive_zone_offset : zone_offset_hour + | '0' '0' ':' '0' '0' { result = 0 } + ; + + + zone_offset_hour : d01_13 ':' minute { result = Rational(val[0] * 60 + val[2], 1440) } + | '1' '4' ':' '0' '0' { result = Rational(840, 1440) } + | '0' '0' ':' d01_59 { result = Rational(val[3], 1440) } + ; + + year : digit digit digit digit { + result = val.zip([1000,100,10,1]).reduce(0) { |s,(a,b)| s += a * b } + } + + month : d01_12 + day : d01_31 + + year_month : year '-' month { result = [val[0], val[2]] } + + # We raise an exception if there are two many days for the month, but + # do not consider leap years, as the EDTF BNF did not either. + # NB: an exception will be raised regardless, because the Ruby Date + # implementation calculates leap years. + year_month_day : year_month '-' day { + result = val[0] << val[2] + if result[2] > 31 || (result[2] > 30 && [2,4,6,9,11].include?(result[1])) || (result[2] > 29 && result[1] == 2) + raise ArgumentError, "invalid date (invalid days #{result[2]} for month #{result[1]})" + end + } + + hour : d00_23 + minute : d00_59 + second : d00_59 + + # Completely covered by level_1_interval + # level_0_interval : date '/' date { result = Interval.new(val[0], val[1]) } + + + # ---- Level 1 Extension Rules ---- + + # NB: Uncertain/approximate Dates are covered by the Level 2 rules + level_1_expression : unspecified | level_1_interval | long_year_simple | season + + # uncertain_or_approximate_date : date UA { result = uoa(val[0], val[1]) } + + unspecified : unspecified_year + { + result = Date.new(val[0][0]).year_precision! + result.unspecified.year[2,2] = val[0][1] + } + | unspecified_month + | unspecified_day + | unspecified_day_and_month + ; + + unspecified_year : + digit digit digit U + { + result = [val[0,3].zip([1000,100,10]).reduce(0) { |s,(a,b)| s += a * b }, [false,true]] + } + | digit digit U U + { + result = [val[0,2].zip([1000,100]).reduce(0) { |s,(a,b)| s += a * b }, [true, true]] + } + + unspecified_month : year '-' U U { + result = Date.new(val[0]).unspecified!(:month) + result.precision = :month + } + + unspecified_day : year_month '-' U U { + result = Date.new(*val[0]).unspecified!(:day) + } + + unspecified_day_and_month : year '-' U U '-' U U { + result = Date.new(val[0]).unspecified!([:day,:month]) + } + + + level_1_interval : level_1_start '/' level_1_end { + result = Interval.new(val[0], val[2]) + } + + level_1_start : date | partial_uncertain_or_approximate | unspecified | partial_unspecified | UNKNOWN + + level_1_end : level_1_start | OPEN + + + long_year_simple : + LONGYEAR long_year + { + result = Date.new(val[1]) + result.precision = :year + } + | LONGYEAR '-' long_year + { + result = Date.new(-1 * val[2]) + result.precision = :year + } + ; + + long_year : + positive_digit digit digit digit digit { + result = val.zip([10000,1000,100,10,1]).reduce(0) { |s,(a,b)| s += a * b } + } + | long_year digit { result = 10 * val[0] + val[1] } + ; + + + season : year '-' season_number ua { + result = Season.new(val[0], val[2]) + val[3].each { |ua| result.send(ua) } + } + + season_number : '2' '1' { result = 21 } + | '2' '2' { result = 22 } + | '2' '3' { result = 23 } + | '2' '4' { result = 24 } + ; + + + # ---- Level 2 Extension Rules ---- + + # NB: Level 2 Intervals are covered by the Level 1 Interval rules. + level_2_expression : season_qualified + | partial_uncertain_or_approximate + | partial_unspecified + | choice_list + | inclusive_list + | masked_precision + | date_and_calendar + | long_year_scientific + ; + + + season_qualified : season '^' { result = val[0]; result.qualifier = val[1] } + + + long_year_scientific : + long_year_simple E integer + { + result = Date.new(val[0].year * 10 ** val[2]).year_precision! + } + | LONGYEAR int1_4 E integer + { + result = Date.new(val[1] * 10 ** val[3]).year_precision! + } + | LONGYEAR '-' int1_4 E integer + { + result = Date.new(-1 * val[2] * 10 ** val[4]).year_precision! + } + ; + + + date_and_calendar : date '^' { result = val[0]; result.calendar = val[1] } + + + masked_precision : + digit digit digit X + { + d = val[0,3].zip([1000,100,10]).reduce(0) { |s,(a,b)| s += a * b } + result = EDTF::Decade.new(d) + } + | digit digit X X + { + d = val[0,2].zip([1000,100]).reduce(0) { |s,(a,b)| s += a * b } + result = EDTF::Century.new(d) + } + ; + + + choice_list : '[' list ']' { result = val[1].choice! } + + inclusive_list : '{' list '}' { result = val[1] } + + list : earlier { result = EDTF::Set.new(val[0]).earlier! } + | earlier ',' list_elements ',' later { result = EDTF::Set.new([val[0]] + val[2] + [val[4]]).earlier!.later! } + | earlier ',' list_elements { result = EDTF::Set.new([val[0]] + val[2]).earlier! } + | earlier ',' later { result = EDTF::Set.new([val[0]] + [val[2]]).earlier!.later! } + | list_elements ',' later { result = EDTF::Set.new(val[0] + [val[2]]).later! } + | list_elements { result = EDTF::Set.new(*val[0]) } + | later { result = EDTF::Set.new(val[0]).later! } + ; + + list_elements : list_element { result = [val[0]].flatten } + | list_elements ',' list_element { result = val[0] + [val[2]].flatten } + ; + + list_element : atomic + | consecutives + ; + + atomic : date + | partial_uncertain_or_approximate + | unspecified + ; + + earlier : DOTS date { result = val[1] } + + later : year_month_day DOTS { result = Date.new(*val[0]).year_precision! } + | year_month DOTS { result = Date.new(*val[0]).month_precision! } + | year DOTS { result = Date.new(val[0]).year_precision! } + ; + + consecutives : year_month_day DOTS year_month_day { result = (Date.new(val[0]).day_precision! .. Date.new(val[2]).day_precision!) } + | year_month DOTS year_month { result = (Date.new(val[0]).month_precision! .. Date.new(val[2]).month_precision!) } + | year DOTS year { result = (Date.new(val[0]).year_precision! .. Date.new(val[2]).year_precision!) } + ; + + partial_unspecified : + unspecified_year '-' month '-' day + { + result = Date.new(val[0][0], val[2], val[4]) + result.unspecified.year[2,2] = val[0][1] + } + | unspecified_year '-' U U '-' day + { + result = Date.new(val[0][0], 1, val[5]) + result.unspecified.year[2,2] = val[0][1] + result.unspecified!(:month) + } + | unspecified_year '-' U U '-' U U + { + result = Date.new(val[0][0], 1, 1) + result.unspecified.year[2,2] = val[0][1] + result.unspecified!([:month, :day]) + } + | unspecified_year '-' month '-' U U + { + result = Date.new(val[0][0], val[2], 1) + result.unspecified.year[2,2] = val[0][1] + result.unspecified!(:day) + } + | year '-' U U '-' day + { + result = Date.new(val[0], 1, val[5]) + result.unspecified!(:month) + } + ; + + + partial_uncertain_or_approximate : pua_base + | '(' pua_base ')' UA { result = uoa(val[1], val[3]) } + + pua_base : + pua_year { result = val[0].year_precision! } + | pua_year_month { result = val[0][0].month_precision! } + | pua_year_month_day { result = val[0].day_precision! } + + pua_year : year UA { result = uoa(Date.new(val[0]), val[1], :year) } + + pua_year_month : + pua_year '-' month ua { + result = [uoa(val[0].change(:month => val[2]), val[3], [:month, :year])] + } + | year '-' month UA { + result = [uoa(Date.new(val[0], val[2]), val[3], [:year, :month])] + } + | year '-(' month ')' UA { + result = [uoa(Date.new(val[0], val[2]), val[4], [:month]), true] + } + | pua_year '-(' month ')' UA { + result = [uoa(val[0].change(:month => val[2]), val[4], [:month]), true] + } + ; + + pua_year_month_day : + pua_year_month '-' day ua { + result = uoa(val[0][0].change(:day => val[2]), val[3], val[0][1] ? [:day] : nil) + } + | pua_year_month '-(' day ')' UA { + result = uoa(val[0][0].change(:day => val[2]), val[4], [:day]) + } + | year '-(' month ')' UA day ua { + result = uoa(uoa(Date.new(val[0], val[2], val[5]), val[4], :month), val[6], :day) + } + | year_month '-' day UA { + result = uoa(Date.new(val[0][0], val[0][1], val[2]), val[3]) + } + | year_month '-(' day ')' UA { + result = uoa(Date.new(val[0][0], val[0][1], val[2]), val[4], [:day]) + } + | year '-(' month '-' day ')' UA { + result = uoa(Date.new(val[0], val[2], val[4]), val[6], [:month, :day]) + } + | year '-(' month '-(' day ')' UA ')' UA { + result = Date.new(val[0], val[2], val[4]) + result = uoa(result, val[6], [:day]) + result = uoa(result, val[8], [:month, :day]) + } + | pua_year '-(' month '-' day ')' UA { + result = val[0].change(:month => val[2], :day => val[4]) + result = uoa(result, val[6], [:month, :day]) + } + | pua_year '-(' month '-(' day ')' UA ')' UA { + result = val[0].change(:month => val[2], :day => val[4]) + result = uoa(result, val[6], [:day]) + result = uoa(result, val[8], [:month, :day]) + } + # | '(' pua_year '-(' month ')' UA ')' UA '-' day ua { + # result = val[1].change(:month => val[3], :day => val[9]) + # result = uoa(result, val[5], [:month]) + # result = [uoa(result, val[7], [:year]), true] + # } + ; + + ua : { result = [] } | UA + + # ---- Auxiliary Rules ---- + + digit : '0' { result = 0 } + | positive_digit + ; + + positive_digit : '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' + + d01_12 : '0' positive_digit { result = val[1] } + | '1' '0' { result = 10 } + | '1' '1' { result = 11 } + | '1' '2' { result = 12 } + ; + + d01_13 : d01_12 + | '1' '3' { result = 13 } + ; + + d01_23 : '0' positive_digit { result = val[1] } + | '1' digit { result = 10 + val[1] } + | '2' '0' { result = 20 } + | '2' '1' { result = 21 } + | '2' '2' { result = 22 } + | '2' '3' { result = 23 } + ; + + d00_23 : '0' '0' + | d01_23 + ; + + d01_29 : d01_23 + | '2' '4' { result = 24 } + | '2' '5' { result = 25 } + | '2' '6' { result = 26 } + | '2' '7' { result = 27 } + | '2' '8' { result = 28 } + | '2' '9' { result = 29 } + ; + + d01_30 : d01_29 + | '3' '0' { result = 30 } + ; + + d01_31 : d01_30 + | '3' '1' { result = 31 } + ; + + d01_59 : d01_29 + | '3' digit { result = 30 + val[1] } + | '4' digit { result = 40 + val[1] } + | '5' digit { result = 50 + val[1] } + ; + + d00_59 : '0' '0' + | d01_59 + ; + + int1_4 : positive_digit { result = val[0] } + | positive_digit digit { result = 10 * val[0] + val[1] } + | positive_digit digit digit + { + result = val.zip([100,10,1]).reduce(0) { |s,(a,b)| s += a * b } + } + | positive_digit digit digit digit + { + result = val.zip([1000,100,10,1]).reduce(0) { |s,(a,b)| s += a * b } + } + ; + + integer : positive_digit { result = val[0] } + | integer digit { result = 10 * val[0] + val[1] } + ; + + + +---- header +require 'strscan' + +---- inner + + @defaults = { + :level => 2, + :debug => false + }.freeze + + class << self; attr_reader :defaults; end + + attr_reader :options + + def initialize(options = {}) + @options = Parser.defaults.merge(options) + end + + def debug? + !!(options[:debug] || ENV['DEBUG']) + end + + def parse(input) + parse!(input) + rescue => e + warn e.message if debug? + nil + end + + def parse!(input) + @yydebug = debug? + @src = StringScanner.new(input) + do_parse + end + + def on_error(tid, value, stack) + raise ArgumentError, + "failed to parse date: unexpected '#{value}' at #{stack.inspect}" + end + + def apply_uncertainty(date, uncertainty, scope = nil) + uncertainty.each do |u| + scope.nil? ? date.send(u) : date.send(u, scope) + end + date + end + + alias uoa apply_uncertainty + + def next_token + case + when @src.eos? + nil + # when @src.scan(/\s+/) + # ignore whitespace + when @src.scan(/\(/) + ['(', @src.matched] + # when @src.scan(/\)\?~-/) + # [:PUA, [:uncertain!, :approximate!]] + # when @src.scan(/\)\?-/) + # [:PUA, [:uncertain!]] + # when @src.scan(/\)~-/) + # [:PUA, [:approximate!]] + when @src.scan(/\)/) + [')', @src.matched] + when @src.scan(/\[/) + ['[', @src.matched] + when @src.scan(/\]/) + [']', @src.matched] + when @src.scan(/\{/) + ['{', @src.matched] + when @src.scan(/\}/) + ['}', @src.matched] + when @src.scan(/T/) + [:T, @src.matched] + when @src.scan(/Z/) + [:Z, @src.matched] + when @src.scan(/\?~/) + [:UA, [:uncertain!, :approximate!]] + when @src.scan(/\?/) + [:UA, [:uncertain!]] + when @src.scan(/~/) + [:UA, [:approximate!]] + when @src.scan(/open/i) + [:OPEN, :open] + when @src.scan(/unkn?own/i) # matches 'unkown' typo too + [:UNKNOWN, :unknown] + when @src.scan(/u/) + [:U, @src.matched] + when @src.scan(/x/i) + [:X, @src.matched] + when @src.scan(/y/) + [:LONGYEAR, @src.matched] + when @src.scan(/e/) + [:E, @src.matched] + when @src.scan(/\+/) + ['+', @src.matched] + when @src.scan(/-\(/) + ['-(', @src.matched] + when @src.scan(/-/) + ['-', @src.matched] + when @src.scan(/:/) + [':', @src.matched] + when @src.scan(/\//) + ['/', @src.matched] + when @src.scan(/\s*\.\.\s*/) + [:DOTS, '..'] + when @src.scan(/\s*,\s*/) + [',', ','] + when @src.scan(/\^\w+/) + ['^', @src.matched[1..-1]] + when @src.scan(/\d/) + [@src.matched, @src.matched.to_i] + else @src.scan(/./) + [:UNMATCHED, @src.rest] + end + end + + +# -*- racc -*- diff --git a/test/racc/assets/err.y b/test/racc/assets/err.y new file mode 100644 index 0000000000..ae280957cc --- /dev/null +++ b/test/racc/assets/err.y @@ -0,0 +1,60 @@ + +class ErrTestp + +rule + +target: lines + ; + +lines: line + | lines line + ; + +line: A B C D E + | error E + ; + +end + +---- inner + +def initialize + @yydebug = false + @q = [ + [:A, 'a'], + # [:B, 'b'], + [:C, 'c'], + [:D, 'd'], + [:E, 'e'], + + [:A, 'a'], + [:B, 'b'], + [:C, 'c'], + [:D, 'd'], + [:E, 'e'], + + [:A, 'a'], + [:B, 'b'], + # [:C, 'c'], + [:D, 'd'], + [:E, 'e'], + [false, nil] + ] +end + +def next_token + @q.shift +end + +def on_error( t, val, values ) + $stderr.puts "error on token '#{val}'(#{t})" +end + +def parse + do_parse +end + +---- footer + +p = ErrTestp.new +p.parse diff --git a/test/racc/assets/error_recovery.y b/test/racc/assets/error_recovery.y new file mode 100644 index 0000000000..1fd21ac7d0 --- /dev/null +++ b/test/racc/assets/error_recovery.y @@ -0,0 +1,35 @@ +# Regression test case for the bug discussed here: +# https://github.com/whitequark/parser/issues/93 +# In short, a Racc-generated parser could go into an infinite loop when +# attempting error recovery at EOF + +class InfiniteLoop + +rule + + stmts: stmt + | error stmt + + stmt: '%' stmt + +end + +---- inner + + def parse + @errors = [] + do_parse + end + + def next_token + nil + end + + def on_error(error_token, error_value, value_stack) + # oh my, an error + @errors << [error_token, error_value] + end + +---- footer + +InfiniteLoop.new.parse \ No newline at end of file diff --git a/test/racc/assets/expect.y b/test/racc/assets/expect.y new file mode 100644 index 0000000000..24c27443e2 --- /dev/null +++ b/test/racc/assets/expect.y @@ -0,0 +1,7 @@ +class E + expect 1 +rule + list: inlist inlist + inlist: + | A +end diff --git a/test/racc/assets/firstline.y b/test/racc/assets/firstline.y new file mode 100644 index 0000000000..ab0692e543 --- /dev/null +++ b/test/racc/assets/firstline.y @@ -0,0 +1,4 @@ +class T +rule + a: A B C +end diff --git a/test/racc/assets/huia.y b/test/racc/assets/huia.y new file mode 100644 index 0000000000..de9d45150c --- /dev/null +++ b/test/racc/assets/huia.y @@ -0,0 +1,318 @@ +# Copyright (c) 2014 James Harton +# +# MIT License +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +class Huia::Parser + + token + IDENTIFIER EQUAL PLUS MINUS ASTERISK FWD_SLASH COLON FLOAT INTEGER STRING + EXPO INDENT OUTDENT OPAREN CPAREN DOT SIGNATURE NL EOF PIPE COMMA NIL TRUE + FALSE EQUALITY CALL SELF CONSTANT CHAR DOUBLE_TICK_STRING + DOUBLE_TICK_STRING_END INTERPOLATE_START INTERPOLATE_END BOX LSQUARE + RSQUARE FACES LFACE RFACE BANG TILDE RETURN NOT_EQUALITY OR AND GT LT + GTE LTE AT + + prechigh + left EXPO + left BANG TILDE + left ASTERISK FWD_SLASH PERCENT + left PLUS MINUS + + right EQUAL + preclow + + rule + statements: statement + | statements statement { return scope } + + statement: expr eol { return scope.append val[0] } + | expr { return scope.append val[0] } + | eol { return scope } + + eol: NL | EOF + nlq: NL | + + expr: literal + | grouped_expr + | binary_op + | unary_op + | method_call + | constant + | variable + | array + | hash + | return + + return: return_expr + | return_nil + return_expr: RETURN expr { return n(:Return, val[1]) } + return_nil: RETURN { return n(:Return, n(:Nil)) } + + array: empty_array + | array_list + + empty_array: BOX { return n :Array } + + array_list: LSQUARE array_items RSQUARE { return val[1] } + array_items: expr { return n :Array, [val[0]] } + | array_items COMMA expr { val[0].append(val[2]); return val[0] } + + hash: empty_hash + | hash_list + empty_hash: FACES { return n :Hash } + hash_list: LFACE hash_items RFACE { return val[1] } + hash_items: hash_item { return n :Hash, val[0] } + | hash_items COMMA hash_item { val[0].append(val[2]); return val[0] } + hash_item: expr COLON expr { return n :HashItem, val[0], val[2] } + + constant: CONSTANT { return constant val[0] } + + indented: indented_w_stmts + | indented_w_expr + | indented_wo_stmts + indented_w_stmts: indent statements outdent { return val[0] } + indented_w_expr: indent expr outdent { return val[0].append(val[1]) } + indented_wo_stmts: indent outdent { return val[0] } + outdent: OUTDENT { return pop_scope } + + + indent_w_args: indent_pipe indent_args PIPE nlq INDENT { return val[0] } + indent_pipe: PIPE { return push_scope } + indent_wo_args: INDENT { return push_scope } + indent: indent_w_args + | indent_wo_args + + indent_args: indent_arg + | indent_args COMMA indent_arg + indent_arg: arg_var { return scope.add_argument val[0] } + | arg_var EQUAL expr { return n :Assignment, val[0], val[2] } + arg_var: IDENTIFIER { return n :Variable, val[0] } + + method_call: method_call_on_object + | method_call_on_self + | method_call_on_closure + method_call_on_object: expr DOT call_signature { return n :MethodCall, val[0], val[2] } + | expr DOT IDENTIFIER { return n :MethodCall, val[0], n(:CallSignature, val[2]) } + method_call_on_self: call_signature { return n :MethodCall, scope_instance, val[0] } + + method_call_on_closure: AT call_signature { return n :MethodCall, this_closure, val[1] } + | AT IDENTIFIER { return n :MethodCall, this_closure, n(:CallSignature, val[1]) } + + call_signature: call_arguments + | call_simple_name + call_simple_name: CALL { return n :CallSignature, val[0] } + call_argument: SIGNATURE call_passed_arg { return n :CallSignature, val[0], [val[1]] } + call_passed_arg: call_passed_simple + | call_passed_indented + call_passed_simple: expr + | expr NL + call_passed_indented: indented + | indented NL + call_arguments: call_argument { return val[0] } + | call_arguments call_argument { return val[0].concat_signature val[1] } + + grouped_expr: OPAREN expr CPAREN { return n :Expression, val[1] } + + variable: IDENTIFIER { return allocate_local val[0] } + + binary_op: assignment + | addition + | subtraction + | multiplication + | division + | exponentiation + | modulo + | equality + | not_equality + | logical_or + | logical_and + | greater_than + | less_than + | greater_or_eq + | less_or_eq + + assignment: IDENTIFIER EQUAL expr { return allocate_local_assignment val[0], val[2] } + addition: expr PLUS expr { return binary val[0], val[2], 'plus:' } + subtraction: expr MINUS expr { return binary val[0], val[2], 'minus:' } + multiplication: expr ASTERISK expr { return binary val[0], val[2], 'multiplyBy:' } + division: expr FWD_SLASH expr { return binary val[0], val[2], 'divideBy:' } + exponentiation: expr EXPO expr { return binary val[0], val[2], 'toThePowerOf:' } + modulo: expr PERCENT expr { return binary val[0], val[2], 'moduloOf:' } + equality: expr EQUALITY expr { return binary val[0], val[2], 'isEqualTo:' } + not_equality: expr NOT_EQUALITY expr { return binary val[0], val[2], 'isNotEqualTo:' } + logical_or: expr OR expr { return binary val[0], val[2], 'logicalOr:' } + logical_and: expr AND expr { return binary val[0], val[2], 'logicalAnd:' } + greater_than: expr GT expr { return binary val[0], val[2], 'isGreaterThan:' } + less_than: expr LT expr { return binary val[0], val[2], 'isLessThan:' } + greater_or_eq: expr GTE expr { return binary val[0], val[2], 'isGreaterOrEqualTo:' } + less_or_eq: expr LTE expr { return binary val[0], val[2], 'isLessOrEqualTo:' } + + unary_op: unary_not + | unary_plus + | unary_minus + | unary_complement + + unary_not: BANG expr { return unary val[1], 'unaryNot' } + unary_plus: PLUS expr { return unary val[1], 'unaryPlus' } + unary_minus: MINUS expr { return unary val[1], 'unaryMinus' } + unary_complement: TILDE expr { return unary val[1], 'unaryComplement' } + + literal: integer + | float + | string + | nil + | true + | false + | self + + float: FLOAT { return n :Float, val[0] } + integer: INTEGER { return n :Integer, val[0] } + nil: NIL { return n :Nil } + true: TRUE { return n :True } + false: FALSE { return n :False } + self: SELF { return n :Self } + + string: STRING { return n :String, val[0] } + | interpolated_string + | empty_string + + interpolated_string: DOUBLE_TICK_STRING interpolated_string_contents DOUBLE_TICK_STRING_END { return val[1] } + interpolation: INTERPOLATE_START expr INTERPOLATE_END { return val[1] } + interpolated_string_contents: interpolated_string_chunk { return n :InterpolatedString, val[0] } + | interpolated_string_contents interpolated_string_chunk { val[0].append(val[1]); return val[0] } + interpolated_string_chunk: chars { return val[0] } + | interpolation { return to_string(val[0]) } + empty_string: DOUBLE_TICK_STRING DOUBLE_TICK_STRING_END { return n :String, '' } + + chars: CHAR { return n :String, val[0] } + | chars CHAR { val[0].append(val[1]); return val[0] } +end + +---- inner + +attr_accessor :lexer, :scopes, :state + +def initialize lexer + @lexer = lexer + @state = [] + @scopes = [] + push_scope +end + +def ast + @ast ||= do_parse + @scopes.first +end + +def on_error t, val, vstack + line = lexer.line + col = lexer.column + message = "Unexpected #{token_to_str t} at #{lexer.filename} line #{line}:#{col}:\n\n" + + start = line - 5 > 0 ? line - 5 : 0 + i_size = line.to_s.size + (start..(start + 5)).each do |i| + message << sprintf("\t%#{i_size}d: %s\n", i, lexer.get_line(i)) + message << "\t#{' ' * i_size} #{'-' * (col - 1)}^\n" if i == line + end + + raise SyntaxError, message +end + +def next_token + nt = lexer.next_computed_token + # just use a state stack for now, we'll have to do something + # more sophisticated soon. + if nt && nt.first == :state + if nt.last + state.push << nt.last + else + state.pop + end + next_token + else + nt + end +end + +def push_scope + new_scope = Huia::AST::Scope.new scope + new_scope.file = lexer.filename + new_scope.line = lexer.line + new_scope.column = lexer.column + scopes.push new_scope + new_scope +end + +def pop_scope + scopes.pop +end + +def scope + scopes.last +end + +def binary left, right, method + node(:MethodCall, left, node(:CallSignature, method, [right])) +end + +def unary left, method + node(:MethodCall, left, node(:CallSignature, method)) +end + +def node type, *args + Huia::AST.const_get(type).new(*args).tap do |n| + n.file = lexer.filename + n.line = lexer.line + n.column = lexer.column + end +end +alias n node + +def allocate_local name + node(:Variable, name).tap do |n| + scope.allocate_local n + end +end + +def allocate_local_assignment name, value + node(:Assignment, name, value).tap do |n| + scope.allocate_local n + end +end + +def this_closure + allocate_local('@') +end + +def scope_instance + node(:ScopeInstance, scope) +end + +def constant name + return scope_instance if name == 'self' + node(:Constant, name) +end + +def to_string expr + node(:MethodCall, expr, node(:CallSignature, 'toString')) +end diff --git a/test/racc/assets/ichk.y b/test/racc/assets/ichk.y new file mode 100644 index 0000000000..1d359df83e --- /dev/null +++ b/test/racc/assets/ichk.y @@ -0,0 +1,102 @@ +class Calculator + + prechigh + left '*' '/' + left '+' '-' + preclow + + convert + NUMBER 'Number' + end + +rule + + target : exp + | /* none */ { result = 0 } + + exp : exp '+' exp { result += val[2]; a = 'plus' } + | exp '-' exp { result -= val[2]; a = "string test" } + | exp '*' exp { result *= val[2] } + | exp '/' exp { result /= val[2] } + | '(' { $emb = true } exp ')' + { + raise 'must not happen' unless $emb + result = val[2] + } + | '-' NUMBER { result = -val[1] } + | NUMBER + +----header + +class Number +end + +----inner + + def initialize + @racc_debug_out = $stdout + @yydebug = false + end + + def validate(expected, src) + result = parse(src) + unless result == expected + raise "test #{@test_number} fail" + end + @test_number += 1 + end + + def parse(src) + @src = src + @test_number = 1 + yyparse self, :scan + end + + def scan(&block) + @src.each(&block) + end + +----footer + +calc = Calculator.new + +calc.validate(9, [[Number, 9], nil]) + +calc.validate(-3, + [[Number, 5], + ['*', '*'], + [Number, 1], + ['-', '*'], + [Number, 1], + ['*', '*'], + [Number, 8], + nil]) + +calc.validate(-1, + [[Number, 5], + ['+', '+'], + [Number, 2], + ['-', '-'], + [Number, 5], + ['+', '+'], + [Number, 2], + ['-', '-'], + [Number, 5], + nil]) + +calc.validate(-4, + [['-', 'UMINUS'], + [Number, 4], + nil]) + +calc.validate(40, + [[Number, 7], + ['*', '*'], + ['(', '('], + [Number, 4], + ['+', '+'], + [Number, 3], + [')', ')'], + ['-', '-'], + [Number, 9], + nil]) diff --git a/test/racc/assets/intp.y b/test/racc/assets/intp.y new file mode 100644 index 0000000000..24e547da61 --- /dev/null +++ b/test/racc/assets/intp.y @@ -0,0 +1,546 @@ +# +# intp +# + +class Intp::Parser + +prechigh + nonassoc UMINUS + left '*' '/' + left '+' '-' + nonassoc EQ +preclow + +rule + + program : stmt_list + { + result = RootNode.new( val[0] ) + } + + stmt_list : + { + result = [] + } + | stmt_list stmt EOL + { + result.push val[1] + } + | stmt_list EOL + + stmt : expr + | assign + | IDENT realprim + { + result = FuncallNode.new( @fname, val[0][0], + val[0][1], [val[1]] ) + } + | if_stmt + | while_stmt + | defun + + if_stmt : IF stmt THEN EOL stmt_list else_stmt END + { + result = IfNode.new( @fname, val[0][0], + val[1], val[4], val[5] ) + } + + else_stmt : ELSE EOL stmt_list + { + result = val[2] + } + | + { + result = nil + } + + while_stmt: WHILE stmt DO EOL stmt_list END + { + result = WhileNode.new(@fname, val[0][0], + val[1], val[4]) + } + + defun : DEF IDENT param EOL stmt_list END + { + result = DefNode.new(@fname, val[0][0], val[1][1], + Function.new(@fname, val[0][0], val[2], val[4])) + } + + param : '(' name_list ')' + { + result = val[1] + } + | '(' ')' + { + result = [] + } + | + { + result = [] + } + + name_list : IDENT + { + result = [ val[0][1] ] + } + | name_list ',' IDENT + { + result.push val[2][1] + } + + assign : IDENT '=' expr + { + result = AssignNode.new(@fname, val[0][0], val[0][1], val[2]) + } + + expr : expr '+' expr + { + result = FuncallNode.new(@fname, val[0].lineno, '+', [val[0], val[2]]) + } + | expr '-' expr + { + result = FuncallNode.new(@fname, val[0].lineno, '-', [val[0], val[2]]) + } + | expr '*' expr + { + result = FuncallNode.new(@fname, val[0].lineno, '*', [val[0], val[2]]) + } + | expr '/' expr + { + result = FuncallNode.new(@fname, val[0].lineno, + '/', [val[0], val[2]]) + } + | expr EQ expr + { + result = FuncallNode.new(@fname, val[0].lineno, '==', [val[0], val[2]]) + } + | primary + + primary : realprim + | '(' expr ')' + { + result = val[1] + } + | '-' expr =UMINUS + { + result = FuncallNode.new(@fname, val[0][0], '-@', [val[1]]) + } + + realprim : IDENT + { + result = VarRefNode.new(@fname, val[0][0], + val[0][1]) + } + | NUMBER + { + result = LiteralNode.new(@fname, *val[0]) + } + | STRING + { + result = StringNode.new(@fname, *val[0]) + } + | TRUE + { + result = LiteralNode.new(@fname, *val[0]) + } + | FALSE + { + result = LiteralNode.new(@fname, *val[0]) + } + | NIL + { + result = LiteralNode.new(@fname, *val[0]) + } + | funcall + + funcall : IDENT '(' args ')' + { + result = FuncallNode.new(@fname, val[0][0], val[0][1], val[2]) + } + | IDENT '(' ')' + { + result = FuncallNode.new(@fname, val[0][0], val[0][1], []) + } + + args : expr + { + result = val + } + | args ',' expr + { + result.push val[2] + } + +end + +---- header +# +# intp/parser.rb +# + +---- inner + + def initialize + @scope = {} + end + + RESERVED = { + 'if' => :IF, + 'else' => :ELSE, + 'while' => :WHILE, + 'then' => :THEN, + 'do' => :DO, + 'def' => :DEF, + 'true' => :TRUE, + 'false' => :FALSE, + 'nil' => :NIL, + 'end' => :END + } + + RESERVED_V = { + 'true' => true, + 'false' => false, + 'nil' => nil + } + + def parse(f, fname) + @q = [] + @fname = fname + lineno = 1 + f.each do |line| + line.strip! + until line.empty? + case line + when /\A\s+/, /\A\#.*/ + ; + when /\A[a-zA-Z_]\w*/ + word = $& + @q.push [(RESERVED[word] || :IDENT), + [lineno, RESERVED_V.key?(word) ? RESERVED_V[word] : word.intern]] + when /\A\d+/ + @q.push [:NUMBER, [lineno, $&.to_i]] + when /\A"(?:[^"\\]+|\\.)*"/, /\A'(?:[^'\\]+|\\.)*'/ + @q.push [:STRING, [lineno, eval($&)]] + when /\A==/ + @q.push [:EQ, [lineno, '==']] + when /\A./ + @q.push [$&, [lineno, $&]] + else + raise RuntimeError, 'must not happen' + end + line = $' + end + @q.push [:EOL, [lineno, nil]] + lineno += 1 + end + @q.push [false, '$'] + do_parse + end + + def next_token + @q.shift + end + + def on_error(t, v, values) + if v + line = v[0] + v = v[1] + else + line = 'last' + end + raise Racc::ParseError, "#{@fname}:#{line}: syntax error on #{v.inspect}" + end + +---- footer +# intp/node.rb + +module Intp + + class IntpError < StandardError; end + class IntpArgumentError < IntpError; end + + class Core + + def initialize + @ftab = {} + @obj = Object.new + @stack = [] + @stack.push Frame.new '(toplevel)' + end + + def frame + @stack[-1] + end + + def define_function(fname, node) + raise IntpError, "function #{fname} defined twice" if @ftab.key?(fname) + @ftab[fname] = node + end + + def call_function_or(fname, args) + call_intp_function_or(fname, args) { + call_ruby_toplevel_or(fname, args) { + yield + } + } + end + + def call_intp_function_or(fname, args) + if func = @ftab[fname] + frame = Frame.new(fname) + @stack.push frame + func.call self, frame, args + @stack.pop + else + yield + end + end + + def call_ruby_toplevel_or(fname, args) + if @obj.respond_to? fname, true + @obj.send fname, *args + else + yield + end + end + + end + + class Frame + + def initialize(fname) + @fname = fname + @lvars = {} + end + + attr :fname + + def lvar?(name) + @lvars.key? name + end + + def [](key) + @lvars[key] + end + + def []=(key, val) + @lvars[key] = val + end + + end + + + class Node + + def initialize(fname, lineno) + @filename = fname + @lineno = lineno + end + + attr_reader :filename + attr_reader :lineno + + def exec_list(intp, nodes) + v = nil + nodes.each {|i| v = i.evaluate(intp) } + v + end + + def intp_error!(msg) + raise IntpError, "in #{filename}:#{lineno}: #{msg}" + end + + def inspect + "#{self.class.name}/#{lineno}" + end + + end + + + class RootNode < Node + + def initialize(tree) + super nil, nil + @tree = tree + end + + def evaluate + exec_list Core.new, @tree + end + + end + + + class DefNode < Node + + def initialize(file, lineno, fname, func) + super file, lineno + @funcname = fname + @funcobj = func + end + + def evaluate(intp) + intp.define_function @funcname, @funcobj + end + + end + + class FuncallNode < Node + + def initialize(file, lineno, func, args) + super file, lineno + @funcname = func + @args = args + end + + def evaluate(intp) + args = @args.map {|i| i.evaluate intp } + begin + intp.call_intp_function_or(@funcname, args) { + if args.empty? or not args[0].respond_to?(@funcname) + intp.call_ruby_toplevel_or(@funcname, args) { + intp_error! "undefined function #{@funcname.id2name}" + } + else + recv = args.shift + recv.send @funcname, *args + end + } + rescue IntpArgumentError, ArgumentError + intp_error! $!.message + end + end + + end + + class Function < Node + + def initialize(file, lineno, params, body) + super file, lineno + @params = params + @body = body + end + + def call(intp, frame, args) + unless args.size == @params.size + raise IntpArgumentError, + "wrong # of arg for #{frame.fname}() (#{args.size} for #{@params.size})" + end + args.each_with_index do |v,i| + frame[@params[i]] = v + end + exec_list intp, @body + end + + end + + + class IfNode < Node + + def initialize(fname, lineno, cond, tstmt, fstmt) + super fname, lineno + @condition = cond + @tstmt = tstmt + @fstmt = fstmt + end + + def evaluate(intp) + if @condition.evaluate(intp) + exec_list intp, @tstmt + else + exec_list intp, @fstmt if @fstmt + end + end + + end + + class WhileNode < Node + + def initialize(fname, lineno, cond, body) + super fname, lineno + @condition = cond + @body = body + end + + def evaluate(intp) + while @condition.evaluate(intp) + exec_list intp, @body + end + end + + end + + + class AssignNode < Node + + def initialize(fname, lineno, vname, val) + super fname, lineno + @vname = vname + @val = val + end + + def evaluate(intp) + intp.frame[@vname] = @val.evaluate(intp) + end + + end + + class VarRefNode < Node + + def initialize(fname, lineno, vname) + super fname, lineno + @vname = vname + end + + def evaluate(intp) + if intp.frame.lvar?(@vname) + intp.frame[@vname] + else + intp.call_function_or(@vname, []) { + intp_error! "unknown method or local variable #{@vname.id2name}" + } + end + end + + end + + class StringNode < Node + + def initialize(fname, lineno, str) + super fname, lineno + @val = str + end + + def evaluate(intp) + @val.dup + end + + end + + class LiteralNode < Node + + def initialize(fname, lineno, val) + super fname, lineno + @val = val + end + + def evaluate(intp) + @val + end + + end + +end # module Intp + +begin + tree = nil + fname = 'src.intp' + File.open(fname) {|f| + tree = Intp::Parser.new.parse(f, fname) + } + tree.evaluate +rescue Racc::ParseError, Intp::IntpError, Errno::ENOENT + raise #### + $stderr.puts "#{File.basename $0}: #{$!}" + exit 1 +end diff --git a/test/racc/assets/journey.y b/test/racc/assets/journey.y new file mode 100644 index 0000000000..c2640f3339 --- /dev/null +++ b/test/racc/assets/journey.y @@ -0,0 +1,47 @@ +class Journey::Parser + +token SLASH LITERAL SYMBOL LPAREN RPAREN DOT STAR OR + +rule + expressions + : expressions expression { result = Cat.new(val.first, val.last) } + | expression { result = val.first } + | or + ; + expression + : terminal + | group + | star + ; + group + : LPAREN expressions RPAREN { result = Group.new(val[1]) } + ; + or + : expressions OR expression { result = Or.new([val.first, val.last]) } + ; + star + : STAR { result = Star.new(Symbol.new(val.last)) } + ; + terminal + : symbol + | literal + | slash + | dot + ; + slash + : SLASH { result = Slash.new('/') } + ; + symbol + : SYMBOL { result = Symbol.new(val.first) } + ; + literal + : LITERAL { result = Literal.new(val.first) } + dot + : DOT { result = Dot.new(val.first) } + ; + +end + +---- header + +require 'journey/parser_extras' diff --git a/test/racc/assets/liquor.y b/test/racc/assets/liquor.y new file mode 100644 index 0000000000..8045a072a4 --- /dev/null +++ b/test/racc/assets/liquor.y @@ -0,0 +1,313 @@ +# Copyright (c) 2012-2013 Peter Zotov +# 2012 Yaroslav Markin +# 2012 Nate Gadgibalaev +# +# MIT License +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +class Liquor::Parser + token comma dot endtag ident integer keyword lblock lblock2 lbracket + linterp lparen op_div op_eq op_gt op_geq op_lt op_leq op_minus + op_mod op_mul op_neq op_not op_plus pipe plaintext rblock + rbracket rinterp rparen string tag_ident + + prechigh + left dot + nonassoc op_uminus op_not + left op_mul op_div op_mod + left op_plus op_minus + left op_eq op_neq op_lt op_leq op_gt op_geq + left op_and + left op_or + preclow + + expect 15 + + start block + +rule + block: /* empty */ + { result = [] } + | plaintext block + { result = [ val[0], *val[1] ] } + | interp block + { result = [ val[0], *val[1] ] } + | tag block + { result = [ val[0], *val[1] ] } + + interp: + linterp expr rinterp + { result = [ :interp, retag(val), val[1] ] } + | linterp filter_chain rinterp + { result = [ :interp, retag(val), val[1] ] } + + primary_expr: + ident + | lparen expr rparen + { result = [ val[1][0], retag(val), *val[1][2..-1] ] } + + expr: + integer + | string + | tuple + | ident function_args + { result = [ :call, retag(val), val[0], val[1] ] } + | expr lbracket expr rbracket + { result = [ :index, retag(val), val[0], val[2] ] } + | expr dot ident function_args + { result = [ :external, retag(val), val[0], val[2], val[3] ] } + | expr dot ident + { result = [ :external, retag(val), val[0], val[2], nil ] } + | op_minus expr =op_uminus + { result = [ :uminus, retag(val), val[1] ] } + | op_not expr + { result = [ :not, retag(val), val[1] ] } + | expr op_mul expr + { result = [ :mul, retag(val), val[0], val[2] ] } + | expr op_div expr + { result = [ :div, retag(val), val[0], val[2] ] } + | expr op_mod expr + { result = [ :mod, retag(val), val[0], val[2] ] } + | expr op_plus expr + { result = [ :plus, retag(val), val[0], val[2] ] } + | expr op_minus expr + { result = [ :minus, retag(val), val[0], val[2] ] } + | expr op_eq expr + { result = [ :eq, retag(val), val[0], val[2] ] } + | expr op_neq expr + { result = [ :neq, retag(val), val[0], val[2] ] } + | expr op_lt expr + { result = [ :lt, retag(val), val[0], val[2] ] } + | expr op_leq expr + { result = [ :leq, retag(val), val[0], val[2] ] } + | expr op_gt expr + { result = [ :gt, retag(val), val[0], val[2] ] } + | expr op_geq expr + { result = [ :geq, retag(val), val[0], val[2] ] } + | expr op_and expr + { result = [ :and, retag(val), val[0], val[2] ] } + | expr op_or expr + { result = [ :or, retag(val), val[0], val[2] ] } + | primary_expr + + tuple: + lbracket tuple_content rbracket + { result = [ :tuple, retag(val), val[1].compact ] } + + tuple_content: + expr comma tuple_content + { result = [ val[0], *val[2] ] } + | expr + { result = [ val[0] ] } + | /* empty */ + { result = [ ] } + + function_args: + lparen function_args_inside rparen + { result = [ :args, retag(val), *val[1] ] } + + function_args_inside: + expr function_keywords + { result = [ val[0], val[1][2] ] } + | function_keywords + { result = [ nil, val[0][2] ] } + + function_keywords: + keyword expr function_keywords + { name = val[0][2].to_sym + tail = val[2][2] + loc = retag([ val[0], val[1] ]) + + if tail.include? name + @errors << SyntaxError.new("duplicate keyword argument `#{val[0][2]}'", + tail[name][1]) + end + + hash = { + name => [ val[1][0], loc, *val[1][2..-1] ] + }.merge(tail) + + result = [ :keywords, retag([ loc, val[2] ]), hash ] + } + | /* empty */ + { result = [ :keywords, nil, {} ] } + + filter_chain: + expr pipe filter_chain_cont + { result = [ val[0], *val[2] ]. + reduce { |tree, node| node[3][2] = tree; node } + } + + filter_chain_cont: + filter_call pipe filter_chain_cont + { result = [ val[0], *val[2] ] } + | filter_call + { result = [ val[0] ] } + + filter_call: + ident function_keywords + { ident_loc = val[0][1] + empty_args_loc = { line: ident_loc[:line], + start: ident_loc[:end] + 1, + end: ident_loc[:end] + 1, } + result = [ :call, val[0][1], val[0], + [ :args, val[1][1] || empty_args_loc, nil, val[1][2] ] ] + } + + tag: + lblock ident expr tag_first_cont + { result = [ :tag, retag(val), val[1], val[2], *reduce_tag_args(val[3][2]) ] } + | lblock ident tag_first_cont + { result = [ :tag, retag(val), val[1], nil, *reduce_tag_args(val[2][2]) ] } + + # Racc cannot do lookahead across rules. I had to add states + # explicitly to avoid S/R conflicts. You are not expected to + # understand this. + + tag_first_cont: + rblock + { result = [ :cont, retag(val), [] ] } + | keyword tag_first_cont2 + { result = [ :cont, retag(val), [ val[0], *val[1][2] ] ] } + + tag_first_cont2: + rblock block lblock2 tag_next_cont + { result = [ :cont2, val[0][1], [ [:block, val[0][1], val[1] ], *val[3] ] ] } + | expr tag_first_cont + { result = [ :cont2, retag(val), [ val[0], *val[1][2] ] ] } + + tag_next_cont: + endtag rblock + { result = [] } + | keyword tag_next_cont2 + { result = [ val[0], *val[1] ] } + + tag_next_cont2: + rblock block lblock2 tag_next_cont + { result = [ [:block, val[0][1], val[1] ], *val[3] ] } + | expr keyword tag_next_cont3 + { result = [ val[0], val[1], *val[2] ] } + + tag_next_cont3: + rblock block lblock2 tag_next_cont + { result = [ [:block, val[0][1], val[1] ], *val[3] ] } + | expr tag_next_cont + { result = [ val[0], *val[1] ] } + +---- inner + attr_reader :errors, :ast + + def initialize(tags={}) + super() + + @errors = [] + @ast = nil + @tags = tags + end + + def success? + @errors.empty? + end + + def parse(string, name='(code)') + @errors.clear + @name = name + @ast = nil + + begin + @stream = Lexer.lex(string, @name, @tags) + @ast = do_parse + rescue Liquor::SyntaxError => e + @errors << e + end + + success? + end + + def next_token + tok = @stream.shift + [ tok[0], tok ] if tok + end + + TOKEN_NAME_MAP = { + :comma => ',', + :dot => '.', + :lblock => '{%', + :rblock => '%}', + :linterp => '{{', + :rinterp => '}}', + :lbracket => '[', + :rbracket => ']', + :lparen => '(', + :rparen => ')', + :pipe => '|', + :op_not => '!', + :op_mul => '*', + :op_div => '/', + :op_mod => '%', + :op_plus => '+', + :op_minus => '-', + :op_eq => '==', + :op_neq => '!=', + :op_lt => '<', + :op_leq => '<=', + :op_gt => '>', + :op_geq => '>=', + :keyword => 'keyword argument name', + :kwarg => 'keyword argument', + :ident => 'identifier', + } + + def on_error(error_token_id, error_token, value_stack) + if token_to_str(error_token_id) == "$end" + raise Liquor::SyntaxError.new("unexpected end of program", { + file: @name + }) + else + type, (loc, value) = error_token + type = TOKEN_NAME_MAP[type] || type + + raise Liquor::SyntaxError.new("unexpected token `#{type}'", loc) + end + end + + def retag(nodes) + loc = nodes.map { |node| node[1] }.compact + first, *, last = loc + return first if last.nil? + + { + file: first[:file], + line: first[:line], + start: first[:start], + end: last[:end], + } + end + + def reduce_tag_args(list) + list.each_slice(2).reduce([]) { |args, (k, v)| + if v[0] == :block + args << [ :blockarg, retag([ k, v ]), k, v[2] || [] ] + else + args << [ :kwarg, retag([ k, v ]), k, v ] + end + } + end \ No newline at end of file diff --git a/test/racc/assets/machete.y b/test/racc/assets/machete.y new file mode 100644 index 0000000000..ea92d47a69 --- /dev/null +++ b/test/racc/assets/machete.y @@ -0,0 +1,423 @@ +# Copyright (c) 2011 SUSE +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + +class Machete::Parser + +token NIL +token TRUE +token FALSE +token INTEGER +token SYMBOL +token STRING +token REGEXP +token ANY +token EVEN +token ODD +token METHOD_NAME +token CLASS_NAME + +start expression + +rule + +expression : primary + | expression "|" primary { + result = if val[0].is_a?(ChoiceMatcher) + ChoiceMatcher.new(val[0].alternatives << val[2]) + else + ChoiceMatcher.new([val[0], val[2]]) + end + } + +primary : node + | array + | literal + | any + +node : CLASS_NAME { + result = NodeMatcher.new(val[0].to_sym) + } + | CLASS_NAME "<" attrs ">" { + result = NodeMatcher.new(val[0].to_sym, val[2]) + } + +attrs : attr + | attrs "," attr { result = val[0].merge(val[2]) } + +attr : method_name "=" expression { result = { val[0].to_sym => val[2] } } + | method_name "^=" SYMBOL { + result = { + val[0].to_sym => SymbolRegexpMatcher.new( + Regexp.new("^" + Regexp.escape(symbol_value(val[2]).to_s)) + ) + } + } + | method_name "$=" SYMBOL { + result = { + val[0].to_sym => SymbolRegexpMatcher.new( + Regexp.new(Regexp.escape(symbol_value(val[2]).to_s) + "$") + ) + } + } + | method_name "*=" SYMBOL { + result = { + val[0].to_sym => SymbolRegexpMatcher.new( + Regexp.new(Regexp.escape(symbol_value(val[2]).to_s)) + ) + } + } + | method_name "^=" STRING { + result = { + val[0].to_sym => StringRegexpMatcher.new( + Regexp.new("^" + Regexp.escape(string_value(val[2]))) + ) + } + } + | method_name "$=" STRING { + result = { + val[0].to_sym => StringRegexpMatcher.new( + Regexp.new(Regexp.escape(string_value(val[2])) + "$") + ) + } + } + | method_name "*=" STRING { + result = { + val[0].to_sym => StringRegexpMatcher.new( + Regexp.new(Regexp.escape(string_value(val[2]))) + ) + } + } + | method_name "*=" REGEXP { + result = { + val[0].to_sym => IndifferentRegexpMatcher.new( + Regexp.new(regexp_value(val[2])) + ) + } + } + +# Hack to overcome the fact that some tokens will lex as simple tokens, not +# METHOD_NAME tokens, and that "reserved words" will lex as separate kinds of +# tokens. +method_name : METHOD_NAME + | NIL + | TRUE + | FALSE + | ANY + | EVEN + | ODD + | "*" + | "+" + | "<" + | ">" + | "^" + | "|" + +array : "[" items_opt "]" { result = ArrayMatcher.new(val[1]) } + +items_opt : /* empty */ { result = [] } + | items + +items : item { result = [val[0]] } + | items "," item { result = val[0] << val[2] } + +item : expression + | expression quantifier { result = Quantifier.new(val[0], *val[1]) } + +quantifier : "*" { result = [0, nil, 1] } + | "+" { result = [1, nil, 1] } + | "?" { result = [0, 1, 1] } + | "{" INTEGER "}" { + result = [integer_value(val[1]), integer_value(val[1]), 1] + } + | "{" INTEGER "," "}" { + result = [integer_value(val[1]), nil, 1] + } + | "{" "," INTEGER "}" { + result = [0, integer_value(val[2]), 1] + } + | "{" INTEGER "," INTEGER "}" { + result = [integer_value(val[1]), integer_value(val[3]), 1] + } + | "{" EVEN "}" { result = [0, nil, 2] } + | "{" ODD "}" { result = [1, nil, 2] } + +literal : NIL { result = LiteralMatcher.new(nil) } + | TRUE { result = LiteralMatcher.new(true) } + | FALSE { result = LiteralMatcher.new(false) } + | INTEGER { result = LiteralMatcher.new(integer_value(val[0])) } + | SYMBOL { result = LiteralMatcher.new(symbol_value(val[0])) } + | STRING { result = LiteralMatcher.new(string_value(val[0])) } + | REGEXP { result = LiteralMatcher.new(regexp_value(val[0])) } + +any : ANY { result = AnyMatcher.new } + +---- inner + +include Matchers + +class SyntaxError < StandardError; end + +def parse(input) + @input = input + @pos = 0 + + do_parse +end + +private + +def integer_value(value) + if value =~ /^0[bB]/ + value[2..-1].to_i(2) + elsif value =~ /^0[oO]/ + value[2..-1].to_i(8) + elsif value =~ /^0[dD]/ + value[2..-1].to_i(10) + elsif value =~ /^0[xX]/ + value[2..-1].to_i(16) + elsif value =~ /^0/ + value.to_i(8) + else + value.to_i + end +end + +def symbol_value(value) + value[1..-1].to_sym +end + +def string_value(value) + quote = value[0..0] + if quote == "'" + value[1..-2].gsub("\\\\", "\\").gsub("\\'", "'") + elsif quote == '"' + value[1..-2]. + gsub("\\\\", "\\"). + gsub('\\"', '"'). + gsub("\\n", "\n"). + gsub("\\t", "\t"). + gsub("\\r", "\r"). + gsub("\\f", "\f"). + gsub("\\v", "\v"). + gsub("\\a", "\a"). + gsub("\\e", "\e"). + gsub("\\b", "\b"). + gsub("\\s", "\s"). + gsub(/\\([0-7]{1,3})/) { $1.to_i(8).chr }. + gsub(/\\x([0-9a-fA-F]{1,2})/) { $1.to_i(16).chr } + else + raise "Unknown quote: #{quote.inspect}." + end +end + +REGEXP_OPTIONS = { + 'i' => Regexp::IGNORECASE, + 'm' => Regexp::MULTILINE, + 'x' => Regexp::EXTENDED +} + +def regexp_value(value) + /\A\/(.*)\/([imx]*)\z/ =~ value + pattern, options = $1, $2 + + Regexp.new(pattern, options.chars.map { |ch| REGEXP_OPTIONS[ch] }.inject(:|)) +end + +# "^" needs to be here because if it were among operators recognized by +# METHOD_NAME, "^=" would be recognized as two tokens. +SIMPLE_TOKENS = [ + "|", + "<", + ">", + ",", + "=", + "^=", + "^", + "$=", + "[", + "]", + "*=", + "*", + "+", + "?", + "{", + "}" +] + +COMPLEX_TOKENS = [ + [:NIL, /^nil/], + [:TRUE, /^true/], + [:FALSE, /^false/], + # INTEGER needs to be before METHOD_NAME, otherwise e.g. "+1" would be + # recognized as two tokens. + [ + :INTEGER, + /^ + [+-]? # sign + ( + 0[bB][01]+(_[01]+)* # binary (prefixed) + | + 0[oO][0-7]+(_[0-7]+)* # octal (prefixed) + | + 0[dD]\d+(_\d+)* # decimal (prefixed) + | + 0[xX][0-9a-fA-F]+(_[0-9a-fA-F]+)* # hexadecimal (prefixed) + | + 0[0-7]*(_[0-7]+)* # octal (unprefixed) + | + [1-9]\d*(_\d+)* # decimal (unprefixed) + ) + /x + ], + [ + :SYMBOL, + /^ + : + ( + # class name + [A-Z][a-zA-Z0-9_]* + | + # regular method name + [a-z_][a-zA-Z0-9_]*[?!=]? + | + # instance variable name + @[a-zA-Z_][a-zA-Z0-9_]* + | + # class variable name + @@[a-zA-Z_][a-zA-Z0-9_]* + | + # operator (sorted by length, then alphabetically) + (<=>|===|\[\]=|\*\*|\+@|-@|<<|<=|==|=~|>=|>>|\[\]|[%&*+\-\/<>^`|~]) + ) + /x + ], + [ + :STRING, + /^ + ( + ' # sinqle-quoted string + ( + \\[\\'] # escape + | + [^'] # regular character + )* + ' + | + " # double-quoted string + ( + \\ # escape + ( + [\\"ntrfvaebs] # one-character escape + | + [0-7]{1,3} # octal number escape + | + x[0-9a-fA-F]{1,2} # hexadecimal number escape + ) + | + [^"] # regular character + )* + " + ) + /x + ], + [ + :REGEXP, + /^ + \/ + ( + \\ # escape + ( + [\\\/ntrfvaebs\(\)\[\]\{\}\-\.\?\*\+\|\^\$] # one-character escape + | + [0-7]{2,3} # octal number escape + | + x[0-9a-fA-F]{1,2} # hexadecimal number escape + ) + | + [^\/] # regular character + )* + \/ + [imx]* + /x + ], + # ANY, EVEN and ODD need to be before METHOD_NAME, otherwise they would be + # recognized as method names. + [:ANY, /^any/], + [:EVEN, /^even/], + [:ODD, /^odd/], + # We exclude "*", "+", "<", ">", "^" and "|" from method names since they are + # lexed as simple tokens. This is because they have also other meanings in + # Machette patterns beside Ruby method names. + [ + :METHOD_NAME, + /^ + ( + # regular name + [a-z_][a-zA-Z0-9_]*[?!=]? + | + # operator (sorted by length, then alphabetically) + (<=>|===|\[\]=|\*\*|\+@|-@|<<|<=|==|=~|>=|>>|\[\]|[%&\-\/`~]) + ) + /x + ], + [:CLASS_NAME, /^[A-Z][a-zA-Z0-9_]*/] +] + +def next_token + skip_whitespace + + return false if remaining_input.empty? + + # Complex tokens need to be before simple tokens, otherwise e.g. "<<" would be + # recognized as two tokens. + + COMPLEX_TOKENS.each do |type, regexp| + if remaining_input =~ regexp + @pos += $&.length + return [type, $&] + end + end + + SIMPLE_TOKENS.each do |token| + if remaining_input[0...token.length] == token + @pos += token.length + return [token, token] + end + end + + raise SyntaxError, "Unexpected character: #{remaining_input[0..0].inspect}." +end + +def skip_whitespace + if remaining_input =~ /\A^[ \t\r\n]+/ + @pos += $&.length + end +end + +def remaining_input + @input[@pos..-1] +end + +def on_error(error_token_id, error_value, value_stack) + raise SyntaxError, "Unexpected token: #{error_value.inspect}." +end diff --git a/test/racc/assets/macruby.y b/test/racc/assets/macruby.y new file mode 100644 index 0000000000..5ede008308 --- /dev/null +++ b/test/racc/assets/macruby.y @@ -0,0 +1,2197 @@ +# Copyright (c) 2013 Peter Zotov +# +# Parts of the source are derived from ruby_parser: +# Copyright (c) Ryan Davis, seattle.rb +# +# MIT License +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +class Parser::MacRuby + +token kCLASS kMODULE kDEF kUNDEF kBEGIN kRESCUE kENSURE kEND kIF kUNLESS + kTHEN kELSIF kELSE kCASE kWHEN kWHILE kUNTIL kFOR kBREAK kNEXT + kREDO kRETRY kIN kDO kDO_COND kDO_BLOCK kDO_LAMBDA kRETURN kYIELD kSUPER + kSELF kNIL kTRUE kFALSE kAND kOR kNOT kIF_MOD kUNLESS_MOD kWHILE_MOD + kUNTIL_MOD kRESCUE_MOD kALIAS kDEFINED klBEGIN klEND k__LINE__ + k__FILE__ k__ENCODING__ tIDENTIFIER tFID tGVAR tIVAR tCONSTANT + tLABEL tCVAR tNTH_REF tBACK_REF tSTRING_CONTENT tINTEGER tFLOAT + tREGEXP_END tUPLUS tUMINUS tUMINUS_NUM tPOW tCMP tEQ tEQQ tNEQ + tGEQ tLEQ tANDOP tOROP tMATCH tNMATCH tDOT tDOT2 tDOT3 tAREF + tASET tLSHFT tRSHFT tCOLON2 tCOLON3 tOP_ASGN tASSOC tLPAREN + tLPAREN2 tRPAREN tLPAREN_ARG tLBRACK tLBRACK2 tRBRACK tLBRACE + tLBRACE_ARG tSTAR tSTAR2 tAMPER tAMPER2 tTILDE tPERCENT tDIVIDE + tPLUS tMINUS tLT tGT tPIPE tBANG tCARET tLCURLY tRCURLY + tBACK_REF2 tSYMBEG tSTRING_BEG tXSTRING_BEG tREGEXP_BEG tREGEXP_OPT + tWORDS_BEG tQWORDS_BEG tSTRING_DBEG tSTRING_DVAR tSTRING_END + tSTRING tSYMBOL tNL tEH tCOLON tCOMMA tSPACE tSEMI tLAMBDA tLAMBEG + tCHARACTER + +prechigh + right tBANG tTILDE tUPLUS + right tPOW + right tUMINUS_NUM tUMINUS + left tSTAR2 tDIVIDE tPERCENT + left tPLUS tMINUS + left tLSHFT tRSHFT + left tAMPER2 + left tPIPE tCARET + left tGT tGEQ tLT tLEQ + nonassoc tCMP tEQ tEQQ tNEQ tMATCH tNMATCH + left tANDOP + left tOROP + nonassoc tDOT2 tDOT3 + right tEH tCOLON + left kRESCUE_MOD + right tEQL tOP_ASGN + nonassoc kDEFINED + right kNOT + left kOR kAND + nonassoc kIF_MOD kUNLESS_MOD kWHILE_MOD kUNTIL_MOD + nonassoc tLBRACE_ARG + nonassoc tLOWEST +preclow + +rule + + program: top_compstmt + + top_compstmt: top_stmts opt_terms + { + result = @builder.compstmt(val[0]) + } + + top_stmts: # nothing + { + result = [] + } + | top_stmt + { + result = [ val[0] ] + } + | top_stmts terms top_stmt + { + result = val[0] << val[2] + } + | error top_stmt + { + result = [ val[1] ] + } + + top_stmt: stmt + | klBEGIN tLCURLY top_compstmt tRCURLY + { + result = @builder.preexe(val[0], val[1], val[2], val[3]) + } + + bodystmt: compstmt opt_rescue opt_else opt_ensure + { + rescue_bodies = val[1] + else_t, else_ = val[2] + ensure_t, ensure_ = val[3] + + if rescue_bodies.empty? && !else_.nil? + diagnostic :warning, :useless_else, nil, else_t + end + + result = @builder.begin_body(val[0], + rescue_bodies, + else_t, else_, + ensure_t, ensure_) + } + + compstmt: stmts opt_terms + { + result = @builder.compstmt(val[0]) + } + + stmts: # nothing + { + result = [] + } + | stmt + { + result = [ val[0] ] + } + | stmts terms stmt + { + result = val[0] << val[2] + } + | error stmt + { + result = [ val[1] ] + } + + stmt: kALIAS fitem + { + @lexer.state = :expr_fname + } + fitem + { + result = @builder.alias(val[0], val[1], val[3]) + } + | kALIAS tGVAR tGVAR + { + result = @builder.alias(val[0], + @builder.gvar(val[1]), + @builder.gvar(val[2])) + } + | kALIAS tGVAR tBACK_REF + { + result = @builder.alias(val[0], + @builder.gvar(val[1]), + @builder.back_ref(val[2])) + } + | kALIAS tGVAR tNTH_REF + { + diagnostic :error, :nth_ref_alias, nil, val[2] + } + | kUNDEF undef_list + { + result = @builder.undef_method(val[0], val[1]) + } + | stmt kIF_MOD expr_value + { + result = @builder.condition_mod(val[0], nil, + val[1], val[2]) + } + | stmt kUNLESS_MOD expr_value + { + result = @builder.condition_mod(nil, val[0], + val[1], val[2]) + } + | stmt kWHILE_MOD expr_value + { + result = @builder.loop_mod(:while, val[0], val[1], val[2]) + } + | stmt kUNTIL_MOD expr_value + { + result = @builder.loop_mod(:until, val[0], val[1], val[2]) + } + | stmt kRESCUE_MOD stmt + { + rescue_body = @builder.rescue_body(val[1], + nil, nil, nil, + nil, val[2]) + + result = @builder.begin_body(val[0], [ rescue_body ]) + } + | klEND tLCURLY compstmt tRCURLY + { + result = @builder.postexe(val[0], val[1], val[2], val[3]) + } + | lhs tEQL command_call + { + result = @builder.assign(val[0], val[1], val[2]) + } + | mlhs tEQL command_call + { + result = @builder.multi_assign(val[0], val[1], val[2]) + } + | var_lhs tOP_ASGN command_call + { + result = @builder.op_assign(val[0], val[1], val[2]) + } + | primary_value tLBRACK2 opt_call_args rbracket tOP_ASGN command_call + { + result = @builder.op_assign( + @builder.index( + val[0], val[1], val[2], val[3]), + val[4], val[5]) + } + | primary_value tDOT tIDENTIFIER tOP_ASGN command_call + { + result = @builder.op_assign( + @builder.call_method( + val[0], val[1], val[2]), + val[3], val[4]) + } + | primary_value tDOT tCONSTANT tOP_ASGN command_call + { + result = @builder.op_assign( + @builder.call_method( + val[0], val[1], val[2]), + val[3], val[4]) + } + | primary_value tCOLON2 tCONSTANT tOP_ASGN command_call + { + result = @builder.op_assign( + @builder.call_method( + val[0], val[1], val[2]), + val[3], val[4]) + } + | primary_value tCOLON2 tIDENTIFIER tOP_ASGN command_call + { + result = @builder.op_assign( + @builder.call_method( + val[0], val[1], val[2]), + val[3], val[4]) + } + | backref tOP_ASGN command_call + { + @builder.op_assign(val[0], val[1], val[2]) + } + | lhs tEQL mrhs + { + result = @builder.assign(val[0], val[1], + @builder.array(nil, val[2], nil)) + } + | mlhs tEQL arg_value + { + result = @builder.multi_assign(val[0], val[1], val[2]) + } + | mlhs tEQL mrhs + { + result = @builder.multi_assign(val[0], val[1], + @builder.array(nil, val[2], nil)) + } + | expr + + expr: command_call + | expr kAND expr + { + result = @builder.logical_op(:and, val[0], val[1], val[2]) + } + | expr kOR expr + { + result = @builder.logical_op(:or, val[0], val[1], val[2]) + } + | kNOT opt_nl expr + { + result = @builder.not_op(val[0], nil, val[2], nil) + } + | tBANG command_call + { + result = @builder.not_op(val[0], nil, val[1], nil) + } + | arg + + expr_value: expr + + command_call: command + | block_command + + block_command: block_call + | block_call tDOT operation2 command_args + { + result = @builder.call_method(val[0], val[1], val[2], + *val[3]) + } + | block_call tCOLON2 operation2 command_args + { + result = @builder.call_method(val[0], val[1], val[2], + *val[3]) + } + + cmd_brace_block: tLBRACE_ARG + { + @static_env.extend_dynamic + } + opt_block_param compstmt tRCURLY + { + result = [ val[0], val[2], val[3], val[4] ] + + @static_env.unextend + } + + command: operation command_args =tLOWEST + { + result = @builder.call_method(nil, nil, val[0], + *val[1]) + } + | operation command_args cmd_brace_block + { + method_call = @builder.call_method(nil, nil, val[0], + *val[1]) + + begin_t, args, body, end_t = val[2] + result = @builder.block(method_call, + begin_t, args, body, end_t) + } + | primary_value tDOT operation2 command_args =tLOWEST + { + result = @builder.call_method(val[0], val[1], val[2], + *val[3]) + } + | primary_value tDOT operation2 command_args cmd_brace_block + { + method_call = @builder.call_method(val[0], val[1], val[2], + *val[3]) + + begin_t, args, body, end_t = val[4] + result = @builder.block(method_call, + begin_t, args, body, end_t) + } + | primary_value tCOLON2 operation2 command_args =tLOWEST + { + result = @builder.call_method(val[0], val[1], val[2], + *val[3]) + } + | primary_value tCOLON2 operation2 command_args cmd_brace_block + { + method_call = @builder.call_method(val[0], val[1], val[2], + *val[3]) + + begin_t, args, body, end_t = val[4] + result = @builder.block(method_call, + begin_t, args, body, end_t) + } + | kSUPER command_args + { + result = @builder.keyword_cmd(:super, val[0], + *val[1]) + } + | kYIELD command_args + { + result = @builder.keyword_cmd(:yield, val[0], + *val[1]) + } + | kRETURN call_args + { + result = @builder.keyword_cmd(:return, val[0], + nil, val[1], nil) + } + | kBREAK call_args + { + result = @builder.keyword_cmd(:break, val[0], + nil, val[1], nil) + } + | kNEXT call_args + { + result = @builder.keyword_cmd(:next, val[0], + nil, val[1], nil) + } + + mlhs: mlhs_basic + { + result = @builder.multi_lhs(nil, val[0], nil) + } + | tLPAREN mlhs_inner rparen + { + result = @builder.begin(val[0], val[1], val[2]) + } + + mlhs_inner: mlhs_basic + { + result = @builder.multi_lhs(nil, val[0], nil) + } + | tLPAREN mlhs_inner rparen + { + result = @builder.multi_lhs(val[0], val[1], val[2]) + } + + mlhs_basic: mlhs_head + | mlhs_head mlhs_item + { + result = val[0]. + push(val[1]) + } + | mlhs_head tSTAR mlhs_node + { + result = val[0]. + push(@builder.splat(val[1], val[2])) + } + | mlhs_head tSTAR mlhs_node tCOMMA mlhs_post + { + result = val[0]. + push(@builder.splat(val[1], val[2])). + concat(val[4]) + } + | mlhs_head tSTAR + { + result = val[0]. + push(@builder.splat(val[1])) + } + | mlhs_head tSTAR tCOMMA mlhs_post + { + result = val[0]. + push(@builder.splat(val[1])). + concat(val[3]) + } + | tSTAR mlhs_node + { + result = [ @builder.splat(val[0], val[1]) ] + } + | tSTAR mlhs_node tCOMMA mlhs_post + { + result = [ @builder.splat(val[0], val[1]), + *val[3] ] + } + | tSTAR + { + result = [ @builder.splat(val[0]) ] + } + | tSTAR tCOMMA mlhs_post + { + result = [ @builder.splat(val[0]), + *val[2] ] + } + + mlhs_item: mlhs_node + | tLPAREN mlhs_inner rparen + { + result = @builder.begin(val[0], val[1], val[2]) + } + + mlhs_head: mlhs_item tCOMMA + { + result = [ val[0] ] + } + | mlhs_head mlhs_item tCOMMA + { + result = val[0] << val[1] + } + + mlhs_post: mlhs_item + { + result = [ val[0] ] + } + | mlhs_post tCOMMA mlhs_item + { + result = val[0] << val[2] + } + + mlhs_node: variable + { + result = @builder.assignable(val[0]) + } + | primary_value tLBRACK2 opt_call_args rbracket + { + result = @builder.index_asgn(val[0], val[1], val[2], val[3]) + } + | primary_value tDOT tIDENTIFIER + { + result = @builder.attr_asgn(val[0], val[1], val[2]) + } + | primary_value tCOLON2 tIDENTIFIER + { + result = @builder.attr_asgn(val[0], val[1], val[2]) + } + | primary_value tDOT tCONSTANT + { + result = @builder.attr_asgn(val[0], val[1], val[2]) + } + | primary_value tCOLON2 tCONSTANT + { + result = @builder.assignable( + @builder.const_fetch(val[0], val[1], val[2])) + } + | tCOLON3 tCONSTANT + { + result = @builder.assignable( + @builder.const_global(val[0], val[1])) + } + | backref + { + result = @builder.assignable(val[0]) + } + + lhs: variable + { + result = @builder.assignable(val[0]) + } + | primary_value tLBRACK2 opt_call_args rbracket + { + result = @builder.index_asgn(val[0], val[1], val[2], val[3]) + } + | primary_value tDOT tIDENTIFIER + { + result = @builder.attr_asgn(val[0], val[1], val[2]) + } + | primary_value tCOLON2 tIDENTIFIER + { + result = @builder.attr_asgn(val[0], val[1], val[2]) + } + | primary_value tDOT tCONSTANT + { + result = @builder.attr_asgn(val[0], val[1], val[2]) + } + | primary_value tCOLON2 tCONSTANT + { + result = @builder.assignable( + @builder.const_fetch(val[0], val[1], val[2])) + } + | tCOLON3 tCONSTANT + { + result = @builder.assignable( + @builder.const_global(val[0], val[1])) + } + | backref + { + result = @builder.assignable(val[0]) + } + + cname: tIDENTIFIER + { + diagnostic :error, :module_name_const, nil, val[0] + } + | tCONSTANT + + cpath: tCOLON3 cname + { + result = @builder.const_global(val[0], val[1]) + } + | cname + { + result = @builder.const(val[0]) + } + | primary_value tCOLON2 cname + { + result = @builder.const_fetch(val[0], val[1], val[2]) + } + + fname: tIDENTIFIER | tCONSTANT | tFID + | op + | reswords + + fsym: fname + { + result = @builder.symbol(val[0]) + } + | symbol + + fitem: fsym + | dsym + + undef_list: fitem + { + result = [ val[0] ] + } + | undef_list tCOMMA + { + @lexer.state = :expr_fname + } + fitem + { + result = val[0] << val[3] + } + + op: tPIPE | tCARET | tAMPER2 | tCMP | tEQ | tEQQ + | tMATCH | tNMATCH | tGT | tGEQ | tLT | tLEQ + | tNEQ | tLSHFT | tRSHFT | tPLUS | tMINUS | tSTAR2 + | tSTAR | tDIVIDE | tPERCENT | tPOW | tBANG | tTILDE + | tUPLUS | tUMINUS | tAREF | tASET | tBACK_REF2 + + reswords: k__LINE__ | k__FILE__ | k__ENCODING__ | klBEGIN | klEND + | kALIAS | kAND | kBEGIN | kBREAK | kCASE + | kCLASS | kDEF | kDEFINED | kDO | kELSE + | kELSIF | kEND | kENSURE | kFALSE | kFOR + | kIN | kMODULE | kNEXT | kNIL | kNOT + | kOR | kREDO | kRESCUE | kRETRY | kRETURN + | kSELF | kSUPER | kTHEN | kTRUE | kUNDEF + | kWHEN | kYIELD | kIF | kUNLESS | kWHILE + | kUNTIL + + arg: lhs tEQL arg + { + result = @builder.assign(val[0], val[1], val[2]) + } + | lhs tEQL arg kRESCUE_MOD arg + { + rescue_body = @builder.rescue_body(val[3], + nil, nil, nil, + nil, val[4]) + + rescue_ = @builder.begin_body(val[2], [ rescue_body ]) + + result = @builder.assign(val[0], val[1], rescue_) + } + | var_lhs tOP_ASGN arg + { + result = @builder.op_assign(val[0], val[1], val[2]) + } + | var_lhs tOP_ASGN arg kRESCUE_MOD arg + { + rescue_body = @builder.rescue_body(val[3], + nil, nil, nil, + nil, val[4]) + + rescue_ = @builder.begin_body(val[2], [ rescue_body ]) + + result = @builder.op_assign(val[0], val[1], rescue_) + } + | primary_value tLBRACK2 opt_call_args rbracket tOP_ASGN arg + { + result = @builder.op_assign( + @builder.index( + val[0], val[1], val[2], val[3]), + val[4], val[5]) + } + | primary_value tDOT tIDENTIFIER tOP_ASGN arg + { + result = @builder.op_assign( + @builder.call_method( + val[0], val[1], val[2]), + val[3], val[4]) + } + | primary_value tDOT tCONSTANT tOP_ASGN arg + { + result = @builder.op_assign( + @builder.call_method( + val[0], val[1], val[2]), + val[3], val[4]) + } + | primary_value tCOLON2 tIDENTIFIER tOP_ASGN arg + { + result = @builder.op_assign( + @builder.call_method( + val[0], val[1], val[2]), + val[3], val[4]) + } + | primary_value tCOLON2 tCONSTANT tOP_ASGN arg + { + diagnostic :error, :dynamic_const, nil, val[2], [ val[3] ] + } + | tCOLON3 tCONSTANT tOP_ASGN arg + { + diagnostic :error, :dynamic_const, nil, val[1], [ val[2] ] + } + | backref tOP_ASGN arg + { + result = @builder.op_assign(val[0], val[1], val[2]) + } + | arg tDOT2 arg + { + result = @builder.range_inclusive(val[0], val[1], val[2]) + } + | arg tDOT3 arg + { + result = @builder.range_exclusive(val[0], val[1], val[2]) + } + | arg tPLUS arg + { + result = @builder.binary_op(val[0], val[1], val[2]) + } + | arg tMINUS arg + { + result = @builder.binary_op(val[0], val[1], val[2]) + } + | arg tSTAR2 arg + { + result = @builder.binary_op(val[0], val[1], val[2]) + } + | arg tDIVIDE arg + { + result = @builder.binary_op(val[0], val[1], val[2]) + } + | arg tPERCENT arg + { + result = @builder.binary_op(val[0], val[1], val[2]) + } + | arg tPOW arg + { + result = @builder.binary_op(val[0], val[1], val[2]) + } + | tUMINUS_NUM tINTEGER tPOW arg + { + result = @builder.unary_op(val[0], + @builder.binary_op( + @builder.integer(val[1]), + val[2], val[3])) + } + | tUMINUS_NUM tFLOAT tPOW arg + { + result = @builder.unary_op(val[0], + @builder.binary_op( + @builder.float(val[1]), + val[2], val[3])) + } + | tUPLUS arg + { + result = @builder.unary_op(val[0], val[1]) + } + | tUMINUS arg + { + result = @builder.unary_op(val[0], val[1]) + } + | arg tPIPE arg + { + result = @builder.binary_op(val[0], val[1], val[2]) + } + | arg tCARET arg + { + result = @builder.binary_op(val[0], val[1], val[2]) + } + | arg tAMPER2 arg + { + result = @builder.binary_op(val[0], val[1], val[2]) + } + | arg tCMP arg + { + result = @builder.binary_op(val[0], val[1], val[2]) + } + | arg tGT arg + { + result = @builder.binary_op(val[0], val[1], val[2]) + } + | arg tGEQ arg + { + result = @builder.binary_op(val[0], val[1], val[2]) + } + | arg tLT arg + { + result = @builder.binary_op(val[0], val[1], val[2]) + } + | arg tLEQ arg + { + result = @builder.binary_op(val[0], val[1], val[2]) + } + | arg tEQ arg + { + result = @builder.binary_op(val[0], val[1], val[2]) + } + | arg tEQQ arg + { + result = @builder.binary_op(val[0], val[1], val[2]) + } + | arg tNEQ arg + { + result = @builder.binary_op(val[0], val[1], val[2]) + } + | arg tMATCH arg + { + result = @builder.match_op(val[0], val[1], val[2]) + } + | arg tNMATCH arg + { + result = @builder.binary_op(val[0], val[1], val[2]) + } + | tBANG arg + { + result = @builder.not_op(val[0], nil, val[1], nil) + } + | tTILDE arg + { + result = @builder.unary_op(val[0], val[1]) + } + | arg tLSHFT arg + { + result = @builder.binary_op(val[0], val[1], val[2]) + } + | arg tRSHFT arg + { + result = @builder.binary_op(val[0], val[1], val[2]) + } + | arg tANDOP arg + { + result = @builder.logical_op(:and, val[0], val[1], val[2]) + } + | arg tOROP arg + { + result = @builder.logical_op(:or, val[0], val[1], val[2]) + } + | kDEFINED opt_nl arg + { + result = @builder.keyword_cmd(:defined?, val[0], nil, [ val[2] ], nil) + } + + | arg tEH arg opt_nl tCOLON arg + { + result = @builder.ternary(val[0], val[1], + val[2], val[4], val[5]) + } + | primary + + arg_value: arg + + aref_args: none + | args trailer + | args tCOMMA assocs trailer + { + result = val[0] << @builder.associate(nil, val[2], nil) + } + | assocs trailer + { + result = [ @builder.associate(nil, val[0], nil) ] + } + + paren_args: tLPAREN2 opt_call_args rparen + { + result = val + } + + opt_paren_args: # nothing + { + result = [ nil, [], nil ] + } + | paren_args + + opt_call_args: # nothing + { + result = [] + } + | call_args + + call_args: command + { + result = [ val[0] ] + } + | args opt_block_arg + { + result = val[0].concat(val[1]) + } + | assocs opt_block_arg + { + result = [ @builder.associate(nil, val[0], nil) ] + result.concat(val[1]) + } + | args tCOMMA assocs opt_block_arg + { + assocs = @builder.associate(nil, val[2], nil) + result = val[0] << assocs + result.concat(val[3]) + } + | args tCOMMA assocs tCOMMA args opt_block_arg + { + val[2][-1] = @builder.objc_varargs(val[2][-1], val[4]) + assocs = @builder.associate(nil, val[2], nil) + result = val[0] << assocs + result.concat(val[5]) + } + | block_arg + { + result = [ val[0] ] + } + + call_args2: arg_value tCOMMA args opt_block_arg + { + result = [ val[0], *val[2].concat(val[3]) ] + } + | arg_value tCOMMA block_arg + { + result = [ val[0], val[2] ] + } + | assocs opt_block_arg + { + result = [ @builder.associate(nil, val[0], nil), + *val[1] ] + } + | arg_value tCOMMA assocs opt_block_arg + { + result = [ val[0], + @builder.associate(nil, val[2], nil), + *val[3] ] + } + | arg_value tCOMMA args tCOMMA assocs opt_block_arg + { + result = [ val[0], + *val[2]. + push(@builder.associate(nil, val[4], nil)). + concat(val[5]) ] + } + | block_arg + { + result = [ val[0] ] + } + + command_args: { + result = @lexer.cmdarg.dup + @lexer.cmdarg.push(true) + } + open_args + { + @lexer.cmdarg = val[0] + + result = val[1] + } + + open_args: call_args + { + result = [ nil, val[0], nil ] + } + | tLPAREN_ARG + { + @lexer.state = :expr_endarg + } + rparen + { + result = [ val[0], [], val[2] ] + } + | tLPAREN_ARG call_args2 + { + @lexer.state = :expr_endarg + } + rparen + { + result = [ val[0], val[1], val[3] ] + } + + block_arg: tAMPER arg_value + { + result = @builder.block_pass(val[0], val[1]) + } + + opt_block_arg: tCOMMA block_arg + { + result = [ val[1] ] + } + | tCOMMA + { + result = [] + } + | # nothing + { + result = [] + } + + args: arg_value + { + result = [ val[0] ] + } + | tSTAR arg_value + { + result = [ @builder.splat(val[0], val[1]) ] + } + | args tCOMMA arg_value + { + result = val[0] << val[2] + } + | args tCOMMA tSTAR arg_value + { + result = val[0] << @builder.splat(val[2], val[3]) + } + + mrhs: args tCOMMA arg_value + { + result = val[0] << val[2] + } + | args tCOMMA tSTAR arg_value + { + result = val[0] << @builder.splat(val[2], val[3]) + } + | tSTAR arg_value + { + result = [ @builder.splat(val[0], val[1]) ] + } + + primary: literal + | strings + | xstring + | regexp + | words + | qwords + | var_ref + | backref + | tFID + { + result = @builder.call_method(nil, nil, val[0]) + } + | kBEGIN bodystmt kEND + { + result = @builder.begin_keyword(val[0], val[1], val[2]) + } + | tLPAREN_ARG expr + { + @lexer.state = :expr_endarg + } + rparen + { + result = @builder.begin(val[0], val[1], val[3]) + } + | tLPAREN compstmt tRPAREN + { + result = @builder.begin(val[0], val[1], val[2]) + } + | primary_value tCOLON2 tCONSTANT + { + result = @builder.const_fetch(val[0], val[1], val[2]) + } + | tCOLON3 tCONSTANT + { + result = @builder.const_global(val[0], val[1]) + } + | tLBRACK aref_args tRBRACK + { + result = @builder.array(val[0], val[1], val[2]) + } + | tLBRACE assoc_list tRCURLY + { + result = @builder.associate(val[0], val[1], val[2]) + } + | kRETURN + { + result = @builder.keyword_cmd(:return, val[0]) + } + | kYIELD tLPAREN2 call_args rparen + { + result = @builder.keyword_cmd(:yield, val[0], val[1], val[2], val[3]) + } + | kYIELD tLPAREN2 rparen + { + result = @builder.keyword_cmd(:yield, val[0], val[1], [], val[2]) + } + | kYIELD + { + result = @builder.keyword_cmd(:yield, val[0]) + } + | kDEFINED opt_nl tLPAREN2 expr rparen + { + result = @builder.keyword_cmd(:defined?, val[0], + val[2], [ val[3] ], val[4]) + } + | kNOT tLPAREN2 expr rparen + { + result = @builder.not_op(val[0], val[1], val[2], val[3]) + } + | kNOT tLPAREN2 rparen + { + result = @builder.not_op(val[0], val[1], nil, val[2]) + } + | operation brace_block + { + method_call = @builder.call_method(nil, nil, val[0]) + + begin_t, args, body, end_t = val[1] + result = @builder.block(method_call, + begin_t, args, body, end_t) + } + | method_call + | method_call brace_block + { + begin_t, args, body, end_t = val[1] + result = @builder.block(val[0], + begin_t, args, body, end_t) + } + | tLAMBDA lambda + { + lambda_call = @builder.call_lambda(val[0]) + + args, (begin_t, body, end_t) = val[1] + result = @builder.block(lambda_call, + begin_t, args, body, end_t) + } + | kIF expr_value then compstmt if_tail kEND + { + else_t, else_ = val[4] + result = @builder.condition(val[0], val[1], val[2], + val[3], else_t, + else_, val[5]) + } + | kUNLESS expr_value then compstmt opt_else kEND + { + else_t, else_ = val[4] + result = @builder.condition(val[0], val[1], val[2], + else_, else_t, + val[3], val[5]) + } + | kWHILE + { + @lexer.cond.push(true) + } + expr_value do + { + @lexer.cond.pop + } + compstmt kEND + { + result = @builder.loop(:while, val[0], val[2], val[3], + val[5], val[6]) + } + | kUNTIL + { + @lexer.cond.push(true) + } + expr_value do + { + @lexer.cond.pop + } + compstmt kEND + { + result = @builder.loop(:until, val[0], val[2], val[3], + val[5], val[6]) + } + | kCASE expr_value opt_terms case_body kEND + { + *when_bodies, (else_t, else_body) = *val[3] + + result = @builder.case(val[0], val[1], + when_bodies, else_t, else_body, + val[4]) + } + | kCASE opt_terms case_body kEND + { + *when_bodies, (else_t, else_body) = *val[2] + + result = @builder.case(val[0], nil, + when_bodies, else_t, else_body, + val[3]) + } + | kFOR for_var kIN + { + @lexer.cond.push(true) + } + expr_value do + { + @lexer.cond.pop + } + compstmt kEND + { + result = @builder.for(val[0], val[1], + val[2], val[4], + val[5], val[7], val[8]) + } + | kCLASS cpath superclass + { + @static_env.extend_static + @lexer.push_cmdarg + } + bodystmt kEND + { + if in_def? + diagnostic :error, :class_in_def, nil, val[0] + end + + lt_t, superclass = val[2] + result = @builder.def_class(val[0], val[1], + lt_t, superclass, + val[4], val[5]) + + @lexer.pop_cmdarg + @static_env.unextend + } + | kCLASS tLSHFT expr term + { + result = @def_level + @def_level = 0 + + @static_env.extend_static + @lexer.push_cmdarg + } + bodystmt kEND + { + result = @builder.def_sclass(val[0], val[1], val[2], + val[5], val[6]) + + @lexer.pop_cmdarg + @static_env.unextend + + @def_level = val[4] + } + | kMODULE cpath + { + @static_env.extend_static + @lexer.push_cmdarg + } + bodystmt kEND + { + if in_def? + diagnostic :error, :module_in_def, nil, val[0] + end + + result = @builder.def_module(val[0], val[1], + val[3], val[4]) + + @lexer.pop_cmdarg + @static_env.unextend + } + | kDEF fname + { + @def_level += 1 + @static_env.extend_static + @lexer.push_cmdarg + } + f_arglist bodystmt kEND + { + result = @builder.def_method(val[0], val[1], + val[3], val[4], val[5]) + + @lexer.pop_cmdarg + @static_env.unextend + @def_level -= 1 + } + | kDEF singleton dot_or_colon + { + @lexer.state = :expr_fname + } + fname + { + @def_level += 1 + @static_env.extend_static + @lexer.push_cmdarg + } + f_arglist bodystmt kEND + { + result = @builder.def_singleton(val[0], val[1], val[2], + val[4], val[6], val[7], val[8]) + + @lexer.pop_cmdarg + @static_env.unextend + @def_level -= 1 + } + | kBREAK + { + result = @builder.keyword_cmd(:break, val[0]) + } + | kNEXT + { + result = @builder.keyword_cmd(:next, val[0]) + } + | kREDO + { + result = @builder.keyword_cmd(:redo, val[0]) + } + | kRETRY + { + result = @builder.keyword_cmd(:retry, val[0]) + } + + primary_value: primary + + then: term + | kTHEN + | term kTHEN + { + result = val[1] + } + + do: term + | kDO_COND + + if_tail: opt_else + | kELSIF expr_value then compstmt if_tail + { + else_t, else_ = val[4] + result = [ val[0], + @builder.condition(val[0], val[1], val[2], + val[3], else_t, + else_, nil), + ] + } + + opt_else: none + | kELSE compstmt + { + result = val + } + + for_var: lhs + | mlhs + + f_marg: f_norm_arg + | tLPAREN f_margs rparen + { + result = @builder.multi_lhs(val[0], val[1], val[2]) + } + + f_marg_list: f_marg + { + result = [ val[0] ] + } + | f_marg_list tCOMMA f_marg + { + result = val[0] << val[2] + } + + f_margs: f_marg_list + | f_marg_list tCOMMA tSTAR f_norm_arg + { + result = val[0]. + push(@builder.objc_restarg(val[2], val[3])) + } + | f_marg_list tCOMMA tSTAR f_norm_arg tCOMMA f_marg_list + { + result = val[0]. + push(@builder.objc_restarg(val[2], val[3])). + concat(val[5]) + } + | f_marg_list tCOMMA tSTAR + { + result = val[0]. + push(@builder.objc_restarg(val[2])) + } + | f_marg_list tCOMMA tSTAR tCOMMA f_marg_list + { + result = val[0]. + push(@builder.objc_restarg(val[2])). + concat(val[4]) + } + | tSTAR f_norm_arg + { + result = [ @builder.objc_restarg(val[0], val[1]) ] + } + | tSTAR f_norm_arg tCOMMA f_marg_list + { + result = [ @builder.objc_restarg(val[0], val[1]), + *val[3] ] + } + | tSTAR + { + result = [ @builder.objc_restarg(val[0]) ] + } + | tSTAR tCOMMA f_marg_list + { + result = [ @builder.objc_restarg(val[0]), + *val[2] ] + } + + block_param: f_arg tCOMMA f_block_optarg tCOMMA f_rest_arg opt_f_block_arg + { + result = val[0]. + concat(val[2]). + concat(val[4]). + concat(val[5]) + } + | f_arg tCOMMA f_block_optarg tCOMMA f_rest_arg tCOMMA f_arg opt_f_block_arg + { + result = val[0]. + concat(val[2]). + concat(val[4]). + concat(val[6]). + concat(val[7]) + } + | f_arg tCOMMA f_block_optarg opt_f_block_arg + { + result = val[0]. + concat(val[2]). + concat(val[3]) + } + | f_arg tCOMMA f_block_optarg tCOMMA f_arg opt_f_block_arg + { + result = val[0]. + concat(val[2]). + concat(val[4]). + concat(val[5]) + } + | f_arg tCOMMA f_rest_arg opt_f_block_arg + { + result = val[0]. + concat(val[2]). + concat(val[3]) + } + | f_arg tCOMMA + | f_arg tCOMMA f_rest_arg tCOMMA f_arg opt_f_block_arg + { + result = val[0]. + concat(val[2]). + concat(val[4]). + concat(val[5]) + } + | f_arg opt_f_block_arg + { + result = val[0].concat(val[1]) + } + | f_block_optarg tCOMMA f_rest_arg opt_f_block_arg + { + result = val[0]. + concat(val[2]). + concat(val[3]) + } + | f_block_optarg tCOMMA f_rest_arg tCOMMA f_arg opt_f_block_arg + { + result = val[0]. + concat(val[2]). + concat(val[4]). + concat(val[5]) + } + | f_block_optarg opt_f_block_arg + { + result = val[0]. + concat(val[1]) + } + | f_block_optarg tCOMMA f_arg opt_f_block_arg + { + result = val[0]. + concat(val[2]). + concat(val[3]) + } + | f_rest_arg opt_f_block_arg + { + result = val[0]. + concat(val[1]) + } + | f_rest_arg tCOMMA f_arg opt_f_block_arg + { + result = val[0]. + concat(val[2]). + concat(val[3]) + } + | f_block_arg + { + result = [ val[0] ] + } + + opt_block_param: # nothing + { + result = @builder.args(nil, [], nil) + } + | block_param_def + { + @lexer.state = :expr_value + } + + block_param_def: tPIPE opt_bv_decl tPIPE + { + result = @builder.args(val[0], val[1], val[2]) + } + | tOROP + { + result = @builder.args(val[0], [], val[0]) + } + | tPIPE block_param opt_bv_decl tPIPE + { + result = @builder.args(val[0], val[1].concat(val[2]), val[3]) + } + + opt_bv_decl: # nothing + { + result = [] + } + | tSEMI bv_decls + { + result = val[1] + } + + bv_decls: bvar + { + result = [ val[0] ] + } + | bv_decls tCOMMA bvar + { + result = val[0] << val[2] + } + + bvar: tIDENTIFIER + { + result = @builder.shadowarg(val[0]) + } + | f_bad_arg + + lambda: { + @static_env.extend_dynamic + } + f_larglist lambda_body + { + result = [ val[1], val[2] ] + + @static_env.unextend + } + + f_larglist: tLPAREN2 f_args opt_bv_decl rparen + { + result = @builder.args(val[0], val[1].concat(val[2]), val[3]) + } + | f_args + { + result = @builder.args(nil, val[0], nil) + } + + lambda_body: tLAMBEG compstmt tRCURLY + { + result = [ val[0], val[1], val[2] ] + } + | kDO_LAMBDA compstmt kEND + { + result = [ val[0], val[1], val[2] ] + } + + do_block: kDO_BLOCK + { + @static_env.extend_dynamic + } + opt_block_param compstmt kEND + { + result = [ val[0], val[2], val[3], val[4] ] + + @static_env.unextend + } + + block_call: command do_block + { + begin_t, block_args, body, end_t = val[1] + result = @builder.block(val[0], + begin_t, block_args, body, end_t) + } + | block_call tDOT operation2 opt_paren_args + { + lparen_t, args, rparen_t = val[3] + result = @builder.call_method(val[0], val[1], val[2], + lparen_t, args, rparen_t) + } + | block_call tCOLON2 operation2 opt_paren_args + { + lparen_t, args, rparen_t = val[3] + result = @builder.call_method(val[0], val[1], val[2], + lparen_t, args, rparen_t) + } + + method_call: operation paren_args + { + lparen_t, args, rparen_t = val[1] + result = @builder.call_method(nil, nil, val[0], + lparen_t, args, rparen_t) + } + | primary_value tDOT operation2 opt_paren_args + { + lparen_t, args, rparen_t = val[3] + result = @builder.call_method(val[0], val[1], val[2], + lparen_t, args, rparen_t) + } + | primary_value tCOLON2 operation2 paren_args + { + lparen_t, args, rparen_t = val[3] + result = @builder.call_method(val[0], val[1], val[2], + lparen_t, args, rparen_t) + } + | primary_value tCOLON2 operation3 + { + result = @builder.call_method(val[0], val[1], val[2]) + } + | primary_value tDOT paren_args + { + lparen_t, args, rparen_t = val[2] + result = @builder.call_method(val[0], val[1], nil, + lparen_t, args, rparen_t) + } + | primary_value tCOLON2 paren_args + { + lparen_t, args, rparen_t = val[2] + result = @builder.call_method(val[0], val[1], nil, + lparen_t, args, rparen_t) + } + | kSUPER paren_args + { + lparen_t, args, rparen_t = val[1] + result = @builder.keyword_cmd(:super, val[0], + lparen_t, args, rparen_t) + } + | kSUPER + { + result = @builder.keyword_cmd(:zsuper, val[0]) + } + | primary_value tLBRACK2 opt_call_args rbracket + { + result = @builder.index(val[0], val[1], val[2], val[3]) + } + + brace_block: tLCURLY + { + @static_env.extend_dynamic + } + opt_block_param compstmt tRCURLY + { + result = [ val[0], val[2], val[3], val[4] ] + + @static_env.unextend + } + | kDO + { + @static_env.extend_dynamic + } + opt_block_param compstmt kEND + { + result = [ val[0], val[2], val[3], val[4] ] + + @static_env.unextend + } + + case_body: kWHEN args then compstmt cases + { + result = [ @builder.when(val[0], val[1], val[2], val[3]), + *val[4] ] + } + + cases: opt_else + { + result = [ val[0] ] + } + | case_body + + opt_rescue: kRESCUE exc_list exc_var then compstmt opt_rescue + { + assoc_t, exc_var = val[2] + + if val[1] + exc_list = @builder.array(nil, val[1], nil) + end + + result = [ @builder.rescue_body(val[0], + exc_list, assoc_t, exc_var, + val[3], val[4]), + *val[5] ] + } + | + { + result = [] + } + + exc_list: arg_value + { + result = [ val[0] ] + } + | mrhs + | none + + exc_var: tASSOC lhs + { + result = [ val[0], val[1] ] + } + | none + + opt_ensure: kENSURE compstmt + { + result = [ val[0], val[1] ] + } + | none + + literal: numeric + | symbol + | dsym + + strings: string + { + result = @builder.string_compose(nil, val[0], nil) + } + + string: string1 + { + result = [ val[0] ] + } + | string string1 + { + result = val[0] << val[1] + } + + string1: tSTRING_BEG string_contents tSTRING_END + { + result = @builder.string_compose(val[0], val[1], val[2]) + } + | tSTRING + { + result = @builder.string(val[0]) + } + | tCHARACTER + { + result = @builder.character(val[0]) + } + + xstring: tXSTRING_BEG xstring_contents tSTRING_END + { + result = @builder.xstring_compose(val[0], val[1], val[2]) + } + + regexp: tREGEXP_BEG regexp_contents tSTRING_END tREGEXP_OPT + { + opts = @builder.regexp_options(val[3]) + result = @builder.regexp_compose(val[0], val[1], val[2], opts) + } + + words: tWORDS_BEG word_list tSTRING_END + { + result = @builder.words_compose(val[0], val[1], val[2]) + } + + word_list: # nothing + { + result = [] + } + | word_list word tSPACE + { + result = val[0] << @builder.word(val[1]) + } + + word: string_content + { + result = [ val[0] ] + } + | word string_content + { + result = val[0] << val[1] + } + + qwords: tQWORDS_BEG qword_list tSTRING_END + { + result = @builder.words_compose(val[0], val[1], val[2]) + } + + qword_list: # nothing + { + result = [] + } + | qword_list tSTRING_CONTENT tSPACE + { + result = val[0] << @builder.string_internal(val[1]) + } + + string_contents: # nothing + { + result = [] + } + | string_contents string_content + { + result = val[0] << val[1] + } + +xstring_contents: # nothing + { + result = [] + } + | xstring_contents string_content + { + result = val[0] << val[1] + } + +regexp_contents: # nothing + { + result = [] + } + | regexp_contents string_content + { + result = val[0] << val[1] + } + + string_content: tSTRING_CONTENT + { + result = @builder.string_internal(val[0]) + } + | tSTRING_DVAR string_dvar + { + result = val[1] + } + | tSTRING_DBEG + { + @lexer.cond.push(false) + @lexer.cmdarg.push(false) + } + compstmt tRCURLY + { + @lexer.cond.lexpop + @lexer.cmdarg.lexpop + + result = @builder.begin(val[0], val[2], val[3]) + } + + string_dvar: tGVAR + { + result = @builder.gvar(val[0]) + } + | tIVAR + { + result = @builder.ivar(val[0]) + } + | tCVAR + { + result = @builder.cvar(val[0]) + } + | backref + + + symbol: tSYMBOL + { + result = @builder.symbol(val[0]) + } + + dsym: tSYMBEG xstring_contents tSTRING_END + { + result = @builder.symbol_compose(val[0], val[1], val[2]) + } + + numeric: tINTEGER + { + result = @builder.integer(val[0]) + } + | tFLOAT + { + result = @builder.float(val[0]) + } + | tUMINUS_NUM tINTEGER =tLOWEST + { + result = @builder.negate(val[0], + @builder.integer(val[1])) + } + | tUMINUS_NUM tFLOAT =tLOWEST + { + result = @builder.negate(val[0], + @builder.float(val[1])) + } + + variable: tIDENTIFIER + { + result = @builder.ident(val[0]) + } + | tIVAR + { + result = @builder.ivar(val[0]) + } + | tGVAR + { + result = @builder.gvar(val[0]) + } + | tCONSTANT + { + result = @builder.const(val[0]) + } + | tCVAR + { + result = @builder.cvar(val[0]) + } + | kNIL + { + result = @builder.nil(val[0]) + } + | kSELF + { + result = @builder.self(val[0]) + } + | kTRUE + { + result = @builder.true(val[0]) + } + | kFALSE + { + result = @builder.false(val[0]) + } + | k__FILE__ + { + result = @builder.__FILE__(val[0]) + } + | k__LINE__ + { + result = @builder.__LINE__(val[0]) + } + | k__ENCODING__ + { + result = @builder.__ENCODING__(val[0]) + } + + var_ref: variable + { + result = @builder.accessible(val[0]) + } + + var_lhs: variable + { + result = @builder.assignable(val[0]) + } + + backref: tNTH_REF + { + result = @builder.nth_ref(val[0]) + } + | tBACK_REF + { + result = @builder.back_ref(val[0]) + } + + superclass: term + { + result = nil + } + | tLT expr_value term + { + result = [ val[0], val[1] ] + } + | error term + { + yyerrok + result = nil + } + + f_arglist: tLPAREN2 f_args rparen + { + result = @builder.args(val[0], val[1], val[2]) + + @lexer.state = :expr_value + } + | f_args term + { + result = @builder.args(nil, val[0], nil) + } + + f_args: f_arg tCOMMA f_optarg tCOMMA f_rest_arg opt_f_block_arg + { + result = val[0]. + concat(val[2]). + concat(val[4]). + concat(val[5]) + } + | f_arg tCOMMA f_optarg tCOMMA f_rest_arg tCOMMA f_arg opt_f_block_arg + { + result = val[0]. + concat(val[2]). + concat(val[4]). + concat(val[6]). + concat(val[7]) + } + | f_arg tCOMMA f_optarg opt_f_block_arg + { + result = val[0]. + concat(val[2]). + concat(val[3]) + } + | f_arg tCOMMA f_optarg tCOMMA f_arg opt_f_block_arg + { + result = val[0]. + concat(val[2]). + concat(val[4]). + concat(val[5]) + } + | f_arg tCOMMA f_rest_arg opt_f_block_arg + { + result = val[0]. + concat(val[2]). + concat(val[3]) + } + | f_arg tCOMMA f_rest_arg tCOMMA f_arg opt_f_block_arg + { + result = val[0]. + concat(val[2]). + concat(val[4]). + concat(val[5]) + } + | f_arg opt_f_block_arg + { + result = val[0]. + concat(val[1]) + } + | f_optarg tCOMMA f_rest_arg opt_f_block_arg + { + result = val[0]. + concat(val[2]). + concat(val[3]) + } + | f_optarg tCOMMA f_rest_arg tCOMMA f_arg opt_f_block_arg + { + result = val[0]. + concat(val[2]). + concat(val[4]). + concat(val[5]) + } + | f_optarg opt_f_block_arg + { + result = val[0]. + concat(val[1]) + } + | f_optarg tCOMMA f_arg opt_f_block_arg + { + result = val[0]. + concat(val[2]). + concat(val[3]) + } + | f_rest_arg opt_f_block_arg + { + result = val[0]. + concat(val[1]) + } + | f_rest_arg tCOMMA f_arg opt_f_block_arg + { + result = val[0]. + concat(val[2]). + concat(val[3]) + } + | f_block_arg + { + result = [ val[0] ] + } + | # nothing + { + result = [] + } + + f_bad_arg: tCONSTANT + { + diagnostic :error, :argument_const, nil, val[0] + } + | tIVAR + { + diagnostic :error, :argument_ivar, nil, val[0] + } + | tGVAR + { + diagnostic :error, :argument_gvar, nil, val[0] + } + | tCVAR + { + diagnostic :error, :argument_cvar, nil, val[0] + } + + f_norm_arg: f_bad_arg + | tIDENTIFIER + { + @static_env.declare val[0][0] + + result = @builder.arg(val[0]) + } + | tIDENTIFIER tASSOC tIDENTIFIER + { + @static_env.declare val[2][0] + + result = @builder.objc_kwarg(val[0], val[1], val[2]) + } + | tLABEL tIDENTIFIER + { + @static_env.declare val[1][0] + + result = @builder.objc_kwarg(val[0], nil, val[1]) + } + + f_arg_item: f_norm_arg + | tLPAREN f_margs rparen + { + result = @builder.multi_lhs(val[0], val[1], val[2]) + } + + f_arg: f_arg_item + { + result = [ val[0] ] + } + | f_arg tCOMMA f_arg_item + { + result = val[0] << val[2] + } + + f_opt: tIDENTIFIER tEQL arg_value + { + @static_env.declare val[0][0] + + result = @builder.optarg(val[0], val[1], val[2]) + } + + f_block_opt: tIDENTIFIER tEQL primary_value + { + @static_env.declare val[0][0] + + result = @builder.optarg(val[0], val[1], val[2]) + } + + f_block_optarg: f_block_opt + { + result = [ val[0] ] + } + | f_block_optarg tCOMMA f_block_opt + { + result = val[0] << val[2] + } + + f_optarg: f_opt + { + result = [ val[0] ] + } + | f_optarg tCOMMA f_opt + { + result = val[0] << val[2] + } + + restarg_mark: tSTAR2 | tSTAR + + f_rest_arg: restarg_mark tIDENTIFIER + { + @static_env.declare val[1][0] + + result = [ @builder.restarg(val[0], val[1]) ] + } + | restarg_mark + { + result = [ @builder.restarg(val[0]) ] + } + + blkarg_mark: tAMPER2 | tAMPER + + f_block_arg: blkarg_mark tIDENTIFIER + { + @static_env.declare val[1][0] + + result = @builder.blockarg(val[0], val[1]) + } + + opt_f_block_arg: tCOMMA f_block_arg + { + result = [ val[1] ] + } + | # nothing + { + result = [] + } + + singleton: var_ref + | tLPAREN2 expr rparen + { + result = val[1] + } + + assoc_list: # nothing + { + result = [] + } + | assocs trailer + + assocs: assoc + { + result = [ val[0] ] + } + | assocs tCOMMA assoc + { + result = val[0] << val[2] + } + + assoc: arg_value tASSOC arg_value + { + result = @builder.pair(val[0], val[1], val[2]) + } + | tLABEL arg_value + { + result = @builder.pair_keyword(val[0], val[1]) + } + + operation: tIDENTIFIER | tCONSTANT | tFID + operation2: tIDENTIFIER | tCONSTANT | tFID | op + operation3: tIDENTIFIER | tFID | op + dot_or_colon: tDOT | tCOLON2 + opt_terms: | terms + opt_nl: | tNL + rparen: opt_nl tRPAREN + { + result = val[1] + } + rbracket: opt_nl tRBRACK + { + result = val[1] + } + trailer: | tNL | tCOMMA + + term: tSEMI + { + yyerrok + } + | tNL + + terms: term + | terms tSEMI + + none: # nothing + { + result = nil + } +end + +---- header + +require 'parser' + +Parser.check_for_encoding_support + +---- inner + + def version + 19 # closest released match: v1_9_0_2 + end + + def default_encoding + Encoding::BINARY + end diff --git a/test/racc/assets/mailp.y b/test/racc/assets/mailp.y new file mode 100644 index 0000000000..da332a33ba --- /dev/null +++ b/test/racc/assets/mailp.y @@ -0,0 +1,437 @@ +# +# mailp for test +# + +class Testp + +rule + + content : DateH datetime { @field.date = val[1] } + | RecvH received + | RetpathH returnpath + | MaddrH addrs { @field.addrs.replace val[1] } + | SaddrH addr { @field.addr = val[1] } + | MmboxH mboxes { @field.addrs.replace val[1] } + | SmboxH mbox { @field.addr = val[1] } + | MsgidH msgid { @field.msgid = val[1] } + | KeyH keys { @field.keys.replace val[1] } + | EncH enc + | VersionH version + | CTypeH ctype + | CEncodingH cencode + | CDispositionH cdisp + | Mbox mbox + { + mb = val[1] + @field.phrase = mb.phrase + @field.setroute mb.route + @field.local = mb.local + @field.domain = mb.domain + } + | Spec spec + { + mb = val[1] + @field.local = mb.local + @field.domain = mb.domain + } + ; + + datetime : day DIGIT ATOM DIGIT hour zone + # 0 1 2 3 4 5 + # day month year + { + t = Time.gm( val[3].to_i, val[2], val[1].to_i, 0, 0, 0 ) + result = (t + val[4] - val[5]).localtime + } + ; + + day : /* none */ + | ATOM ',' + ; + + hour : DIGIT ':' DIGIT + { + result = (result.to_i * 60 * 60) + (val[2].to_i * 60) + } + | DIGIT ':' DIGIT ':' DIGIT + { + result = (result.to_i * 60 * 60) + + (val[2].to_i * 60) + + val[4].to_i + } + ; + + zone : ATOM + { + result = ::TMail.zonestr2i( val[0] ) * 60 + } + ; + + received : from by via with id for recvdatetime + ; + + from : /* none */ + | FROM domain + { + @field.from = Address.join( val[1] ) + } + | FROM domain '@' domain + { + @field.from = Address.join( val[3] ) + } + | FROM domain DOMLIT + { + @field.from = Address.join( val[1] ) + } + ; + + by : /* none */ + | BY domain + { + @field.by = Address.join( val[1] ) + } + ; + + via : /* none */ + | VIA ATOM + { + @field.via = val[1] + } + ; + + with : /* none */ + | WITH ATOM + { + @field.with.push val[1] + } + ; + + id : /* none */ + | ID msgid + { + @field.msgid = val[1] + } + | ID ATOM + { + @field.msgid = val[1] + } + ; + + for : /* none */ + | FOR addr + { + @field.for_ = val[1].address + } + ; + + recvdatetime + : /* none */ + | ';' datetime + { + @field.date = val[1] + } + ; + + returnpath: '<' '>' + | routeaddr + { + @field.route.replace result.route + @field.addr = result.addr + } + ; + + addrs : addr { result = val } + | addrs ',' addr { result.push val[2] } + ; + + addr : mbox + | group + ; + + mboxes : mbox + { + result = val + } + | mboxes ',' mbox + { + result.push val[2] + } + ; + + mbox : spec + | routeaddr + | phrase routeaddr + { + val[1].phrase = HFdecoder.decode( result ) + result = val[1] + } + ; + + group : phrase ':' mboxes ';' + { + result = AddressGroup.new( result, val[2] ) + } + # | phrase ':' ';' { result = AddressGroup.new( result ) } + ; + + routeaddr : '<' route spec '>' + { + result = val[2] + result.route = val[1] + } + | '<' spec '>' + { + result = val[1] + } + ; + + route : at_domains ':' + ; + + at_domains: '@' domain { result = [ val[1] ] } + | at_domains ',' '@' domain { result.push val[3] } + ; + + spec : local '@' domain { result = Address.new( val[0], val[2] ) } + | local { result = Address.new( result, nil ) } + ; + + local : word { result = val } + | local '.' word { result.push val[2] } + ; + + domain : domword { result = val } + | domain '.' domword { result.push val[2] } + ; + + domword : atom + | DOMLIT + | DIGIT + ; + + msgid : '<' spec '>' + { + val[1] = val[1].addr + result = val.join('') + } + ; + + phrase : word + | phrase word { result << ' ' << val[1] } + ; + + word : atom + | QUOTED + | DIGIT + ; + + keys : phrase + | keys ',' phrase + ; + + enc : word + { + @field.encrypter = val[0] + } + | word word + { + @field.encrypter = val[0] + @field.keyword = val[1] + } + ; + + version : DIGIT '.' DIGIT + { + @field.major = val[0].to_i + @field.minor = val[2].to_i + } + ; + + ctype : TOKEN '/' TOKEN params + { + @field.main = val[0] + @field.sub = val[2] + } + | TOKEN params + { + @field.main = val[0] + @field.sub = '' + } + ; + + params : /* none */ + | params ';' TOKEN '=' value + { + @field.params[ val[2].downcase ] = val[4] + } + ; + + value : TOKEN + | QUOTED + ; + + cencode : TOKEN + { + @field.encoding = val[0] + } + ; + + cdisp : TOKEN disp_params + { + @field.disposition = val[0] + } + ; + + disp_params + : /* none */ + | disp_params ';' disp_param + ; + + disp_param: /* none */ + | TOKEN '=' value + { + @field.params[ val[0].downcase ] = val[2] + } + ; + + atom : ATOM + | FROM + | BY + | VIA + | WITH + | ID + | FOR + ; + +end + + +---- header +# +# mailp for test +# + +require 'tmail/mails' + + +module TMail + +---- inner + + MAILP_DEBUG = false + + def initialize + self.debug = MAILP_DEBUG + end + + def debug=( flag ) + @yydebug = flag && Racc_debug_parser + @scanner_debug = flag + end + + def debug + @yydebug + end + + + def Mailp.parse( str, obj, ident ) + new.parse( str, obj, ident ) + end + + + NATIVE_ROUTINE = { + 'TMail::MsgidH' => :msgid_parse, + 'TMail::RefH' => :refs_parse + } + + def parse( str, obj, ident ) + return if /\A\s*\z/ === str + + @field = obj + + if mid = NATIVE_ROUTINE[ obj.type.name ] then + send mid, str + else + unless ident then + ident = obj.type.name.split('::')[-1].to_s + cmt = [] + obj.comments.replace cmt + else + cmt = nil + end + + @scanner = MailScanner.new( str, ident, cmt ) + @scanner.debug = @scanner_debug + @first = [ ident.intern, ident ] + @pass_array = [nil, nil] + + do_parse + end + end + + + private + + + def next_token + if @first then + ret = @first + @first = nil + ret + else + @scanner.scan @pass_array + end + end + + def on_error( tok, val, vstack ) + raise ParseError, + "\nparse error in '#{@field.name}' header, on token #{val.inspect}" + end + + + + def refs_parse( str ) + arr = [] + + while mdata = ::TMail::MSGID.match( str ) do + str = mdata.post_match + + pre = mdata.pre_match + pre.strip! + proc_phrase pre, arr unless pre.empty? + arr.push mdata.to_s + end + str.strip! + proc_phrase str, arr if not pre or pre.empty? + + @field.refs.replace arr + end + + def proc_phrase( str, arr ) + while mdata = /"([^\\]*(?:\\.[^"\\]*)*)"/.match( str ) do + str = mdata.post_match + + pre = mdata.pre_match + pre.strip! + arr.push pre unless pre.empty? + arr.push mdata[1] + end + str.strip! + arr.push unless str.empty? + end + + + def msgid_parse( str ) + if mdata = ::TMail::MSGID.match( str ) then + @field.msgid = mdata.to_s + else + raise ParseError, "wrong Message-ID format: #{str}" + end + end + +---- footer + +end # module TMail + +mp = TMail::Testp.new +mp.parse diff --git a/test/racc/assets/mediacloth.y b/test/racc/assets/mediacloth.y new file mode 100644 index 0000000000..94cc411ea7 --- /dev/null +++ b/test/racc/assets/mediacloth.y @@ -0,0 +1,599 @@ +# Copyright (c) 2006 Pluron Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +# The parser for the MediaWiki language. +# +# Usage together with a lexer: +# inputFile = File.new("data/input1", "r") +# input = inputFile.read +# parser = MediaWikiParser.new +# parser.lexer = MediaWikiLexer.new +# parser.parse(input) + +class MediaWikiParser + +token TEXT BOLD_START BOLD_END ITALIC_START ITALIC_END LINK_START LINK_END LINKSEP + INTLINK_START INTLINK_END INTLINKSEP RESOURCESEP CHAR_ENT + PRE_START PRE_END PREINDENT_START PREINDENT_END + SECTION_START SECTION_END HLINE SIGNATURE_NAME SIGNATURE_DATE SIGNATURE_FULL + PARA_START PARA_END UL_START UL_END OL_START OL_END LI_START LI_END + DL_START DL_END DT_START DT_END DD_START DD_END TAG_START TAG_END ATTR_NAME ATTR_VALUE + TABLE_START TABLE_END ROW_START ROW_END HEAD_START HEAD_END CELL_START CELL_END + KEYWORD TEMPLATE_START TEMPLATE_END CATEGORY PASTE_START PASTE_END + + +rule + +wiki: + repeated_contents + { + @nodes.push WikiAST.new(0, @wiki_ast_length) + #@nodes.last.children.insert(0, val[0]) + #puts val[0] + @nodes.last.children += val[0] + } + ; + +contents: + text + { + result = val[0] + } + | bulleted_list + { + result = val[0] + } + | numbered_list + { + result = val[0] + } + | dictionary_list + { + list = ListAST.new(@ast_index, @ast_length) + list.list_type = :Dictionary + list.children = val[0] + result = list + } + | preformatted + { + result = val[0] + } + | section + { + result = val[0] + } + | tag + { + result = val[0] + } + | template + { + result = val[0] + } + | KEYWORD + { + k = KeywordAST.new(@ast_index, @ast_length) + k.text = val[0] + result = k + } + | PARA_START para_contents PARA_END + { + p = ParagraphAST.new(@ast_index, @ast_length) + p.children = val[1] + result = p + } + | LINK_START link_contents LINK_END + { + l = LinkAST.new(@ast_index, @ast_length) + l.link_type = val[0] + l.url = val[1][0] + l.children += val[1][1..-1] if val[1].length > 1 + result = l + } + | PASTE_START para_contents PASTE_END + { + p = PasteAST.new(@ast_index, @ast_length) + p.children = val[1] + result = p + } + | INTLINK_START TEXT RESOURCESEP TEXT reslink_repeated_contents INTLINK_END + { + l = ResourceLinkAST.new(@ast_index, @ast_length) + l.prefix = val[1] + l.locator = val[3] + l.children = val[4] unless val[4].nil? or val[4].empty? + result = l + } + | INTLINK_START TEXT intlink_repeated_contents INTLINK_END + { + l = InternalLinkAST.new(@ast_index, @ast_length) + l.locator = val[1] + l.children = val[2] unless val[2].nil? or val[2].empty? + result = l + } + | INTLINK_START CATEGORY TEXT cat_sort_contents INTLINK_END + { + l = CategoryAST.new(@ast_index, @ast_length) + l.locator = val[2] + l.sort_as = val[3] + result = l + } + | INTLINK_START RESOURCESEP CATEGORY TEXT intlink_repeated_contents INTLINK_END + { + l = CategoryLinkAST.new(@ast_index, @ast_length) + l.locator = val[3] + l.children = val[4] unless val[4].nil? or val[4].empty? + result = l + } + | table + ; + +para_contents: + { + result = nil + } + | repeated_contents + { + result = val[0] + } + ; + +tag: + TAG_START tag_attributes TAG_END + { + if val[0] != val[2] + raise Racc::ParseError.new("XHTML end tag #{val[2]} does not match start tag #{val[0]}") + end + elem = ElementAST.new(@ast_index, @ast_length) + elem.name = val[0] + elem.attributes = val[1] + result = elem + } + | TAG_START tag_attributes repeated_contents TAG_END + { + if val[0] != val[3] + raise Racc::ParseError.new("XHTML end tag #{val[3]} does not match start tag #{val[0]}") + end + elem = ElementAST.new(@ast_index, @ast_length) + elem.name = val[0] + elem.attributes = val[1] + elem.children += val[2] + result = elem + } + ; + +tag_attributes: + { + result = nil + } + | ATTR_NAME tag_attributes + { + attr_map = val[2] ? val[2] : {} + attr_map[val[0]] = true + result = attr_map + } + | ATTR_NAME ATTR_VALUE tag_attributes + { + attr_map = val[2] ? val[2] : {} + attr_map[val[0]] = val[1] + result = attr_map + } + ; + + +link_contents: + TEXT + { + result = val + } + | TEXT LINKSEP link_repeated_contents + { + result = [val[0]] + result += val[2] + } + ; + + +link_repeated_contents: + repeated_contents + { + result = val[0] + } + | repeated_contents LINKSEP link_repeated_contents + { + result = val[0] + result += val[2] if val[2] + } + ; + + +intlink_repeated_contents: + { + result = nil + } + | INTLINKSEP repeated_contents + { + result = val[1] + } + ; + +cat_sort_contents: + { + result = nil + } + | INTLINKSEP TEXT + { + result = val[1] + } + ; + +reslink_repeated_contents: + { + result = nil + } + | INTLINKSEP reslink_repeated_contents + { + result = val[1] + } + | INTLINKSEP repeated_contents reslink_repeated_contents + { + i = InternalLinkItemAST.new(@ast_index, @ast_length) + i.children = val[1] + result = [i] + result += val[2] if val[2] + } + ; + +repeated_contents: contents + { + result = [] + result << val[0] + } + | repeated_contents contents + { + result = [] + result += val[0] + result << val[1] + } + ; + +text: element + { + p = TextAST.new(@ast_index, @ast_length) + p.formatting = val[0][0] + p.contents = val[0][1] + result = p + } + | formatted_element + { + result = val[0] + } + ; + +table: + TABLE_START table_contents TABLE_END + { + table = TableAST.new(@ast_index, @ast_length) + table.children = val[1] unless val[1].nil? or val[1].empty? + result = table + } + | TABLE_START TEXT table_contents TABLE_END + { + table = TableAST.new(@ast_index, @ast_length) + table.options = val[1] + table.children = val[2] unless val[2].nil? or val[2].empty? + result = table + } + +table_contents: + { + result = nil + } + | ROW_START row_contents ROW_END table_contents + { + row = TableRowAST.new(@ast_index, @ast_length) + row.children = val[1] unless val[1].nil? or val[1].empty? + result = [row] + result += val[3] unless val[3].nil? or val[3].empty? + } + | ROW_START TEXT row_contents ROW_END table_contents + { + row = TableRowAST.new(@ast_index, @ast_length) + row.children = val[2] unless val[2].nil? or val[2].empty? + row.options = val[1] + result = [row] + result += val[4] unless val[4].nil? or val[4].empty? + } + +row_contents: + { + result = nil + } + | HEAD_START HEAD_END row_contents + { + cell = TableCellAST.new(@ast_index, @ast_length) + cell.type = :head + result = [cell] + result += val[2] unless val[2].nil? or val[2].empty? + } + | HEAD_START repeated_contents HEAD_END row_contents + { + cell = TableCellAST.new(@ast_index, @ast_length) + cell.children = val[1] unless val[1].nil? or val[1].empty? + cell.type = :head + result = [cell] + result += val[3] unless val[3].nil? or val[3].empty? + } + | CELL_START CELL_END row_contents + { + cell = TableCellAST.new(@ast_index, @ast_length) + cell.type = :body + result = [cell] + result += val[2] unless val[2].nil? or val[2].empty? + } + | CELL_START repeated_contents CELL_END row_contents + { + if val[2] == 'attributes' + result = [] + else + cell = TableCellAST.new(@ast_index, @ast_length) + cell.children = val[1] unless val[1].nil? or val[1].empty? + cell.type = :body + result = [cell] + end + result += val[3] unless val[3].nil? or val[3].empty? + if val[2] == 'attributes' and val[3] and val[3].first.class == TableCellAST + val[3].first.attributes = val[1] + end + result + } + + +element: + TEXT + { return [:None, val[0]] } + | HLINE + { return [:HLine, val[0]] } + | CHAR_ENT + { return [:CharacterEntity, val[0]] } + | SIGNATURE_DATE + { return [:SignatureDate, val[0]] } + | SIGNATURE_NAME + { return [:SignatureName, val[0]] } + | SIGNATURE_FULL + { return [:SignatureFull, val[0]] } + ; + +formatted_element: + BOLD_START BOLD_END + { + result = FormattedAST.new(@ast_index, @ast_length) + result.formatting = :Bold + result + } + | ITALIC_START ITALIC_END + { + result = FormattedAST.new(@ast_index, @ast_length) + result.formatting = :Italic + result + } + | BOLD_START repeated_contents BOLD_END + { + p = FormattedAST.new(@ast_index, @ast_length) + p.formatting = :Bold + p.children += val[1] + result = p + } + | ITALIC_START repeated_contents ITALIC_END + { + p = FormattedAST.new(@ast_index, @ast_length) + p.formatting = :Italic + p.children += val[1] + result = p + } + ; + +bulleted_list: UL_START list_item list_contents UL_END + { + list = ListAST.new(@ast_index, @ast_length) + list.list_type = :Bulleted + list.children << val[1] + list.children += val[2] + result = list + } + ; + +numbered_list: OL_START list_item list_contents OL_END + { + list = ListAST.new(@ast_index, @ast_length) + list.list_type = :Numbered + list.children << val[1] + list.children += val[2] + result = list + } + ; + +list_contents: + { result = [] } + list_item list_contents + { + result << val[1] + result += val[2] + } + | + { result = [] } + ; + +list_item: + LI_START LI_END + { + result = ListItemAST.new(@ast_index, @ast_length) + } + | LI_START repeated_contents LI_END + { + li = ListItemAST.new(@ast_index, @ast_length) + li.children += val[1] + result = li + } + ; + +dictionary_list: + DL_START dictionary_term dictionary_contents DL_END + { + result = [val[1]] + result += val[2] + } + | DL_START dictionary_contents DL_END + { + result = val[1] + } + ; + +dictionary_term: + DT_START DT_END + { + result = ListTermAST.new(@ast_index, @ast_length) + } + | DT_START repeated_contents DT_END + { + term = ListTermAST.new(@ast_index, @ast_length) + term.children += val[1] + result = term + } + +dictionary_contents: + dictionary_definition dictionary_contents + { + result = [val[0]] + result += val[1] if val[1] + } + | + { + result = [] + } + +dictionary_definition: + DD_START DD_END + { + result = ListDefinitionAST.new(@ast_index, @ast_length) + } + | DD_START repeated_contents DD_END + { + term = ListDefinitionAST.new(@ast_index, @ast_length) + term.children += val[1] + result = term + } + +preformatted: PRE_START repeated_contents PRE_END + { + p = PreformattedAST.new(@ast_index, @ast_length) + p.children += val[1] + result = p + } + | PREINDENT_START repeated_contents PREINDENT_END + { + p = PreformattedAST.new(@ast_index, @ast_length) + p.indented = true + p.children += val[1] + result = p + } + ; + +section: SECTION_START repeated_contents SECTION_END + { result = [val[1], val[0].length] + s = SectionAST.new(@ast_index, @ast_length) + s.children = val[1] + s.level = val[0].length + result = s + } + ; + +template: TEMPLATE_START TEXT template_parameters TEMPLATE_END + { + t = TemplateAST.new(@ast_index, @ast_length) + t.template_name = val[1] + t.children = val[2] unless val[2].nil? or val[2].empty? + result = t + } + ; + +template_parameters: + { + result = nil + } + | INTLINKSEP TEXT template_parameters + { + p = TemplateParameterAST.new(@ast_index, @ast_length) + p.parameter_value = val[1] + result = [p] + result += val[2] if val[2] + } + | INTLINKSEP template template_parameters + { + p = TemplateParameterAST.new(@ast_index, @ast_length) + p.children << val[1] + result = [p] + result += val[2] if val[2] + } + ; + +end + +---- header ---- +require 'mediacloth/mediawikiast' + +---- inner ---- + +attr_accessor :lexer + +def initialize + @nodes = [] + @context = [] + @wiki_ast_length = 0 + super +end + +#Tokenizes input string and parses it. +def parse(input) + @yydebug=true + lexer.tokenize(input) + do_parse + return @nodes.last +end + +#Asks the lexer to return the next token. +def next_token + token = @lexer.lex + if token[0].to_s.upcase.include? "_START" + @context << token[2..3] + elsif token[0].to_s.upcase.include? "_END" + @ast_index = @context.last[0] + @ast_length = token[2] + token[3] - @context.last[0] + @context.pop + else + @ast_index = token[2] + @ast_length = token[3] + end + + @wiki_ast_length += token[3] + + return token[0..1] +end diff --git a/test/racc/assets/mof.y b/test/racc/assets/mof.y new file mode 100644 index 0000000000..1adc5ade14 --- /dev/null +++ b/test/racc/assets/mof.y @@ -0,0 +1,649 @@ +# Distributed under the Ruby license +# See http://www.ruby-lang.org/en/LICENSE.txt for the full license text +# Copyright (c) 2010 Klaus Kämpf + +/* + * According to appendix A of + * http://www.dmtf.org/standards/cim/cim_spec_v22 + */ + +class MOF::Parser + prechigh +/* nonassoc UMINUS */ + left '*' '/' + left '+' '-' + preclow + + token PRAGMA INCLUDE IDENTIFIER CLASS ASSOCIATION INDICATION + AMENDED ENABLEOVERRIDE DISABLEOVERRIDE RESTRICTED TOSUBCLASS TOINSTANCE + TRANSLATABLE QUALIFIER SCOPE SCHEMA PROPERTY REFERENCE + METHOD PARAMETER FLAVOR INSTANCE + AS REF ANY OF + DT_VOID + DT_UINT8 DT_SINT8 DT_UINT16 DT_SINT16 DT_UINT32 DT_SINT32 + DT_UINT64 DT_SINT64 DT_REAL32 DT_REAL64 DT_CHAR16 DT_STR + DT_BOOLEAN DT_DATETIME + positiveDecimalValue + stringValue + realValue + charValue + booleanValue + nullValue + binaryValue + octalValue + decimalValue + hexValue + +rule + + /* Returns a Hash of filename and MofResult */ + mofSpecification + : /* empty */ + { result = Hash.new } + | mofProduction + { result = { @name => @result } } + | mofSpecification mofProduction + { result = val[0] + result[@name] = @result + } + ; + + mofProduction + : compilerDirective + | classDeclaration + { #puts "Class '#{val[0].name}'" + @result.classes << val[0] + } + | qualifierDeclaration + { @result.qualifiers << val[0] + @qualifiers[val[0].name.downcase] = val[0] + } + | instanceDeclaration + { @result.instances << val[0] } + ; + +/*** + * compilerDirective + * + */ + + compilerDirective + : "#" PRAGMA INCLUDE pragmaParameters_opt + { raise MOF::Helper::Error.new(@name,@lineno,@line,"Missing filename after '#pragma include'") unless val[3] + open val[3], :pragma + } + | "#" PRAGMA pragmaName pragmaParameters_opt + | "#" INCLUDE pragmaParameters_opt + { raise StyleError.new(@name,@lineno,@line,"Use '#pragma include' instead of '#include'") unless @style == :wmi + raise MOF::Helper::Error.new(@name,@lineno,@line,"Missing filename after '#include'") unless val[2] + open val[2], :pragma + } + ; + + pragmaName + : IDENTIFIER + ; + + pragmaParameters_opt + : /* empty */ + { raise StyleError.new(@name,@lineno,@line,"#pragma parameter missing") unless @style == :wmi } + | "(" pragmaParameterValues ")" + { result = val[1] } + ; + + pragmaParameterValues + : pragmaParameterValue + | pragmaParameterValues "," pragmaParameterValue + ; + + pragmaParameterValue + : string + | integerValue + { raise StyleError.new(@name,@lineno,@line,"#pragma parameter missing") unless @style == :wmi } + | IDENTIFIER + ; + +/*** + * classDeclaration + * + */ + + classDeclaration + : qualifierList_opt CLASS className alias_opt superClass_opt "{" classFeatures "}" ";" + { qualifiers = val[0] + features = val[6] + # FIXME: features must not include references + result = CIM::Class.new(val[2],qualifiers,val[3],val[4],features) + } + ; + + classFeatures + : /* empty */ + { result = [] } + | classFeatures classFeature + { result = val[0] << val[1] } + ; + + classFeature + : propertyDeclaration + | methodDeclaration + | referenceDeclaration /* must have association qualifier */ + ; + + + qualifierList_opt + : /* empty */ + | qualifierList + { result = CIM::QualifierSet.new val[0] } + ; + + qualifierList + : "[" qualifier qualifiers "]" + { result = val[2] + result.unshift val[1] if val[1] } + ; + + qualifiers + : /* empty */ + { result = [] } + | qualifiers "," qualifier + { result = val[0] + result << val[2] if val[2] + } + ; + + qualifier + : qualifierName qualifierParameter_opt flavor_opt + { # Get qualifier decl + qualifier = case val[0] + when CIM::Qualifier then val[0].definition + when CIM::QualifierDeclaration then val[0] + when String then @qualifiers[val[0].downcase] + else + nil + end + raise MOF::Helper::Error.new(@name,@lineno,@line,"'#{val[0]}' is not a valid qualifier") unless qualifier + value = val[1] + raise MOF::Helper::Error.new(@name,@lineno,@line,"#{value.inspect} does not match qualifier type '#{qualifier.type}'") unless qualifier.type.matches?(value)||@style == :wmi + # Don't propagate a boolean 'false' + if qualifier.type == :boolean && value == false + result = nil + else + result = CIM::Qualifier.new(qualifier,value,val[2]) + end + } + ; + + flavor_opt + : /* empty */ + | ":" flavor + { result = CIM::QualifierFlavors.new val[1] } + ; + + qualifierParameter_opt + : /* empty */ + | qualifierParameter + ; + + qualifierParameter + : "(" constantValue ")" + { result = val[1] } + | arrayInitializer + ; + + /* CIM::Flavors */ + flavor + : AMENDED | ENABLEOVERRIDE | DISABLEOVERRIDE | RESTRICTED | TOSUBCLASS | TRANSLATABLE | TOINSTANCE + { case val[0].to_sym + when :amended, :toinstance + raise StyleError.new(@name,@lineno,@line,"'#{val[0]}' is not a valid flavor") unless @style == :wmi + end + } + ; + + alias_opt + : /* empty */ + | alias + ; + + superClass_opt + : /* empty */ + | superClass + ; + + className + : IDENTIFIER /* must be _ in CIM v2.x */ + { raise ParseError.new("Class name must be prefixed by '_'") unless val[0].include?("_") || @style == :wmi } + ; + + alias + : AS aliasIdentifier + { result = val[1] } + ; + + aliasIdentifier + : "$" IDENTIFIER /* NO whitespace ! */ + { result = val[1] } + ; + + superClass + : ":" className + { result = val[1] } + ; + + + propertyDeclaration + : qualifierList_opt dataType propertyName array_opt defaultValue_opt ";" + { if val[3] + type = CIM::Array.new val[3],val[1] + else + type = val[1] + end + result = CIM::Property.new(type,val[2],val[0],val[4]) + } + ; + + referenceDeclaration + : qualifierList_opt objectRef referenceName array_opt defaultValue_opt ";" + { if val[4] + raise StyleError.new(@name,@lineno,@line,"Array not allowed in reference declaration") unless @style == :wmi + end + result = CIM::Reference.new(val[1],val[2],val[0],val[4]) } + ; + + methodDeclaration + : qualifierList_opt dataType methodName "(" parameterList_opt ")" ";" + { result = CIM::Method.new(val[1],val[2],val[0],val[4]) } + ; + + propertyName + : IDENTIFIER + | PROPERTY + { # tmplprov.mof has 'string Property;' + raise StyleError.new(@name,@lineno,@line,"Invalid keyword '#{val[0]}' used for property name") unless @style == :wmi + } + ; + + referenceName + : IDENTIFIER + | INDICATION + { result = "Indication" } + ; + + methodName + : IDENTIFIER + ; + + dataType + : DT_UINT8 + | DT_SINT8 + | DT_UINT16 + | DT_SINT16 + | DT_UINT32 + | DT_SINT32 + | DT_UINT64 + | DT_SINT64 + | DT_REAL32 + | DT_REAL64 + | DT_CHAR16 + | DT_STR + | DT_BOOLEAN + | DT_DATETIME + | DT_VOID + { raise StyleError.new(@name,@lineno,@line,"'void' is not a valid datatype") unless @style == :wmi } + ; + + objectRef + : className + { # WMI uses class names as data types (without REF ?!) + raise StyleError.new(@name,@lineno,@line,"Expected 'ref' keyword after classname '#{val[0]}'") unless @style == :wmi + result = CIM::ReferenceType.new val[0] + } + + | className REF + { result = CIM::ReferenceType.new val[0] } + ; + + parameterList_opt + : /* empty */ + | parameterList + ; + + parameterList + : parameter parameters + { result = val[1].unshift val[0] } + ; + + parameters + : /* empty */ + { result = [] } + | parameters "," parameter + { result = val[0] << val[2] } + ; + + parameter + : qualifierList_opt typespec parameterName array_opt parameterValue_opt + { if val[3] + type = CIM::Array.new val[3], val[1] + else + type = val[1] + end + result = CIM::Property.new(type,val[2],val[0]) + } + ; + + typespec + : dataType + | objectRef + ; + + parameterName + : IDENTIFIER + ; + + array_opt + : /* empty */ + | array + ; + + parameterValue_opt + : /* empty */ + | defaultValue + { raise "Default parameter value not allowed in syntax style '{@style}'" unless @style == :wmi } + ; + + array + : "[" positiveDecimalValue_opt "]" + { result = val[1] } + ; + + positiveDecimalValue_opt + : /* empty */ + { result = -1 } + | positiveDecimalValue + ; + + defaultValue_opt + : /* empty */ + | defaultValue + ; + + defaultValue + : "=" initializer + { result = val[1] } + ; + + initializer + : constantValue + | arrayInitializer + | referenceInitializer + ; + + arrayInitializer + : "{" constantValues "}" + { result = val[1] } + ; + + constantValues + : /* empty */ + | constantValue + { result = [ val[0] ] } + | constantValues "," constantValue + { result = val[0] << val[2] } + ; + + constantValue + : integerValue + | realValue + | charValue + | string + | booleanValue + | nullValue + | instance + { raise "Instance as property value not allowed in syntax style '{@style}'" unless @style == :wmi } + ; + + integerValue + : binaryValue + | octalValue + | decimalValue + | positiveDecimalValue + | hexValue + ; + + string + : stringValue + | string stringValue + { result = val[0] + val[1] } + ; + + referenceInitializer + : objectHandle + | aliasIdentifier + ; + + objectHandle + : namespace_opt modelPath + ; + + namespace_opt + : /* empty */ + | namespaceHandle ":" + ; + + namespaceHandle + : IDENTIFIER + ; + + /* + * Note + : structure depends on type of namespace + */ + + modelPath + : className "." keyValuePairList + ; + + keyValuePairList + : keyValuePair keyValuePairs + ; + + keyValuePairs + : /* empty */ + | keyValuePairs "," keyValuePair + ; + + keyValuePair + : keyname "=" initializer + ; + + keyname + : propertyName | referenceName + ; + +/*** + * qualifierDeclaration + * + */ + + qualifierDeclaration + /* 0 1 2 3 4 */ + : QUALIFIER qualifierName qualifierType scope defaultFlavor_opt ";" + { result = CIM::QualifierDeclaration.new( val[1], val[2][0], val[2][1], val[3], val[4]) } + ; + + defaultFlavor_opt + : /* empty */ + | defaultFlavor + ; + + qualifierName + : IDENTIFIER + | ASSOCIATION /* meta qualifier */ + | INDICATION /* meta qualifier */ + | REFERENCE /* Added in DSP0004 2.7.0 */ + | SCHEMA + ; + + /* [type, value] */ + qualifierType + : ":" dataType array_opt defaultValue_opt + { type = val[2].nil? ? val[1] : CIM::Array.new(val[2],val[1]) + result = [ type, val[3] ] + } + ; + + scope + : "," SCOPE "(" metaElements ")" + { result = CIM::QualifierScopes.new(val[3]) } + ; + + metaElements + : metaElement + { result = [ val[0] ] } + | metaElements "," metaElement + { result = val[0] << val[2] } + ; + + metaElement + : SCHEMA + | CLASS + | ASSOCIATION + | INDICATION + | QUALIFIER + | PROPERTY + | REFERENCE + | METHOD + | PARAMETER + | ANY + ; + + defaultFlavor + : "," FLAVOR "(" flavors ")" + { result = CIM::QualifierFlavors.new val[3] } + ; + + flavors + : flavor + { result = [ val[0] ] } + | flavors "," flavor + { result = val[0] << val[2] } + ; + +/*** + * instanceDeclaration + * + */ + + instanceDeclaration + : instance ";" + ; + + instance + : qualifierList_opt INSTANCE OF className alias_opt "{" valueInitializers "}" + ; + + valueInitializers + : valueInitializer + | valueInitializers valueInitializer + ; + + valueInitializer + : qualifierList_opt keyname "=" initializer ";" + | qualifierList_opt keyname ";" + { raise "Instance property '#{val[1]} must have a value" unless @style == :wmi } + ; + +end # class Parser + +---- header ---- + +# parser.rb - generated by racc + +require 'strscan' +require 'rubygems' +require 'cim' +require File.join(File.dirname(__FILE__), 'result') +require File.join(File.dirname(__FILE__), 'scanner') +require File.join(File.dirname(__FILE__), 'helper') + +---- inner ---- + +# +# Initialize MOF::Parser +# MOF::Parser.new options = {} +# +# options -> Hash of options +# :debug -> boolean +# :includes -> array of include dirs +# :style -> :cim or :wmi +# +def initialize options = {} + @yydebug = options[:debug] + @includes = options[:includes] || [] + @quiet = options[:quiet] + @style = options[:style] || :cim # default to style CIM v2.2 syntax + + @lineno = 1 + @file = nil + @iconv = nil + @eol = "\n" + @fname = nil + @fstack = [] + @in_comment = false + @seen_files = [] + @qualifiers = {} +end + +# +# Make options hash from argv +# +# returns [ files, options ] +# + + def self.argv_handler name, argv + files = [] + options = { :namespace => "" } + while argv.size > 0 + case opt = argv.shift + when "-h" + $stderr.puts "Ruby MOF compiler" + $stderr.puts "#{name} [-h] [-d] [-I ] []" + $stderr.puts "Compiles " + $stderr.puts "\t-d debug" + $stderr.puts "\t-h this help" + $stderr.puts "\t-I include dir" + $stderr.puts "\t-f force" + $stderr.puts "\t-n " + $stderr.puts "\t-o " + $stderr.puts "\t-s