citrus 1.7.0 → 1.8.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/README +217 -154
- data/doc/{background.rdoc → background.markdown} +35 -32
- data/doc/example.markdown +145 -0
- data/doc/index.markdown +18 -0
- data/doc/{license.rdoc → license.markdown} +2 -1
- data/doc/links.markdown +13 -0
- data/doc/syntax.markdown +129 -0
- data/examples/calc.citrus +55 -49
- data/examples/calc.rb +55 -49
- data/examples/ip.rb +1 -1
- data/lib/citrus.rb +118 -89
- data/lib/citrus/debug.rb +1 -1
- data/lib/citrus/file.rb +75 -154
- data/test/alias_test.rb +2 -4
- data/test/and_predicate_test.rb +1 -1
- data/test/but_predicate_test.rb +36 -0
- data/test/choice_test.rb +5 -5
- data/test/expression_test.rb +1 -1
- data/test/file_test.rb +17 -15
- data/test/fixed_width_test.rb +2 -2
- data/test/grammar_test.rb +8 -8
- data/test/helper.rb +54 -6
- data/test/label_test.rb +3 -3
- data/test/match_test.rb +5 -5
- data/test/not_predicate_test.rb +1 -1
- data/test/repeat_test.rb +17 -17
- data/test/rule_test.rb +5 -9
- data/test/sequence_test.rb +3 -3
- data/test/super_test.rb +2 -2
- metadata +11 -9
- data/doc/example.rdoc +0 -115
- data/doc/index.rdoc +0 -15
- data/doc/links.rdoc +0 -18
- data/doc/syntax.rdoc +0 -96
data/lib/citrus/debug.rb
CHANGED
data/lib/citrus/file.rb
CHANGED
@@ -10,31 +10,25 @@ module Citrus
|
|
10
10
|
|
11
11
|
rule :file do
|
12
12
|
all(:space, zero_or_more(any(:require, :grammar))) {
|
13
|
-
|
14
|
-
|
15
|
-
find(:grammar).map {|g| g.value }
|
16
|
-
end
|
13
|
+
find(:require).each { |r| require r.value }
|
14
|
+
find(:grammar).map { |g| g.value }
|
17
15
|
}
|
18
16
|
end
|
19
17
|
|
20
18
|
rule :grammar do
|
21
19
|
all(:grammar_keyword, :module_name, :grammar_body, :end_keyword) {
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
grammar.root(root.value) if root
|
35
|
-
find(:rule).each {|r| grammar.rule(r.rule_name.value, r.value) }
|
36
|
-
grammar
|
37
|
-
end
|
20
|
+
code = '%s = Citrus::Grammar.new' % module_name.value
|
21
|
+
grammar = eval(code, TOPLEVEL_BINDING)
|
22
|
+
|
23
|
+
modules = find(:include).map { |inc| eval(inc.value, TOPLEVEL_BINDING) }
|
24
|
+
modules.each { |mod| grammar.include(mod) }
|
25
|
+
|
26
|
+
root = find(:root).last
|
27
|
+
grammar.root(root.value) if root
|
28
|
+
|
29
|
+
find(:rule).each { |r| grammar.rule(r.rule_name.value, r.value) }
|
30
|
+
|
31
|
+
grammar
|
38
32
|
}
|
39
33
|
end
|
40
34
|
|
@@ -44,214 +38,157 @@ module Citrus
|
|
44
38
|
|
45
39
|
rule :rule do
|
46
40
|
all(:rule_keyword, :rule_name, :rule_body, :end_keyword) {
|
47
|
-
|
48
|
-
rule_body.value
|
49
|
-
end
|
41
|
+
rule_body.value
|
50
42
|
}
|
51
43
|
end
|
52
44
|
|
53
45
|
rule :rule_body do
|
54
46
|
all(:sequence, :choice) {
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
def values
|
60
|
-
choices.map {|c| c.value }
|
61
|
-
end
|
62
|
-
|
63
|
-
def value
|
64
|
-
choices.length > 1 ? Choice.new(values) : values[0]
|
65
|
-
end
|
47
|
+
@choices ||= [ sequence ] + choice.value
|
48
|
+
values = @choices.map { |c| c.value }
|
49
|
+
values.length > 1 ? Choice.new(values) : values[0]
|
66
50
|
}
|
67
51
|
end
|
68
52
|
|
69
53
|
rule :choice do
|
70
54
|
zero_or_more([ :bar, :sequence ]) {
|
71
|
-
|
72
|
-
matches.map {|m| m.matches[1] }
|
73
|
-
end
|
55
|
+
matches.map { |m| m.matches[1] }
|
74
56
|
}
|
75
57
|
end
|
76
58
|
|
77
59
|
rule :sequence do
|
78
60
|
zero_or_more(:appendix) {
|
79
|
-
|
80
|
-
|
81
|
-
end
|
82
|
-
|
83
|
-
def value
|
84
|
-
matches.length > 1 ? Sequence.new(values) : values[0]
|
85
|
-
end
|
61
|
+
values = matches.map { |m| m.value }
|
62
|
+
values.length > 1 ? Sequence.new(values) : values[0]
|
86
63
|
}
|
87
64
|
end
|
88
65
|
|
89
66
|
rule :appendix do
|
90
67
|
all(:prefix, zero_or_one(:extension)) {
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
rule
|
96
|
-
end
|
68
|
+
rule = prefix.value
|
69
|
+
extension = matches[1].first
|
70
|
+
rule.extension = extension.value if extension
|
71
|
+
rule
|
97
72
|
}
|
98
73
|
end
|
99
74
|
|
100
75
|
rule :prefix do
|
101
76
|
all(zero_or_one(:predicate), :suffix) {
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
rule
|
107
|
-
end
|
77
|
+
rule = suffix.value
|
78
|
+
predicate = matches[0].first
|
79
|
+
rule = predicate.value(rule) if predicate
|
80
|
+
rule
|
108
81
|
}
|
109
82
|
end
|
110
83
|
|
111
84
|
rule :suffix do
|
112
85
|
all(:primary, zero_or_one(:repeat)) {
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
rule
|
118
|
-
end
|
86
|
+
rule = primary.value
|
87
|
+
repeat = matches[1].first
|
88
|
+
rule = repeat.value(rule) if repeat
|
89
|
+
rule
|
119
90
|
}
|
120
91
|
end
|
121
92
|
|
122
93
|
rule :primary do
|
123
|
-
any(:
|
94
|
+
any(:grouping, :proxy, :terminal)
|
124
95
|
end
|
125
96
|
|
126
97
|
rule :grouping do
|
127
|
-
all(:lparen, :rule_body, :rparen) {
|
128
|
-
def value
|
129
|
-
rule_body.value
|
130
|
-
end
|
131
|
-
}
|
98
|
+
all(:lparen, :rule_body, :rparen) { rule_body.value }
|
132
99
|
end
|
133
100
|
|
134
101
|
## Lexical syntax
|
135
102
|
|
136
103
|
rule :require do
|
137
|
-
all(:require_keyword, :quoted_string) {
|
138
|
-
def value
|
139
|
-
quoted_string.value
|
140
|
-
end
|
141
|
-
}
|
104
|
+
all(:require_keyword, :quoted_string) { quoted_string.value }
|
142
105
|
end
|
143
106
|
|
144
107
|
rule :include do
|
145
|
-
all(:include_keyword, :module_name) {
|
146
|
-
def value
|
147
|
-
module_name.value
|
148
|
-
end
|
149
|
-
}
|
108
|
+
all(:include_keyword, :module_name) { module_name.value }
|
150
109
|
end
|
151
110
|
|
152
111
|
rule :root do
|
153
|
-
all(:root_keyword, :rule_name) {
|
154
|
-
def value
|
155
|
-
rule_name.value
|
156
|
-
end
|
157
|
-
}
|
112
|
+
all(:root_keyword, :rule_name) { rule_name.value }
|
158
113
|
end
|
159
114
|
|
160
115
|
# Rule names may contain letters, numbers, underscores, and dashes. They
|
161
116
|
# MUST start with a letter.
|
162
117
|
rule :rule_name do
|
163
|
-
all(/[a-zA-Z][a-zA-Z0-9_-]*/, :space) {
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
118
|
+
all(/[a-zA-Z][a-zA-Z0-9_-]*/, :space) { first.to_s }
|
119
|
+
end
|
120
|
+
|
121
|
+
rule :proxy do
|
122
|
+
any(:super, :alias)
|
168
123
|
end
|
169
124
|
|
170
125
|
rule :super do
|
171
126
|
all('super', :space) {
|
172
|
-
|
173
|
-
Super.new
|
174
|
-
end
|
127
|
+
Super.new
|
175
128
|
}
|
176
129
|
end
|
177
130
|
|
178
131
|
rule :alias do
|
179
132
|
all(notp(:end_keyword), :rule_name) {
|
180
|
-
|
181
|
-
Alias.new(rule_name.value)
|
182
|
-
end
|
133
|
+
Alias.new(rule_name.value)
|
183
134
|
}
|
184
135
|
end
|
185
136
|
|
186
137
|
rule :terminal do
|
187
|
-
any(:quoted_string, :character_class, :
|
188
|
-
|
189
|
-
Rule.new(super)
|
190
|
-
end
|
138
|
+
any(:quoted_string, :character_class, :dot, :regular_expression) {
|
139
|
+
Rule.new(super())
|
191
140
|
}
|
192
141
|
end
|
193
142
|
|
194
143
|
rule :quoted_string do
|
195
144
|
all(/(["'])(?:\\?.)*?\1/, :space) {
|
196
|
-
|
197
|
-
eval(first.text)
|
198
|
-
end
|
145
|
+
eval(first.to_s)
|
199
146
|
}
|
200
147
|
end
|
201
148
|
|
202
149
|
rule :character_class do
|
203
150
|
all(/\[(?:\\?.)*?\]/, :space) {
|
204
|
-
|
205
|
-
Regexp.new('\A' + first.text, nil, 'n')
|
206
|
-
end
|
151
|
+
Regexp.new('\A' + first.to_s, nil, 'n')
|
207
152
|
}
|
208
153
|
end
|
209
154
|
|
210
|
-
rule :
|
155
|
+
rule :dot do
|
211
156
|
all('.', :space) {
|
212
|
-
|
213
|
-
/./m # Match newlines
|
214
|
-
end
|
157
|
+
DOT
|
215
158
|
}
|
216
159
|
end
|
217
160
|
|
218
161
|
rule :regular_expression do
|
219
162
|
all(/\/(?:\\?.)*?\/[imxouesn]*/, :space) {
|
220
|
-
|
221
|
-
eval(first.text)
|
222
|
-
end
|
163
|
+
eval(first.to_s)
|
223
164
|
}
|
224
165
|
end
|
225
166
|
|
226
167
|
rule :predicate do
|
227
|
-
any(:and, :not, :label)
|
168
|
+
any(:and, :not, :but, :label)
|
228
169
|
end
|
229
170
|
|
230
171
|
rule :and do
|
231
|
-
all('&', :space) {
|
232
|
-
|
233
|
-
AndPredicate.new(rule)
|
234
|
-
end
|
172
|
+
all('&', :space) { |rule|
|
173
|
+
AndPredicate.new(rule)
|
235
174
|
}
|
236
175
|
end
|
237
176
|
|
238
177
|
rule :not do
|
239
|
-
all('!', :space) {
|
240
|
-
|
241
|
-
|
242
|
-
|
178
|
+
all('!', :space) { |rule|
|
179
|
+
NotPredicate.new(rule)
|
180
|
+
}
|
181
|
+
end
|
182
|
+
|
183
|
+
rule :but do
|
184
|
+
all('~', :space) { |rule|
|
185
|
+
ButPredicate.new(rule)
|
243
186
|
}
|
244
187
|
end
|
245
188
|
|
246
189
|
rule :label do
|
247
|
-
all(/[a-zA-Z0-9_]+/, :space, ':', :space) {
|
248
|
-
|
249
|
-
Label.new(value, rule)
|
250
|
-
end
|
251
|
-
|
252
|
-
def value
|
253
|
-
first.text
|
254
|
-
end
|
190
|
+
all(/[a-zA-Z0-9_]+/, :space, ':', :space) { |rule|
|
191
|
+
Label.new(rule, first.to_s)
|
255
192
|
}
|
256
193
|
end
|
257
194
|
|
@@ -261,25 +198,19 @@ module Citrus
|
|
261
198
|
|
262
199
|
rule :tag do
|
263
200
|
all(:lt, :module_name, :gt) {
|
264
|
-
|
265
|
-
eval(module_name.value, TOPLEVEL_BINDING)
|
266
|
-
end
|
201
|
+
eval(module_name.value, TOPLEVEL_BINDING)
|
267
202
|
}
|
268
203
|
end
|
269
204
|
|
270
205
|
rule :block do
|
271
|
-
all(:lcurly, zero_or_more(any(:block, /[^
|
272
|
-
|
273
|
-
eval('Proc.new ' + text)
|
274
|
-
end
|
206
|
+
all(:lcurly, zero_or_more(any(:block, /[^}]+/)), :rcurly) {
|
207
|
+
eval('Proc.new ' + to_s)
|
275
208
|
}
|
276
209
|
end
|
277
210
|
|
278
211
|
rule :repeat do
|
279
|
-
any(:question, :plus, :
|
280
|
-
|
281
|
-
Repeat.new(min, max, rule)
|
282
|
-
end
|
212
|
+
any(:question, :plus, :star) { |rule|
|
213
|
+
Repeat.new(rule, min, max)
|
283
214
|
}
|
284
215
|
end
|
285
216
|
|
@@ -297,30 +228,19 @@ module Citrus
|
|
297
228
|
}
|
298
229
|
end
|
299
230
|
|
300
|
-
rule :
|
231
|
+
rule :star do
|
301
232
|
all(/[0-9]*/, '*', /[0-9]*/, :space) {
|
302
|
-
def min
|
303
|
-
|
304
|
-
end
|
305
|
-
|
306
|
-
def max
|
307
|
-
matches[2] == '' ? Infinity : matches[2].text.to_i
|
308
|
-
end
|
233
|
+
def min; matches[0] == '' ? 0 : matches[0].to_i end
|
234
|
+
def max; matches[2] == '' ? Infinity : matches[2].to_i end
|
309
235
|
}
|
310
236
|
end
|
311
237
|
|
312
238
|
rule :module_name do
|
313
239
|
all(one_or_more([ zero_or_one('::'), :constant ]), :space) {
|
314
|
-
|
315
|
-
first.text
|
316
|
-
end
|
240
|
+
first.to_s
|
317
241
|
}
|
318
242
|
end
|
319
243
|
|
320
|
-
rule :constant do
|
321
|
-
/[A-Z][a-zA-Z0-9_]*/
|
322
|
-
end
|
323
|
-
|
324
244
|
rule :require_keyword, [ 'require', :space ]
|
325
245
|
rule :include_keyword, [ 'include', :space ]
|
326
246
|
rule :grammar_keyword, [ 'grammar', :space ]
|
@@ -335,6 +255,7 @@ module Citrus
|
|
335
255
|
rule :lt, [ '<', :space ]
|
336
256
|
rule :gt, [ '>', :space ]
|
337
257
|
|
258
|
+
rule :constant, /[A-Z][a-zA-Z0-9_]*/
|
338
259
|
rule :white, /[ \t\n\r]/
|
339
260
|
rule :comment, /#.*/
|
340
261
|
rule :space, zero_or_more(any(:white, :comment))
|
data/test/alias_test.rb
CHANGED
@@ -16,16 +16,14 @@ class AliasTest < Test::Unit::TestCase
|
|
16
16
|
|
17
17
|
match = grammar.parse('b')
|
18
18
|
assert(match)
|
19
|
-
assert_equal('b', match
|
19
|
+
assert_equal('b', match)
|
20
20
|
assert_equal(1, match.length)
|
21
21
|
end
|
22
22
|
|
23
23
|
def test_match_renamed
|
24
24
|
grammar = Grammar.new {
|
25
25
|
rule :a, ext(:b) {
|
26
|
-
|
27
|
-
'a' + text
|
28
|
-
end
|
26
|
+
'a' + to_s
|
29
27
|
}
|
30
28
|
rule :b, 'b'
|
31
29
|
}
|
data/test/and_predicate_test.rb
CHANGED
@@ -0,0 +1,36 @@
|
|
1
|
+
require File.expand_path('../helper', __FILE__)
|
2
|
+
|
3
|
+
class ButPredicateTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def test_terminal?
|
6
|
+
rule = ButPredicate.new
|
7
|
+
assert_equal(false, rule.terminal?)
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_match
|
11
|
+
rule = ButPredicate.new('a')
|
12
|
+
|
13
|
+
match = rule.match(input('b'))
|
14
|
+
assert(match)
|
15
|
+
assert_equal('b', match)
|
16
|
+
assert_equal(1, match.length)
|
17
|
+
|
18
|
+
match = rule.match(input('bbba'))
|
19
|
+
assert(match)
|
20
|
+
assert_equal('bbb', match)
|
21
|
+
assert_equal(3, match.length)
|
22
|
+
|
23
|
+
match = rule.match(input('a'))
|
24
|
+
assert_equal(nil, match)
|
25
|
+
|
26
|
+
# ButPredicate must match at least one character.
|
27
|
+
match = rule.match(input(''))
|
28
|
+
assert_equal(nil, match)
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_to_s
|
32
|
+
rule = ButPredicate.new('a')
|
33
|
+
assert_equal('~"a"', rule.to_s)
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|