Skip to content

Commit

Permalink
Sequences ending in an epsilon result have intervals that include the…
Browse files Browse the repository at this point in the history
…ir endpoint.

Because an epsilon indicates reliance on not a range, but a point in the buffer, it is important that the disturbance of this point cause the expiration of a result that depends upon the epsilon node. Whereas normal results have a range, which would naturally be contained by the range of the parent result, epsilons can only be contained by the range of the parent if they include their endpoint.
  • Loading branch information
Nathan Sobo committed Mar 14, 2008
1 parent eb61542 commit 8cffa31
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 15 deletions.
3 changes: 2 additions & 1 deletion lib/treetop/compiler/node_classes/sequence.rb
Expand Up @@ -8,7 +8,8 @@ def compile(address, builder, parent_expression = nil)
compile_sequence_elements(sequence_elements)
builder.assign '@max_terminal_failure_last_index', 'local_max_terminal_failure_last_index'
builder.if__ "!#{accumulator_var}.last.is_a?(ParseFailure)" do
assign_result "(#{node_class_name}).new(input, #{start_index_var}...index, #{accumulator_var})"
builder.assign "interval", "#{accumulator_var}.last.epsilon? ? #{start_index_var}..index : #{start_index_var}...index"
assign_result "(#{node_class_name}).new(input, interval, #{accumulator_var})"
extend_result sequence_element_accessor_module_name if sequence_element_accessor_module_name
extend_result_with_inline_module
end
Expand Down
4 changes: 4 additions & 0 deletions lib/treetop/runtime/propagation.rb
Expand Up @@ -12,6 +12,10 @@ def initialize(element)
def resume_index
element.resume_index
end

def epsilon?
element.epsilon?
end

def inspect
"Propagation(#{element.inspect})"
Expand Down
106 changes: 95 additions & 11 deletions spec/compiler/and_predicate_spec.rb
Expand Up @@ -4,13 +4,14 @@ module AndPredicateSpec
include Runtime

describe "An &-predicated terminal symbol" do
attr_reader :result

testing_expression '&"foo"'

attr_reader :input, :result

describe "the result of parsing input matching the predicated subexpression" do
before do
@result = parse('foo', :consume_all_input => false)
@input = 'foo'
@result = parse(input, :consume_all_input => false)
end

it "is an epsilon node" do
Expand Down Expand Up @@ -46,10 +47,11 @@ module AndPredicateSpec
testing_expression '"foo" &"bar"'

describe "the result of parsing input that matches both terminals" do
attr_reader :result
attr_reader :input, :result

before do
@result = parse('foobar', :consume_all_input => false)
@input = 'foobar'
@result = parse(input, :consume_all_input => false)
end

it "is successful" do
Expand All @@ -64,16 +66,98 @@ module AndPredicateSpec
it "depends on its elements" do
result.dependencies.should == result.elements
end

it "is expired when a character is inserted between 'foo' and 'bar'" do
node_cache.should have_result(:expression_under_test, 0)
input.replace('fooxbar')
expire(3..3, 1)
node_cache.should_not have_result(:expression_under_test, 0)
end
end

it "fails to parse input matching only the first terminal, recording a terminal failure at index 3" do
parse('foo') do |result|

describe "upon parsing input that matches the first terminal but not the second" do
attr_reader :input, :result
before do
@input = 'foolish'
@result = parse(input)
end

it "results is a failure" do
result.should be_nil
end

it "records a terminal failure at index 3" do
terminal_failures = parser.terminal_failures
terminal_failures.size.should == 1
failure = terminal_failures[0]
failure.index.should == 3
failure.expected_string.should == 'bar'
terminal_failures.first.index.should == 3
terminal_failures.first.expected_string.should == 'bar'
end

it "will subsequently expire the result if input is inserted at index 3" do
node_cache.should have_result(:expression_under_test, 0)

input.replace('foobarlish')
node_cache.expire(3..3, 3)

node_cache.should_not have_result(:expression_under_test, 0)
parser.consume_all_input = false
reparse.should_not be_nil
end
end

describe "the result of parsing input that matches only the first terminal" do
attr_reader :input, :result

before do
@input = 'foo'
@result = parse(input)
end

it "the result is a failure" do
result.should be_nil
end

it "a terminal failure is recorded at index 3" do
terminal_failures = parser.terminal_failures
terminal_failures.size.should == 1
terminal_failures.first.index.should == 3
terminal_failures.first.expected_string.should == 'bar'
end

it "will subsequently expire the result if input is inserted at index 3" do
node_cache.should have_result(:expression_under_test, 0)

input.replace('foobar')
node_cache.expire(3..3, 3)

node_cache.should_not have_result(:expression_under_test, 0)
end
end
end

describe "An expression for a character not followed by a space" do
testing_expression "'a' &(!' ' .)"

attr_reader :input, :result

describe "after parsing that character followed by a space" do
before do
@input = "a "
@result = parse(input, :consume_all_input => false)
end

it "results in a failure" do
result.should be_nil
end

it "expires the failure when a character is subsequently inserted between the character and the space" do
node_cache.should have_result(:expression_under_test, 0)

input.replace('ab ')
parser.expire(1..1, 1)

node_cache.should_not have_result(:expression_under_test, 0)
reparse.should_not be_nil
end
end
end
Expand Down
3 changes: 0 additions & 3 deletions spec/iterative_parsing/iterative_parsing_spec.rb
Expand Up @@ -260,12 +260,9 @@ module IterativeParsingSpec
end

it "successfully expires repetitions when the site of the repetition-terminating failure is disturbed" do
pending "Better expiration of repetitions and other nodes that depend on failure at the end"
input = '(1)'
parse(input).should_not be_nil

print_dependencies(node_cache.get(:number, 1))

input.replace('(12)')
expire(2..2, 1)
node_cache.should_not have_result(:number, 1)
Expand Down
5 changes: 5 additions & 0 deletions spec/runtime/propagation_spec.rb
Expand Up @@ -22,5 +22,10 @@ module PropagationSpec
it "proxies #resume_index to its element" do
propagation.resume_index.should == propagated_syntax_node.resume_index
end

it "proxies #epsilon? to its element" do
mock.proxy(propagated_syntax_node).epsilon?
propagation.epsilon?
end
end
end

0 comments on commit 8cffa31

Please sign in to comment.