ghostwheel 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +46 -0
- data/lib/ghost_wheel.rb +28 -0
- data/lib/ghost_wheel/build_parser.rb +11 -0
- data/lib/ghost_wheel/errors.rb +9 -0
- data/lib/ghost_wheel/expression.rb +22 -0
- data/lib/ghost_wheel/expression/alternation.rb +25 -0
- data/lib/ghost_wheel/expression/empty.rb +15 -0
- data/lib/ghost_wheel/expression/end_of_file.rb +19 -0
- data/lib/ghost_wheel/expression/literal.rb +31 -0
- data/lib/ghost_wheel/expression/look_ahead.rb +33 -0
- data/lib/ghost_wheel/expression/optional.rb +26 -0
- data/lib/ghost_wheel/expression/query.rb +32 -0
- data/lib/ghost_wheel/expression/repetition.rb +44 -0
- data/lib/ghost_wheel/expression/rule.rb +24 -0
- data/lib/ghost_wheel/expression/sequence.rb +30 -0
- data/lib/ghost_wheel/expression/transform.rb +30 -0
- data/lib/ghost_wheel/parse_results.rb +9 -0
- data/lib/ghost_wheel/parser.rb +71 -0
- data/lib/ghost_wheel/parser_builder/ghost_wheel.rb +100 -0
- data/lib/ghost_wheel/parser_builder/ruby.rb +175 -0
- data/lib/ghost_wheel/scanner.rb +42 -0
- data/setup.rb +1360 -0
- data/test/dsl/tc_build_parser.rb +29 -0
- data/test/dsl/tc_ghost_wheel_dsl.rb +143 -0
- data/test/dsl/tc_ruby_dsl.rb +227 -0
- data/test/example/tc_json_core.rb +95 -0
- data/test/example/tc_json_ghost_wheel.rb +48 -0
- data/test/example/tc_json_ruby.rb +81 -0
- data/test/helpers/ghost_wheel_namespace.rb +24 -0
- data/test/helpers/json_tests.rb +63 -0
- data/test/helpers/parse_helpers.rb +83 -0
- data/test/parser/tc_alternation_expression.rb +27 -0
- data/test/parser/tc_empty_expression.rb +15 -0
- data/test/parser/tc_end_of_file_expression.rb +27 -0
- data/test/parser/tc_literal_expression.rb +55 -0
- data/test/parser/tc_look_ahead_expression.rb +41 -0
- data/test/parser/tc_memoization.rb +31 -0
- data/test/parser/tc_optional_expression.rb +31 -0
- data/test/parser/tc_parser.rb +78 -0
- data/test/parser/tc_query_expression.rb +40 -0
- data/test/parser/tc_repetition_expression.rb +76 -0
- data/test/parser/tc_rule_expression.rb +32 -0
- data/test/parser/tc_scanning.rb +192 -0
- data/test/parser/tc_sequence_expression.rb +42 -0
- data/test/parser/tc_transform_expression.rb +77 -0
- data/test/ts_all.rb +7 -0
- data/test/ts_dsl.rb +8 -0
- data/test/ts_example.rb +7 -0
- data/test/ts_parser.rb +19 -0
- metadata +94 -0
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
|
3
|
+
require "test/unit"
|
4
|
+
|
5
|
+
require "ghost_wheel"
|
6
|
+
|
7
|
+
class TestRubyDSL < Test::Unit::TestCase
|
8
|
+
def test_build_parser
|
9
|
+
# with instance_eval()
|
10
|
+
parser = GhostWheel.build_parser do
|
11
|
+
parser(:test_rule, "one")
|
12
|
+
end
|
13
|
+
assert_instance_of(GhostWheel::Parser, parser)
|
14
|
+
assert_instance_of(GhostWheel::Expression::Rule, parser[:test_rule])
|
15
|
+
|
16
|
+
assert_respond_to(parser, :parse_test_rule)
|
17
|
+
assert_respond_to(parser, :parse)
|
18
|
+
|
19
|
+
# without instance_eval()
|
20
|
+
parser = GhostWheel.build_parser do |b|
|
21
|
+
b.parser(:test_rule, "one")
|
22
|
+
end
|
23
|
+
assert_instance_of(GhostWheel::Parser, parser)
|
24
|
+
assert_instance_of(GhostWheel::Expression::Rule, parser[:test_rule])
|
25
|
+
|
26
|
+
assert_respond_to(parser, :parse_test_rule)
|
27
|
+
assert_respond_to(parser, :parse)
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
|
3
|
+
require "test/unit"
|
4
|
+
|
5
|
+
require File.join(File.dirname(__FILE__), %w[.. helpers ghost_wheel_namespace])
|
6
|
+
|
7
|
+
class TestGhostWheelDSL < Test::Unit::TestCase
|
8
|
+
include GhostWheelNamespace
|
9
|
+
|
10
|
+
def test_rule_definition
|
11
|
+
build "word = 'word'"
|
12
|
+
assert_instance_of(Rule, @parser[:word])
|
13
|
+
assert_equal(Rule.new(Literal.new('word')), @parser[:word])
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_parser_definition
|
17
|
+
build "word := 'word'"
|
18
|
+
assert_instance_of(Rule, @parser[:word])
|
19
|
+
assert_equal(Rule.new(Literal.new('word')), @parser[:word])
|
20
|
+
assert_respond_to(@parser, :parse_word)
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_literal_single_quoted_string
|
24
|
+
build "str = 'string'"
|
25
|
+
assert_equal(Literal.new("string"), @parser[:str].expression)
|
26
|
+
|
27
|
+
build "str = '\\'escaped\\' string'"
|
28
|
+
assert_equal(Literal.new("'escaped' string"), @parser[:str].expression)
|
29
|
+
|
30
|
+
build "str = '\\'tricky\\\\\\' string'"
|
31
|
+
assert_equal(Literal.new("'tricky\\' string"), @parser[:str].expression)
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_literal_double_quoted_string
|
35
|
+
build 'str = "string"'
|
36
|
+
assert_equal(Literal.new("string"), @parser[:str].expression)
|
37
|
+
|
38
|
+
build 'str = "\"escaped\" string"'
|
39
|
+
assert_equal(Literal.new(%Q{"escaped" string}), @parser[:str].expression)
|
40
|
+
|
41
|
+
build 'str = "\"tricky\\\\\" string"'
|
42
|
+
assert_equal(Literal.new(%Q{"tricky\\" string}), @parser[:str].expression)
|
43
|
+
|
44
|
+
build 'str = "\t"'
|
45
|
+
assert_equal(Literal.new("\t"), @parser[:str].expression)
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_literal_regexp
|
49
|
+
build "re = /regexp/"
|
50
|
+
assert_equal(Literal.new(/regexp/), @parser[:re].expression)
|
51
|
+
|
52
|
+
build "re = /\\/escaped\\/ regexp/"
|
53
|
+
assert_equal(Literal.new(%r{/escaped/ regexp}), @parser[:re].expression)
|
54
|
+
|
55
|
+
build "re = /\\/tricky\\\\\\/ regexp/"
|
56
|
+
assert_equal(Literal.new(%r{/tricky\\/ regexp}), @parser[:re].expression)
|
57
|
+
|
58
|
+
build "re = /\\t/"
|
59
|
+
assert_equal(Literal.new(/\t/), @parser[:re].expression)
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_reference
|
63
|
+
build "ref = another"
|
64
|
+
assert_instance_of(Rule, @parser[:another])
|
65
|
+
assert_equal(Rule.new(Rule.new), @parser[:ref])
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_sequence
|
69
|
+
build "seq = 'a' 'b'"
|
70
|
+
assert_equal( Sequence.new(Literal.new("a"), Literal.new("b")),
|
71
|
+
@parser[:seq].expression )
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_transformation
|
75
|
+
build "trans := 'a' { ast * 2 }"
|
76
|
+
assert_instance_of(Transform, @parser[:trans].expression)
|
77
|
+
assert_equal(Literal.new("a"), @parser[:trans].expression.expression)
|
78
|
+
assert_equal("aa", @parser.parse_trans("a"))
|
79
|
+
end
|
80
|
+
|
81
|
+
def test_alternation
|
82
|
+
build "alt = 'a' | 'b'"
|
83
|
+
assert_equal( Alternation.new(Literal.new("a"), Literal.new("b")),
|
84
|
+
@parser[:alt].expression )
|
85
|
+
end
|
86
|
+
|
87
|
+
def test_modifiers
|
88
|
+
build "opt = 'a'?"
|
89
|
+
assert_equal(Optional.new(Literal.new("a")), @parser[:opt].expression)
|
90
|
+
|
91
|
+
build "rep = 'a'*"
|
92
|
+
assert_equal(Repetition.new(Literal.new("a")), @parser[:rep].expression)
|
93
|
+
|
94
|
+
build "rep = 'a'+"
|
95
|
+
assert_equal(Repetition.new(Literal.new("a"), 1), @parser[:rep].expression)
|
96
|
+
end
|
97
|
+
|
98
|
+
def test_eof
|
99
|
+
build "eof = EOF"
|
100
|
+
assert_equal(EndOfFile.instance, @parser[:eof].expression)
|
101
|
+
end
|
102
|
+
|
103
|
+
def test_precedence
|
104
|
+
build "prec = 'a' 'b'? 'c'"
|
105
|
+
assert_equal( Sequence.new( Literal.new("a"),
|
106
|
+
Optional.new(Literal.new("b")),
|
107
|
+
Literal.new("c") ),
|
108
|
+
@parser[:prec].expression )
|
109
|
+
|
110
|
+
build "prec = 'a' 'b' | 'c'"
|
111
|
+
assert_equal( Alternation.new( Sequence.new( Literal.new("a"),
|
112
|
+
Literal.new("b") ),
|
113
|
+
Literal.new("c") ),
|
114
|
+
@parser[:prec].expression )
|
115
|
+
|
116
|
+
build "prec := 'a' 'b' { ast * 2 } | 'c' { ast * 3 }"
|
117
|
+
assert_instance_of(Alternation, @parser[:prec].expression)
|
118
|
+
left, right = @parser[:prec].expression.expressions
|
119
|
+
assert_instance_of(Transform, left)
|
120
|
+
assert_equal( Sequence.new(Literal.new("a"), Literal.new("b")),
|
121
|
+
left.expression )
|
122
|
+
assert_equal(%w[a b a b], @parser.parse_prec("ab"))
|
123
|
+
assert_instance_of(Transform, right)
|
124
|
+
assert_equal(Literal.new("c"), right.expression)
|
125
|
+
assert_equal("ccc", @parser.parse_prec("c"))
|
126
|
+
end
|
127
|
+
|
128
|
+
def test_multiple_assignments
|
129
|
+
build <<-END_GRAMMAR
|
130
|
+
one = 'one'
|
131
|
+
two = 'two'
|
132
|
+
END_GRAMMAR
|
133
|
+
assert_equal(Literal.new("one"), @parser[:one].expression)
|
134
|
+
assert_equal(Literal.new("two"), @parser[:two].expression)
|
135
|
+
end
|
136
|
+
|
137
|
+
private
|
138
|
+
|
139
|
+
def build(grammar)
|
140
|
+
@builder = ParserBuilder::GhostWheel.new(grammar)
|
141
|
+
@parser = @builder.to_parser
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,227 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
|
3
|
+
require "test/unit"
|
4
|
+
|
5
|
+
require File.join(File.dirname(__FILE__), %w[.. helpers ghost_wheel_namespace])
|
6
|
+
|
7
|
+
class TestRubyDSL < Test::Unit::TestCase
|
8
|
+
include GhostWheelNamespace
|
9
|
+
|
10
|
+
def setup
|
11
|
+
@builder = ParserBuilder::Ruby.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_to_parser
|
15
|
+
assert_instance_of(Parser, @builder.to_parser)
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_dual_build_interface
|
19
|
+
build do |b|
|
20
|
+
assert_instance_of(TestRubyDSL, self)
|
21
|
+
assert_instance_of(ParserBuilder::Ruby, b)
|
22
|
+
end
|
23
|
+
|
24
|
+
test_case = self # preparing for instance_eval()
|
25
|
+
build { test_case.assert_instance_of(ParserBuilder::Ruby, self) }
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_rule
|
29
|
+
build { rule(:test_rule, nil) }
|
30
|
+
|
31
|
+
assert_instance_of(Rule, @parser[:test_rule])
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_parser_builds_rule
|
35
|
+
build { parser(:test_parser, nil) }
|
36
|
+
|
37
|
+
assert_instance_of(Rule, @parser[:test_parser])
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_parser_wraps_rule_adding_scanner_for_source
|
41
|
+
build { parser(:test_parser, nil) }
|
42
|
+
|
43
|
+
assert_respond_to(@parser, :parse_test_parser)
|
44
|
+
assert_raise(EmptyRuleError) { @parser.parse_test_parser("not_used") }
|
45
|
+
|
46
|
+
@parser[:test_parser].expression = Literal.new(/\w+/)
|
47
|
+
assert_equal("first", @parser.parse_test_parser("first word"))
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_parser_adds_parse_shortcut
|
51
|
+
assert( !@builder.to_parser.respond_to?(:parse),
|
52
|
+
"A parse() method was defined, but not expected." )
|
53
|
+
|
54
|
+
build { parser(:first_parser, nil) }
|
55
|
+
assert_respond_to(@builder.to_parser, :parse_first_parser)
|
56
|
+
assert_respond_to(@builder.to_parser, :parse)
|
57
|
+
|
58
|
+
build { parser(:second_parser, nil) }
|
59
|
+
assert_respond_to(@builder.to_parser, :parse_first_parser)
|
60
|
+
assert_respond_to(@builder.to_parser, :parse_second_parser)
|
61
|
+
assert( !@builder.to_parser.respond_to?(:parse),
|
62
|
+
"A parse() method was defined, but not expected." )
|
63
|
+
|
64
|
+
build { parser(:third_parser, nil) }
|
65
|
+
assert_respond_to(@builder.to_parser, :parse_first_parser)
|
66
|
+
assert_respond_to(@builder.to_parser, :parse_second_parser)
|
67
|
+
assert_respond_to(@builder.to_parser, :parse_third_parser)
|
68
|
+
assert( !@builder.to_parser.respond_to?(:parse),
|
69
|
+
"A parse() method was defined, but not expected." )
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_parser_result_handling
|
73
|
+
build { parser(:create_result, Literal.new(/\w+/)) }
|
74
|
+
|
75
|
+
result = @parser.parse_create_result("successful parse")
|
76
|
+
assert_instance_of(String, result)
|
77
|
+
assert_equal("successful", result)
|
78
|
+
|
79
|
+
assert_raise(FailedParseError) { @parser.parse_create_result("") }
|
80
|
+
|
81
|
+
# default
|
82
|
+
@parser[:create_result].expression = Empty.instance
|
83
|
+
assert_raise(EmptyParseError) { @parser.parse_create_result("not_used") }
|
84
|
+
|
85
|
+
# override empty return
|
86
|
+
assert_equal(42, @parser.parse_create_result("not_used", 42))
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_ref
|
90
|
+
# post reference
|
91
|
+
build { parser(:post, Literal.new(/\w+/)) }
|
92
|
+
assert_same(@parser[:post], @builder.ref(:post))
|
93
|
+
|
94
|
+
# pre reference
|
95
|
+
ref = @builder.ref(:pre)
|
96
|
+
assert_instance_of(Rule, ref)
|
97
|
+
assert_nil(ref.expression)
|
98
|
+
build { parser(:pre, Literal.new(/\w+/)) }
|
99
|
+
assert_not_nil(ref.expression)
|
100
|
+
assert_same(@parser[:pre], ref)
|
101
|
+
end
|
102
|
+
|
103
|
+
def test_expression_factories
|
104
|
+
assert_equal(Literal.new("keyword"), @builder.lit("keyword"))
|
105
|
+
|
106
|
+
assert_equal(Literal.new("keyword", true), @builder.skip("keyword"))
|
107
|
+
assert_equal(Literal.new("keyword", true), @builder.lit(true, "keyword"))
|
108
|
+
assert_equal(Literal.new("keyword", true), @builder.lit("keyword", true))
|
109
|
+
|
110
|
+
assert_equal( Sequence.new(Literal.new("one"), Literal.new("two")),
|
111
|
+
@builder.seq(@builder.lit("one"), @builder.lit("two")) )
|
112
|
+
|
113
|
+
assert_equal( Alternation.new(Literal.new("one"), Literal.new("two")),
|
114
|
+
@builder.alt(@builder.lit("one"), @builder.lit("two")) )
|
115
|
+
|
116
|
+
assert_equal(Empty.instance, @builder.empty)
|
117
|
+
|
118
|
+
assert_equal( Optional.new(Literal.new("one")),
|
119
|
+
@builder.opt(@builder.lit("one")) )
|
120
|
+
|
121
|
+
assert_equal( Repetition.new(Literal.new("one")),
|
122
|
+
@builder.zplus(@builder.lit("one")) )
|
123
|
+
assert_equal( Repetition.new(Literal.new("one"), 1),
|
124
|
+
@builder.oplus(@builder.lit("one")) )
|
125
|
+
assert_equal( Repetition.new(Literal.new("one"), 1),
|
126
|
+
@builder.min(1, @builder.lit("one")) )
|
127
|
+
assert_equal( Repetition.new(Literal.new("one"), 0, 5),
|
128
|
+
@builder.max(5, @builder.lit("one")) )
|
129
|
+
assert_equal( Repetition.new(Literal.new("one"), 1, 5),
|
130
|
+
@builder.minmax(1, 5, @builder.lit("one")) )
|
131
|
+
assert_equal( Repetition.new(Literal.new("one"), 5, 5),
|
132
|
+
@builder.count(5, @builder.lit("one")) )
|
133
|
+
|
134
|
+
assert_equal( LookAhead.new(Literal.new("one")),
|
135
|
+
@builder.look(@builder.lit("one")) )
|
136
|
+
|
137
|
+
assert_equal( LookAhead.new(Literal.new("one"), true),
|
138
|
+
@builder.nlook(@builder.lit("one")) )
|
139
|
+
assert_equal( LookAhead.new(Literal.new("one"), true),
|
140
|
+
@builder.look(true, @builder.lit("one")) )
|
141
|
+
assert_equal( LookAhead.new(Literal.new("one"), true),
|
142
|
+
@builder.look(@builder.lit("one"), true) )
|
143
|
+
|
144
|
+
assert_equal(EndOfFile.instance, @builder.eof)
|
145
|
+
|
146
|
+
assert_equal( Query.new(Literal.new("one")) { },
|
147
|
+
@builder.query(@builder.lit("one")) { } )
|
148
|
+
|
149
|
+
assert_equal( Transform.new(Literal.new("one")) { },
|
150
|
+
@builder.trans(@builder.lit("one")) { } )
|
151
|
+
end
|
152
|
+
|
153
|
+
def test_literal_expression_shortcut
|
154
|
+
["one", /\w+/, ?a].each do |literal|
|
155
|
+
%w[ seq alt opt zplus oplus min max minmax count
|
156
|
+
look nlook query trans ].each do |wrapper|
|
157
|
+
wrapped = if %w[min max count].include? wrapper
|
158
|
+
@builder.send(wrapper, 5, literal)
|
159
|
+
elsif wrapper == "minmax"
|
160
|
+
@builder.send(wrapper, 5, 5, literal)
|
161
|
+
else
|
162
|
+
@builder.send(wrapper, literal)
|
163
|
+
end
|
164
|
+
unwrapped = wrapped.expressions.first rescue wrapped.expression
|
165
|
+
assert_equal(Literal.new(literal), unwrapped)
|
166
|
+
end
|
167
|
+
%w[seq alt].each do |wrapper|
|
168
|
+
assert_equal( [Literal.new(literal), Literal.new("two")],
|
169
|
+
@builder.send(wrapper, literal, "two").expressions )
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def test_reference_shortcut
|
175
|
+
build do
|
176
|
+
parser(:test_rule, Literal.new(/\w+/))
|
177
|
+
parser(:another_rule, Literal.new("two"))
|
178
|
+
end
|
179
|
+
%w[ seq alt opt zplus oplus min max minmax count
|
180
|
+
look nlook query trans ].each do |wrapper|
|
181
|
+
wrapped = if %w[min max count].include? wrapper
|
182
|
+
@builder.send(wrapper, 5, :test_rule)
|
183
|
+
elsif wrapper == "minmax"
|
184
|
+
@builder.send(wrapper, 5, 5, :test_rule)
|
185
|
+
else
|
186
|
+
@builder.send(wrapper, :test_rule)
|
187
|
+
end
|
188
|
+
unwrapped = wrapped.expressions.first rescue wrapped.expression
|
189
|
+
assert_same(@parser[:test_rule], unwrapped)
|
190
|
+
end
|
191
|
+
%w[seq alt].each do |wrapper|
|
192
|
+
assert_equal( [@parser[:test_rule], @parser[:another_rule]],
|
193
|
+
@builder.send( wrapper,
|
194
|
+
:test_rule,
|
195
|
+
:another_rule ).expressions )
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def test_transform_expression_shortcut
|
200
|
+
%w[ rule parser ref lit skip seq alt empty opt zplus oplus
|
201
|
+
min max minmax count look nlook eof ].each do |wrapper|
|
202
|
+
wrapped = if %w[rule parser].include? wrapper
|
203
|
+
@builder.send(wrapper, :test_rule, "lit") { 42 }
|
204
|
+
@builder.to_parser[:test_rule].expression
|
205
|
+
elsif wrapper == "ref"
|
206
|
+
@builder.send(wrapper, :test_rule) { 42 }
|
207
|
+
elsif %w[min max count].include? wrapper
|
208
|
+
@builder.send(wrapper, 5, "lit") { 42 }
|
209
|
+
elsif wrapper == "minmax"
|
210
|
+
@builder.send(wrapper, 5, 5, "lit") { 42 }
|
211
|
+
elsif %w[empty eof].include? wrapper
|
212
|
+
@builder.send(wrapper) { 42 }
|
213
|
+
else
|
214
|
+
@builder.send(wrapper, "lit") { 42 }
|
215
|
+
end
|
216
|
+
assert_instance_of(Transform, wrapped)
|
217
|
+
assert_equal(42, wrapped.transformer.call)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
private
|
222
|
+
|
223
|
+
def build(&block)
|
224
|
+
@builder.build(&block)
|
225
|
+
@parser = @builder.to_parser
|
226
|
+
end
|
227
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
|
3
|
+
require "test/unit"
|
4
|
+
|
5
|
+
require File.join(File.dirname(__FILE__), %w[.. helpers ghost_wheel_namespace])
|
6
|
+
require File.join(File.dirname(__FILE__), %w[.. helpers parse_helpers])
|
7
|
+
require File.join(File.dirname(__FILE__), %w[.. helpers json_tests])
|
8
|
+
|
9
|
+
class TestJSONCore < Test::Unit::TestCase
|
10
|
+
include GhostWheelNamespace
|
11
|
+
include ParseHelpers
|
12
|
+
include JSONTests
|
13
|
+
|
14
|
+
def setup
|
15
|
+
value = Rule.new
|
16
|
+
|
17
|
+
keyword = Transform.new(
|
18
|
+
Alternation.new(*%w[true false null].map { |k| Literal.new(k) })
|
19
|
+
) { |keyword| {"true" => true, "false" => false, "null" => nil}[keyword] }
|
20
|
+
|
21
|
+
number = Transform.new(
|
22
|
+
Literal.new(/-?(?:0|[1-9]\d*)(?:\.\d+(?:[eE][+-]?\d+)?)?/)
|
23
|
+
) { |number| number.include?(".") ? Float(number) : Integer(number) }
|
24
|
+
|
25
|
+
string = Transform.new(
|
26
|
+
Sequence.new(
|
27
|
+
Literal.new('"', true),
|
28
|
+
Repetition.new(
|
29
|
+
Alternation.new(
|
30
|
+
Transform.new(Literal.new(%r{\\["\\/]})) { |escape| escape[-1, 1] },
|
31
|
+
Transform.new(
|
32
|
+
Literal.new(/\\[bfnrt]/)
|
33
|
+
) { |escape| Hash[*%W[b \n f \f n \n r \r t \t]][escape[-1, 1]] },
|
34
|
+
Transform.new(
|
35
|
+
Literal.new(/\\u[0-9a-fA-F]{4}/)
|
36
|
+
) { |escape| [Integer("0x#{escape[2..-1]}")].pack("U") },
|
37
|
+
Literal.new(/[^\\"]+/)
|
38
|
+
)
|
39
|
+
),
|
40
|
+
Literal.new('"', true)
|
41
|
+
)
|
42
|
+
) { |string| string.flatten.join }
|
43
|
+
|
44
|
+
array = Transform.new(
|
45
|
+
Sequence.new(
|
46
|
+
Literal.new(/\[\s*/, true),
|
47
|
+
Alternation.new(
|
48
|
+
Transform.new(
|
49
|
+
Sequence.new(
|
50
|
+
Repetition.new(
|
51
|
+
Sequence.new(value, Literal.new(/\s*,\s*/, true)), 1
|
52
|
+
),
|
53
|
+
value
|
54
|
+
)
|
55
|
+
) { |array| array.first.inject(Array.new) { |vs, v| vs.push(*v) } +
|
56
|
+
[array.last] },
|
57
|
+
Sequence.new(Optional.new(value))
|
58
|
+
),
|
59
|
+
Literal.new(/\s*\]/, true)
|
60
|
+
)
|
61
|
+
) { |array| array.first }
|
62
|
+
|
63
|
+
object_pair = Transform.new(
|
64
|
+
Sequence.new(string, Literal.new(/\s*:\s*/), value)
|
65
|
+
) { |key_and_val| {key_and_val.first => key_and_val.last} }
|
66
|
+
object = Transform.new(
|
67
|
+
Sequence.new(
|
68
|
+
Literal.new(/\{\s*/, true),
|
69
|
+
Alternation.new(
|
70
|
+
Transform.new(
|
71
|
+
Sequence.new(
|
72
|
+
Repetition.new(
|
73
|
+
Sequence.new(object_pair, Literal.new(/\s*;\s*/, true)), 1
|
74
|
+
),
|
75
|
+
object_pair
|
76
|
+
)
|
77
|
+
) { |pairs| pairs.first.first + [pairs.last] },
|
78
|
+
Sequence.new(Optional.new(object_pair))
|
79
|
+
),
|
80
|
+
Literal.new(/\}\s*/, true)
|
81
|
+
)
|
82
|
+
) { |pairs| pairs.first.inject(Hash.new) { |hash, pair| hash.merge(pair) } }
|
83
|
+
|
84
|
+
space = Optional.new(Literal.new(/\s+/, true))
|
85
|
+
value.expression = Transform.new(
|
86
|
+
Sequence.new(
|
87
|
+
space, Alternation.new(object, array, string, number, keyword), space
|
88
|
+
)
|
89
|
+
) { |value| value.first }
|
90
|
+
|
91
|
+
@parser = Transform.new(
|
92
|
+
Sequence.new(value, EndOfFile.instance)
|
93
|
+
) { |json| json.first }
|
94
|
+
end
|
95
|
+
end
|