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,48 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
|
3
|
+
require "test/unit"
|
4
|
+
|
5
|
+
require File.join(File.dirname(__FILE__), %w[.. helpers parse_helpers])
|
6
|
+
require File.join(File.dirname(__FILE__), %w[.. helpers json_tests])
|
7
|
+
|
8
|
+
class TestJSONGhostWheel < Test::Unit::TestCase
|
9
|
+
include ParseHelpers
|
10
|
+
include JSONTests
|
11
|
+
|
12
|
+
def setup
|
13
|
+
@parser = GhostWheel.build_parser( %q{
|
14
|
+
keyword = 'true' { true } | 'false' { false } | 'null' { nil }
|
15
|
+
|
16
|
+
number = /-?(?:0|[1-9]\d*)(?:\.\d+(?:[eE][+-]?\d+)?)?/
|
17
|
+
{ ast.include?(".") ? Float(ast) : Integer(ast) }
|
18
|
+
|
19
|
+
string_content = /\\\\["\\\\\/]/ { ast[-1, 1] }
|
20
|
+
| /\\\\[bfnrt]/
|
21
|
+
{ Hash[*%W[b \n f \f n \n r \r t \t]][ast[-1, 1]] }
|
22
|
+
| /\\\\u[0-9a-fA-F]{4}/
|
23
|
+
{ [Integer("0x#{ast[2..-1]}")].pack("U") }
|
24
|
+
| /[^\\\\"]+/
|
25
|
+
string = '"' string_content* '"' { ast.flatten[1..-2].join }
|
26
|
+
|
27
|
+
array_content = value_with_array_sep+ value
|
28
|
+
{ ast[0].inject([]) { |a, v| a.push(*v) } + ast[-1..-1] }
|
29
|
+
| value? { ast.is_a?(EmptyParseResult) ? [] : [ast] }
|
30
|
+
array = /\[\s*/ array_content /\s*\]/ { ast[1] }
|
31
|
+
|
32
|
+
object_pair = string /\s*:\s*/ value { {ast[0] => ast[-1]} }
|
33
|
+
object_pair_and_sep = object_pair /\s*;\s*/ { ast[0] }
|
34
|
+
object_content = object_pair_and_sep+ object_pair { ast.flatten }
|
35
|
+
| object_pair?
|
36
|
+
{ ast.is_a?(EmptyParseResult) ? [] : [ast] }
|
37
|
+
object = /\\\{\s*/ object_content /\\\}\s*/
|
38
|
+
{ ast[1].inject({}) { |h, p| h.merge(p) } }
|
39
|
+
|
40
|
+
value_space = /\s*/
|
41
|
+
value_content = keyword | number | string | array | object
|
42
|
+
value = value_space value_content value_space { ast[1] }
|
43
|
+
value_with_array_sep = value /\s*,\s*/ { ast[0] }
|
44
|
+
|
45
|
+
json := value EOF { ast[0] }
|
46
|
+
} )[:json]
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
|
3
|
+
require "test/unit"
|
4
|
+
|
5
|
+
require File.join(File.dirname(__FILE__), %w[.. helpers parse_helpers])
|
6
|
+
require File.join(File.dirname(__FILE__), %w[.. helpers json_tests])
|
7
|
+
|
8
|
+
class TestJSONRuby < Test::Unit::TestCase
|
9
|
+
include ParseHelpers
|
10
|
+
include JSONTests
|
11
|
+
|
12
|
+
def setup
|
13
|
+
@parser = GhostWheel.build_parser do
|
14
|
+
rule( :keyword,
|
15
|
+
alt("true", "false", "null") { |k|
|
16
|
+
{"true" => true, "false" => false, "null" => nil}[k]
|
17
|
+
} )
|
18
|
+
|
19
|
+
rule( :number,
|
20
|
+
/-?(?:0|[1-9]\d*)(?:\.\d+(?:[eE][+-]?\d+)?)?/ ) { |n|
|
21
|
+
n.include?(".") ? Float(n) : Integer(n)
|
22
|
+
}
|
23
|
+
|
24
|
+
rule( :string,
|
25
|
+
seq(
|
26
|
+
skip('"'),
|
27
|
+
zplus(
|
28
|
+
alt(
|
29
|
+
lit(%r{\\["\\/]}) { |e| e[-1, 1] },
|
30
|
+
lit(/\\[bfnrt]/) { |e|
|
31
|
+
Hash[*%W[b \n f \f n \n r \r t \t]][e[-1, 1]]
|
32
|
+
},
|
33
|
+
lit(/\\u[0-9a-fA-F]{4}/) { |e|
|
34
|
+
[Integer("0x#{e[2..-1]}")].pack("U")
|
35
|
+
},
|
36
|
+
/[^\\"]+/
|
37
|
+
)
|
38
|
+
),
|
39
|
+
skip('"')
|
40
|
+
) { |s| s.flatten.join } )
|
41
|
+
|
42
|
+
rule( :array,
|
43
|
+
seq(
|
44
|
+
skip(/\[\s*/),
|
45
|
+
alt(
|
46
|
+
seq(
|
47
|
+
oplus(seq(:value, skip(/\s*,\s*/))),
|
48
|
+
:value
|
49
|
+
) { |a| a[0].inject([]) { |vs, v| vs.push(*v) } + a[-1..-1] },
|
50
|
+
seq(opt(:value))
|
51
|
+
),
|
52
|
+
skip(/\s*\]/)
|
53
|
+
) { |a| a.first } )
|
54
|
+
|
55
|
+
rule( :object_pair,
|
56
|
+
seq(:string, /\s*:\s*/, :value) { |kv| {kv[0] => kv[-1]} } )
|
57
|
+
rule( :object,
|
58
|
+
seq(
|
59
|
+
skip(/\{\s*/),
|
60
|
+
alt(
|
61
|
+
seq(
|
62
|
+
oplus(seq(:object_pair, skip(/\s*;\s*/))),
|
63
|
+
:object_pair
|
64
|
+
) { |ps| ps[0][0] + ps[-1..-1] },
|
65
|
+
seq(opt(:object_pair))
|
66
|
+
),
|
67
|
+
skip(/\}\s*/)
|
68
|
+
) { |ps| ps[0].inject({}) { |h, p| h.merge(p) } } )
|
69
|
+
|
70
|
+
rule(:value_space, opt(skip(/\s+/)))
|
71
|
+
rule( :value,
|
72
|
+
seq(
|
73
|
+
:value_space,
|
74
|
+
alt(:object, :array, :string, :number, :keyword),
|
75
|
+
:value_space
|
76
|
+
) { |v| v[0] } )
|
77
|
+
|
78
|
+
parser(:json, seq(:value, eof) { |j| j[0] })
|
79
|
+
end[:json]
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
|
3
|
+
require "ghost_wheel"
|
4
|
+
|
5
|
+
#
|
6
|
+
# Including this module in a test case embeds the GhostWheel namespace for those
|
7
|
+
# tests. Constants both in the root GhostWheel module and in the
|
8
|
+
# GhostWheel::Expression class may be used without qualification.
|
9
|
+
#
|
10
|
+
module GhostWheelNamespace
|
11
|
+
#
|
12
|
+
# Includes the GhostWheel module in +test_case+ and builds a
|
13
|
+
# self.const_missing() method to handle lookups in the GhostWheel::Expression
|
14
|
+
# class.
|
15
|
+
#
|
16
|
+
def self.included(test_case)
|
17
|
+
test_case.instance_eval do
|
18
|
+
include GhostWheel
|
19
|
+
def const_missing(const)
|
20
|
+
GhostWheel::Expression.const_get(const) rescue super
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
|
3
|
+
module JSONTests
|
4
|
+
def test_keyword_parsing
|
5
|
+
assert_parses(true, parse("true"))
|
6
|
+
assert_parses(false, parse("false"))
|
7
|
+
assert_parses(nil, parse("null"))
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_number_parsing
|
11
|
+
assert_parses(42, parse("42"))
|
12
|
+
assert_parses(-13, parse("-13"))
|
13
|
+
assert_parses(3.1415, parse("3.1415"))
|
14
|
+
assert_parses(-0.01, parse("-0.01"))
|
15
|
+
|
16
|
+
assert_parses(0.2e1, parse("0.2e1"))
|
17
|
+
assert_parses(0.2e+1, parse("0.2e+1"))
|
18
|
+
assert_parses(0.2e-1, parse("0.2e-1"))
|
19
|
+
assert_parses(0.2E1, parse("0.2e1"))
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_string_parsing
|
23
|
+
assert_parses(String.new, parse(%Q{""}))
|
24
|
+
assert_parses("James", parse(%Q{"James"}))
|
25
|
+
|
26
|
+
assert_parses(%Q{nested "quotes"}, parse('"nested \"quotes\""'))
|
27
|
+
assert_parses("\n", parse(%Q{"\\n"}))
|
28
|
+
assert_parses("a", parse(%Q{"\\u#{"%04X" % ?a}"}))
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_array_parsing
|
32
|
+
assert_parses(Array.new, parse(%Q{[]}))
|
33
|
+
assert_parses(["James", 3.1415, true], parse(%Q{["James", 3.1415, true]}))
|
34
|
+
assert_parses([1, [2, [3]]], parse(%Q{[1, [2, [3]]]}))
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_object_parsing
|
38
|
+
assert_parses(Hash.new, parse(%Q{{}}))
|
39
|
+
assert_parses( {"James" => 3.1415, "Gray" => true},
|
40
|
+
parse(%Q{{"James": 3.1415; "Gray": true}}) )
|
41
|
+
assert_parses( {"Array" => [1, 2, 3], "Object" => {"nested" => "objects"}},
|
42
|
+
parse(<<-END_OBJECT) )
|
43
|
+
{"Array": [1, 2, 3]; "Object": {"nested": "objects"}}
|
44
|
+
END_OBJECT
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_error_parsing
|
48
|
+
assert_doesnt_parse(parse("unknown"))
|
49
|
+
|
50
|
+
assert_doesnt_parse(parse("$1,000"))
|
51
|
+
assert_doesnt_parse(parse("1_000"))
|
52
|
+
assert_doesnt_parse(parse("1K"))
|
53
|
+
|
54
|
+
assert_doesnt_parse(parse(%Q{"}))
|
55
|
+
assert_doesnt_parse(parse(%Q{"\\i"}))
|
56
|
+
|
57
|
+
assert_doesnt_parse(parse("["))
|
58
|
+
assert_doesnt_parse(parse("[1,,2]"))
|
59
|
+
|
60
|
+
assert_doesnt_parse(parse("{"))
|
61
|
+
assert_doesnt_parse(parse(%q{{"key": true false}}))
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
|
3
|
+
require "ghost_wheel"
|
4
|
+
|
5
|
+
require File.join(File.dirname(__FILE__), "ghost_wheel_namespace")
|
6
|
+
|
7
|
+
module ParseHelpers
|
8
|
+
include GhostWheelNamespace
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
##################
|
13
|
+
### Assertions ###
|
14
|
+
##################
|
15
|
+
|
16
|
+
def assert_parses(expected_ast, actual_ast, message = nil)
|
17
|
+
if message.nil?
|
18
|
+
parsed = actual_ast.is_a?(ParseResult) ? actual_ast.value.inspect :
|
19
|
+
actual_ast.class
|
20
|
+
message = "Parsed #{parsed} while expecting #{expected_ast.inspect}."
|
21
|
+
end
|
22
|
+
|
23
|
+
assert_block(message) do
|
24
|
+
actual_ast.is_a?(ParseResult) and actual_ast.value == expected_ast
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def assert_doesnt_parse(actual_ast, message = "Parse succeeded.")
|
29
|
+
assert_block(message) { actual_ast.is_a?(FailedParseResult) }
|
30
|
+
end
|
31
|
+
|
32
|
+
def assert_parses_empty(actual_ast, message = "Parse was not empty.")
|
33
|
+
assert_block(message) { actual_ast.is_a?(EmptyParseResult) }
|
34
|
+
end
|
35
|
+
|
36
|
+
########################
|
37
|
+
### Parser Shortcuts ###
|
38
|
+
########################
|
39
|
+
|
40
|
+
def parse(source)
|
41
|
+
@parser.parse(Scanner.new(source))
|
42
|
+
end
|
43
|
+
|
44
|
+
def parse_alternation(*expressions)
|
45
|
+
Alternation.new(*wrap_literals(expressions)).parse(@source)
|
46
|
+
end
|
47
|
+
|
48
|
+
def parse_eof
|
49
|
+
EndOfFile.instance.parse(@source)
|
50
|
+
end
|
51
|
+
|
52
|
+
def parse_literal(literal, *args)
|
53
|
+
Literal.new(literal, *args).parse(@source)
|
54
|
+
end
|
55
|
+
|
56
|
+
def parse_look_ahead(literal, *args)
|
57
|
+
LookAhead.new(Literal.new(literal), *args).parse(@source)
|
58
|
+
end
|
59
|
+
|
60
|
+
def parse_optional(literal)
|
61
|
+
Optional.new(Literal.new(literal)).parse(@source)
|
62
|
+
end
|
63
|
+
|
64
|
+
def parse_repetition(literal, *args)
|
65
|
+
Repetition.new(Literal.new(literal), *args).parse(@source)
|
66
|
+
end
|
67
|
+
|
68
|
+
def parse_sequence(*expressions)
|
69
|
+
Sequence.new(*wrap_literals(expressions)).parse(@source)
|
70
|
+
end
|
71
|
+
|
72
|
+
########################
|
73
|
+
### Literal Wrappers ###
|
74
|
+
########################
|
75
|
+
|
76
|
+
def wrap_literal(literal)
|
77
|
+
literal.is_a?(Expression) ? literal : Literal.new(literal)
|
78
|
+
end
|
79
|
+
|
80
|
+
def wrap_literals(literals)
|
81
|
+
literals.map { |literal| wrap_literal(literal) }
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,27 @@
|
|
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 TestAlternationExpression < Test::Unit::TestCase
|
9
|
+
include GhostWheelNamespace
|
10
|
+
include ParseHelpers
|
11
|
+
|
12
|
+
def setup
|
13
|
+
@source = Scanner.new("null")
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_match
|
17
|
+
assert_parses("null", parse_alternation(*%w[true false null]))
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_failed_match
|
21
|
+
assert_doesnt_parse(parse_alternation(*%w[true false nil]))
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_empty_match
|
25
|
+
assert_parses_empty(parse_alternation(Empty.instance))
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,15 @@
|
|
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 TestEmptyExpression < Test::Unit::TestCase
|
9
|
+
include GhostWheelNamespace
|
10
|
+
include ParseHelpers
|
11
|
+
|
12
|
+
def test_empty_expression
|
13
|
+
assert_parses_empty(Empty.instance.parse(Scanner.new("not_used")))
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,27 @@
|
|
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 TestEndOfFileExpression < 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
|
+
test_failed_match
|
18
|
+
assert_parses_empty(parse_eof)
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_failed_match
|
22
|
+
4.times do
|
23
|
+
assert_doesnt_parse(parse_eof)
|
24
|
+
@source.scan(/./)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,55 @@
|
|
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 TestLiteralExpression < Test::Unit::TestCase
|
9
|
+
include GhostWheelNamespace
|
10
|
+
include ParseHelpers
|
11
|
+
|
12
|
+
def setup
|
13
|
+
@source = Scanner.new("some words to parse")
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_string_match
|
17
|
+
assert_parses("some", parse_literal("some"))
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_failed_string_match
|
21
|
+
assert_doesnt_parse(parse_literal("doesn't match"))
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_character_match
|
25
|
+
assert_parses("s", parse_literal(?s))
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_failed_character_match
|
29
|
+
assert_doesnt_parse(parse_literal(?S))
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_regexp_match
|
33
|
+
assert_parses("some words ", parse_literal(/(?:\w+\s+){2}/))
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_failed_regexp_match
|
37
|
+
assert_doesnt_parse(parse_literal(/(?=missing)/))
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_skip_literal_match
|
41
|
+
assert_parses_empty(parse_literal("some", true))
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_failed_skip_literal_match
|
45
|
+
assert_doesnt_parse(parse_literal("doesn't match", true))
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_equality
|
49
|
+
assert_not_equal(Literal.new("keyword"), Literal.new("tag"))
|
50
|
+
assert_not_equal(Literal.new("keyword"), Literal.new("keyword", true))
|
51
|
+
|
52
|
+
assert_equal(Literal.new("keyword"), Literal.new("keyword"))
|
53
|
+
assert_equal(Literal.new("keyword"), Literal.new(/keyword/))
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,41 @@
|
|
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 TestLookAheadExpression < Test::Unit::TestCase
|
9
|
+
include GhostWheelNamespace
|
10
|
+
include ParseHelpers
|
11
|
+
|
12
|
+
def setup
|
13
|
+
@source = Scanner.new("ahead")
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_look_ahead_match
|
17
|
+
assert_parses_empty(parse_look_ahead("ahead"))
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_failed_look_ahead_match
|
21
|
+
assert_doesnt_parse(parse_look_ahead("missing"))
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_negative_look_ahead_match
|
25
|
+
assert_parses_empty(parse_look_ahead("missing", true))
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_failed_negative_look_ahead_match
|
29
|
+
assert_doesnt_parse(parse_look_ahead("ahead", true))
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_equality
|
33
|
+
assert_not_equal( LookAhead.new(Literal.new("one")),
|
34
|
+
LookAhead.new(Literal.new("two")) )
|
35
|
+
assert_not_equal( LookAhead.new(Literal.new("one"), true),
|
36
|
+
LookAhead.new(Literal.new("one")) )
|
37
|
+
|
38
|
+
assert_equal( LookAhead.new(Literal.new("one")),
|
39
|
+
LookAhead.new(Literal.new("one")) )
|
40
|
+
end
|
41
|
+
end
|