ghostwheel 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/Rakefile +46 -0
  2. data/lib/ghost_wheel.rb +28 -0
  3. data/lib/ghost_wheel/build_parser.rb +11 -0
  4. data/lib/ghost_wheel/errors.rb +9 -0
  5. data/lib/ghost_wheel/expression.rb +22 -0
  6. data/lib/ghost_wheel/expression/alternation.rb +25 -0
  7. data/lib/ghost_wheel/expression/empty.rb +15 -0
  8. data/lib/ghost_wheel/expression/end_of_file.rb +19 -0
  9. data/lib/ghost_wheel/expression/literal.rb +31 -0
  10. data/lib/ghost_wheel/expression/look_ahead.rb +33 -0
  11. data/lib/ghost_wheel/expression/optional.rb +26 -0
  12. data/lib/ghost_wheel/expression/query.rb +32 -0
  13. data/lib/ghost_wheel/expression/repetition.rb +44 -0
  14. data/lib/ghost_wheel/expression/rule.rb +24 -0
  15. data/lib/ghost_wheel/expression/sequence.rb +30 -0
  16. data/lib/ghost_wheel/expression/transform.rb +30 -0
  17. data/lib/ghost_wheel/parse_results.rb +9 -0
  18. data/lib/ghost_wheel/parser.rb +71 -0
  19. data/lib/ghost_wheel/parser_builder/ghost_wheel.rb +100 -0
  20. data/lib/ghost_wheel/parser_builder/ruby.rb +175 -0
  21. data/lib/ghost_wheel/scanner.rb +42 -0
  22. data/setup.rb +1360 -0
  23. data/test/dsl/tc_build_parser.rb +29 -0
  24. data/test/dsl/tc_ghost_wheel_dsl.rb +143 -0
  25. data/test/dsl/tc_ruby_dsl.rb +227 -0
  26. data/test/example/tc_json_core.rb +95 -0
  27. data/test/example/tc_json_ghost_wheel.rb +48 -0
  28. data/test/example/tc_json_ruby.rb +81 -0
  29. data/test/helpers/ghost_wheel_namespace.rb +24 -0
  30. data/test/helpers/json_tests.rb +63 -0
  31. data/test/helpers/parse_helpers.rb +83 -0
  32. data/test/parser/tc_alternation_expression.rb +27 -0
  33. data/test/parser/tc_empty_expression.rb +15 -0
  34. data/test/parser/tc_end_of_file_expression.rb +27 -0
  35. data/test/parser/tc_literal_expression.rb +55 -0
  36. data/test/parser/tc_look_ahead_expression.rb +41 -0
  37. data/test/parser/tc_memoization.rb +31 -0
  38. data/test/parser/tc_optional_expression.rb +31 -0
  39. data/test/parser/tc_parser.rb +78 -0
  40. data/test/parser/tc_query_expression.rb +40 -0
  41. data/test/parser/tc_repetition_expression.rb +76 -0
  42. data/test/parser/tc_rule_expression.rb +32 -0
  43. data/test/parser/tc_scanning.rb +192 -0
  44. data/test/parser/tc_sequence_expression.rb +42 -0
  45. data/test/parser/tc_transform_expression.rb +77 -0
  46. data/test/ts_all.rb +7 -0
  47. data/test/ts_dsl.rb +8 -0
  48. data/test/ts_example.rb +7 -0
  49. data/test/ts_parser.rb +19 -0
  50. 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