treetop 1.5.3 → 1.6.2

Sign up to get free protection for your applications and to get access to all the features.
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