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,31 @@
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
+
8
+ class TestMemoization < Test::Unit::TestCase
9
+ include GhostWheelNamespace
10
+ include ParseHelpers
11
+
12
+ def setup
13
+ @source = Scanner.new("42")
14
+ end
15
+
16
+ def test_memoization
17
+ calls = 0
18
+ parser = Transform.new(Literal.new(/\d+/)) { |ast| calls += 1; ast }
19
+ cache = Hash.new
20
+
21
+ assert_equal(0, cache.size)
22
+
23
+ parsed = parser.parse(@source, cache)
24
+ @source.pos = 0
25
+ cached = parser.parse(@source, cache)
26
+
27
+ assert_not_equal(0, cache.size)
28
+ assert_same(parsed, cached)
29
+ assert_equal(1, calls)
30
+ end
31
+ end
@@ -0,0 +1,31 @@
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
+
8
+ class TestOptionalExpression < Test::Unit::TestCase
9
+ include GhostWheelNamespace
10
+ include ParseHelpers
11
+
12
+ def setup
13
+ @source = Scanner.new("data")
14
+ end
15
+
16
+ def test_match
17
+ assert_parses("data", parse_optional("data"))
18
+ end
19
+
20
+ def test_failed_match
21
+ assert_parses_empty(parse_optional("missing"))
22
+ end
23
+
24
+ def test_equality
25
+ assert_not_equal( Optional.new(Literal.new("one")),
26
+ Optional.new(Literal.new("two")) )
27
+
28
+ assert_equal( Optional.new(Literal.new("one")),
29
+ Optional.new(Literal.new("one")) )
30
+ end
31
+ end
@@ -0,0 +1,78 @@
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 TestScanning < Test::Unit::TestCase
8
+ include GhostWheelNamespace
9
+
10
+ def setup
11
+ @parser = Parser.new
12
+ end
13
+
14
+ def test_read_and_write_rules
15
+ assert_nil(@parser[:empty])
16
+
17
+ @parser[:empty] = Empty.instance
18
+ @parser[:space] = Literal.new(/\s+/)
19
+ assert_equal(Empty.instance, @parser[:empty])
20
+ assert_equal(Literal.new(/\s+/), @parser[:space])
21
+ end
22
+
23
+ def test_no_parsers_for_rules
24
+ @parser[:empty] = Empty.instance
25
+ assert( !@parser.respond_to?(:parse_empty),
26
+ "A parse_empty() method was defined, but not expected." )
27
+ assert_raise(NoMethodError) { @parser.parse_empty("not_used") }
28
+ assert( !@parser.respond_to?(:parse),
29
+ "A parse() method was defined, but not expected." )
30
+ assert_raise(NoMethodError) { @parser.parse("not_used") }
31
+ end
32
+
33
+ def test_named_parsers
34
+ add_parsers
35
+ assert_respond_to(@parser, :parse_empty)
36
+ assert_raise(GhostWheel::EmptyParseError) do
37
+ @parser.parse_empty("not_used")
38
+ end
39
+ assert_respond_to(@parser, :parse_space)
40
+ assert_equal(" ", @parser.parse_space(" word"))
41
+ end
42
+
43
+ def test_parse
44
+ # parse() works with just one rule
45
+ add_empty_parser
46
+ assert_respond_to(@parser, :parse)
47
+ assert_raise(GhostWheel::EmptyParseError) { @parser.parse("not_used") }
48
+
49
+ # but not with more than one
50
+ add_space_parser
51
+ assert( !@parser.respond_to?(:parse),
52
+ "A parse() method was defined, but not expected." )
53
+ assert_raise(NoMethodError) { @parser.parse("not_used") }
54
+ end
55
+
56
+ def test_pass_extra_arg
57
+ add_empty_parser
58
+ assert_equal(42, @parser.parse_empty("not_used", 42))
59
+ assert_equal(42, @parser.parse("not_used", 42))
60
+ end
61
+
62
+ private
63
+
64
+ def add_empty_parser
65
+ @parser[:empty] = Empty.instance
66
+ @parser.build_parser(:empty)
67
+ end
68
+
69
+ def add_space_parser
70
+ @parser[:space] = Literal.new(/\s+/)
71
+ @parser.build_parser(:space)
72
+ end
73
+
74
+ def add_parsers
75
+ add_empty_parser
76
+ add_space_parser
77
+ end
78
+ end
@@ -0,0 +1,40 @@
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
+
8
+ class TestQueryExpression < Test::Unit::TestCase
9
+ include GhostWheelNamespace
10
+ include ParseHelpers
11
+
12
+ def setup
13
+ @parser = Query.new(Literal.new(/\d+/)) { |i| i.to_i % 2 == 0 }
14
+ end
15
+
16
+ def test_query_match
17
+ assert_parses("12", parse("12"))
18
+ end
19
+
20
+ def test_failed_query_match
21
+ assert_doesnt_parse(parse("13"))
22
+ end
23
+
24
+ def test_yields_empty
25
+ Query.new(Empty.instance) do |empty|
26
+ assert_instance_of(EmptyParseResult, empty)
27
+ end.parse(Scanner.new("not_used"))
28
+ end
29
+
30
+ def test_equality
31
+ assert_not_equal( Query.new(Literal.new("one")) { },
32
+ Query.new(Literal.new("two")) { } )
33
+ assert_not_equal( Query.new(Literal.new("one")) { true },
34
+ Query.new(Literal.new("one")) { } )
35
+
36
+ block = lambda { true } # must be *exact* same block
37
+ assert_equal( Query.new(Literal.new("one"), &block),
38
+ Query.new(Literal.new("one"), &block) )
39
+ end
40
+ end
@@ -0,0 +1,76 @@
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
+
8
+ class TestRepetitionExpression < Test::Unit::TestCase
9
+ include GhostWheelNamespace
10
+ include ParseHelpers
11
+
12
+ def setup
13
+ @source = Scanner.new("X" * 10 + "YZ")
14
+ end
15
+
16
+ def test_zero_or_more_match
17
+ assert_parses(["X"] * 10, parse_repetition("X"))
18
+ end
19
+
20
+ def test_failed_zero_or_more_match
21
+ assert_parses_empty(parse_repetition("Z"))
22
+ end
23
+
24
+ def test_one_or_more_match
25
+ assert_parses(["X"] * 10, parse_repetition("X", 1))
26
+ end
27
+
28
+ def test_failed_one_or_more_match
29
+ assert_doesnt_parse(parse_repetition("Z", 1))
30
+ end
31
+
32
+ def test_with_minimum_match
33
+ assert_parses(["X"] * 10, parse_repetition("X", 5))
34
+ end
35
+
36
+ def test_failed_with_minimum_match
37
+ assert_doesnt_parse(parse_repetition("Z", 15))
38
+ end
39
+
40
+ def test_with_maximum_match
41
+ assert_parses(["X"] * 3, parse_repetition("X", 0, 3))
42
+ assert_parses(["X"] * 7, parse_repetition("X", 0, 20))
43
+ end
44
+
45
+ def test_failed_with_maximum_match
46
+ assert_parses_empty(parse_repetition("X", 0, 0))
47
+ end
48
+
49
+ def test_with_minimum_and_maximum_match
50
+ assert_parses(["X"] * 3, parse_repetition("X", 1, 3))
51
+ end
52
+
53
+ def test_failed_with_minimum_and_maximum_match
54
+ assert_doesnt_parse(parse_repetition("X", 20, 40))
55
+ end
56
+
57
+ def test_exact_count_match
58
+ assert_parses(["X"] * 3, parse_repetition("X", 3, 3))
59
+ end
60
+
61
+ def test_failed_exact_count_match
62
+ assert_doesnt_parse(parse_repetition("X", 11, 11))
63
+ end
64
+
65
+ def test_equality
66
+ assert_not_equal( Repetition.new(Literal.new("one")),
67
+ Repetition.new(Literal.new("two")) )
68
+ assert_not_equal( Repetition.new(Literal.new("one"), 1),
69
+ Repetition.new(Literal.new("one")) )
70
+ assert_not_equal( Repetition.new(Literal.new("one"), 1, 5),
71
+ Repetition.new(Literal.new("one"), 1) )
72
+
73
+ assert_equal( Repetition.new(Literal.new("one")),
74
+ Repetition.new(Literal.new("one")) )
75
+ end
76
+ end
@@ -0,0 +1,32 @@
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
+
8
+ class TestRuleExpression < Test::Unit::TestCase
9
+ include GhostWheelNamespace
10
+ include ParseHelpers
11
+
12
+ def test_simple_wrapping
13
+ assert_parses_empty(Rule.new(Empty.instance).parse(Scanner.new("not_used")))
14
+ end
15
+
16
+ def test_forward_declaration
17
+ rule = Rule.new
18
+ assert_raise(EmptyRuleError) { rule.parse(Scanner.new("not_used")) }
19
+ rule.expression = Empty.instance
20
+ assert_parses_empty(rule.parse(Scanner.new("not_used")))
21
+ end
22
+
23
+ def test_equality
24
+ assert_not_equal(Rule.new(Literal.new("one")), Rule.new)
25
+ assert_not_equal( Rule.new(Literal.new("one")),
26
+ Rule.new(Literal.new("two")) )
27
+
28
+ assert_equal(Rule.new, Rule.new)
29
+ assert_equal( Rule.new(Literal.new("one")),
30
+ Rule.new(Literal.new("one")) )
31
+ end
32
+ end
@@ -0,0 +1,192 @@
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 TestScanning < Test::Unit::TestCase
8
+ include GhostWheelNamespace
9
+
10
+ def setup
11
+ @scanner = Scanner.new("three little words")
12
+ end
13
+
14
+ def test_scanner_advances_on_match
15
+ assert_equal("three little words", @scanner.rest)
16
+ assert_scan_to("little words", /\S+\s+/)
17
+ assert_scan_to("words", /\S+\s+/)
18
+ end
19
+
20
+ def test_failed_match_does_not_advance
21
+ assert_equal("three little words", @scanner.rest)
22
+ assert_doesnt_scan(/fails/)
23
+ assert_scan_to("little words", /\S+\s+/)
24
+ end
25
+
26
+ def test_successful_transaction
27
+ assert_equal("three little words", @scanner.rest)
28
+
29
+ @scanner.transaction do
30
+ assert_doesnt_scan(/fails/)
31
+ assert_scan_to("little words", /\S+\s+/)
32
+ end
33
+
34
+ assert_equal("little words", @scanner.rest)
35
+ end
36
+
37
+ def test_aborted_transaction
38
+ assert_equal("three little words", @scanner.rest)
39
+
40
+ assert_nothing_raised(Exception) do
41
+ @scanner.transaction do
42
+ assert_scan_to("little words", /\S+\s+/)
43
+
44
+ @scanner.abort
45
+
46
+ flunk("Should never reach here.")
47
+ end
48
+ end
49
+
50
+ assert_equal("three little words", @scanner.rest)
51
+ end
52
+
53
+ def test_nested_transactions
54
+ assert_equal("three little words", @scanner.rest)
55
+
56
+ # nested aborts...
57
+ @scanner.transaction do
58
+ assert_scan_to("little words", /\S+\s+/)
59
+
60
+ @scanner.transaction do
61
+ assert_scan_to("words", /\S+\s+/)
62
+
63
+ @scanner.abort
64
+
65
+ flunk("Should never reach here.")
66
+ end
67
+
68
+ assert_equal("little words", @scanner.rest)
69
+
70
+ @scanner.abort
71
+
72
+ flunk("Should never reach here.")
73
+ end
74
+
75
+ assert_equal("three little words", @scanner.rest)
76
+
77
+ # nested success...
78
+ @scanner.transaction do
79
+ assert_scan_to("little words", /\S+\s+/)
80
+
81
+ @scanner.transaction do
82
+ assert_scan_to("words", /\S+\s+/)
83
+ end
84
+
85
+ assert_equal("words", @scanner.rest)
86
+ end
87
+
88
+ assert_equal("words", @scanner.rest)
89
+ end
90
+
91
+ def test_successful_transactions_auto_commit_changes
92
+ assert_equal("three little words", @scanner.rest)
93
+
94
+ @scanner.transaction do
95
+ assert_scan_to("little words", /\S+\s+/)
96
+
97
+ @scanner.transaction do
98
+ assert_scan_to("words", /\S+\s+/)
99
+ end
100
+
101
+ assert_equal("words", @scanner.rest)
102
+
103
+ @scanner.abort
104
+
105
+ flunk("Should never reach here.")
106
+ end
107
+
108
+ assert_equal("three little words", @scanner.rest)
109
+ end
110
+
111
+ def test_exception_aborts_transaction
112
+ assert_equal("three little words", @scanner.rest)
113
+
114
+ @scanner.transaction do
115
+ assert_scan_to("little words", /\S+\s+/)
116
+
117
+ assert_raise(NoMethodError) do
118
+ @scanner.transaction do
119
+ assert_scan_to("words", /\S+\s+/)
120
+
121
+ Object.new.no_such_method # raise an exception
122
+
123
+ flunk("Should never reach here.")
124
+ end
125
+ end
126
+
127
+ assert_equal("little words", @scanner.rest)
128
+ end
129
+
130
+ assert_equal("little words", @scanner.rest)
131
+ end
132
+
133
+ def test_stack_underflow_error
134
+ # abort() without opening a transaction
135
+ assert_raise(StackUnderflowError) { @scanner.abort }
136
+ end
137
+
138
+ def test_eos_detection
139
+ assert(!@scanner.eos?, "Detected eos?() with content left.")
140
+ assert_scan_to(String.new, /.*/m)
141
+ assert(@scanner.eos?, "Failed to detect eos?().")
142
+ end
143
+
144
+ def test_transaction_return_value
145
+ assert_equal("success", @scanner.transaction { "success" })
146
+ assert_equal(
147
+ "failure",
148
+ @scanner.transaction { @scanner.abort("failure"); "success" }
149
+ )
150
+ end
151
+
152
+ def test_pos
153
+ assert_equal("three little words", @scanner.rest)
154
+
155
+ saved_pos = nil
156
+ @scanner.transaction do
157
+ assert_scan_to("little words", /\S+\s+/)
158
+
159
+ @scanner.transaction do
160
+ assert_scan_to("words", /\S+\s+/)
161
+
162
+ saved_pos = @scanner.pos
163
+
164
+ @scanner.abort
165
+
166
+ flunk("Should never reach here.")
167
+ end
168
+
169
+ assert_equal("little words", @scanner.rest)
170
+
171
+ @scanner.abort
172
+
173
+ flunk("Should never reach here.")
174
+ end
175
+
176
+ assert_equal("three little words", @scanner.rest)
177
+
178
+ @scanner.pos = saved_pos
179
+ assert_equal("words", @scanner.rest)
180
+ end
181
+
182
+ private
183
+
184
+ def assert_scan_to(rest, regexp, message = "Scan failed.")
185
+ assert_block(message) { @scanner.scan(regexp) and @scanner.rest == rest }
186
+ end
187
+
188
+ def assert_doesnt_scan(regexp, message = "Scan matched unexpectedly.")
189
+ rest = @scanner.rest
190
+ assert_block(message) { !@scanner.scan(regexp) and @scanner.rest == rest }
191
+ end
192
+ end