Skip to content

Commit

Permalink
Propagations are returned from choice expressions.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Nathan Sobo committed Mar 6, 2008
1 parent 3f2a108 commit 877a6e7
Show file tree
Hide file tree
Showing 7 changed files with 104 additions and 44 deletions.
4 changes: 3 additions & 1 deletion lib/treetop/compiler/node_classes/choice.rb
Expand Up @@ -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

Expand All @@ -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
Expand Down
10 changes: 8 additions & 2 deletions lib/treetop/runtime/compiled_parser.rb
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
4 changes: 4 additions & 0 deletions lib/treetop/runtime/propagation.rb
Expand Up @@ -8,6 +8,10 @@ def initialize(element)
@element = element
@dependencies = [element]
end

def resume_index
element.resume_index
end
end
end
end
112 changes: 71 additions & 41 deletions spec/compiler/choice_spec.rb
Expand Up @@ -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'
Expand All @@ -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

Expand All @@ -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
Expand All @@ -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
Expand Down
12 changes: 12 additions & 0 deletions spec/runtime/compiled_parser_spec.rb
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions spec/runtime/propagation_spec.rb
Expand Up @@ -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
2 changes: 2 additions & 0 deletions spec/spec_helper.rb
Expand Up @@ -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
Expand Down

0 comments on commit 877a6e7

Please sign in to comment.