babel_bridge 0.2.0 → 0.3.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.
@@ -0,0 +1,117 @@
1
+ =begin
2
+ Copyright 2011 Shane Brinkman-Davis
3
+ See README for licence information.
4
+ http://babel-bridge.rubyforge.org/
5
+ =end
6
+
7
+ module BabelBridge
8
+ # non-terminal node
9
+ # subclassed automatically by parser.rule for each unique non-terminal
10
+ class NonTerminalNode < Node
11
+ attr_accessor :matches,:match_names
12
+
13
+ def match_names
14
+ @match_names ||= []
15
+ end
16
+ def matches
17
+ @matches ||= []
18
+ end
19
+
20
+ # length returns the number of sub-nodes
21
+ def length
22
+ matches.length
23
+ end
24
+
25
+ def matches_by_name
26
+ @matches_by_name||= begin
27
+ raise "matches.length #{matches.length} != match_names.length #{match_names.length}" unless matches.length==match_names.length
28
+ mbn={}
29
+ mn=match_names
30
+ matches.each_with_index do |match,i|
31
+ name=mn[i]
32
+ next unless name
33
+ if current=mbn[name] # name already used
34
+ # convert to MultiMatchesArray if not already
35
+ mbn[name]=MultiMatchesArray.new([current]) if !current.kind_of? MultiMatchesArray
36
+ # add to array
37
+ mbn[name]<<match
38
+ else
39
+ mbn[name]=match
40
+ end
41
+ end
42
+ mbn
43
+ end
44
+ end
45
+
46
+ def inspect(options={})
47
+ return "#{self.class}" if matches.length==0
48
+ matches_inspected=matches.collect{|a|a.inspect(options)}.compact
49
+ if matches_inspected.length==0 then nil
50
+ elsif matches_inspected.length==1
51
+ m=matches_inspected[0]
52
+ ret="#{self.class} > "+matches_inspected[0]
53
+ if options[:simple]
54
+ ret=if m["\n"] then m
55
+ else
56
+ # just show the first and last nodes in the chain
57
+ ret.gsub(/( > [A-Z][a-zA-Z0-9:]+ > (\.\.\. > )?)/," > ... > ")
58
+ end
59
+ end
60
+ ret
61
+ else
62
+ (["#{self.class}"]+matches_inspected).join("\n").gsub("\n","\n ")
63
+ end
64
+ end
65
+
66
+ #********************
67
+ # alter methods
68
+ #********************
69
+ def reset_matches_by_name
70
+ @matches_by_name=nil
71
+ end
72
+
73
+ # defines where to forward missing methods to; override for custom behavior
74
+ def forward_to(method_name)
75
+ matches.each {|m| return m if m.respond_to?(method_name)}
76
+ nil
77
+ end
78
+
79
+ def respond_to?(method_name)
80
+ super ||
81
+ matches_by_name[method_name] ||
82
+ forward_to(method_name)
83
+ end
84
+
85
+ def method_missing(method_name, *args) #method_name is a symbol
86
+ unless matches_by_name.has_key? method_name
87
+ if f=forward_to(method_name)
88
+ return f.send(method_name,*args)
89
+ end
90
+ raise "#{self.class}: missing method #{method_name.inspect} / doesn't match named pattern element: #{matches_by_name.keys.inspect}"
91
+ end
92
+ case ret=matches_by_name[method_name]
93
+ when EmptyNode then nil
94
+ else ret
95
+ end
96
+ end
97
+
98
+ # adds a match with name (optional)
99
+ # returns self so you can chain add_match or concat methods
100
+ def add_match(match,name=nil)
101
+ reset_matches_by_name
102
+ matches<<match
103
+ match_names<<name
104
+
105
+ self.match_length=match.next - offset
106
+ self
107
+ end
108
+
109
+ # concatinate all matches from another node
110
+ # returns self so you can chain add_match or concat methods
111
+ def concat(node)
112
+ names=node.match_names
113
+ node.matches.each_with_index { |match,i| add_match(match,names[i])}
114
+ self
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,38 @@
1
+ =begin
2
+ Copyright 2011 Shane Brinkman-Davis
3
+ See README for licence information.
4
+ http://babel-bridge.rubyforge.org/
5
+ =end
6
+
7
+ module BabelBridge
8
+ # used for String and Regexp PatternElements
9
+ # not subclassed
10
+ class TerminalNode < Node
11
+ attr_accessor :pattern
12
+ def initialize(parent,match_length,pattern)
13
+ node_init(parent)
14
+ self.match_length=match_length
15
+ self.pattern=pattern
16
+ @ignore_whitespace = parser.ignore_whitespace?
17
+ consume_trailing_whitespace if @ignore_whitespace
18
+ end
19
+
20
+ def consume_trailing_whitespace
21
+ offset = self.next
22
+ if src[offset..-1].index(/\A\s*/)==0
23
+ range = $~.offset(0)
24
+ self.match_length += range[1]-range[0]
25
+ end
26
+ end
27
+
28
+ def to_s
29
+ @ignore_whitespace ? text.strip : text
30
+ end
31
+
32
+ def inspect(options={})
33
+ "#{text.inspect}" unless options[:simple] && text[/^\s*$/] # if simple && node only matched white-space, return nil
34
+ end
35
+
36
+ def matches; [self]; end
37
+ end
38
+ end
@@ -0,0 +1,285 @@
1
+ module BabelBridge
2
+ # primary object used by the client
3
+ # Used to generate the grammer with .rule methods
4
+ # Used to parse with .parse
5
+ class Parser
6
+
7
+ # Parser sub-class grammaer definition
8
+ # These methods are used in the creation of a Parser Sub-Class to define
9
+ # its grammar
10
+ class <<self
11
+ attr_accessor :rules,:module_name,:root_rule
12
+
13
+ def rules
14
+ @rules||={}
15
+ end
16
+
17
+ # Add a rule to the parser
18
+ #
19
+ # rules can be specified as:
20
+ # rule :name, to_match1, to_match2, etc...
21
+ #or
22
+ # rule :name, [to_match1, to_match2, etc...]
23
+ #
24
+ # Can define rules INSIDE class:
25
+ # class MyParser < BabelBridge::Parser
26
+ # rule :name, to_match1, to_match2, etc...
27
+ # end
28
+ #
29
+ # Or can define rules OUTSIDE class:
30
+ # class MyParser < BabelBridge::Parser
31
+ # end
32
+ # MyParser.rule :name, to_match1, to_match2, etc...
33
+ #
34
+ # The first rule added is the root-rule for the parser.
35
+ # You can override by:
36
+ # class MyParser < BabelBridge::Parser
37
+ # root_rule = :new_root_rool
38
+ # end
39
+ #
40
+ # The block is executed in the context of the rule-varient's node type, a subclass of: NonTerminalNode
41
+ # This allows you to add whatever functionality you want to a your nodes in the final parse tree.
42
+ # Also note you can override the post_match method. This allows you to restructure the parse tree as it is parsed.
43
+ def rule(name,*pattern,&block)
44
+ pattern=pattern[0] if pattern[0].kind_of?(Array)
45
+ rule=self.rules[name]||=Rule.new(name,self)
46
+ self.root_rule||=name
47
+ rule.add_variant(pattern,&block)
48
+ end
49
+
50
+ # options
51
+ # => right_operators: list of all operators that should be evaluated right to left instead of left-to-write
52
+ # typical example is the "**" exponentiation operator which should be evaluated right-to-left.
53
+ def binary_operators_rule(name,elements_pattern,operators,options={},&block)
54
+ right_operators = options[:right_operators]
55
+ rule(name,many(elements_pattern,Tools::array_to_or_regexp(operators))) do
56
+ self.class_eval &block if block
57
+ class <<self
58
+ attr_accessor :operators_from_rule, :right_operators
59
+ def operator_processor
60
+ @operator_processor||=BinaryOperatorProcessor.new(operators_from_rule,self,right_operators)
61
+ end
62
+ end
63
+ self.right_operators = right_operators
64
+ self.operators_from_rule = operators
65
+
66
+ def operator
67
+ @operator||=operator_node.to_s.to_sym
68
+ end
69
+
70
+ # Override the post_match method to take the results of the "many" match
71
+ # and restructure it into a binary tree of nodes based on the precidence of
72
+ # the "operators".
73
+ # TODO - I think maybe post_match should be run after the whole tree matches. If not, will this screw up caching?
74
+ def post_match
75
+ many_match = matches[0]
76
+ operands = many_match.matches
77
+ operators = many_match.delimiter_matches
78
+ # TODO - now! take many_match.matches and many_match.delimiter_matches, mishy-mashy, and make the super-tree!
79
+ self.class.operator_processor.generate_tree operands, operators, parent
80
+ end
81
+ end
82
+ end
83
+
84
+ def node_class(name,&block)
85
+ klass=self.rules[name].node_class
86
+ return klass unless block
87
+ klass.class_eval &block
88
+ end
89
+
90
+ def [](i)
91
+ rules[i]
92
+ end
93
+
94
+ # rule can be symbol-name of one of the rules in rules_array or one of the actual Rule objects in that array
95
+ def root_rule=(rule)
96
+ raise "Symbol required" unless rule.kind_of?(Symbol)
97
+ raise "rule #{rule.inspect} not found" unless rules[rule]
98
+ @root_rule=rule
99
+ end
100
+
101
+ def ignore_whitespace
102
+ @ignore_whitespace = true
103
+ end
104
+
105
+ def ignore_whitespace?
106
+ @ignore_whitespace
107
+ end
108
+ end
109
+
110
+ def ignore_whitespace?
111
+ self.class.ignore_whitespace?
112
+ end
113
+
114
+ #*********************************************
115
+ # pattern construction tools
116
+ #
117
+ # Ex:
118
+ # # match 'keyword'
119
+ # # (succeeds if keyword is matched; advances the read pointer)
120
+ # rule :sample_rule, "keyword"
121
+ # rule :sample_rule, match("keyword")
122
+ #
123
+ # # don't match 'keyword'
124
+ # # (succeeds only if keyword is NOT matched; does not advance the read pointer)
125
+ # rule :sample_rule, match!("keyword")
126
+ # rule :sample_rule, dont.match("keyword")
127
+ #
128
+ # # optionally match 'keyword'
129
+ # # (always succeeds; advances the read pointer if keyword is matched)
130
+ # rule :sample_rule, match?("keyword")
131
+ # rule :sample_rule, optionally.match("keyword")
132
+ #
133
+ # # ensure we could match 'keyword'
134
+ # # (succeeds only if keyword is matched, but does not advance the read pointer)
135
+ # rule :sample_rule, could.match("keyword")
136
+ #
137
+ #*********************************************
138
+ class <<self
139
+ def many(m,delimiter=nil,post_delimiter=nil) PatternElementHash.new.match.many(m).delimiter(delimiter).post_delimiter(post_delimiter) end
140
+ def many?(m,delimiter=nil,post_delimiter=nil) PatternElementHash.new.optionally.match.many(m).delimiter(delimiter).post_delimiter(post_delimiter) end
141
+ def many!(m,delimiter=nil,post_delimiter=nil) PatternElementHash.new.dont.match.many(m).delimiter(delimiter).post_delimiter(post_delimiter) end
142
+
143
+ def match?(*args) PatternElementHash.new.optionally.match(*args) end
144
+ def match(*args) PatternElementHash.new.match(*args) end
145
+ def match!(*args) PatternElementHash.new.dont.match(*args) end
146
+
147
+ def dont; PatternElementHash.new.dont end
148
+ def optionally; PatternElementHash.new.optionally end
149
+ def could; PatternElementHash.new.could end
150
+ end
151
+
152
+
153
+ #*********************************************
154
+ #*********************************************
155
+ # parser instance implementation
156
+ # this methods are used for each actual parse run
157
+ # they are tied to an instnace of the Parser Sub-class to you can have more than one
158
+ # parser active at a time
159
+ attr_accessor :failure_index
160
+ attr_accessor :expecting_list
161
+ attr_accessor :src
162
+ attr_accessor :parse_cache
163
+
164
+ def initialize
165
+ reset_parser_tracking
166
+ end
167
+
168
+ def reset_parser_tracking
169
+ self.src=nil
170
+ self.failure_index=0
171
+ self.expecting_list={}
172
+ self.parse_cache={}
173
+ end
174
+
175
+ def cached(rule_class,offset)
176
+ (parse_cache[rule_class]||={})[offset]
177
+ end
178
+
179
+ def cache_match(rule_class,match)
180
+ (parse_cache[rule_class]||={})[match.offset]=match
181
+ end
182
+
183
+ def cache_no_match(rule_class,offset)
184
+ (parse_cache[rule_class]||={})[offset]=:no_match
185
+ end
186
+
187
+ def log_parsing_failure(index,expecting)
188
+ if index>failure_index
189
+ key=expecting[:pattern]
190
+ @expecting_list={key=>expecting}
191
+ @failure_index = index
192
+ elsif index == failure_index
193
+ key=expecting[:pattern]
194
+ self.expecting_list[key]=expecting
195
+ else
196
+ # ignored
197
+ end
198
+ end
199
+
200
+ def parse(src,offset=0,rule=nil)
201
+ reset_parser_tracking
202
+ @start_time=Time.now
203
+ self.src=src
204
+ root_node=RootNode.new(self)
205
+ raise "No root rule defined." unless rule || self.class.root_rule
206
+ ret=self.class[rule||self.class.root_rule].parse(root_node)
207
+ unless rule
208
+ if ret
209
+ if ret.next<src.length # parse only succeeds if the whole input is matched
210
+ @parsing_did_not_match_entire_input=true
211
+ @failure_index=ret.next
212
+ ret=nil
213
+ else
214
+ reset_parser_tracking
215
+ end
216
+ end
217
+ end
218
+ @end_time=Time.now
219
+ ret
220
+ end
221
+
222
+ def parse_time
223
+ @end_time-@start_time
224
+ end
225
+
226
+ def parse_and_puts_errors(src,out=$stdout)
227
+ ret=parse(src)
228
+ unless ret
229
+ out.puts parser_failure_info
230
+ end
231
+ ret
232
+ end
233
+
234
+ def node_list_string(node_list,common_root=[])
235
+ node_list && node_list[common_root.length..-1].map{|p|"#{p.class}(#{p.offset})"}.join(" > ")
236
+ end
237
+
238
+ def parser_failure_info
239
+ return unless src
240
+ bracketing_lines=5
241
+ line,col=src.line_col(failure_index)
242
+ ret=<<-ENDTXT
243
+ Parsing error at line #{line} column #{col} offset #{failure_index}
244
+
245
+ Source:
246
+ ...
247
+ #{(failure_index==0 ? "" : src[0..(failure_index-1)]).last_lines(bracketing_lines)}<HERE>#{src[(failure_index)..-1].first_lines(bracketing_lines)}
248
+ ...
249
+ ENDTXT
250
+
251
+ if @parsing_did_not_match_entire_input
252
+ ret+="\nParser did not match entire input."
253
+ else
254
+
255
+ common_root=nil
256
+ expecting_list.values.each do |e|
257
+ node=e[:node]
258
+ pl=node.parent_list
259
+ if common_root
260
+ common_root.each_index do |i|
261
+ if pl[i]!=common_root[i]
262
+ common_root=common_root[0..i-1]
263
+ break
264
+ end
265
+ end
266
+ else
267
+ common_root=node.parent_list
268
+ end
269
+ end
270
+ ret+=<<ENDTXT
271
+
272
+ Successfully matched rules up to failure:
273
+ #{node_list_string(common_root)}
274
+
275
+ Expecting#{expecting_list.length>1 ? ' one of' : ''}:
276
+ #{expecting_list.values.collect do |a|
277
+ list=node_list_string(a[:node].parent_list,common_root)
278
+ [list,"#{a[:pattern].inspect} (#{list})"]
279
+ end.sort.map{|i|i[1]}.join("\n ")}
280
+ ENDTXT
281
+ end
282
+ ret
283
+ end
284
+ end
285
+ end
@@ -5,189 +5,190 @@ http://babel-bridge.rubyforge.org/
5
5
  =end
