require 'test/unit' require 'rockit/dparser' require 'rockit/symbol_table' class ATestSpeculativeCodeAction < Test::Unit::TestCase TestGrammar = Rockit::DParser::Grammar.new do start :S, [[:Id, ast(:S, 0)]] rule :Id, [[:AIdent]] rule :AIdent, [ [:Ident, # Speculative code that rejects match unless the ident starts with "A" speculative do |parser| parser.reject if parser.get_value_of_child(0)[0,1] != "A" end, # Final code ast(:A, 0), ] ] term :Ident, /[A-Za-z]+/ end def test_01_no_reject parser = TestGrammar.new_parser ast = parser.parse "ABBA" assert_equal("S", ast.name) assert_kind_of(parser.astclass_of_name(:S), ast) assert_equal(1, ast.num_children) assert_kind_of(parser.astclass_of_name(:A), ast[0]) assert_equal(1, ast[0].num_children) assert_equal("ABBA", ast[0][0]) end def test_02_reject parser = TestGrammar.new_parser # Make sure "CDE" is not a valid parse even though it matches Ident # since it should be rejected by the speculative action code assert_raises(RuntimeError) {ast = parser.parse "CDE"} assert_raises(RuntimeError) {ast = parser.parse "B"} assert_raises(RuntimeError) {ast = parser.parse "D"} assert_raises(RuntimeError) {ast = parser.parse "G"} end # This example is a more realistic one with a SymbolTable that saves # symbols and indicates that they are type names. SymbolTableTestGrammar = Rockit::DParser::Grammar.new do parser_include Rockit::ParserSymbolTable start :TranslationUnit, [ [mult(:Declaration), plus(:Assignment)], ], ast(:TU, 0, 1) rule :Assignment, [ [:Identifier, "=", :Identifier, ast(:Assign, 0, 2)], ] rule :Declaration, [ [plus(:StorageClassSpecifier), :Identifier, ";", # Speculative code that inserts identifier into symbol table # if it is a typedef. speculative do |parser| e0, e1 = parser.get_value_of_child(0), parser.get_value_of_child(1) if e0.include?("typedef") && e1.name == "Identifier" parser.symbol_table.add_typename(e1[0]) end end, ast(:Declaration, 0, 1), ], ] rule :StorageClassSpecifier, [ ['auto'], ['register'], ['static'], ['extern'], ['typedef'], ] rule :Identifier, [ [:Ident, # Speculative code here that rejects the ident if it is in the # symbol table and is a type name speculative do |parser| e0 = parser.get_value_of_child(0) parser.reject if parser.symbol_table.typename?(e0) end, ast(:Identifier, 0) ] ] term :Ident, /[A-Za-z]+/ end def test_03_valid_with_symbol_table_but_without_typedef parser = SymbolTableTestGrammar.new_parser ast = parser.parse "A = B" assert_equal("TU", ast.name) assert_kind_of(parser.astclass_of_name(:TU), ast) assert_equal(2, ast.num_children) assert_equal(0, ast[0].length) assert_equal(1, ast[1].length) assert_equal("Assign", ast[1][0].name) assert_equal("A", ast[1][0][0][0]) assert_equal("B", ast[1][0][1][0]) end def test_03_valid_with_symbol_table_and_typedef parser = SymbolTableTestGrammar.new_parser ast = parser.parse "typedef C; A = B" assert_equal("TU", ast.name) assert_kind_of(parser.astclass_of_name(:TU), ast) assert_equal(2, ast.num_children) assert_equal(1, ast[0].length) assert_equal("Declaration", ast[0][0].name) assert_equal("C", ast[0][0][1][0]) assert_equal(1, ast[1].length) assert_equal("Assign", ast[1][0].name) end def test_04_invalid_with_typedef_and_symbol_table parser = SymbolTableTestGrammar.new_parser # Should not parse ok since the typedef means the A cannot later be an # identifier. assert_raises(RuntimeError) {ast = parser.parse "typedef A; A = B"} assert_raises(RuntimeError) {ast = parser.parse "typedef B; A = B"} end end