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