ghostwheel 0.0.1
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/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
|