6
6
 
7
7
  module BabelBridge
8
- # hash which can be used declaratively
9
- class PatternElementHash < Hash
10
- def method_missing(method_name, *args) #method_name is a symbol
11
- return self if args.length==1 && !args[0] # if nil is provided, don't set anything
12
- self[method_name]=args[0] || true # on the other hand, if no args are provided, assume true
13
- self
14
- end
8
+ # hash which can be used declaratively
9
+ class PatternElementHash < Hash
10
+ def method_missing(method_name, *args) #method_name is a symbol
11
+ return self if args.length==1 && !args[0] # if nil is provided, don't set anything
12
+ raise "More than one argument is not supported. #{self.class}##{method_name} args=#{args.inspect}" if args.length > 1
13
+ self[method_name]=args[0] || true # on the other hand, if no args are provided, assume true
14
+ self
15
+ end
16
+ end
17
+
18
+ # PatternElement provides optimized parsing for each Element of a pattern
19
+ # PatternElement provides all the logic for parsing:
20
+ # :many
21
+ # :optional
22
+ class PatternElement
23
+ attr_accessor :parser,:optional,:negative,:name,:terminal,:could_match
24
+ attr_accessor :match,:rule_variant
25
+
26
+ #match can be:
27
+ # true, Hash, Symbol, String, Regexp
28
+ def initialize(match,rule_variant)
29
+ self.rule_variant=rule_variant
30
+ init(match)
31
+
32
+ raise "pattern element cannot be both :dont and :optional" if negative && optional
15
33
  end
