From 877a6e7f7c8142b272e1de8042784df480b52da2 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 5 Mar 2008 21:49:51 -0800 Subject: [PATCH] Propagations are returned from choice expressions. The compiled parser will always discard a propagation returned as the result of the entire parse unless the return_propagations option is set to true. SyntaxNodes add propagations to their dependencies but add the propagated result to their elements. --- lib/treetop/compiler/node_classes/choice.rb | 4 +- lib/treetop/runtime/compiled_parser.rb | 10 +- lib/treetop/runtime/propagation.rb | 4 + spec/compiler/choice_spec.rb | 112 +++++++++++++------- spec/runtime/compiled_parser_spec.rb | 12 +++ spec/runtime/propagation_spec.rb | 4 + spec/spec_helper.rb | 2 + 7 files changed, 104 insertions(+), 44 deletions(-) diff --git a/lib/treetop/compiler/node_classes/choice.rb b/lib/treetop/compiler/node_classes/choice.rb index dd09a46..cf70514 100644 --- a/lib/treetop/compiler/node_classes/choice.rb +++ b/lib/treetop/compiler/node_classes/choice.rb @@ -7,7 +7,7 @@ def compile(address, builder, parent_expression = nil) use_vars :result, :start_index builder.assign "failed_alternatives", "[]" compile_alternatives(alternatives) - builder << "failed_alternatives.each { |alt| alt.dependent_results.push(#{result_var}) }" + builder << "#{result_var}.dependencies.concat(failed_alternatives)" end_comment(self) end @@ -18,8 +18,10 @@ def compile_alternatives(alternatives) assign_result subexpression_result_var extend_result_with_declared_module extend_result_with_inline_module + assign_result "Propagation.new(#{result_var})" end builder.else_ do + builder.accumulate "failed_alternatives", subexpression_result_var if alternatives.size == 1 reset_index assign_failure start_index_var diff --git a/lib/treetop/runtime/compiled_parser.rb b/lib/treetop/runtime/compiled_parser.rb index d82e37c..b38bebf 100644 --- a/lib/treetop/runtime/compiled_parser.rb +++ b/lib/treetop/runtime/compiled_parser.rb @@ -5,9 +5,10 @@ class CompiledParser attr_reader :input, :index, :terminal_failures, :max_terminal_failure_first_index, :max_terminal_failure_last_index attr_writer :root - attr_accessor :consume_all_input, :return_parse_failure + attr_accessor :consume_all_input, :return_parse_failure, :return_propagations alias :consume_all_input? :consume_all_input alias :return_parse_failure? :return_parse_failure + alias :return_propagations? :return_propagations def initialize self.consume_all_input = true @@ -66,7 +67,12 @@ def parse_root(options = {}) end end return nil if (consume_all_input? && index != input.size) - result + + if return_propagations? + result + else + result.element + end end def prepare_to_parse(input) diff --git a/lib/treetop/runtime/propagation.rb b/lib/treetop/runtime/propagation.rb index af8d5b4..c2c5771 100644 --- a/lib/treetop/runtime/propagation.rb +++ b/lib/treetop/runtime/propagation.rb @@ -8,6 +8,10 @@ def initialize(element) @element = element @dependencies = [element] end + + def resume_index + element.resume_index + end end end end \ No newline at end of file diff --git a/spec/compiler/choice_spec.rb b/spec/compiler/choice_spec.rb index 1898130..c512114 100644 --- a/spec/compiler/choice_spec.rb +++ b/spec/compiler/choice_spec.rb @@ -20,8 +20,7 @@ module ChoiceSpec it "upon parsing a string matching the first alternative, returns a Propagation with the result of the first alternative as its result" do - pending - result = parse('foo') + result = parse('foo', :return_propagations => true) result.should be_an_instance_of(Runtime::Propagation) result.element.should be_terminal result.element.text_value.should == 'foo' @@ -31,67 +30,98 @@ module ChoiceSpec attr_reader :result before do - @result = parse('bar') + @result = parse('bar', :return_propagations => true) end describe "the result" do it "is an instance Propagation" do - pending result.should be_an_instance_of(Runtime::Propagation) end it "has the result of the second alternative as its #element" do - pending result.element.should be_terminal result.element.text_value.should == 'bar' end - it "has the failing result of the first alternative and the successful result of the second alternative as its dependencies" do - pending + it "has the successful result of the second alternative and the failing result of the first alternative as its dependencies" do dependencies = result.dependencies dependencies.size.should == 2 - dependencies[0].should be_an_instance_of(Runtime::TerminalParseFailure) - dependencies[0].expected_string.should == 'foo' - dependencies[1].should == result.element + dependencies[0].should == result.element + dependencies[1].should be_an_instance_of(Runtime::TerminalParseFailure) + dependencies[1].expected_string.should == 'foo' end end - it "records the failure of the first terminal" - end - - it "upon parsing a string matching the second alternative, records the failure of the first terminal" do - result = parse('bar') - terminal_failures = parser.terminal_failures - terminal_failures.size.should == 1 - failure = terminal_failures[0] - failure.expected_string.should == 'foo' - failure.index.should == 0 + it "records the failure of the first terminal" do + terminal_failures = parser.terminal_failures + terminal_failures.size.should == 1 + failure = terminal_failures[0] + failure.expected_string.should == 'foo' + failure.index.should == 0 + end end - + describe "upon parsing a string matching the third alternative" do + attr_reader :result + + before do + @result = parse("baz", :return_propagations => true) + end + describe "the result" do - it "is an instance Propagation" - it "has the result of the third alternative as its #result" - it "has the failing results of the first and second alternatives and the successful result of the third alternative as its dependencies" + it "is an instance Propagation" do + result.should be_an_instance_of(Runtime::Propagation) + end + + it "has the result of the third alternative as its #element" do + result.element.should be_terminal + result.element.text_value.should == 'baz' + end + + it "has the successful result of the third alternative and the failing results of the first and second alternatives as its dependencies" do + dependencies = result.dependencies + dependencies.size.should == 3 + dependencies[0].should == result.element + dependencies[1].should be_an_instance_of(Runtime::TerminalParseFailure) + dependencies[1].expected_string.should == 'foo' + dependencies[2].should be_an_instance_of(Runtime::TerminalParseFailure) + dependencies[2].expected_string.should == 'bar' + end end - it "records the failure of the first terminal and second terminals" + it "records the failure of the first terminal and second terminals" do + terminal_failures = parser.terminal_failures + + terminal_failures.size.should == 2 + + failure_1 = terminal_failures[0] + failure_1.expected_string == 'foo' + failure_1.index.should == 0 + + failure_2 = terminal_failures[1] + failure_2.expected_string == 'bar' + failure_2.index.should == 0 + end end - - it "upon parsing a string matching the third alternative, records the failure of the first two terminals" do - result = parse('baz') - - terminal_failures = parser.terminal_failures - - terminal_failures.size.should == 2 - failure_1 = terminal_failures[0] - failure_1.expected_string == 'foo' - failure_1.index.should == 0 - - failure_2 = terminal_failures[1] - failure_2.expected_string == 'bar' - failure_2.index.should == 0 + describe "the result of parsing non-matching input" do + attr_reader :result + before do + @result = parse('cat', :return_parse_failure => true) + end + + it "is a ParseFailure that depends on the failure of all 3 alternatives" do + result.should be_an_instance_of(Runtime::ParseFailure) + dependencies = result.dependencies + + dependencies.size.should == 3 + dependencies[0].should be_an_instance_of(Runtime::TerminalParseFailure) + dependencies[0].expected_string.should == 'foo' + dependencies[1].should be_an_instance_of(Runtime::TerminalParseFailure) + dependencies[1].expected_string.should == 'bar' + dependencies[2].should be_an_instance_of(Runtime::TerminalParseFailure) + dependencies[2].expected_string.should == 'baz' + end end end @@ -109,7 +139,7 @@ module ChoiceSpec it "extends a match of any of its subexpressions with a module created from the block" do ['a', 'b', 'c'].each do |letter| - parse(letter).should respond_to(:a_method) + parse(letter).element.should respond_to(:a_method) end end end @@ -124,7 +154,7 @@ def a_method it "extends a match of any of its subexpressions with a module created from the block" do ['a', 'b', 'c'].each do |letter| - parse(letter).should respond_to(:a_method) + parse(letter).element.should respond_to(:a_method) end end end diff --git a/spec/runtime/compiled_parser_spec.rb b/spec/runtime/compiled_parser_spec.rb index 296a390..ebb8e16 100644 --- a/spec/runtime/compiled_parser_spec.rb +++ b/spec/runtime/compiled_parser_spec.rb @@ -65,6 +65,18 @@ module CompiledParserSpec parser.failure_line.should == 1 parser.failure_column.should == 1 end + + it "returns the element of the returned Propagation as the result of the parse" do + result = parse('a') + result.should be_an_instance_of(Runtime::SyntaxNode) + result.text_value.should == 'a' + end + + it "returns a Propagation as the result of the parse if the :return_propagations is true" do + result = parse('a', :return_propagations => true) + result.should be_an_instance_of(Runtime::Propagation) + result.element.text_value.should == 'a' + end end describe Runtime::CompiledParser, "#terminal_failures" do diff --git a/spec/runtime/propagation_spec.rb b/spec/runtime/propagation_spec.rb index dee22fd..fb67d34 100644 --- a/spec/runtime/propagation_spec.rb +++ b/spec/runtime/propagation_spec.rb @@ -18,5 +18,9 @@ module PropagationSpec it "returns the propagated SyntaxNode as its only dependency" do propagation.dependencies.should == [propagated_syntax_node] end + + it "proxies #resume_index to its element" do + propagation.resume_index.should == propagated_syntax_node.resume_index + end end end \ No newline at end of file diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index e7d80d9..da423ef 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -72,6 +72,8 @@ def parse(input, options = {}) @parser = parser_class_under_test.new parser.consume_all_input = options[:consume_all_input] if options.has_key?(:consume_all_input) parser.return_parse_failure = options[:return_parse_failure] if options.has_key?(:return_parse_failure) + parser.return_propagations = options[:return_propagations] if options.has_key?(:return_propagations) + result = parser.parse(input, options) yield result if block_given? result