citrus 1.4.0 → 1.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/citrus.gemspec +7 -9
- data/examples/calc.citrus +37 -28
- data/examples/calc.rb +37 -28
- data/lib/citrus.rb +49 -19
- data/lib/citrus/file.rb +7 -9
- data/test/_files/alias.citrus +9 -0
- data/test/_files/grammar1.citrus +1 -0
- data/test/_files/grammar2.citrus +25 -0
- data/test/_files/grammar3.citrus +112 -0
- data/test/_files/rule1.citrus +1 -0
- data/test/_files/rule2.citrus +3 -0
- data/test/_files/rule3.citrus +7 -0
- data/test/_files/super.citrus +13 -0
- data/test/alias_test.rb +3 -3
- data/test/and_predicate_test.rb +1 -1
- data/test/calc_file_test.rb +1 -1
- data/test/calc_test.rb +2 -2
- data/test/choice_test.rb +1 -1
- data/test/expression_test.rb +1 -1
- data/test/file_test.rb +12 -8
- data/test/fixed_width_test.rb +1 -1
- data/test/grammar_test.rb +13 -8
- data/test/helper.rb +23 -57
- data/test/label_test.rb +1 -1
- data/test/match_test.rb +1 -1
- data/test/not_predicate_test.rb +1 -1
- data/test/repeat_test.rb +1 -1
- data/test/rule_test.rb +4 -4
- data/test/sequence_test.rb +1 -1
- data/test/super_test.rb +5 -5
- metadata +22 -9
- data/examples/calc_sugar.rb +0 -87
- data/lib/citrus/sugar.rb +0 -25
- data/test/calc_sugar_test.rb +0 -11
data/citrus.gemspec
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = 'citrus'
|
3
|
-
s.version = '1.
|
4
|
-
s.date = '2010-
|
3
|
+
s.version = '1.5.0'
|
4
|
+
s.date = '2010-07-24'
|
5
5
|
|
6
6
|
s.summary = 'Parsing Expressions for Ruby'
|
7
7
|
s.description = 'Parsing Expressions for Ruby'
|
@@ -11,14 +11,12 @@ Gem::Specification.new do |s|
|
|
11
11
|
|
12
12
|
s.require_paths = %w< lib >
|
13
13
|
|
14
|
-
s.files = Dir['benchmark
|
15
|
-
Dir['
|
16
|
-
Dir['
|
17
|
-
Dir['
|
18
|
-
Dir['examples/**/*'] +
|
19
|
-
Dir['extras/**/*'] +
|
14
|
+
s.files = Dir['benchmark/**'] +
|
15
|
+
Dir['doc/**'] +
|
16
|
+
Dir['examples/**'] +
|
17
|
+
Dir['extras/**'] +
|
20
18
|
Dir['lib/**/*.rb'] +
|
21
|
-
Dir['test
|
19
|
+
Dir['test/**/*'] +
|
22
20
|
%w< citrus.gemspec Rakefile README >
|
23
21
|
|
24
22
|
s.test_files = s.files.select {|path| path =~ /^test\/.*_test.rb/ }
|
data/examples/calc.citrus
CHANGED
@@ -7,9 +7,9 @@ grammar Calc
|
|
7
7
|
end
|
8
8
|
|
9
9
|
rule additive
|
10
|
-
(factor operator:
|
10
|
+
(factor operator:(plus | minus) term) {
|
11
11
|
def value
|
12
|
-
operator.apply(factor
|
12
|
+
operator.apply(factor, term)
|
13
13
|
end
|
14
14
|
}
|
15
15
|
end
|
@@ -19,9 +19,9 @@ grammar Calc
|
|
19
19
|
end
|
20
20
|
|
21
21
|
rule multiplicative
|
22
|
-
(primary operator:
|
22
|
+
(primary operator:(star | slash) factor) {
|
23
23
|
def value
|
24
|
-
operator.apply(primary
|
24
|
+
operator.apply(primary, factor)
|
25
25
|
end
|
26
26
|
}
|
27
27
|
end
|
@@ -38,22 +38,6 @@ grammar Calc
|
|
38
38
|
}
|
39
39
|
end
|
40
40
|
|
41
|
-
rule additive_op
|
42
|
-
(plus | minus) {
|
43
|
-
def apply(factor, term)
|
44
|
-
text.strip == '+' ? factor + term : factor - term
|
45
|
-
end
|
46
|
-
}
|
47
|
-
end
|
48
|
-
|
49
|
-
rule multiplicative_op
|
50
|
-
(star | slash) {
|
51
|
-
def apply(primary, factor)
|
52
|
-
text.strip == '*' ? primary * factor : primary / factor
|
53
|
-
end
|
54
|
-
}
|
55
|
-
end
|
56
|
-
|
57
41
|
rule number
|
58
42
|
float | integer
|
59
43
|
end
|
@@ -74,14 +58,39 @@ grammar Calc
|
|
74
58
|
}
|
75
59
|
end
|
76
60
|
|
77
|
-
rule
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
61
|
+
rule plus
|
62
|
+
('+' space) {
|
63
|
+
def apply(factor, term)
|
64
|
+
factor.value + term.value
|
65
|
+
end
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
rule minus
|
70
|
+
('-' space) {
|
71
|
+
def apply(factor, term)
|
72
|
+
factor.value - term.value
|
73
|
+
end
|
74
|
+
}
|
75
|
+
end
|
76
|
+
|
77
|
+
rule star
|
78
|
+
('*' space) {
|
79
|
+
def apply(primary, factor)
|
80
|
+
primary.value * factor.value
|
81
|
+
end
|
82
|
+
}
|
83
|
+
end
|
83
84
|
|
84
|
-
rule
|
85
|
-
|
85
|
+
rule slash
|
86
|
+
('/' space) {
|
87
|
+
def apply(primary, factor)
|
88
|
+
primary.value / factor.value
|
89
|
+
end
|
90
|
+
}
|
86
91
|
end
|
92
|
+
|
93
|
+
rule lparen '(' space end
|
94
|
+
rule rparen ')' space end
|
95
|
+
rule space [ \t\n\r]* end
|
87
96
|
end
|
data/examples/calc.rb
CHANGED
@@ -3,17 +3,15 @@ require 'citrus'
|
|
3
3
|
# A grammar for mathematical formulas that apply the basic four operations to
|
4
4
|
# non-negative numbers (integers and floats), respecting operator precedence and
|
5
5
|
# ignoring whitespace.
|
6
|
-
|
7
|
-
include Citrus::Grammar
|
8
|
-
|
6
|
+
grammar :Calc do
|
9
7
|
rule :term do
|
10
8
|
any(:additive, :factor)
|
11
9
|
end
|
12
10
|
|
13
11
|
rule :additive do
|
14
|
-
all(:factor, label(:
|
12
|
+
all(:factor, label(any(:plus, :minus), :operator), :term) {
|
15
13
|
def value
|
16
|
-
operator.apply(factor
|
14
|
+
operator.apply(factor, term)
|
17
15
|
end
|
18
16
|
}
|
19
17
|
end
|
@@ -23,9 +21,9 @@ module Calc
|
|
23
21
|
end
|
24
22
|
|
25
23
|
rule :multiplicative do
|
26
|
-
all(:primary, label(:
|
24
|
+
all(:primary, label(any(:star, :slash), :operator), :factor) {
|
27
25
|
def value
|
28
|
-
operator.apply(primary
|
26
|
+
operator.apply(primary, factor)
|
29
27
|
end
|
30
28
|
}
|
31
29
|
end
|
@@ -42,22 +40,6 @@ module Calc
|
|
42
40
|
}
|
43
41
|
end
|
44
42
|
|
45
|
-
rule :additive_op do
|
46
|
-
any(:plus, :minus) {
|
47
|
-
def apply(factor, term)
|
48
|
-
text.strip == '+' ? factor + term : factor - term
|
49
|
-
end
|
50
|
-
}
|
51
|
-
end
|
52
|
-
|
53
|
-
rule :multiplicative_op do
|
54
|
-
any(:star, :slash) {
|
55
|
-
def apply(primary, factor)
|
56
|
-
text.strip == '*' ? primary * factor : primary / factor
|
57
|
-
end
|
58
|
-
}
|
59
|
-
end
|
60
|
-
|
61
43
|
rule :number do
|
62
44
|
any(:float, :integer)
|
63
45
|
end
|
@@ -78,12 +60,39 @@ module Calc
|
|
78
60
|
}
|
79
61
|
end
|
80
62
|
|
63
|
+
rule :plus do
|
64
|
+
all('+', :space) {
|
65
|
+
def apply(factor, term)
|
66
|
+
factor.value + term.value
|
67
|
+
end
|
68
|
+
}
|
69
|
+
end
|
70
|
+
|
71
|
+
rule :minus do
|
72
|
+
all('-', :space) {
|
73
|
+
def apply(factor, term)
|
74
|
+
factor.value - term.value
|
75
|
+
end
|
76
|
+
}
|
77
|
+
end
|
78
|
+
|
79
|
+
rule :star do
|
80
|
+
all('*', :space) {
|
81
|
+
def apply(primary, factor)
|
82
|
+
primary.value * factor.value
|
83
|
+
end
|
84
|
+
}
|
85
|
+
end
|
86
|
+
|
87
|
+
rule :slash do
|
88
|
+
all('/', :space) {
|
89
|
+
def apply(primary, factor)
|
90
|
+
primary.value / factor.value
|
91
|
+
end
|
92
|
+
}
|
93
|
+
end
|
94
|
+
|
81
95
|
rule :lparen, ['(', :space]
|
82
96
|
rule :rparen, [')', :space]
|
83
|
-
rule :plus, ['+', :space]
|
84
|
-
rule :minus, ['-', :space]
|
85
|
-
rule :star, ['*', :space]
|
86
|
-
rule :slash, ['/', :space]
|
87
|
-
|
88
97
|
rule :space, /[ \t\n\r]*/
|
89
98
|
end
|
data/lib/citrus.rb
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
#
|
5
5
|
# http://mjijackson.com/citrus
|
6
6
|
module Citrus
|
7
|
-
VERSION = [1,
|
7
|
+
VERSION = [1, 5, 0]
|
8
8
|
|
9
9
|
Infinity = 1.0 / 0
|
10
10
|
|
@@ -86,6 +86,11 @@ module Citrus
|
|
86
86
|
|
87
87
|
# Contains methods that are available to Grammar modules at the class level.
|
88
88
|
module GrammarMethods
|
89
|
+
def self.extend_object(obj)
|
90
|
+
raise ArgumentError, "Grammars must be Ruby modules" unless Module === obj
|
91
|
+
super
|
92
|
+
end
|
93
|
+
|
89
94
|
# Returns the name of this grammar as a string.
|
90
95
|
def name
|
91
96
|
super.to_s
|
@@ -236,7 +241,7 @@ module Citrus
|
|
236
241
|
def ext(rule, mod=nil)
|
237
242
|
rule = Rule.create(rule)
|
238
243
|
mod = Proc.new if block_given?
|
239
|
-
rule.
|
244
|
+
rule.extension = mod if mod
|
240
245
|
rule
|
241
246
|
end
|
242
247
|
|
@@ -387,13 +392,13 @@ module Citrus
|
|
387
392
|
# Specifies a module that will be used to extend all Match objects that
|
388
393
|
# result from this rule. If +mod+ is a Proc, it is used to create an
|
389
394
|
# anonymous module.
|
390
|
-
def
|
395
|
+
def extension=(mod)
|
391
396
|
mod = Module.new(&mod) if Proc === mod
|
392
|
-
@
|
397
|
+
@extension = mod
|
393
398
|
end
|
394
399
|
|
395
400
|
# The module this rule uses to extend new matches.
|
396
|
-
attr_reader :
|
401
|
+
attr_reader :extension
|
397
402
|
|
398
403
|
# Returns +true+ if this rule is a Terminal.
|
399
404
|
def terminal?
|
@@ -419,7 +424,7 @@ module Citrus
|
|
419
424
|
private
|
420
425
|
|
421
426
|
def extend_match(match, name)
|
422
|
-
match.extensions <<
|
427
|
+
match.extensions << extension if extension
|
423
428
|
match.names << name if name
|
424
429
|
match
|
425
430
|
end
|
@@ -931,26 +936,51 @@ module Citrus
|
|
931
936
|
|
932
937
|
alias eql? ==
|
933
938
|
|
934
|
-
# Uses #match to allow sub-matches of this match to be called by name as
|
935
|
-
# instance methods.
|
936
|
-
def method_missing(sym, *args)
|
937
|
-
# Extend this object only when needed and immediately redefine
|
938
|
-
# #method_missing so that the new version is used on all future calls.
|
939
|
-
extensions.each {|e| extend(e) } if @extensions
|
940
|
-
redefine_method_missing!
|
941
|
-
__send__(sym, *args)
|
942
|
-
end
|
943
|
-
|
944
939
|
private
|
945
940
|
|
946
941
|
def redefine_method_missing! # :nodoc:
|
947
942
|
instance_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
948
943
|
def method_missing(sym, *args)
|
949
|
-
|
950
|
-
|
951
|
-
|
944
|
+
if sym == :to_ary
|
945
|
+
original_method_missing(sym, *args)
|
946
|
+
else
|
947
|
+
m = first(sym)
|
948
|
+
return m if m
|
949
|
+
raise 'No match named "%s" in %s (%s)' % [sym, self, name]
|
950
|
+
end
|
952
951
|
end
|
953
952
|
RUBY
|
954
953
|
end
|
954
|
+
|
955
|
+
alias original_method_missing method_missing
|
956
|
+
|
957
|
+
public
|
958
|
+
|
959
|
+
# Allows sub-matches of this match to be retrieved by name as instance
|
960
|
+
# methods.
|
961
|
+
def method_missing(sym, *args)
|
962
|
+
# Extend this object only when needed and immediately redefine
|
963
|
+
# #method_missing so that the new version is used on all future calls.
|
964
|
+
extensions.each {|e| extend(e) } if @extensions
|
965
|
+
redefine_method_missing!
|
966
|
+
__send__(sym, *args)
|
967
|
+
end
|
968
|
+
end
|
969
|
+
end
|
970
|
+
|
971
|
+
class Object
|
972
|
+
# A sugar method for creating grammars.
|
973
|
+
#
|
974
|
+
# grammar :Calc do
|
975
|
+
# end
|
976
|
+
#
|
977
|
+
# module MyModule
|
978
|
+
# grammar :Calc do
|
979
|
+
# end
|
980
|
+
# end
|
981
|
+
#
|
982
|
+
def grammar(name, &block)
|
983
|
+
obj = respond_to?(:const_set) ? self : Object
|
984
|
+
obj.const_set(name, Citrus::Grammar.new(&block))
|
955
985
|
end
|
956
986
|
end
|
data/lib/citrus/file.rb
CHANGED
@@ -1,11 +1,10 @@
|
|
1
1
|
require 'citrus'
|
2
2
|
|
3
3
|
module Citrus
|
4
|
-
# A grammar for Citrus grammar files. This
|
4
|
+
# A grammar for Citrus grammar files. This grammar is used in Citrus#eval to
|
5
5
|
# parse and evaluate Citrus grammars and serves as a prime example of how to
|
6
6
|
# create a complex grammar complete with semantic interpretation in pure Ruby.
|
7
|
-
|
8
|
-
include Grammar
|
7
|
+
File = Grammar.new do
|
9
8
|
|
10
9
|
## Hierarchical syntax
|
11
10
|
|
@@ -119,7 +118,7 @@ module Citrus
|
|
119
118
|
def value
|
120
119
|
rule = suffix.value
|
121
120
|
extension = matches[1].first
|
122
|
-
|
121
|
+
extension.apply(rule) if extension
|
123
122
|
rule
|
124
123
|
end
|
125
124
|
}
|
@@ -217,7 +216,7 @@ module Citrus
|
|
217
216
|
rule :character_class do
|
218
217
|
all(/\[(?:\\?.)*?\]/, :space) {
|
219
218
|
def value
|
220
|
-
Regexp.new(first.text)
|
219
|
+
Regexp.new('\A' + first.text)
|
221
220
|
end
|
222
221
|
}
|
223
222
|
end
|
@@ -225,7 +224,7 @@ module Citrus
|
|
225
224
|
rule :anything_symbol do
|
226
225
|
all('.', :space) {
|
227
226
|
def value
|
228
|
-
/./m #
|
227
|
+
/./m # Match newlines
|
229
228
|
end
|
230
229
|
}
|
231
230
|
end
|
@@ -272,9 +271,8 @@ module Citrus
|
|
272
271
|
|
273
272
|
rule :extension do
|
274
273
|
any(:tag, :block) {
|
275
|
-
def
|
276
|
-
rule.
|
277
|
-
rule
|
274
|
+
def apply(rule)
|
275
|
+
rule.extension = value
|
278
276
|
end
|
279
277
|
}
|
280
278
|
end
|
@@ -0,0 +1 @@
|
|
1
|
+
grammar Calc end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
grammar Calc
|
2
|
+
rule number
|
3
|
+
(float | integer) {
|
4
|
+
def value
|
5
|
+
first.value
|
6
|
+
end
|
7
|
+
}
|
8
|
+
end
|
9
|
+
|
10
|
+
rule float
|
11
|
+
(integer '.' integer) {
|
12
|
+
def value
|
13
|
+
text.to_f
|
14
|
+
end
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
rule integer
|
19
|
+
[0-9]+ {
|
20
|
+
def value
|
21
|
+
text.to_i
|
22
|
+
end
|
23
|
+
}
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
grammar Calc
|
2
|
+
# If "additive" were not already the first rule declared in this grammar, we
|
3
|
+
# could use the following line to make it the root rule.
|
4
|
+
#root additive
|
5
|
+
|
6
|
+
rule additive
|
7
|
+
(multitive_additive | multitive) {
|
8
|
+
def value
|
9
|
+
first.value
|
10
|
+
end
|
11
|
+
}
|
12
|
+
end
|
13
|
+
|
14
|
+
rule multitive_additive
|
15
|
+
(multitive additive_op additive) {
|
16
|
+
def value
|
17
|
+
if additive_op == '+'
|
18
|
+
multitive.value + additive.value
|
19
|
+
else
|
20
|
+
multitive.value - additive.value
|
21
|
+
end
|
22
|
+
end
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
rule multitive
|
27
|
+
(primary_multitive | primary) {
|
28
|
+
def value
|
29
|
+
first.value
|
30
|
+
end
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
rule primary_multitive
|
35
|
+
(primary multitive_op multitive) {
|
36
|
+
def value
|
37
|
+
if multitive_op == '*'
|
38
|
+
primary.value * multitive.value
|
39
|
+
else
|
40
|
+
primary.value / multitive.value
|
41
|
+
end
|
42
|
+
end
|
43
|
+
}
|
44
|
+
end
|
45
|
+
|
46
|
+
rule primary
|
47
|
+
(additive_paren | number) {
|
48
|
+
def value
|
49
|
+
first.value
|
50
|
+
end
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
rule additive_paren
|
55
|
+
('(' additive ')') {
|
56
|
+
def value
|
57
|
+
additive.value
|
58
|
+
end
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
62
|
+
rule additive_op
|
63
|
+
(plus | minus) {
|
64
|
+
def ==(other)
|
65
|
+
text.strip == other
|
66
|
+
end
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
70
|
+
rule multitive_op
|
71
|
+
(star | slash) {
|
72
|
+
def ==(other)
|
73
|
+
text.strip == other
|
74
|
+
end
|
75
|
+
}
|
76
|
+
end
|
77
|
+
|
78
|
+
rule number
|
79
|
+
(float | integer) {
|
80
|
+
def value
|
81
|
+
first.value
|
82
|
+
end
|
83
|
+
}
|
84
|
+
end
|
85
|
+
|
86
|
+
rule float
|
87
|
+
(integer '.' integer) {
|
88
|
+
def value
|
89
|
+
text.to_f
|
90
|
+
end
|
91
|
+
}
|
92
|
+
end
|
93
|
+
|
94
|
+
rule integer
|
95
|
+
[0-9]+ {
|
96
|
+
def value
|
97
|
+
text.to_i
|
98
|
+
end
|
99
|
+
}
|
100
|
+
end
|
101
|
+
|
102
|
+
rule lparen '(' space end
|
103
|
+
rule rparen ')' space end
|
104
|
+
rule plus '+' space end
|
105
|
+
rule minus '-' space end
|
106
|
+
rule star '*' space end
|
107
|
+
rule slash '/' space end
|
108
|
+
|
109
|
+
rule space
|
110
|
+
/[ \t\n\r]*/
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
rule int '' end
|
data/test/alias_test.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require File.
|
1
|
+
require File.expand_path('../helper', __FILE__)
|
2
2
|
Citrus.load(File.dirname(__FILE__) + '/_files/alias')
|
3
3
|
|
4
4
|
class AliasTest < Test::Unit::TestCase
|
@@ -16,8 +16,8 @@ class AliasTest < Test::Unit::TestCase
|
|
16
16
|
|
17
17
|
match = grammar.parse('b')
|
18
18
|
assert(match)
|
19
|
-
|
20
|
-
|
19
|
+
assert_equal('b', match.text)
|
20
|
+
assert_equal(1, match.length)
|
21
21
|
end
|
22
22
|
|
23
23
|
def test_match_renamed
|
data/test/and_predicate_test.rb
CHANGED
data/test/calc_file_test.rb
CHANGED
data/test/calc_test.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
|
-
require File.
|
1
|
+
require File.expand_path('../helper', __FILE__)
|
2
2
|
|
3
3
|
if defined?(Calc)
|
4
4
|
Object.__send__(:remove_const, :Calc)
|
5
5
|
end
|
6
6
|
|
7
|
-
require File.
|
7
|
+
require File.expand_path('../../examples/calc', __FILE__)
|
8
8
|
|
9
9
|
class CalcTest < Test::Unit::TestCase
|
10
10
|
include CalcTestMethods
|
data/test/choice_test.rb
CHANGED
data/test/expression_test.rb
CHANGED
data/test/file_test.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require File.
|
1
|
+
require File.expand_path('../helper', __FILE__)
|
2
2
|
require 'citrus/file'
|
3
3
|
|
4
4
|
class CitrusFileTest < Test::Unit::TestCase
|
@@ -233,17 +233,17 @@ class CitrusFileTest < Test::Unit::TestCase
|
|
233
233
|
match = grammar.parse('"" <Module>')
|
234
234
|
assert(match)
|
235
235
|
assert_kind_of(Rule, match.value)
|
236
|
-
assert_kind_of(Module, match.value.
|
236
|
+
assert_kind_of(Module, match.value.extension)
|
237
237
|
|
238
238
|
match = grammar.parse('"" {}')
|
239
239
|
assert(match)
|
240
240
|
assert_kind_of(Rule, match.value)
|
241
|
-
assert_kind_of(Module, match.value.
|
241
|
+
assert_kind_of(Module, match.value.extension)
|
242
242
|
|
243
243
|
match = grammar.parse('"" {} ')
|
244
244
|
assert(match)
|
245
245
|
assert_kind_of(Rule, match.value)
|
246
|
-
assert_kind_of(Module, match.value.
|
246
|
+
assert_kind_of(Module, match.value.extension)
|
247
247
|
end
|
248
248
|
|
249
249
|
def test_suffix
|
@@ -408,19 +408,23 @@ class CitrusFileTest < Test::Unit::TestCase
|
|
408
408
|
|
409
409
|
match = grammar.parse('[_]')
|
410
410
|
assert(match)
|
411
|
-
assert_equal(
|
411
|
+
assert_equal(/\A[_]/, match.value)
|
412
412
|
|
413
413
|
match = grammar.parse('[a-z]')
|
414
414
|
assert(match)
|
415
|
-
assert_equal(
|
415
|
+
assert_equal(/\A[a-z]/, match.value)
|
416
416
|
|
417
417
|
match = grammar.parse('[a-z0-9]')
|
418
418
|
assert(match)
|
419
|
-
assert_equal(
|
419
|
+
assert_equal(/\A[a-z0-9]/, match.value)
|
420
|
+
|
421
|
+
match = grammar.parse('[\[-\]]')
|
422
|
+
assert(match)
|
423
|
+
assert_equal(/\A[\[-\]]/, match.value)
|
420
424
|
|
421
425
|
match = grammar.parse('[\\x26-\\x29]')
|
422
426
|
assert(match)
|
423
|
-
assert_equal(
|
427
|
+
assert_equal(/\A[\x26-\x29]/, match.value)
|
424
428
|
end
|
425
429
|
|
426
430
|
def test_anything_symbol
|
data/test/fixed_width_test.rb
CHANGED
data/test/grammar_test.rb
CHANGED
@@ -1,11 +1,16 @@
|
|
1
|
-
require File.
|
1
|
+
require File.expand_path('../helper', __FILE__)
|
2
2
|
|
3
3
|
class GrammarTest < Test::Unit::TestCase
|
4
4
|
|
5
5
|
def test_new
|
6
|
-
Grammar.new
|
7
|
-
|
8
|
-
|
6
|
+
g = Grammar.new
|
7
|
+
assert_kind_of(Module, g)
|
8
|
+
assert(g.include?(Grammar))
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_non_module_fail
|
12
|
+
assert_raise ArgumentError do
|
13
|
+
''.extend(GrammarMethods)
|
9
14
|
end
|
10
15
|
end
|
11
16
|
|
@@ -116,14 +121,14 @@ class GrammarTest < Test::Unit::TestCase
|
|
116
121
|
|
117
122
|
match = grammar.parse('((a))')
|
118
123
|
assert(match)
|
119
|
-
|
120
|
-
|
124
|
+
assert_equal('((a))', match.text)
|
125
|
+
assert_equal(5, match.length)
|
121
126
|
|
122
127
|
str = ('(' * 200) + 'a' + (')' * 200)
|
123
128
|
match = grammar.parse(str)
|
124
129
|
assert(match)
|
125
|
-
|
126
|
-
|
130
|
+
assert_equal(str, match.text)
|
131
|
+
assert_equal(str.length, match.length)
|
127
132
|
end
|
128
133
|
|
129
134
|
end
|
data/test/helper.rb
CHANGED
@@ -44,100 +44,66 @@ class Test::Unit::TestCase
|
|
44
44
|
end
|
45
45
|
|
46
46
|
module CalcTestMethods
|
47
|
-
|
48
|
-
|
47
|
+
# A helper method that tests the successful parsing and evaluation of the
|
48
|
+
# given mathematical expression.
|
49
|
+
def do_test(expr)
|
50
|
+
match = Calc.parse(expr)
|
49
51
|
assert(match)
|
50
|
-
assert_equal(
|
51
|
-
assert_equal(
|
52
|
-
assert_equal(
|
52
|
+
assert_equal(expr, match.text)
|
53
|
+
assert_equal(expr.length, match.length)
|
54
|
+
assert_equal(eval(expr), match.value)
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_int
|
58
|
+
do_test('3')
|
53
59
|
end
|
54
60
|
|
55
61
|
def test_float
|
56
|
-
|
57
|
-
assert(match)
|
58
|
-
assert_equal('1.5', match.text)
|
59
|
-
assert_equal(3, match.length)
|
60
|
-
assert_equal(1.5, match.value)
|
62
|
+
do_test('1.5')
|
61
63
|
end
|
62
64
|
|
63
65
|
def test_addition
|
64
|
-
|
65
|
-
assert(match)
|
66
|
-
assert_equal('1+2', match.text)
|
67
|
-
assert_equal(3, match.length)
|
68
|
-
assert_equal(3, match.value)
|
66
|
+
do_test('1+2')
|
69
67
|
end
|
70
68
|
|
71
69
|
def test_addition_multi
|
72
|
-
|
73
|
-
assert(match)
|
74
|
-
assert_equal('1+2+3', match.text)
|
75
|
-
assert_equal(5, match.length)
|
76
|
-
assert_equal(6, match.value)
|
70
|
+
do_test('1+2+3')
|
77
71
|
end
|
78
72
|
|
79
73
|
def test_addition_float
|
80
|
-
|
81
|
-
assert(match)
|
82
|
-
assert_equal('1.5+3', match.text)
|
83
|
-
assert_equal(5, match.length)
|
84
|
-
assert_equal(4.5, match.value)
|
74
|
+
do_test('1.5+3')
|
85
75
|
end
|
86
76
|
|
87
77
|
def test_subtraction
|
88
|
-
|
89
|
-
assert(match)
|
90
|
-
assert_equal(1, match.value)
|
78
|
+
do_test('3-2')
|
91
79
|
end
|
92
80
|
|
93
81
|
def test_subtraction_float
|
94
|
-
|
95
|
-
assert(match)
|
96
|
-
assert_equal('4.5-3', match.text)
|
97
|
-
assert_equal(5, match.length)
|
98
|
-
assert_equal(1.5, match.value)
|
82
|
+
do_test('4.5-3')
|
99
83
|
end
|
100
84
|
|
101
85
|
def test_multiplication
|
102
|
-
|
103
|
-
assert(match)
|
104
|
-
assert_equal(10, match.value)
|
86
|
+
do_test('2*5')
|
105
87
|
end
|
106
88
|
|
107
89
|
def test_multiplication_float
|
108
|
-
|
109
|
-
assert(match)
|
110
|
-
assert_equal('1.5*3', match.text)
|
111
|
-
assert_equal(5, match.length)
|
112
|
-
assert_equal(4.5, match.value)
|
90
|
+
do_test('1.5*3')
|
113
91
|
end
|
114
92
|
|
115
93
|
def test_division
|
116
|
-
|
117
|
-
assert(match)
|
118
|
-
assert_equal(4, match.value)
|
94
|
+
do_test('20/5')
|
119
95
|
end
|
120
96
|
|
121
97
|
def test_division_float
|
122
|
-
|
123
|
-
assert(match)
|
124
|
-
assert_equal('4.5/3', match.text)
|
125
|
-
assert_equal(5, match.length)
|
126
|
-
assert_equal(1.5, match.value)
|
98
|
+
do_test('4.5/3')
|
127
99
|
end
|
128
100
|
|
129
101
|
def test_complex
|
130
|
-
|
131
|
-
assert(match)
|
132
|
-
assert_equal('7*4+3.5*(4.5/3)', match.text)
|
133
|
-
assert_equal(33.25, match.value)
|
102
|
+
do_test('7*4+3.5*(4.5/3)')
|
134
103
|
end
|
135
104
|
|
136
105
|
def test_complex_spaced
|
137
|
-
|
138
|
-
assert(match)
|
139
|
-
assert_equal(33.25, match.value)
|
106
|
+
do_test('7 * 4 + 3.5 * (4.5 / 3)')
|
140
107
|
end
|
141
108
|
end
|
142
|
-
|
143
109
|
end
|
data/test/label_test.rb
CHANGED
data/test/match_test.rb
CHANGED
data/test/not_predicate_test.rb
CHANGED
data/test/repeat_test.rb
CHANGED
data/test/rule_test.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require File.
|
1
|
+
require File.expand_path('../helper', __FILE__)
|
2
2
|
|
3
3
|
class RuleTest < Test::Unit::TestCase
|
4
4
|
|
@@ -22,7 +22,7 @@ class RuleTest < Test::Unit::TestCase
|
|
22
22
|
|
23
23
|
def test_match_module
|
24
24
|
rule = EqualRule.new('a')
|
25
|
-
rule.
|
25
|
+
rule.extension = MatchModule
|
26
26
|
match = rule.match(input('a'))
|
27
27
|
assert(match)
|
28
28
|
assert_equal(:test, match.a_test)
|
@@ -30,7 +30,7 @@ class RuleTest < Test::Unit::TestCase
|
|
30
30
|
|
31
31
|
def test_numeric_proc
|
32
32
|
rule = EqualRule.new(1)
|
33
|
-
rule.
|
33
|
+
rule.extension = NumericProc
|
34
34
|
match = rule.match(input('1'))
|
35
35
|
assert(match)
|
36
36
|
assert_equal(1, match.to_i)
|
@@ -39,7 +39,7 @@ class RuleTest < Test::Unit::TestCase
|
|
39
39
|
|
40
40
|
def test_numeric_module
|
41
41
|
rule = EqualRule.new(1)
|
42
|
-
rule.
|
42
|
+
rule.extension = NumericModule
|
43
43
|
match = rule.match(input('1'))
|
44
44
|
assert(match)
|
45
45
|
assert_equal(1, match.to_i)
|
data/test/sequence_test.rb
CHANGED
data/test/super_test.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require File.
|
1
|
+
require File.expand_path('../helper', __FILE__)
|
2
2
|
Citrus.load(File.dirname(__FILE__) + '/_files/super')
|
3
3
|
|
4
4
|
class SuperTest < Test::Unit::TestCase
|
@@ -20,13 +20,13 @@ class SuperTest < Test::Unit::TestCase
|
|
20
20
|
|
21
21
|
match = grammar2.parse('b')
|
22
22
|
assert(match)
|
23
|
-
|
24
|
-
|
23
|
+
assert_equal('b', match.text)
|
24
|
+
assert_equal(1, match.length)
|
25
25
|
|
26
26
|
match = grammar2.parse('a')
|
27
27
|
assert(match)
|
28
|
-
|
29
|
-
|
28
|
+
assert_equal('a', match.text)
|
29
|
+
assert_equal(1, match.length)
|
30
30
|
end
|
31
31
|
|
32
32
|
def test_peg
|
metadata
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: citrus
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
+
hash: 3
|
4
5
|
prerelease: false
|
5
6
|
segments:
|
6
7
|
- 1
|
7
|
-
-
|
8
|
+
- 5
|
8
9
|
- 0
|
9
|
-
version: 1.
|
10
|
+
version: 1.5.0
|
10
11
|
platform: ruby
|
11
12
|
authors:
|
12
13
|
- Michael Jackson
|
@@ -14,16 +15,18 @@ autorequire:
|
|
14
15
|
bindir: bin
|
15
16
|
cert_chain: []
|
16
17
|
|
17
|
-
date: 2010-
|
18
|
+
date: 2010-07-24 00:00:00 -06:00
|
18
19
|
default_executable:
|
19
20
|
dependencies:
|
20
21
|
- !ruby/object:Gem::Dependency
|
21
22
|
name: builder
|
22
23
|
prerelease: false
|
23
24
|
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
24
26
|
requirements:
|
25
27
|
- - ">="
|
26
28
|
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
27
30
|
segments:
|
28
31
|
- 0
|
29
32
|
version: "0"
|
@@ -33,9 +36,11 @@ dependencies:
|
|
33
36
|
name: rake
|
34
37
|
prerelease: false
|
35
38
|
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
36
40
|
requirements:
|
37
41
|
- - ">="
|
38
42
|
- !ruby/object:Gem::Version
|
43
|
+
hash: 3
|
39
44
|
segments:
|
40
45
|
- 0
|
41
46
|
version: "0"
|
@@ -50,9 +55,9 @@ extensions: []
|
|
50
55
|
extra_rdoc_files:
|
51
56
|
- README
|
52
57
|
files:
|
53
|
-
- benchmark/seqpar.rb
|
54
58
|
- benchmark/seqpar.citrus
|
55
59
|
- benchmark/seqpar.gnuplot
|
60
|
+
- benchmark/seqpar.rb
|
56
61
|
- doc/background.rdoc
|
57
62
|
- doc/example.rdoc
|
58
63
|
- doc/index.rdoc
|
@@ -61,16 +66,21 @@ files:
|
|
61
66
|
- doc/syntax.rdoc
|
62
67
|
- examples/calc.citrus
|
63
68
|
- examples/calc.rb
|
64
|
-
- examples/calc_sugar.rb
|
65
69
|
- extras/citrus.vim
|
66
70
|
- lib/citrus/debug.rb
|
67
71
|
- lib/citrus/file.rb
|
68
|
-
- lib/citrus/sugar.rb
|
69
72
|
- lib/citrus.rb
|
73
|
+
- test/_files/alias.citrus
|
74
|
+
- test/_files/grammar1.citrus
|
75
|
+
- test/_files/grammar2.citrus
|
76
|
+
- test/_files/grammar3.citrus
|
77
|
+
- test/_files/rule1.citrus
|
78
|
+
- test/_files/rule2.citrus
|
79
|
+
- test/_files/rule3.citrus
|
80
|
+
- test/_files/super.citrus
|
70
81
|
- test/alias_test.rb
|
71
82
|
- test/and_predicate_test.rb
|
72
83
|
- test/calc_file_test.rb
|
73
|
-
- test/calc_sugar_test.rb
|
74
84
|
- test/calc_test.rb
|
75
85
|
- test/choice_test.rb
|
76
86
|
- test/expression_test.rb
|
@@ -103,23 +113,27 @@ rdoc_options:
|
|
103
113
|
require_paths:
|
104
114
|
- lib
|
105
115
|
required_ruby_version: !ruby/object:Gem::Requirement
|
116
|
+
none: false
|
106
117
|
requirements:
|
107
118
|
- - ">="
|
108
119
|
- !ruby/object:Gem::Version
|
120
|
+
hash: 3
|
109
121
|
segments:
|
110
122
|
- 0
|
111
123
|
version: "0"
|
112
124
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
125
|
+
none: false
|
113
126
|
requirements:
|
114
127
|
- - ">="
|
115
128
|
- !ruby/object:Gem::Version
|
129
|
+
hash: 3
|
116
130
|
segments:
|
117
131
|
- 0
|
118
132
|
version: "0"
|
119
133
|
requirements: []
|
120
134
|
|
121
135
|
rubyforge_project:
|
122
|
-
rubygems_version: 1.3.
|
136
|
+
rubygems_version: 1.3.7
|
123
137
|
signing_key:
|
124
138
|
specification_version: 3
|
125
139
|
summary: Parsing Expressions for Ruby
|
@@ -127,7 +141,6 @@ test_files:
|
|
127
141
|
- test/alias_test.rb
|
128
142
|
- test/and_predicate_test.rb
|
129
143
|
- test/calc_file_test.rb
|
130
|
-
- test/calc_sugar_test.rb
|
131
144
|
- test/calc_test.rb
|
132
145
|
- test/choice_test.rb
|
133
146
|
- test/expression_test.rb
|
data/examples/calc_sugar.rb
DELETED
@@ -1,87 +0,0 @@
|
|
1
|
-
require 'citrus/sugar'
|
2
|
-
|
3
|
-
# A grammar for mathematical formulas that apply the basic four operations to
|
4
|
-
# non-negative numbers (integers and floats), respecting operator precedence and
|
5
|
-
# ignoring whitespace.
|
6
|
-
Calc = Citrus::Grammar.new {
|
7
|
-
rule term do
|
8
|
-
any(additive, factor)
|
9
|
-
end
|
10
|
-
|
11
|
-
rule additive do
|
12
|
-
all(factor, label(additive_op, operator), term) {
|
13
|
-
def value
|
14
|
-
operator.apply(factor.value, term.value)
|
15
|
-
end
|
16
|
-
}
|
17
|
-
end
|
18
|
-
|
19
|
-
rule factor do
|
20
|
-
any(multiplicative, primary)
|
21
|
-
end
|
22
|
-
|
23
|
-
rule multiplicative do
|
24
|
-
all(primary, label(multiplicative_op, operator), factor) {
|
25
|
-
def value
|
26
|
-
operator.apply(primary.value, factor.value)
|
27
|
-
end
|
28
|
-
}
|
29
|
-
end
|
30
|
-
|
31
|
-
rule primary do
|
32
|
-
any(term_paren, number)
|
33
|
-
end
|
34
|
-
|
35
|
-
rule term_paren do
|
36
|
-
all(lparen, term, rparen) {
|
37
|
-
def value
|
38
|
-
term.value
|
39
|
-
end
|
40
|
-
}
|
41
|
-
end
|
42
|
-
|
43
|
-
rule additive_op do
|
44
|
-
any(plus, minus) {
|
45
|
-
def apply(factor, term)
|
46
|
-
text.strip == '+' ? factor + term : factor - term
|
47
|
-
end
|
48
|
-
}
|
49
|
-
end
|
50
|
-
|
51
|
-
rule multiplicative_op do
|
52
|
-
any(star, slash) {
|
53
|
-
def apply(primary, factor)
|
54
|
-
text.strip == '*' ? primary * factor : primary / factor
|
55
|
-
end
|
56
|
-
}
|
57
|
-
end
|
58
|
-
|
59
|
-
rule number do
|
60
|
-
any(float, integer)
|
61
|
-
end
|
62
|
-
|
63
|
-
rule float do
|
64
|
-
all(/[0-9]+/, '.', /[0-9]+/, space) {
|
65
|
-
def value
|
66
|
-
text.strip.to_f
|
67
|
-
end
|
68
|
-
}
|
69
|
-
end
|
70
|
-
|
71
|
-
rule integer do
|
72
|
-
all(/[0-9]+/, space) {
|
73
|
-
def value
|
74
|
-
text.strip.to_i
|
75
|
-
end
|
76
|
-
}
|
77
|
-
end
|
78
|
-
|
79
|
-
rule lparen, ['(', space]
|
80
|
-
rule rparen, [')', space]
|
81
|
-
rule plus, ['+', space]
|
82
|
-
rule minus, ['-', space]
|
83
|
-
rule star, ['*', space]
|
84
|
-
rule slash, ['/', space]
|
85
|
-
|
86
|
-
rule space, /[ \t\n\r]*/
|
87
|
-
}
|
data/lib/citrus/sugar.rb
DELETED
@@ -1,25 +0,0 @@
|
|
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
|
data/test/calc_sugar_test.rb
DELETED