babel_bridge 0.4.1 → 0.5.0
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.
- data/Rakefile +6 -0
- data/lib/nodes.rb +1 -1
- data/lib/nodes/empty_node.rb +1 -20
- data/lib/nodes/node.rb +33 -32
- data/lib/nodes/non_terminal_node.rb +10 -13
- data/lib/nodes/root_node.rb +4 -0
- data/lib/nodes/rule_node.rb +97 -44
- data/lib/nodes/terminal_node.rb +3 -3
- data/lib/parser.rb +95 -61
- data/lib/pattern_element.rb +71 -43
- data/lib/rule.rb +12 -6
- data/lib/rule_variant.rb +39 -15
- data/lib/string.rb +2 -9
- data/lib/tools.rb +26 -6
- data/lib/version.rb +2 -1
- data/spec/advanced_parsers_spec.rb +49 -0
- data/spec/basic_parsing_spec.rb +94 -0
- data/spec/bb_spec.rb +7 -163
- data/spec/ignore_whitespace_spec.rb +227 -0
- data/spec/inspect_spec.rb +50 -0
- data/spec/many_spec.rb +60 -0
- data/spec/node_spec.rb +117 -0
- data/spec/pattern_generators_spec.rb +41 -0
- data/spec/rule_parsing_spec.rb +61 -0
- data/spec/spec_helper.rb +31 -0
- data/spec/tools_spec.rb +21 -0
- metadata +13 -4
- data/lib/nodes/many_node.rb +0 -53
- data/test/test_bb.rb +0 -458
- data/test/test_helper.rb +0 -44
data/Rakefile
ADDED
data/lib/nodes.rb
CHANGED
data/lib/nodes/empty_node.rb
CHANGED
@@ -11,29 +11,10 @@ module BabelBridge
|
|
11
11
|
# not subclassed
|
12
12
|
class EmptyNode < Node
|
13
13
|
def inspect(options={})
|
14
|
-
"EmptyNode"
|
14
|
+
"EmptyNode" if options[:verbose]
|
15
15
|
end
|
16
16
|
|
17
17
|
def matches; [self]; end
|
18
|
-
|
19
18
|
end
|
20
19
|
|
21
|
-
class RollbackWhitespaceNode < Node
|
22
|
-
def inspect(options={})
|
23
|
-
"RollbackWhitespace" unless options[:simple]
|
24
|
-
end
|
25
|
-
|
26
|
-
def matches; [self]; end
|
27
|
-
|
28
|
-
def initialize(parent)
|
29
|
-
super
|
30
|
-
self.match_length = 0
|
31
|
-
self.offset = parent.postwhitespace_range.first
|
32
|
-
end
|
33
|
-
|
34
|
-
def postwhitespace_range
|
35
|
-
@postwhitespace_range ||= offset_after_match .. offset_after_match-1
|
36
|
-
end
|
37
|
-
|
38
|
-
end
|
39
20
|
end
|
data/lib/nodes/node.rb
CHANGED
@@ -7,15 +7,10 @@ end
|
|
7
7
|
|
8
8
|
# base class for all parse-tree nodes
|
9
9
|
class Node
|
10
|
-
attr_accessor :src,:offset,:match_length,:parent,:parser,:
|
10
|
+
attr_accessor :src,:offset,:match_length,:parent,:parser,:delimiter,:many_delimiter
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
# It should always be false again once parsing completes or fails.
|
15
|
-
attr_accessor :no_postwhitespace
|
16
|
-
|
17
|
-
def whitespace_regexp
|
18
|
-
parser.whitespace_regexp
|
12
|
+
def relative_class_name
|
13
|
+
(self.class.to_s.split(parser.class.to_s+"::",2)[1]||self.class.to_s).strip
|
19
14
|
end
|
20
15
|
|
21
16
|
# the index of the first character after the match
|
@@ -23,29 +18,30 @@ class Node
|
|
23
18
|
offset + match_length
|
24
19
|
end
|
25
20
|
|
26
|
-
def
|
27
|
-
|
21
|
+
def remaining_src(sub_offset)
|
22
|
+
src[self.next+sub_offset..-1]
|
28
23
|
end
|
29
24
|
|
30
|
-
def
|
31
|
-
|
25
|
+
def match_range
|
26
|
+
offset..(offset+match_length-1)
|
32
27
|
end
|
33
28
|
|
34
|
-
|
35
|
-
|
36
|
-
no_postwhitespace ? r.first..r.first-1 : r
|
29
|
+
# called when a ruled is matched
|
30
|
+
def on_matched
|
37
31
|
end
|
38
32
|
|
39
|
-
def
|
40
|
-
src
|
33
|
+
def init_line_column
|
34
|
+
@line, @column = Tools.line_column(src, offset)
|
41
35
|
end
|
42
36
|
|
43
|
-
def
|
44
|
-
|
37
|
+
def line
|
38
|
+
init_line_column unless @line
|
39
|
+
@line
|
45
40
|
end
|
46
41
|
|
47
|
-
|
48
|
-
|
42
|
+
def column
|
43
|
+
init_line_column unless @column
|
44
|
+
@column
|
49
45
|
end
|
50
46
|
|
51
47
|
def to_s
|
@@ -67,7 +63,6 @@ class Node
|
|
67
63
|
self.parent=parent_or_parser
|
68
64
|
self.parser=parent.parser
|
69
65
|
self.offset=parent.next
|
70
|
-
self.prewhitespace_range=parent.postwhitespace_range
|
71
66
|
self.src=parent.src
|
72
67
|
raise "parent node does not have parser set" unless parser
|
73
68
|
else
|
@@ -79,12 +74,6 @@ class Node
|
|
79
74
|
node_init(parent)
|
80
75
|
end
|
81
76
|
|
82
|
-
# after a node has been matched, the node will get this called on itself
|
83
|
-
# It can then rewrite itself however it wishes
|
84
|
-
def post_match
|
85
|
-
self
|
86
|
-
end
|
87
|
-
|
88
77
|
# Returns a human-readable representation of the parse tree
|
89
78
|
# options
|
90
79
|
# :simple => output a simplified representation of the parse tree
|
@@ -95,7 +84,7 @@ class Node
|
|
95
84
|
#********************
|
96
85
|
# info methods
|
97
86
|
#********************
|
98
|
-
|
87
|
+
alias :next :offset_after_match
|
99
88
|
def text; src[match_range] end # the substring in src matched
|
100
89
|
|
101
90
|
# length returns the number of sub-nodes
|
@@ -107,11 +96,23 @@ class Node
|
|
107
96
|
return parent ? parent.parent_list+[parent] : []
|
108
97
|
end
|
109
98
|
|
99
|
+
# walk down the children chain as long as there is only one child at each level
|
100
|
+
# log and return the path
|
101
|
+
def onlychildren_list
|
102
|
+
if matches.length == 1
|
103
|
+
[self] + matches[0].onlychildren_list
|
104
|
+
else
|
105
|
+
[self]
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def path_string(node_list)
|
110
|
+
node_list.collect{|n|n.class}.join ' > '
|
111
|
+
end
|
112
|
+
|
110
113
|
def node_path
|
111
|
-
|
114
|
+
path_string parent_list
|
112
115
|
end
|
113
116
|
end
|
114
117
|
|
115
|
-
class RootNode < Node
|
116
|
-
end
|
117
118
|
end
|
@@ -8,18 +8,9 @@ module BabelBridge
|
|
8
8
|
# rule node
|
9
9
|
# subclassed automatically by parser.rule for each unique non-terminal
|
10
10
|
class NonTerminalNode < Node
|
11
|
-
attr_accessor :last_non_empty_node
|
12
|
-
|
13
|
-
def postwhitespace_range_without_no_postwhitespace
|
14
|
-
if last_non_empty_node
|
15
|
-
last_non_empty_node.postwhitespace_range
|
16
|
-
else
|
17
|
-
prewhitespace_range || (0..-1)
|
18
|
-
end
|
19
|
-
end
|
20
11
|
|
21
12
|
def update_match_length
|
22
|
-
@match_length =
|
13
|
+
@match_length = last_match ? last_match.offset_after_match - offset : 0
|
23
14
|
end
|
24
15
|
|
25
16
|
#*****************************
|
@@ -29,15 +20,21 @@ class NonTerminalNode < Node
|
|
29
20
|
@matches ||= []
|
30
21
|
end
|
31
22
|
|
23
|
+
def last_match
|
24
|
+
matches[-1]
|
25
|
+
end
|
26
|
+
|
32
27
|
include Enumerable
|
33
28
|
def length
|
34
29
|
matches.length
|
35
30
|
end
|
36
31
|
|
37
32
|
def add_match(node)
|
38
|
-
|
39
|
-
|
40
|
-
|
33
|
+
return if !node || node.kind_of?(EmptyNode) || node == self
|
34
|
+
node.tap do
|
35
|
+
matches << node
|
36
|
+
update_match_length
|
37
|
+
end
|
41
38
|
end
|
42
39
|
|
43
40
|
def [](i)
|
data/lib/nodes/rule_node.rb
CHANGED
@@ -9,40 +9,50 @@ module BabelBridge
|
|
9
9
|
# subclassed automatically by parser.rule for each unique non-terminal
|
10
10
|
class RuleNode < NonTerminalNode
|
11
11
|
|
12
|
+
def initialize(parent, delimiter_pattern = nil)
|
13
|
+
@num_match_attempts = 0
|
14
|
+
@delimiter_pattern = delimiter_pattern
|
15
|
+
super parent
|
16
|
+
end
|
17
|
+
|
12
18
|
def match_names
|
13
19
|
@match_names ||= []
|
14
20
|
end
|
15
21
|
|
16
|
-
def
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
name=mn[i]
|
23
|
-
next unless name
|
24
|
-
if current=mbn[name] # name already used
|
25
|
-
# convert to MultiMatchesArray if not already
|
26
|
-
mbn[name]=MultiMatchesArray.new([current]) if !current.kind_of? MultiMatchesArray
|
27
|
-
# add to array
|
28
|
-
mbn[name]<<match
|
29
|
-
else
|
30
|
-
mbn[name]=match
|
31
|
-
end
|
32
|
-
end
|
33
|
-
mbn
|
22
|
+
def match_name_is_poly(name)
|
23
|
+
return unless name
|
24
|
+
if current = matches_by_name[name]
|
25
|
+
matches_by_name[name] = MultiMatchesArray.new([current]) if !current.kind_of? MultiMatchesArray
|
26
|
+
else
|
27
|
+
matches_by_name[name] = MultiMatchesArray.new
|
34
28
|
end
|
35
29
|
end
|
36
30
|
|
31
|
+
def add_match_name(match,name)
|
32
|
+
return unless name
|
33
|
+
if current = matches_by_name[name]
|
34
|
+
matches_by_name[name] = MultiMatchesArray.new([current]) if !current.kind_of? MultiMatchesArray
|
35
|
+
matches_by_name[name] << match
|
36
|
+
else
|
37
|
+
matches_by_name[name] = match
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def matches_by_name
|
42
|
+
@matches_by_name||={}
|
43
|
+
end
|
44
|
+
|
37
45
|
def inspect(options={})
|
38
|
-
return
|
39
|
-
|
46
|
+
return relative_class_name if matches.length==0
|
47
|
+
matches = @matches
|
48
|
+
matches=matches.select{|m|!m.many_delimiter} unless options[:verbose]
|
49
|
+
matches_inspected = matches.collect{|a|a.inspect(options)}.compact
|
40
50
|
if matches_inspected.length==0 then nil
|
41
51
|
elsif matches_inspected.length==1
|
42
|
-
m=matches_inspected[0]
|
43
|
-
ret="#{
|
52
|
+
m = matches_inspected[0]
|
53
|
+
ret = "#{relative_class_name} > "+matches_inspected[0]
|
44
54
|
if options[:simple]
|
45
|
-
ret=if m["\n"] then m
|
55
|
+
ret = if m["\n"] then m
|
46
56
|
else
|
47
57
|
# just show the first and last nodes in the chain
|
48
58
|
ret.gsub(/( > [A-Z][a-zA-Z0-9:]+ > (\.\.\. > )?)/," > ... > ")
|
@@ -50,18 +60,17 @@ class RuleNode < NonTerminalNode
|
|
50
60
|
end
|
51
61
|
ret
|
52
62
|
else
|
53
|
-
(["#{
|
63
|
+
(["#{relative_class_name}"]+matches_inspected).join("\n").gsub("\n","\n ")
|
54
64
|
end
|
55
65
|
end
|
56
66
|
|
57
67
|
#********************
|
58
68
|
# alter methods
|
59
69
|
#********************
|
60
|
-
def reset_matches_by_name
|
61
|
-
@matches_by_name=nil
|
62
|
-
end
|
63
70
|
|
64
|
-
#
|
71
|
+
# returns where to forward missing methods calls to (safe to override for custom behavior; respond_to? and method_missing will "do the right thing")
|
72
|
+
# returns nil if there is no object to forward to that will respond to the call
|
73
|
+
# default: forward to the first match that responds to method_name
|
65
74
|
def forward_to(method_name)
|
66
75
|
matches.each {|m| return m if m.respond_to?(method_name)}
|
67
76
|
nil
|
@@ -74,29 +83,73 @@ class RuleNode < NonTerminalNode
|
|
74
83
|
end
|
75
84
|
|
76
85
|
def method_missing(method_name, *args) #method_name is a symbol
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
match_path = [self]
|
82
|
-
while match_path[-1].matches.length==1
|
83
|
-
match_path<<match_path[-1].matches[0]
|
84
|
-
end
|
85
|
-
raise "#{match_path.collect{|m|m.class}.join(' > ')}: no methods or named pattern elements match: #{method_name.inspect}"
|
86
|
-
end
|
87
|
-
case ret=matches_by_name[method_name]
|
88
|
-
when EmptyNode then nil
|
89
|
-
else ret
|
86
|
+
return matches_by_name[method_name] if matches_by_name.has_key?(method_name)
|
87
|
+
|
88
|
+
if f = forward_to(method_name)
|
89
|
+
return f.send(method_name,*args)
|
90
90
|
end
|
91
|
+
|
92
|
+
raise "#{path_string onlychildren_list}: no methods or named pattern elements match: #{method_name.inspect} on #{self.class} instance"
|
91
93
|
end
|
92
94
|
|
93
95
|
# adds a match with name (optional)
|
94
96
|
def add_match(match,name=nil)
|
95
|
-
|
96
|
-
|
97
|
-
|
97
|
+
return unless match
|
98
|
+
return match if match==self
|
99
|
+
|
100
|
+
add_match_name(super(match),name)
|
98
101
|
|
99
102
|
update_match_length
|
103
|
+
match
|
104
|
+
end
|
105
|
+
|
106
|
+
def pop_match
|
107
|
+
matches.pop.tap {update_match_length}
|
108
|
+
end
|
109
|
+
|
110
|
+
# Attempts to match the pattern_element starting at the end of what has already been matched
|
111
|
+
# If successful, adds the resulting Node to matches.
|
112
|
+
# returns nil on if pattern_element wasn't matched; non-nil if it was skipped or matched
|
113
|
+
def match(pattern_element)
|
114
|
+
@num_match_attempts += 1
|
115
|
+
return :no_pattern_element unless pattern_element
|
116
|
+
return :skipped if pattern_element.delimiter &&
|
117
|
+
(
|
118
|
+
if last_match
|
119
|
+
last_match.delimiter # don't match two delimiters in a row
|
120
|
+
else
|
121
|
+
@num_match_attempts > 1 # don't match a delimiter as the first element unless this is the first match attempt
|
122
|
+
end
|
123
|
+
)
|
124
|
+
|
125
|
+
if result = pattern_element.parse(self)
|
126
|
+
add_match result, pattern_element.name # success, but don't keep EmptyNodes
|
127
|
+
end
|
100
128
|
end
|
129
|
+
|
130
|
+
def match_delimiter
|
131
|
+
match @delimiter_pattern
|
132
|
+
end
|
133
|
+
|
134
|
+
# called after matching is done and it was a success
|
135
|
+
# returns the node which is actually added to the parse tree
|
136
|
+
def post_match_processing
|
137
|
+
on_matched
|
138
|
+
self
|
139
|
+
end
|
140
|
+
|
141
|
+
# a simple "transaction" - logs the curent number of matches,
|
142
|
+
# if the block's result is false, it discards all new matches
|
143
|
+
def attempt_match
|
144
|
+
matches_before = matches.length
|
145
|
+
match_length_before = match_length
|
146
|
+
(yield && match_length > match_length_before).tap do |success| # match_length test returns failure if no progress is made (our source position isn't advanced)
|
147
|
+
unless success
|
148
|
+
@matches = matches[0..matches_before-1]
|
149
|
+
update_match_length
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
101
154
|
end
|
102
155
|
end
|
data/lib/nodes/terminal_node.rb
CHANGED
@@ -8,7 +8,7 @@ module BabelBridge
|
|
8
8
|
# used for String and Regexp PatternElements
|
9
9
|
# not subclassed
|
10
10
|
class TerminalNode < Node
|
11
|
-
attr_accessor :pattern
|
11
|
+
attr_accessor :pattern
|
12
12
|
def initialize(parent,range,pattern)
|
13
13
|
node_init(parent)
|
14
14
|
self.offset = range.min
|
@@ -17,10 +17,10 @@ class TerminalNode < Node
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def inspect(options={})
|
20
|
-
"#{text.inspect}" unless options[:
|
20
|
+
"#{text.inspect}" unless !options[:verbose] && text[/^\s*$/] # if only show whitespace matches if verbose
|
21
21
|
end
|
22
22
|
|
23
|
-
def matches; [
|
23
|
+
def matches; []; end
|
24
24
|
|
25
25
|
end
|
26
26
|
end
|
data/lib/parser.rb
CHANGED
@@ -8,7 +8,7 @@ class Parser
|
|
8
8
|
# These methods are used in the creation of a Parser Sub-Class to define
|
9
9
|
# its grammar
|
10
10
|
class <<self
|
11
|
-
attr_accessor :rules, :module_name, :root_rule, :
|
11
|
+
attr_accessor :rules, :module_name, :root_rule, :delimiter_pattern
|
12
12
|
|
13
13
|
def rules
|
14
14
|
@rules||={}
|
@@ -37,19 +37,20 @@ class Parser
|
|
37
37
|
#
|
38
38
|
# The block is executed in the context of the rule-varient's node type, a subclass of: RuleNode
|
39
39
|
# This allows you to add whatever functionality you want to a your nodes in the final parse tree.
|
40
|
-
# Also note you can override the
|
40
|
+
# Also note you can override the on_post_match method. This allows you to restructure the parse tree as it is parsed.
|
41
41
|
def rule(name,*pattern,&block)
|
42
42
|
rule = self.rules[name] ||= Rule.new(name,self)
|
43
|
-
|
44
|
-
|
43
|
+
@root_rule ||= name
|
44
|
+
options = pattern[-1].kind_of?(Hash) ? pattern.pop : {}
|
45
|
+
rule.add_variant options.merge(:pattern => pattern), &block
|
45
46
|
end
|
46
47
|
|
47
48
|
# options
|
48
49
|
# => right_operators: list of all operators that should be evaluated right to left instead of left-to-write
|
49
50
|
# typical example is the "**" exponentiation operator which should be evaluated right-to-left.
|
50
|
-
def binary_operators_rule(name,
|
51
|
+
def binary_operators_rule(name,operand_rule_name,operators,options={},&block)
|
51
52
|
right_operators = options[:right_operators]
|
52
|
-
rule
|
53
|
+
rule name, many(operand_rule_name,Tools::array_to_or_regexp(operators)).delimiter_name(:operators).as(:operands) do
|
53
54
|
self.class_eval &block if block
|
54
55
|
class <<self
|
55
56
|
attr_accessor :operators_from_rule, :right_operators
|
@@ -64,16 +65,17 @@ class Parser
|
|
64
65
|
@operator||=operator_node.to_s.to_sym
|
65
66
|
end
|
66
67
|
|
67
|
-
|
68
|
+
def operator_processor
|
69
|
+
self.class.operator_processor
|
70
|
+
end
|
71
|
+
|
72
|
+
# Override the on_post_match method to take the results of the "many" match
|
68
73
|
# and restructure it into a binary tree of nodes based on the precidence of
|
69
74
|
# the "operators".
|
70
|
-
# TODO -
|
71
|
-
def
|
72
|
-
|
73
|
-
operands
|
74
|
-
operators = many_match.delimiter_matches
|
75
|
-
# TODO - now! take many_match.matches and many_match.delimiter_matches, mishy-mashy, and make the super-tree!
|
76
|
-
self.class.operator_processor.generate_tree operands, operators, parent
|
75
|
+
# TODO - Should on_post_match be run after the whole tree matches? If not, will this screw up caching?
|
76
|
+
def post_match_processing
|
77
|
+
super
|
78
|
+
operator_processor.generate_tree operands, operators, parent
|
77
79
|
end
|
78
80
|
end
|
79
81
|
end
|
@@ -95,13 +97,25 @@ class Parser
|
|
95
97
|
@root_rule=rule
|
96
98
|
end
|
97
99
|
|
98
|
-
def ignore_whitespace
|
99
|
-
|
100
|
+
def ignore_whitespace
|
101
|
+
delimiter /\s*/
|
102
|
+
end
|
103
|
+
|
104
|
+
def delimiter(*pattern)
|
105
|
+
@delimiter = pattern
|
106
|
+
end
|
107
|
+
|
108
|
+
def delimiter_pattern
|
109
|
+
@delimiter_pattern ||= @delimiter && PatternElement.new(@delimiter, :parser_class => self, :delimiter => true)
|
100
110
|
end
|
101
111
|
end
|
102
112
|
|
103
|
-
def
|
104
|
-
self.class.
|
113
|
+
def delimiter_pattern
|
114
|
+
self.class.delimiter_pattern
|
115
|
+
end
|
116
|
+
|
117
|
+
def rules
|
118
|
+
self.class.rules
|
105
119
|
end
|
106
120
|
|
107
121
|
#*********************************************
|
@@ -137,11 +151,10 @@ class Parser
|
|
137
151
|
def match(*args) PatternElementHash.new.match(*args) end
|
138
152
|
def match!(*args) PatternElementHash.new.dont.match(*args) end
|
139
153
|
|
140
|
-
def rewind_whitespace; PatternElementHash.new.rewind_whitespace end
|
141
|
-
|
142
154
|
def dont; PatternElementHash.new.dont end
|
143
155
|
def optionally; PatternElementHash.new.optionally end
|
144
156
|
def could; PatternElementHash.new.could end
|
157
|
+
def custom_parser(&block); PatternElementHash.new.parser(lambda &block) end
|
145
158
|
end
|
146
159
|
|
147
160
|
|
@@ -162,24 +175,12 @@ class Parser
|
|
162
175
|
end
|
163
176
|
|
164
177
|
def reset_parser_tracking
|
178
|
+
@matching_negative_depth = 0
|
165
179
|
@parsing_did_not_match_entire_input = false
|
166
180
|
@src = nil
|
167
181
|
@failure_index = 0
|
168
182
|
@expecting_list = {}
|
169
183
|
@parse_cache = {}
|
170
|
-
@white_space_ranges = {}
|
171
|
-
end
|
172
|
-
|
173
|
-
# memoizing whitespace parser
|
174
|
-
def white_space_range(start)
|
175
|
-
@white_space_ranges[start]||=begin
|
176
|
-
# src should always be a string - unless this is called AFTER parsing is done. Currently this can happen with the way ManyNode handles .match_length and .next
|
177
|
-
# We should be able to just use:
|
178
|
-
# src[start..-1].index whitespace_regexp
|
179
|
-
((src||"")[start..-1]||"").index whitespace_regexp
|
180
|
-
r = $~.offset 0
|
181
|
-
start+r[0] .. start+r[1]-1
|
182
|
-
end
|
183
184
|
end
|
184
185
|
|
185
186
|
def cached(rule_class,offset)
|
@@ -195,7 +196,9 @@ class Parser
|
|
195
196
|
end
|
196
197
|
|
197
198
|
def log_parsing_failure(index,expecting)
|
198
|
-
if
|
199
|
+
if matching_negative?
|
200
|
+
# ignored
|
201
|
+
elsif index>failure_index
|
199
202
|
@expecting_list = {expecting[:pattern] => expecting}
|
200
203
|
@failure_index = index
|
201
204
|
elsif index == failure_index
|
@@ -205,25 +208,45 @@ class Parser
|
|
205
208
|
end
|
206
209
|
end
|
207
210
|
|
208
|
-
def
|
211
|
+
def matching_negative
|
212
|
+
@matching_negative_depth||=0
|
213
|
+
@matching_negative_depth+=1
|
214
|
+
end
|
215
|
+
|
216
|
+
def unmatching_negative
|
217
|
+
@matching_negative_depth-=1
|
218
|
+
end
|
219
|
+
|
220
|
+
def matching_negative?
|
221
|
+
(@matching_negative_depth||0) > 0
|
222
|
+
end
|
223
|
+
|
224
|
+
# parse a string, return the root node of the parse tree.
|
225
|
+
# If nil is returned, parsing failed. Call .parser_failure_info after failure for a human-readable description of the failure.
|
226
|
+
# src: the string to parse
|
227
|
+
# options:
|
228
|
+
# offset: where to start in the string for parsing
|
229
|
+
# rule: lets you specify the root rule for matching
|
230
|
+
# partial_match: allow partial matching
|
231
|
+
def parse(src, options={})
|
232
|
+
offset = options[:offset] || 0
|
233
|
+
rule = options[:rule] || self.class.root_rule
|
209
234
|
reset_parser_tracking
|
210
|
-
@start_time=Time.now
|
211
|
-
self.src=src
|
212
|
-
root_node=RootNode.new(self)
|
213
|
-
raise "No root rule defined." unless rule
|
214
|
-
ret=
|
215
|
-
|
216
|
-
if ret
|
217
|
-
if ret.next
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
@failed_parse = ret
|
222
|
-
end
|
223
|
-
ret=nil
|
224
|
-
else
|
225
|
-
reset_parser_tracking
|
235
|
+
@start_time = Time.now
|
236
|
+
self.src = src
|
237
|
+
root_node = RootNode.new(self)
|
238
|
+
raise "No root rule defined." unless rule
|
239
|
+
ret = rules[rule].parse(root_node)
|
240
|
+
if ret
|
241
|
+
if ret.next<src.length && !options[:partial_match] # parse only succeeds if the whole input is matched
|
242
|
+
if ret.next >= @failure_index
|
243
|
+
@parsing_did_not_match_entire_input=true
|
244
|
+
@failure_index = ret.next
|
245
|
+
@failed_parse = ret
|
226
246
|
end
|
247
|
+
ret=nil
|
248
|
+
else
|
249
|
+
reset_parser_tracking
|
227
250
|
end
|
228
251
|
end
|
229
252
|
@end_time=Time.now
|
@@ -242,8 +265,18 @@ class Parser
|
|
242
265
|
ret
|
243
266
|
end
|
244
267
|
|
245
|
-
|
246
|
-
|
268
|
+
# options[:verbose] => false
|
269
|
+
def node_list_string(node_list,common_root=[],options={})
|
270
|
+
return unless node_list
|
271
|
+
if options[:verbose]
|
272
|
+
node_list[common_root.length..-1]
|
273
|
+
else
|
274
|
+
[node_list[-1]]
|
275
|
+
end.select do |p|
|
276
|
+
p.class.to_s.index(BabelBridge.to_s)!=0
|
277
|
+
end.map do |p|
|
278
|
+
"#{p.relative_class_name}"
|
279
|
+
end.join(" > ")
|
247
280
|
end
|
248
281
|
|
249
282
|
def nodes_interesting_parse_path(node)
|
@@ -254,7 +287,8 @@ class Parser
|
|
254
287
|
end
|
255
288
|
|
256
289
|
|
257
|
-
|
290
|
+
# options[:verbose] => false
|
291
|
+
def expecting_output(options={})
|
258
292
|
return "" if expecting_list.length==0
|
259
293
|
common_root=nil
|
260
294
|
expecting_list.values.each do |e|
|
@@ -275,13 +309,13 @@ class Parser
|
|
275
309
|
<<ENDTXT
|
276
310
|
|
277
311
|
Parse path at failure:
|
278
|
-
#{node_list_string(common_root)}
|
312
|
+
#{node_list_string(common_root,[],:verbose=>true)}
|
279
313
|
|
280
314
|
Expecting#{expecting_list.length>1 ? ' one of' : ''}:
|
281
|
-
|
282
|
-
list=node_list_string(nodes_interesting_parse_path(a[:node]),common_root)
|
283
|
-
|
284
|
-
end.sort.
|
315
|
+
#{Tools.uniform_tabs(Tools.indent(expecting_list.values.collect do |a|
|
316
|
+
list=node_list_string(nodes_interesting_parse_path(a[:node]),common_root,options)
|
317
|
+
"#{a[:pattern].inspect}\t#{list}"
|
318
|
+
end.sort.join("\n")," "))}
|
285
319
|
ENDTXT
|
286
320
|
end
|
287
321
|
|
@@ -290,7 +324,7 @@ ENDTXT
|
|
290
324
|
return unless src
|
291
325
|
verbose = options[:verbose]
|
292
326
|
bracketing_lines=5
|
293
|
-
line,col=
|
327
|
+
line,col = Tools.line_column(src, failure_index)
|
294
328
|
ret=<<-ENDTXT
|
295
329
|
Parsing error at line #{line} column #{col} offset #{failure_index}
|
296
330
|
|
@@ -303,7 +337,7 @@ ENDTXT
|
|
303
337
|
if @parsing_did_not_match_entire_input
|
304
338
|
ret+="\nParser did not match entire input.\n"
|
305
339
|
if verbose
|
306
|
-
ret+="\nParsed:\n#{Tools::indent failed_parse.inspect}\n"
|
340
|
+
ret+="\nParsed:\n#{Tools::indent failed_parse.inspect||"(nothing)"}\n"
|
307
341
|
end
|
308
342
|
end
|
309
343
|
|