16
34
 
17
- # PatternElement provides optimized parsing for each Element of a pattern
18
- # PatternElement provides all the logic for parsing:
19
- # :many
20
- # :optional
21
- class PatternElement
22
- attr_accessor :parser,:optional,:negative,:name,:terminal,:could_match
23
- attr_accessor :match,:rule_variant
24
-
25
- #match can be:
26
- # true, Hash, Symbol, String, Regexp
27
- def initialize(match,rule_variant)
28
- self.rule_variant=rule_variant
29
- init(match)
30
-
31
- raise "pattern element cannot be both :dont and :optional" if negative && optional
32
- end
35
+ def to_s
36
+ match.inspect
37
+ end
33
38
 
34
- def to_s
35
- match.inspect
36
- end
39
+ # attempt to match the pattern defined in self.parser in parent_node.src starting at offset parent_node.next
40
+ def parse(parent_node)
41
+ # run element parser
42
+ match=parser.call(parent_node)
37
43
 
38
- # attempt to match the pattern defined in self.parser in parent_node.src starting at offset parent_node.next
39
- def parse(parent_node)
40
- # run element parser
41
- match=parser.call(parent_node)
44
+ # Negative patterns (PEG: !element)
45
+ match=match ? nil : EmptyNode.new(parent_node) if negative
42
46
 
