treetop 1.5.3 → 1.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. checksums.yaml +5 -13
  2. data/Rakefile +9 -3
  3. data/doc/pitfalls_and_advanced_techniques.markdown +1 -1
  4. data/doc/sitegen.rb +1 -1
  5. data/doc/syntactic_recognition.markdown +2 -0
  6. data/doc/tt.1 +1 -1
  7. data/lib/treetop/compiler/metagrammar.rb +81 -13
  8. data/lib/treetop/compiler/metagrammar.treetop +67 -13
  9. data/lib/treetop/compiler/node_classes/anything_symbol.rb +5 -1
  10. data/lib/treetop/compiler/node_classes/character_class.rb +6 -2
  11. data/lib/treetop/compiler/node_classes/choice.rb +9 -5
  12. data/lib/treetop/compiler/node_classes/nonterminal.rb +2 -2
  13. data/lib/treetop/compiler/node_classes/parenthesized_expression.rb +5 -1
  14. data/lib/treetop/compiler/node_classes/parsing_expression.rb +12 -2
  15. data/lib/treetop/compiler/node_classes/predicate.rb +8 -1
  16. data/lib/treetop/compiler/node_classes/predicate_block.rb +7 -0
  17. data/lib/treetop/compiler/node_classes/repetition.rb +31 -11
  18. data/lib/treetop/compiler/node_classes/sequence.rb +5 -1
  19. data/lib/treetop/compiler/node_classes/terminal.rb +12 -2
  20. data/lib/treetop/runtime/compiled_parser.rb +12 -6
  21. data/lib/treetop/runtime/syntax_node.rb +24 -15
  22. data/lib/treetop/runtime/terminal_parse_failure.rb +4 -3
  23. data/lib/treetop/version.rb +2 -2
  24. data/spec/compiler/and_predicate_spec.rb +1 -1
  25. data/spec/compiler/anything_symbol_spec.rb +4 -1
  26. data/spec/compiler/character_class_spec.rb +4 -1
  27. data/spec/compiler/choice_spec.rb +20 -11
  28. data/spec/compiler/failure_propagation_functional_spec.rb +1 -1
  29. data/spec/compiler/grammar_spec.rb +4 -1
  30. data/spec/compiler/not_predicate_spec.rb +20 -6
  31. data/spec/compiler/occurrence_range_spec.rb +25 -28
  32. data/spec/compiler/one_or_more_spec.rb +2 -2
  33. data/spec/compiler/optional_spec.rb +2 -2
  34. data/spec/compiler/parenthesized_expression_spec.rb +15 -1
  35. data/spec/compiler/semantic_predicate_spec.rb +17 -16
  36. data/spec/compiler/sequence_spec.rb +1 -1
  37. data/spec/compiler/terminal_spec.rb +8 -1
  38. data/spec/compiler/terminal_symbol_spec.rb +4 -1
  39. data/spec/compiler/zero_or_more_spec.rb +4 -2
  40. data/spec/runtime/compiled_parser_spec.rb +33 -3
  41. metadata +20 -21
  42. data/examples/lambda_calculus/lambda_calculus +0 -0
@@ -6,7 +6,7 @@ module Treetop
6
6
  builder.if__ "index < input_length" do
7
7
  if address == 0 || decorated?
8
8
  assign_result "instantiate_node(#{node_class_name},input, index...(index + 1))"
9
- extend_result_with_inline_module
9
+ extend_result_with_inline_module parent_expression
10
10
  else
11
11
  assign_lazily_instantiated_node
12
12
  end
@@ -17,6 +17,10 @@ module Treetop
17
17
  assign_result 'nil'
18
18
  end
19
19
  end
20
+
21
+ def expected
22
+ '"any character"'
23
+ end
20
24
  end
21
25
  end
22
26
  end
@@ -7,18 +7,22 @@ module Treetop
7
7
  builder.if__ "has_terminal?(@regexps[gr = #{grounded_regexp(text_value)}] ||= Regexp.new(gr), :regexp, index)" do
