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.
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