43
- # Negative patterns (PEG: !element)
44
- match=match ? nil : EmptyNode.new(parent_node) if negative
47
+ # Optional patterns (PEG: element?)
48
+ match=EmptyNode.new(parent_node) if !match && optional
45
49
 
46
- # Optional patterns (PEG: element?)
47
- match=EmptyNode.new(parent_node) if !match && optional
50
+ # Could-match patterns (PEG: &element)
51
+ match.match_length=0 if match && could_match
48
52
 
49
- # Could-match patterns (PEG: &element)
50
- match.match_length=0 if match && could_match
53
+ # return match
54
+ match
55
+ end
51
56
 
52
- # return match
53
- match
57
+ private
58
+
59
+ # initialize PatternElement based on the type of: match
60
+ def init(match)
61
+ self.match=match
62
+ case match
63
+ when TrueClass then init_true
64
+ when String then init_string match
65
+ when Regexp then init_regex match
66
+ when Symbol then init_rule match
67
+ when Hash then init_hash match
68
+ else raise "invalid pattern type: #{match.inspect}"
54
69
  end
70
+ end
55
71
 
56
- private
57
-
58
- # initialize PatternElement based on the type of: match
59
- def init(match)
60
- self.match=match
61
- case match
62
- when TrueClass then init_true
63
- when String then init_string match
64
- when Regexp then init_regex match
65
- when Symbol then init_rule match
66
- when Hash then init_hash match
67
- else raise "invalid pattern type: #{match.inspect}"
68
- end
69
- end
72
+ # "true" parser always matches the empty string
73
+ def init_true
74
+ self.parser=lambda {|parent_node| EmptyNode.new(parent_node)}
75
+ end
70
76
 