8
8
  if address == 0 || decorated?
9
9
  assign_result "instantiate_node(#{node_class_name},input, index...(index + 1))"
10
- extend_result_with_inline_module
10
+ extend_result_with_inline_module parent_expression
11
11
  else
12
12
  assign_lazily_instantiated_node
13
13
  end
14
14
  builder << "@index += 1" # Always one character
15
15
  end
16
16
  builder.else_ do
17
- builder << "terminal_parse_failure(#{single_quote('['+characters+']')})"
17
+ builder << "terminal_parse_failure(#{expected})"
18
18
  assign_result 'nil'
19
19
  end
20
20
  end
21
21
 
22
+ def expected
23
+ single_quote('['+characters+']')
24
+ end
25
+
22
26
  def grounded_regexp(string)
23
27
  # Double any backslashes, then backslash any single-quotes:
24
28
  "'\\A#{string.gsub(/\\/) { '\\\\' }.gsub(/'/) { "\\'"}}'"
@@ -5,11 +5,11 @@ module Treetop
5
5
  super
6
6
  begin_comment(self)
7
7
  use_vars :result, :start_index
8
- compile_alternatives(alternatives)
8
+ compile_alternatives(alternatives, parent_expression)
9
9
  end_comment(self)
10
10
  end
11
-
12
- def compile_alternatives(alternatives)
11
+
12
+ def compile_alternatives(alternatives, parent_expression)
13
13
  obtain_new_subexpression_address
14
14
  alternatives.first.compile(subexpression_address, builder)
15
15
  builder.if__ subexpression_success? do
@@ -17,17 +17,21 @@ module Treetop
17
17
  builder << "#{subexpression_result_var} = SyntaxNode.new(input, (index-1)...index) if #{subexpression_result_var} == true"
18
18
  assign_result subexpression_result_var
19
19
  extend_result_with_declared_module
20
- extend_result_with_inline_module
20
+ extend_result_with_inline_module parent_expression
21
21
  end
22
22
  builder.else_ do
23
23
  if alternatives.size == 1
24
24
  reset_index
25
25
  assign_failure start_index_var
26
26
  else
27
- compile_alternatives(alternatives[1..-1])
27
+ compile_alternatives(alternatives[1..-1], parent_expression)
28
28
  end
29
29
  end
30
30
  end
31
+
32
+ def expected
33
+ '"(any alternative)"'
34
+ end
31
35
  end
32
36
  end
33
37
  end
@@ -6,8 +6,8 @@ module Treetop
6
6
  use_vars :result
7
7
  assign_result text_value == 'super' ? 'super' : "_nt_#{text_value}"
8
8
  extend_result_with_declared_module
9
- extend_result_with_inline_module
9
+ extend_result_with_inline_module parent_expression
10
10
  end
11
11
  end
12
12
  end
13
- end
13
+ end
@@ -4,6 +4,10 @@ module Treetop
4
4
  def compile(address, builder, parent_expression = nil)
5
5
  elements[2].compile(address, builder, parent_expression)
6
6
  end
7
+
8
+ def expected
9
+ elements[2].expected
10
+ end
7
11
  end
8
12
  end
9
- end
13
+ end
@@ -78,8 +78,14 @@ module Treetop
78
78
  extend_result declared_module_name if declared_module_name
79
79
  end
80
80
 
81
- def extend_result_with_inline_module
82
- extend_result inline_module_name if inline_module_name
81
+ def extend_result_with_inline_module parent_expression = nil
82
+ # debugger if parent_expression && !parent_expression.respond_to?(:parent_modules)
83
+ if parent_expression && parent_expression.parent_modules.size > 0
84
+ parent_expression.parent_modules.each do |inline|
85
+ extend_result inline.module_name
86
+ end
87
+ end
88
+ extend_result inline_module_name if inline_module_name
83
89
  end
84
90
 
85
91
  def reset_index
@@ -141,6 +147,10 @@ module Treetop
141
147
  def on_one_line(expression)
