citrus 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,37 @@
1
+ require 'citrus'
2
+ require 'builder'
3
+
4
+ module Citrus
5
+ class Match
6
+ # Creates a Builder::XmlMarkup object from this match. Useful when
7
+ # inspecting a nested match. The +xml+ argument may be a Hash of
8
+ # Builder::XmlMarkup options.
9
+ def to_markup(xml={})
10
+ if xml.is_a?(Hash)
11
+ opt = { :indent => 2 }.merge(xml)
12
+ xml = Builder::XmlMarkup.new(opt)
13
+ xml.instruct!
14
+ end
15
+
16
+ if matches.empty?
17
+ xml.match("name" => name, "text" => text, "offset" => offset)
18
+ else
19
+ xml.match("name" => name, "text" => text, "offset" => offset) do
20
+ matches.each {|m| m.to_markup(xml) }
21
+ end
22
+ end
23
+
24
+ xml
25
+ end
26
+
27
+ # Returns the target of #to_markup which is an XML string unless another
28
+ # target is specified in +opt+.
29
+ def to_xml(opt={})
30
+ to_markup(opt).target!
31
+ end
32
+
33
+ def inspect # :nodoc:
34
+ to_xml
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,375 @@
1
+ require 'citrus'
2
+
3
+ module Citrus
4
+ # A grammar for Citrus-flavored parsing expression grammars. This module is
5
+ # used in Citrus#eval to parse and evaluate Citrus PEG's and serves as a prime
6
+ # example of how to create a complex grammar complete with semantic
7
+ # interpretation in pure Ruby.
8
+ module PEG
9
+ include Grammar
10
+
11
+ ## Hierarchical syntax
12
+
13
+ rule :file do
14
+ all(:space, zero_or_more(any(:require, :grammar))) {
15
+ def requires
16
+ find(:require)
17
+ end
18
+
19
+ def grammars
20
+ find(:grammar)
21
+ end
22
+
23
+ def value
24
+ requires.each {|r| require r.value }
25
+ grammars.map {|g| g.value }
26
+ end
27
+ }
28
+ end
29
+
30
+ rule :grammar do
31
+ all(:grammar_keyword, :module_name, :grammar_body, :end_keyword) {
32
+ def includes
33
+ find(:include)
34
+ end
35
+
36
+ def modules
37
+ includes.map {|inc| eval(inc.value, TOPLEVEL_BINDING) }
38
+ end
39
+
40
+ def root
41
+ find(:root).last
42
+ end
43
+
44
+ def rules
45
+ find(:rule)
46
+ end
47
+
48
+ def value
49
+ code = '%s = Citrus::Grammar.new' % module_name.value
50
+ grammar = eval(code, TOPLEVEL_BINDING)
51
+ modules.each {|mod| grammar.include(mod) }
52
+ grammar.root(root.value) if root
53
+ rules.each {|rule| grammar.rule(rule.rule_name.value, rule.value) }
54
+ grammar
55
+ end
56
+ }
57
+ end
58
+
59
+ rule :grammar_body do
60
+ zero_or_more(any(:include, :root, :rule))
61
+ end
62
+
63
+ rule :rule do
64
+ all(:rule_keyword, :rule_name, :rule_body, :end_keyword) {
65
+ def value
66
+ rule_body.value
67
+ end
68
+ }
69
+ end
70
+
71
+ rule :rule_body do
72
+ all(:sequence, :choice) {
73
+ def choices
74
+ @choices ||= [ sequence ] + choice.sequences
75
+ end
76
+
77
+ def values
78
+ choices.map {|s| s.value }
79
+ end
80
+
81
+ def value
82
+ choices.length > 1 ? Choice.new(values) : values[0]
83
+ end
84
+ }
85
+ end
86
+
87
+ rule :choice do
88
+ zero_or_more([ :bar, :sequence ]) {
89
+ def sequences
90
+ matches.map {|m| m.matches[1] }
91
+ end
92
+ }
93
+ end
94
+
95
+ rule :sequence do
96
+ zero_or_more(:prefix) {
97
+ def values
98
+ matches.map {|m| m.value }
99
+ end
100
+
101
+ def value
102
+ matches.length > 1 ? Sequence.new(values) : values[0]
103
+ end
104
+ }
105
+ end
106
+
107
+ rule :prefix do
108
+ all(zero_or_one(:qualifier), :appendix) {
109
+ def value
110
+ rule = appendix.value
111
+ qualifier = matches[0].first
112
+ rule = qualifier.wrap(rule) if qualifier
113
+ rule
114
+ end
115
+ }
116
+ end
117
+
118
+ rule :appendix do
119
+ all(:suffix, zero_or_one(:extension)) {
120
+ def value
121
+ rule = suffix.value
122
+ extension = matches[1].first
123
+ rule = extension.wrap(rule) if extension
124
+ rule
125
+ end
126
+ }
127
+ end
128
+
129
+ rule :suffix do
130
+ all(:primary, zero_or_one(:quantifier)) {
131
+ def value
132
+ rule = primary.value
133
+ quantifier = matches[1].first
134
+ rule = quantifier.wrap(rule) if quantifier
135
+ rule
136
+ end
137
+ }
138
+ end
139
+
140
+ rule :primary do
141
+ any(:super, :alias, :rule_body_paren, :terminal) {
142
+ def value
143
+ first.value
144
+ end
145
+ }
146
+ end
147
+
148
+ rule :rule_body_paren do
149
+ all(:lparen, :rule_body, :rparen) {
150
+ def value
151
+ rule_body.value
152
+ end
153
+ }
154
+ end
155
+
156
+ ## Lexical syntax
157
+
158
+ rule :require do
159
+ all(:require_keyword, :quoted_string) {
160
+ def value
161
+ quoted_string.value
162
+ end
163
+ }
164
+ end
165
+
166
+ rule :include do
167
+ all(:include_keyword, :module_name) {
168
+ def value
169
+ module_name.value
170
+ end
171
+ }
172
+ end
173
+
174
+ rule :root do
175
+ all(:root_keyword, :rule_name) {
176
+ def value
177
+ rule_name.value
178
+ end
179
+ }
180
+ end
181
+
182
+ rule :rule_name do
183
+ all(/[a-z][a-zA-Z0-9_]*/, :space) {
184
+ def value
185
+ first.text
186
+ end
187
+ }
188
+ end
189
+
190
+ rule :super do
191
+ all('super', :space) {
192
+ def value
193
+ Super.new
194
+ end
195
+ }
196
+ end
197
+
198
+ rule :alias do
199
+ all(notp(:end_keyword), :rule_name) {
200
+ def value
201
+ Alias.new(rule_name.value)
202
+ end
203
+ }
204
+ end
205
+
206
+ rule :terminal do
207
+ any(:quoted_string, :character_class, :anything_symbol, :regular_expression) {
208
+ def value
209
+ Rule.create(first.value)
210
+ end
211
+ }
212
+ end
213
+
214
+ rule :quoted_string do
215
+ all(/(["'])(?:\\?.)*?\1/, :space) {
216
+ def value
217
+ eval(first.text)
218
+ end
219
+ }
220
+ end
221
+
222
+ rule :character_class do
223
+ all(/\[(?:\\?.)*?\]/, :space) {
224
+ def value
225
+ Regexp.new(first.text)
226
+ end
227
+ }
228
+ end
229
+
230
+ rule :anything_symbol do
231
+ all('.', :space) {
232
+ def value
233
+ /./m # The dot matches newlines
234
+ end
235
+ }
236
+ end
237
+
238
+ rule :regular_expression do
239
+ all(/\/(?:\\?.)*?\/[imxouesn]*/, :space) {
240
+ def value
241
+ eval(first.text)
242
+ end
243
+ }
244
+ end
245
+
246
+ rule :qualifier do
247
+ any(:and, :not, :label) {
248
+ def wrap(rule)
249
+ first.wrap(rule)
250
+ end
251
+ }
252
+ end
253
+
254
+ rule :and do
255
+ all('&', :space) {
256
+ def wrap(rule)
257
+ AndPredicate.new(rule)
258
+ end
259
+ }
260
+ end
261
+
262
+ rule :not do
263
+ all('!', :space) {
264
+ def wrap(rule)
265
+ NotPredicate.new(rule)
266
+ end
267
+ }
268
+ end
269
+
270
+ rule :label do
271
+ all(/[a-zA-Z0-9_]+/, :space, ':', :space) {
272
+ def wrap(rule)
273
+ Label.new(value, rule)
274
+ end
275
+
276
+ def value
277
+ first.text
278
+ end
279
+ }
280
+ end
281
+
282
+ rule :extension do
283
+ any(:tag, :block) {
284
+ def wrap(rule)
285
+ rule.ext = first.value
286
+ rule
287
+ end
288
+ }
289
+ end
290
+
291
+ rule :tag do
292
+ all(:lt, :module_name, :gt) {
293
+ def value
294
+ eval(module_name.value, TOPLEVEL_BINDING)
295
+ end
296
+ }
297
+ end
298
+
299
+ rule :block do
300
+ all(:lcurly, zero_or_more(any(:block, /[^{}]+/)), :rcurly) {
301
+ def value
302
+ eval('Proc.new ' + text)
303
+ end
304
+ }
305
+ end
306
+
307
+ rule :quantifier do
308
+ any(:question, :plus, :repeat) {
309
+ def min; first.min end
310
+ def max; first.max end
311
+
312
+ def wrap(rule)
313
+ Repeat.new(min, max, rule)
314
+ end
315
+ }
316
+ end
317
+
318
+ rule :question do
319
+ all('?', :space) {
320
+ def min; 0 end
321
+ def max; 1 end
322
+ }
323
+ end
324
+
325
+ rule :plus do
326
+ all('+', :space) {
327
+ def min; 1 end
328
+ def max; Infinity end
329
+ }
330
+ end
331
+
332
+ rule :repeat do
333
+ all(/[0-9]*/, '*', /[0-9]*/, :space) {
334
+ def min
335
+ matches[0] == '' ? 0 : matches[0].text.to_i
336
+ end
337
+
338
+ def max
339
+ matches[2] == '' ? Infinity : matches[2].text.to_i
340
+ end
341
+ }
342
+ end
343
+
344
+ rule :module_name do
345
+ all(one_or_more([ zero_or_one('::'), :constant ]), :space) {
346
+ def value
347
+ first.text
348
+ end
349
+ }
350
+ end
351
+
352
+ rule :constant do
353
+ /[A-Z][a-zA-Z0-9_]*/
354
+ end
355
+
356
+ rule :require_keyword, [ 'require', :space ]
357
+ rule :include_keyword, [ 'include', :space ]
358
+ rule :grammar_keyword, [ 'grammar', :space ]
359
+ rule :super_keyword, [ 'super', :space ]
360
+ rule :root_keyword, [ 'root', :space ]
361
+ rule :rule_keyword, [ 'rule', :space ]
362
+ rule :end_keyword, [ 'end', :space ]
363
+ rule :lparen, [ '(', :space ]
364
+ rule :rparen, [ ')', :space ]
365
+ rule :lcurly, [ '{', :space ]
366
+ rule :rcurly, [ '}', :space ]
367
+ rule :bar, [ '|', :space ]
368
+ rule :lt, [ '<', :space ]
369
+ rule :gt, [ '>', :space ]
370
+
371
+ rule :white, /[ \t\n\r]/
372
+ rule :comment, /#.*/
373
+ rule :space, zero_or_more(any(:white, :comment))
374
+ end
375
+ end
@@ -0,0 +1,25 @@
1
+ require 'citrus'
2
+
3
+ module Citrus
4
+ module GrammarMethods
5
+ # Permits creation of aliases within rule definitions in Ruby grammars using
6
+ # the bare name of another rule instead of a Symbol, e.g.:
7
+ #
8
+ # rule :value do
9
+ # any(:alpha, :num)
10
+ # end
11
+ #
12
+ # can now be written as
13
+ #
14
+ # rule value do
15
+ # any(alpha, num)
16
+ # end
17
+ #
18
+ # The only caveat is that since this hack uses +method_missing+ you must
19
+ # still use symbols for rules that have the same name as any of the methods
20
+ # in GrammarMethods (root, rule, rules, etc.)
21
+ def method_missing(sym, *args)
22
+ sym
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,66 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+ Citrus.load(File.dirname(__FILE__) + '/_files/alias')
3
+
4
+ class AliasTest < Test::Unit::TestCase
5
+
6
+ def test_terminal?
7
+ rule = Alias.new
8
+ assert_equal(false, rule.terminal?)
9
+ end
10
+
11
+ def test_match
12
+ grammar = Grammar.new {
13
+ rule :a, :b
14
+ rule :b, 'b'
15
+ }
16
+
17
+ match = grammar.parse('b')
18
+ assert(match)
19
+ assert('b', match.text)
20
+ assert(1, match.length)
21
+ end
22
+
23
+ def test_match_renamed
24
+ grammar = Grammar.new {
25
+ rule :a, ext(:b) {
26
+ def value
27
+ 'a' + text
28
+ end
29
+ }
30
+ rule :b, 'b'
31
+ }
32
+
33
+ match = grammar.parse('b')
34
+ assert(match)
35
+ assert('ab', match.value)
36
+
37
+ assert_raise RuntimeError do
38
+ match.b
39
+ end
40
+ end
41
+
42
+ def test_peg
43
+ match = AliasOne.parse('a')
44
+ assert(match)
45
+ end
46
+
47
+ def test_included
48
+ grammar1 = Grammar.new {
49
+ rule :a, 'a'
50
+ }
51
+
52
+ grammar2 = Grammar.new {
53
+ include grammar1
54
+ rule :b, :a
55
+ }
56
+
57
+ match = grammar2.parse('a')
58
+ assert(match)
59
+ end
60
+
61
+ def test_to_s
62
+ rule = Alias.new(:alpha)
63
+ assert_equal('alpha', rule.to_s)
64
+ end
65
+
66
+ end