71
- # "true" parser always matches the empty string
72
- def init_true
73
- self.parser=lambda {|parent_node| EmptyNode.new(parent_node)}
77
+ # initialize PatternElement as a parser that matches exactly the string specified
78
+ def init_string(string)
79
+ self.parser=lambda do |parent_node|
80
+ if parent_node.src[parent_node.next,string.length]==string
81
+ TerminalNode.new(parent_node,string.length,string)
82
+ end
74
83
  end
84
+ self.terminal=true
85
+ end
75
86
 
76
- # initialize PatternElement as a parser that matches exactly the string specified
77
- def init_string(string)
78
- self.parser=lambda do |parent_node|
79
- if parent_node.src[parent_node.next,string.length]==string
80
- TerminalNode.new(parent_node,string.length,string)
81
- end
87
+ # initialize PatternElement as a parser that matches the given regex
88
+ def init_regex(regex)
89
+ optimized_regex=/\A#{regex}/ # anchor the search
90
+ self.parser=lambda do |parent_node|
91
+ offset=parent_node.next
92
+ if parent_node.src[offset..-1].index(optimized_regex)==0
93
+ range=$~.offset(0)
94
+ TerminalNode.new(parent_node,range[1]-range[0],regex)
82
95
  end
83
- self.terminal=true
84
96
  end