142
148
  expression.text_value.tr("\n", ' ')
143
149
  end
150
+
151
+ def expected
152
+ nil # Overridden for terminal parse failures
153
+ end
144
154
  end
145
155
  end
146
156
  end
@@ -13,6 +13,7 @@ module Treetop
13
13
  end
14
14
 
15
15
  def assign_failure
16
+ reset_index
16
17
  super(start_index_var)
17
18
  end
18
19
 
@@ -35,11 +36,17 @@ module Treetop
35
36
  class NotPredicate < Predicate
36
37
  def when_success
37
38
  assign_failure
39
+ if (e = parent.atomic.expected)
40
+ builder << "terminal_parse_failure(#{e}, true)"
41
+ end
38
42
  end
39
43
 
40
44
  def when_failure
45
+ if (e = parent.atomic.expected)
46
+ builder << "terminal_failures.pop"
47
+ end
41
48
  assign_success
42
49
  end
43
50
  end
44
51
  end
45
- end
52
+ end
@@ -10,6 +10,13 @@ module Treetop
10
10
  p = parent
11
11
  p = p.parent while p && !p.respond_to?(:accumulator_var)
12
12
  assign_result "lambda #{text_value}.call(#{p ? p.accumulator_var : ""})"
13
+ builder.if_ '!'+result_var do
14
+ builder << "terminal_parse_failure(#{expected})"
15
+ end
16
+ end
17
+
18
+ def expected
19
+ '"<semantic predicate>"' # Should I include (some of) the text_value here?
13
20
  end
14
21
  end
15
22
  end
@@ -28,9 +28,9 @@ module Treetop
28
28
  parent_expression.inline_module_name
29
29
  end
30
30
 
31
- def assign_and_extend_result
31
+ def assign_and_extend_result parent_expression
32
32
  assign_result "instantiate_node(#{node_class_name},input, #{start_index_var}...index, #{accumulator_var})"
33
- extend_result_with_inline_module
33
+ extend_result_with_inline_module parent_expression
34
34
  end
35
35
  end
36
36
 
@@ -38,7 +38,7 @@ module Treetop
38
38
  class ZeroOrMore < Repetition
39
39
  def compile(address, builder, parent_expression)
40
40
  super
41
- assign_and_extend_result
41
+ assign_and_extend_result parent_expression
42
42
  end_comment(parent_expression)
43
43
  end
44
44
 
@@ -55,7 +55,7 @@ module Treetop
55
55
  assign_failure start_index_var
56
56
  end
57
57
  builder.else_ do
58
- assign_and_extend_result
58
+ assign_and_extend_result parent_expression
59
59
  end
60
60
  end_comment(parent_expression)
61
61
  end
@@ -63,26 +63,46 @@ module Treetop
63
63
  def max
64
64
  nil
65
65
  end
66
+
67
+ def expected
68
+ parent_expression.atomic.expected && '"at least one "+'+parent_expression.atomic.expected
69
+ end
66
70
  end
67
71
 
68
72
  class OccurrenceRange < Repetition
69
73
  def compile(address, builder, parent_expression)
70
74
  super
71
75
 
72
- if min.empty? || min.text_value.to_i == 0
73
- assign_and_extend_result
74
- else
76
+ if !min.empty? && min.text_value.to_i != 0
75
77
  # We got some, but fewer than we wanted. There'll be a failure reported already
76
78
  builder.if__ "#{accumulator_var}.size < #{min.text_value}" do
77
79
  reset_index
78
80
  assign_failure start_index_var
79
- end
81
+ end
80
82
  builder.else_ do
81
- assign_and_extend_result
82
- end
83
- end
83
+ clean_unsaturated
84
+ assign_and_extend_result parent_expression
85
+ end
86
+ else
87
+ clean_unsaturated
88
+ assign_and_extend_result parent_expression
89
+ end
90
+
84
91
  end_comment(parent_expression)
85
92
  end