97
+ self.terminal=true
98
+ end
85
99
 
86
- # initialize PatternElement as a parser that matches the given regex
87
- def init_regex(regex)
88
- optimized_regex=/\A#{regex}/ # anchor the search
89
- self.parser=lambda do |parent_node|
90
- offset=parent_node.next
91
- if parent_node.src[offset..-1].index(optimized_regex)==0
92
- range=$~.offset(0)
93
- TerminalNode.new(parent_node,range[1]-range[0],regex)
94
- end
95
- end
96
- self.terminal=true
100
+ # initialize PatternElement as a parser that matches a named sub-rule
101
+ def init_rule(rule_name)
102
+ rule_name.to_s[/^([^?!]*)([?!])?$/]
103
+ rule_name=$1.to_sym
104
+ option=$2
105
+ match_rule=rule_variant.rule.parser.rules[rule_name]
106
+ raise "no rule for #{rule_name}" unless match_rule
107
+
108
+ self.parser = lambda {|parent_node| match_rule.parse(parent_node)}
109
+ self.name = rule_name
110
+ case option
111
+ when "?" then self.optional=true
112
+ when "!" then self.negative=true
97
113
  end
114
+ end
98
115
 
99
- # initialize PatternElement as a parser that matches a named sub-rule
100
- def init_rule(rule_name)
101
- rule_name.to_s[/^([^?!]*)([?!])?$/]
102
- rule_name=$1.to_sym
103
- option=$2
104
- match_rule=rule_variant.rule.parser.rules[rule_name]
105
- raise "no rule for #{rule_name}" unless match_rule
106
-
107
- self.parser = lambda {|parent_node| match_rule.parse(parent_node)}
108
- self.name = rule_name
109
- case option
110
- when "?" then self.optional=true
111
- when "!" then self.negative=true
112
- end
116
+ # initialize the PatternElement from hashed parameters
117
+ def init_hash(hash)
118
+ if hash[:parser]
119
+ self.parser=hash[:parser]
120
+ elsif hash[:many]
121
+ init_many hash
122
+ elsif hash[:match]
123
+ init hash[:match]
124
+ else
125
+ raise "extended-options patterns (specified by a hash) must have either :parser=> or a :match=> set"
113
126
  end
114
127
 
115
- # initialize the PatternElement from hashed parameters
116
- def init_hash(hash)
117
- if hash[:parser]
118
- self.parser=hash[:parser]
119
- elsif hash[:many]
120
- init_many hash
121
- elsif hash[:match]
122
- init hash[:match]
123
- else
124
- raise "extended-options patterns (specified by a hash) must have either :parser=> or a :match=> set"
125
- end
128
+ self.name = hash[:as] || self.name
129
+ self.optional ||= hash[:optional] || hash[:optionally]
130
+ self.could_match ||= hash[:could]
131
+ self.negative ||= hash[:dont]
132
+ end
133
+
134
+ # initialize the PatternElement as a many-parser from hashed parameters (hash[:many] is assumed to be set)
135
+ def init_many(hash)
136
+ # generate single_parser
137
+ init hash[:many]
138
+ single_parser=parser
139
+
140
+ # generate delimiter_pattern_element
141
+ delimiter_pattern_element= hash[:delimiter] && PatternElement.new(hash[:delimiter],rule_variant)
126
142
 