93
+
94
+ # remove the last terminal_failure if we merely failed to reach the maximum
95
+ def clean_unsaturated
96
+ if !max.empty? && max.text_value.to_i > 0
97
+ builder.if_ "#{accumulator_var}.size < #{max.text_value}" do
98
+ builder << 'terminal_failures.pop' # Ignore the last failure.
99
+ end
100
+ end
101
+ end
102
+
103
+ def expected
104
+ parent_expression.atomic.expected && "at least #{min.text_value} "+parent_expression.atomic.expected
105
+ end
86
106
  end
87
107
 
88
108
  end
@@ -9,7 +9,7 @@ module Treetop
9
9
  builder.if__ "#{accumulator_var}.last" do
10
10
  assign_result "instantiate_node(#{node_class_name},input, #{start_index_var}...index, #{accumulator_var})"
11
11
  extend_result sequence_element_accessor_module_name if sequence_element_accessor_module_name
12
- extend_result_with_inline_module
12
+ extend_result_with_inline_module parent_expression
13
13
  end
14
14
  builder.else_ do
15
15
  reset_index
@@ -40,6 +40,10 @@ module Treetop
40
40
  def sequence_element_accessor_module_name
41
41
  sequence_element_accessor_module.module_name
42
42
  end
43
+
44
+ def expected
45
+ '"<a sequence>"'
46
+ end
43
47
  end
44
48
 
45
49
  class SequenceElementAccessorModule
@@ -25,17 +25,27 @@ module Treetop
25
25
  builder.if__ "(match_len = has_terminal?(#{str}, #{mode}, index))" do
26
26
  if address == 0 || decorated? || mode != 'false' || string_length > 1
27
27
  assign_result "instantiate_node(#{node_class_name},input, index...(index + match_len))"
28
- extend_result_with_inline_module
28
+ # debugger if parent_expression and parent_expression.inline_modules.size > 0
29
+ # extend_result_with_inline_module parent_expression
30
+ if parent_expression
31
+ parent_expression.inline_modules.each do |inline|
32
+ extend_result inline.module_name
33
+ end
34
+ end
29
35
  else
30
36
  assign_lazily_instantiated_node
31
37
  end
32
38
  builder << "@index += match_len"
33
39
  end
34
40
  builder.else_ do
35
- builder << "terminal_parse_failure(#{string})"
41
+ builder << "terminal_parse_failure(#{expected})"
36
42
  assign_result 'nil'
37
43
  end
38
44
  end
45
+
46
+ def expected
47
+ single_quote(string)
48
+ end
39
49
  end
40
50
  end
41
51
  end
@@ -17,7 +17,12 @@ module Treetop
17
17
  @index = options[:index] if options[:index]
18
18
  result = send("_nt_#{options[:root] || root}")
19
19
  should_consume_all = options.include?(:consume_all_input) ? options[:consume_all_input] : consume_all_input?
20
- return nil if (should_consume_all && index != input.size)
20
+ if (should_consume_all && index != input.size)
21
+ if index > max_terminal_failure_index # Otherwise the failure is already explained
22
+ terminal_parse_failure('<END OF INPUT>', true)
23
+ end
24
+ return nil
25
+ end
21
26
  return SyntaxNode.new(input, index...(index + 1)) if result == true
22
27
  return result
23
28
  end
@@ -34,15 +39,16 @@ module Treetop
34
39
  @terminal_failures && input.column_of(failure_index)
35
40
  end
36
41
 
42
+ OtherThan = 'something other than '
37
43
  def failure_reason
38
44
  return nil unless (tf = terminal_failures) && tf.size > 0
39
45
  "Expected " +
40
46
  (tf.size == 1 ?
41
- tf[0].expected_string :
42
- "one of #{tf.map{|f| f.expected_string}.uniq*', '}"
47
+ (tf[0].unexpected ? OtherThan : '')+tf[0].expected_string :
48
+ "one of #{tf.map{|f| (f.unexpected ? OtherThan : '')+f.expected_string}.uniq*', '}"
43
49
  ) +
44
50
  " at line #{failure_line}, column #{failure_column} (byte #{failure_index+1})" +
45
- " after #{input[index...failure_index]}"
51
+ (failure_index > 0 ? " after #{input[index...failure_index]}" : '')
46
52
  end
47
53
 
48
54
  def terminal_failures
@@ -106,13 +112,13 @@ module Treetop
106
112
  end
107
113
  end
108
114
 
109
- def terminal_parse_failure(expected_string)
115
+ def terminal_parse_failure(expected_string, unexpected = false)
110
116
  return nil if index < max_terminal_failure_index
111
117
  if index > max_terminal_failure_index
112
118
  @max_terminal_failure_index = index
113
119
  @terminal_failures = []
114
120
  end
115
- @terminal_failures << [index, expected_string]
121
+ @terminal_failures << [index, expected_string, unexpected]
116
122
  return nil
117
123
  end
118
124
  end
@@ -7,7 +7,9 @@ module Treetop
7
7
  def initialize(input, interval, elements = nil)
8
8
  @input = input
9
9
  @interval = interval
10
- @elements = elements
10
+ if (@elements = elements)
11
+ @elements.each { |e| e.equal?(true) or e.parent = self }
12
+ end
11
13
  end
12
14
 
13
15
  def elements
@@ -18,8 +20,8 @@ module Treetop
18
20
  if element == true
19
21
  index = last_element ? last_element.interval.last : interval.first
20
22
  element = SyntaxNode.new(input, index...(index + 1))
23
+ element.parent = self
21
24
  end
22
- element.parent = self
23
25
  last_element = element
24
26
  end
25
27
 
@@ -58,7 +60,7 @@ module Treetop
58
60
  end
59
61
  end
60
62
 
61
- def inspect(indent="")
63
+ def inspect_self(indent="")
62
64
  em = extension_modules
63
65
  interesting_methods = methods-[em.last ? em.last.methods : nil]-self.class.instance_methods
64
66
  im = interesting_methods.size > 0 ? " (#{interesting_methods.join(",")})" : ""
@@ -70,18 +72,25 @@ module Treetop
70
72
  em.map{|m| "+"+m.to_s.sub(/.*:/,'')}*"" +
71
73
  " offset=#{interval.first}" +
72
74
  ", #{tv.inspect}" +
73
- im +
74
- (elements && elements.size > 0 ?
75
- ":" +
76
- (elements||[]).map{|e|
77
- begin
78
- "\n"+e.inspect(indent+" ")
79
- rescue # Defend against inspect not taking a parameter
80
- "\n"+indent+" "+e.inspect
81
- end
82
- }.join("") :
83
- ""
84
- )
75
+ im
76
+ end
77
+
78
+ def inspect_children(indent="")
79
+ return '' unless elements && elements.size > 0
80
+ ":" +
81
+ elements.map do |e|
82
+ begin
83
+ "\n"+e.inspect(indent+" ")
84
+ rescue # Defend against inspect not taking a parameter
85
+ "\n"+indent+" "+e.inspect
86
+ end
87
+ end.
88
+ join("")
89
+ end
90
+
91
+ def inspect(indent="")
92
+ inspect_self(indent) +
93
+ inspect_children(indent)
85
94
  end
86
95
 
87
96
  @@dot_id_counter = 0
@@ -1,15 +1,16 @@
1
1
  module Treetop
2
2
  module Runtime
3
3
  class TerminalParseFailure
4
- attr_reader :index, :expected_string
4
+ attr_reader :index, :expected_string, :unexpected
5
5
 
6
- def initialize(index, expected_string)
6
+ def initialize(index, expected_string, unexpected = false)
7
7
  @index = index
8
8
  @expected_string = expected_string
9
+ @unexpected = unexpected
9
10
  end
10
11
 
11
12
  def to_s
12
- "String matching #{expected_string} expected."
13
+ "String matching #{expected_string} #{@unexpected ? 'not ' : ''}expected."
13
14
  end
14
15
  end
15
16
  end