127
- self.name = hash[:as] || self.name
128
- self.optional ||= hash[:optional] || hash[:optionally]
129
- self.could_match ||= hash[:could]
130
- self.negative ||= hash[:dont]
143
+ # generate post_delimiter_element
144
+ post_delimiter_element=hash[:post_delimiter] && case hash[:post_delimiter]
145
+ when TrueClass then delimiter_pattern_element
146
+ else PatternElement.new(hash[:post_delimiter],rule_variant)
131
147
  end
132
148
 
133
- # initialize the PatternElement as a many-parser from hashed parameters (hash[:many] is assumed to be set)
134
- def init_many(hash)
135
- # generate single_parser
136
- init hash[:many]
137
- single_parser=parser
149
+ # generate many-parser
150
+ self.parser= lambda do |parent_node|
151
+ last_match=single_parser.call(parent_node)
152
+ many_node=ManyNode.new(parent_node)
138
153
 
139
- # generate delimiter_pattern_element
140
- delimiter_pattern_element= hash[:delimiter] && PatternElement.new(hash[:delimiter],rule_variant)
154
+ if delimiter_pattern_element
155
+ # delimited matching
156
+ while last_match
157
+ many_node<<last_match
141
158
 
142
- # generate post_delimiter_element
143
- post_delimiter_element=hash[:post_delimiter] && case hash[:post_delimiter]
144
- when TrueClass then delimiter_pattern_element
145
- else PatternElement.new(hash[:post_delimiter],rule_variant)
146
- end
159
+ #match delimiter
160
+ delimiter_match = delimiter_pattern_element.parse(many_node)
161
+ break unless delimiter_match
162
+ many_node.delimiter_matches<<delimiter_match
147
163
 
148
- # generate many-parser
149
- self.parser= lambda do |parent_node|
150
- last_match=single_parser.call(parent_node)
151
- many_node=ManyNode.new(parent_node)
152
-
153
- if delimiter_pattern_element
154
- # delimited matching
155
- while last_match
156
- many_node<<last_match
157
-
158
- #match delimiter
159
- delimiter_match=delimiter_pattern_element.parse(many_node)
160
- break unless delimiter_match
161
- many_node.delimiter_matches<<delimiter_match
162
-
163
- #match next
164
- last_match=single_parser.call(many_node)
165
- end
166
- else
167
- # not delimited matching
168
- while last_match
169
- many_node<<last_match
170
- last_match=single_parser.call(many_node)
171
- end
164
+ #match next
165
+ last_match=single_parser.call(many_node)
172
166
  end
167
+ else
168
+ # not delimited matching
169
+ while last_match
170
+ many_node<<last_match
171
+ last_match=single_parser.call(many_node)
172
+ end
173
+ end
173
174
 
174
- # success only if we have at least one match
175
- return nil unless many_node.length>0
176
-
177
- # pop the post delimiter matched with delimiter_pattern_element
178
- many_node.delimiter_matches.pop if many_node.length==many_node.delimiter_matches.length
175
+ # success only if we have at least one match
176
+ return nil unless many_node.length>0
179
177
 
180
- # If post_delimiter is requested, many_node and delimiter_matches will be the same length
181
- if post_delimiter_element
182
- post_delimiter_match=post_delimiter_element.parse(many_node)
178
+ # pop the post delimiter matched with delimiter_pattern_element
179
+ many_node.delimiter_matches.pop if many_node.length==many_node.delimiter_matches.length
183
180
 
184
- # fail if post_delimiter didn't match
185
- return nil unless post_delimiter_match
186
- many_node.delimiter_matches<<post_delimiter_match
187
- end
181
+ # If post_delimiter is requested, many_node and delimiter_matches will be the same length
182
+ if post_delimiter_element
183
+ post_delimiter_match=post_delimiter_element.parse(many_node)
188
184
 
189
- many_node
185
+ # fail if post_delimiter didn't match
186
+ return nil unless post_delimiter_match
187
+ many_node.delimiter_matches<<post_delimiter_match
190
188
  end
189
+
190
+ many_node
191
191
  end
192
192
  end
193
+ end
193
194
  end