hammer-parser 0.2.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.
- checksums.yaml +7 -0
- data/README.md +101 -0
- data/lib/hammer-parser.rb +47 -0
- data/lib/hammer/internal.rb +346 -0
- data/lib/hammer/parser.rb +224 -0
- data/lib/hammer/parser_builder.rb +124 -0
- data/lib/minitest/hamer-parser_plugin.rb +31 -0
- data/test/autogen_test.rb +755 -0
- data/test/parser_test.rb +132 -0
- metadata +83 -0
@@ -0,0 +1,224 @@
|
|
1
|
+
require 'hammer/internal'
|
2
|
+
|
3
|
+
module Hammer
|
4
|
+
class Parser
|
5
|
+
|
6
|
+
@@saved_objects = Hammer::Internal::DynamicVariable.new nil, "Hammer parse-time pins"
|
7
|
+
|
8
|
+
# Don't create new instances with Hammer::Parser.new,
|
9
|
+
# use the constructor methods instead (i.e. Hammer::Parser.int64 etc.)
|
10
|
+
#
|
11
|
+
# name: Name of the parser. Should be a symbol.
|
12
|
+
# h_parser: The pointer to the parser as returned by hammer.
|
13
|
+
# dont_gc: Pass additional data that's used by the parser and needs to be saved from the garbage collector (at least as long this object lives).
|
14
|
+
def initialize(name, h_parser, dont_gc=[])
|
15
|
+
@name = name
|
16
|
+
@h_parser = h_parser
|
17
|
+
# Always store as array, so we can easily add stuff later on
|
18
|
+
dont_gc = [dont_gc] unless dont_gc.is_a? Array
|
19
|
+
@dont_gc = dont_gc.dup
|
20
|
+
end
|
21
|
+
|
22
|
+
# dont_gc is required to build a fuzzer from the declaration of Hammer::Parser object.
|
23
|
+
attr_reader :name
|
24
|
+
attr_reader :h_parser
|
25
|
+
attr_reader :dont_gc
|
26
|
+
|
27
|
+
# Parse the given data. Returns the parse result if successful, nil otherwise.
|
28
|
+
#
|
29
|
+
# data: A string containing the data to parse.
|
30
|
+
def parse(data)
|
31
|
+
raise RuntimeError, '@h_parser is nil' if @h_parser.nil?
|
32
|
+
raise ArgumentError, 'expecting a String' unless data.is_a? String # TODO: Not needed, FFI checks that.
|
33
|
+
|
34
|
+
ibuf = FFI::MemoryPointer.from_string(data)
|
35
|
+
@@saved_objects.with([]) do
|
36
|
+
result = Hammer::Internal.h_parse(@h_parser, ibuf, data.bytesize) # Don't include the trailing null
|
37
|
+
if result.null?
|
38
|
+
return nil
|
39
|
+
else
|
40
|
+
# NOTE:
|
41
|
+
# The parse result *must* hold a reference to the parser that created it!
|
42
|
+
# Otherwise, the parser might get garbage-collected while the result is still valid.
|
43
|
+
# Any pointers to token strings will then be invalid.
|
44
|
+
result.instance_variable_set :@parser, self
|
45
|
+
result.instance_variable_set :@pins, @@saved_objects.value
|
46
|
+
return result
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Binds an indirect parser.
|
52
|
+
def bind(other_parser)
|
53
|
+
raise RuntimeError, 'can only bind indirect parsers' unless self.name == :indirect
|
54
|
+
Hammer::Internal.h_bind_indirect(self.h_parser, other_parser.h_parser)
|
55
|
+
@dont_gc << other_parser
|
56
|
+
end
|
57
|
+
|
58
|
+
# Can pass the action either as a Proc in second parameter, or as block.
|
59
|
+
def self.action(parser, action=nil, &block)
|
60
|
+
action = block if action.nil?
|
61
|
+
raise ArgumentError, 'no action' if action.nil?
|
62
|
+
|
63
|
+
real_action = Proc.new {|hpr|
|
64
|
+
ret = action.call(hpr.ast)
|
65
|
+
# Pin the result
|
66
|
+
@@saved_objects.value << ret
|
67
|
+
hpt = hpr.arena_alloc(Hammer::Internal::HParsedToken)
|
68
|
+
unless hpr.ast.nil?
|
69
|
+
hpt[:index] = hpr[:ast][:index]
|
70
|
+
hpt[:bit_offset] = hpr[:ast][:bit_offset]
|
71
|
+
end
|
72
|
+
hpt[:token_type] = :"com.upstandinghackers.hammer.ruby.object"
|
73
|
+
hpt[:data][:uint] = ret.object_id
|
74
|
+
hpt
|
75
|
+
}
|
76
|
+
|
77
|
+
h_parser = Hammer::Internal.h_action(parser.h_parser, real_action)
|
78
|
+
return Hammer::Parser.new(:action, h_parser, [parser, action, real_action])
|
79
|
+
end
|
80
|
+
|
81
|
+
# Can pass the predicate either as a Proc in second parameter, or as block.
|
82
|
+
def self.attr_bool(parser, predicate=nil, &block)
|
83
|
+
predicate = block if predicate.nil?
|
84
|
+
raise ArgumentError, 'no predicate' if predicate.nil?
|
85
|
+
|
86
|
+
real_pred = Proc.new {|hpr| predicate.call hpr.ast}
|
87
|
+
|
88
|
+
h_parser = Hammer::Internal.h_attr_bool(parser.h_parser, real_pred)
|
89
|
+
return Hammer::Parser.new(:attr_bool, h_parser, [parser, predicate, real_pred])
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.token(string)
|
93
|
+
# Need to copy string to a memory buffer (not just string.dup)
|
94
|
+
# * Original string might be modified, this must not affect existing tokens
|
95
|
+
# * We need a constant memory address (Ruby string might be moved around by the Ruby VM)
|
96
|
+
buffer = FFI::MemoryPointer.from_string(string)
|
97
|
+
h_parser = Hammer::Internal.h_token(buffer, buffer.size-1) # buffer.size includes the null byte at the end
|
98
|
+
encoding = string.encoding
|
99
|
+
|
100
|
+
wrapping_action = Proc.new {|hpr|
|
101
|
+
hstr = hpr.arena_alloc(Hammer::Internal::HString)
|
102
|
+
hstr[:content] = hpr[:ast][:data][:bytes]
|
103
|
+
hstr[:encoding] = encoding.object_id
|
104
|
+
|
105
|
+
hpt = hpr.arena_alloc(Hammer::Internal::HParsedToken)
|
106
|
+
hpt[:token_type] = :"com.upstandinghackers.hammer.ruby.encodedStr"
|
107
|
+
hpt[:data][:user] = hstr.to_ptr
|
108
|
+
hpt[:bit_offset] = hpr[:ast][:bit_offset]
|
109
|
+
hpt[:index] = hpr[:ast][:index]
|
110
|
+
hpt
|
111
|
+
}
|
112
|
+
wrapped_parser = Hammer::Internal.h_action(h_parser, wrapping_action)
|
113
|
+
return Hammer::Parser.new(:token, wrapped_parser, [buffer, string, encoding, wrapping_action, h_parser])
|
114
|
+
end
|
115
|
+
|
116
|
+
def self.marshal_ch_arg(num)
|
117
|
+
if num.is_a?(String)
|
118
|
+
raise ArgumentError, "Expecting either a fixnum in 0..255 or a single-byte String" unless num.bytesize == 1
|
119
|
+
num = num.bytes.first
|
120
|
+
end
|
121
|
+
raise ArgumentError, 'Expecting a Fixnum in 0..255 or a single-byte String' unless num.is_a?(Fixnum) and num.between?(0, 255)
|
122
|
+
return num
|
123
|
+
end
|
124
|
+
private_class_method :marshal_ch_arg
|
125
|
+
|
126
|
+
def self.ch_parser_wrapper(parser)
|
127
|
+
return Hammer::Parser.action(parser) {|x| x.data.chr}
|
128
|
+
end
|
129
|
+
|
130
|
+
def self.ch(ch)
|
131
|
+
num = marshal_ch_arg(ch)
|
132
|
+
h_parser = Hammer::Internal.h_ch(num)
|
133
|
+
|
134
|
+
return ch_parser_wrapper(Hammer::Parser.new(:ch, h_parser, nil))
|
135
|
+
end
|
136
|
+
|
137
|
+
def self.ch_range(ch1, ch2)
|
138
|
+
ch1 = marshal_ch_arg(ch1)
|
139
|
+
ch2 = marshal_ch_arg(ch2)
|
140
|
+
h_parser = Hammer::Internal.h_ch_range(ch1, ch2)
|
141
|
+
return ch_parser_wrapper(Hammer::Parser.new(:ch_range, h_parser, nil))
|
142
|
+
end
|
143
|
+
|
144
|
+
def self.int_range(parser, i1, i2)
|
145
|
+
h_parser = Hammer::Internal.h_int_range(parser.h_parser, i1, i2)
|
146
|
+
return Hammer::Parser.new(:int_range, h_parser, [parser])
|
147
|
+
end
|
148
|
+
|
149
|
+
def self.in(charset)
|
150
|
+
raise ArgumentError, "Expected a String" unless charset.is_a?(String)
|
151
|
+
ibuf = FFI::MemoryPointer.from_string(charset)
|
152
|
+
h_parser = Hammer::Internal.h_in(ibuf, charset.bytesize)
|
153
|
+
return ch_parser_wrapper(Hammer::Parser.new(:in, h_parser, nil))
|
154
|
+
end
|
155
|
+
|
156
|
+
def self.repeat_n(parser, count)
|
157
|
+
h_parser = Hammer::Internal.h_repeat_n(parser.h_parser, count)
|
158
|
+
return Hammer::Parser.new(:repeat_n, h_parser, nil)
|
159
|
+
end
|
160
|
+
|
161
|
+
def self.not_in(charset)
|
162
|
+
raise ArgumentError, "Expected a String" unless charset.is_a?(String)
|
163
|
+
ibuf = FFI::MemoryPointer.from_string(charset)
|
164
|
+
h_parser = Hammer::Internal.h_not_in(ibuf, charset.bytesize)
|
165
|
+
return ch_parser_wrapper(Hammer::Parser.new(:not_in, h_parser, nil))
|
166
|
+
end
|
167
|
+
|
168
|
+
# Defines a parser constructor with the given name.
|
169
|
+
# Options:
|
170
|
+
# hammer_function: name of the hammer function to call (default: 'h_'+name)
|
171
|
+
# varargs: Whether the function is taking a variable number of arguments (default: false)
|
172
|
+
def self.define_parser(name, options = {})
|
173
|
+
hammer_function = options[:hammer_function] || ('h_' + name.to_s).to_sym
|
174
|
+
varargs = options[:varargs] || false
|
175
|
+
|
176
|
+
# Define a new class method
|
177
|
+
define_singleton_method name do |*parsers|
|
178
|
+
if varargs
|
179
|
+
args = parsers.flat_map { |p| [:pointer, p.h_parser] }
|
180
|
+
args += [:pointer, nil]
|
181
|
+
else
|
182
|
+
args = parsers.map(&:h_parser)
|
183
|
+
end
|
184
|
+
h_parser = Hammer::Internal.send hammer_function, *args
|
185
|
+
|
186
|
+
return Hammer::Parser.new(name, h_parser, parsers)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
private_class_method :define_parser
|
190
|
+
|
191
|
+
define_parser :sequence, varargs: true
|
192
|
+
define_parser :choice, varargs: true
|
193
|
+
|
194
|
+
define_parser :int64
|
195
|
+
define_parser :int32
|
196
|
+
define_parser :int16
|
197
|
+
define_parser :int8
|
198
|
+
define_parser :uint64
|
199
|
+
define_parser :uint32
|
200
|
+
define_parser :uint16
|
201
|
+
define_parser :uint8
|
202
|
+
define_parser :whitespace
|
203
|
+
define_parser :left
|
204
|
+
define_parser :right
|
205
|
+
define_parser :middle
|
206
|
+
define_parser :end_p
|
207
|
+
define_parser :nothing_p
|
208
|
+
define_parser :butnot
|
209
|
+
define_parser :difference
|
210
|
+
define_parser :xor
|
211
|
+
define_parser :many
|
212
|
+
define_parser :many1
|
213
|
+
define_parser :optional
|
214
|
+
define_parser :ignore
|
215
|
+
define_parser :sepBy
|
216
|
+
define_parser :sepBy1
|
217
|
+
define_parser :epsilon_p
|
218
|
+
define_parser :length_value
|
219
|
+
define_parser :and
|
220
|
+
define_parser :not
|
221
|
+
define_parser :indirect
|
222
|
+
|
223
|
+
end
|
224
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# TODO: Find a way to make docile an optional dependency
|
2
|
+
# (autoload for this file? and throw some informative error when docile isn't available.
|
3
|
+
# should also check gem version with a 'gem' call and appropriate version specifier.)
|
4
|
+
require 'docile'
|
5
|
+
|
6
|
+
module Hammer
|
7
|
+
|
8
|
+
class Parser
|
9
|
+
def self.build(&block)
|
10
|
+
ParserBuilder.new.sequence(&block).build
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.build_choice(&block)
|
14
|
+
ParserBuilder.new.choice(&block).build
|
15
|
+
end
|
16
|
+
end # class Parser
|
17
|
+
|
18
|
+
class ParserBuilder
|
19
|
+
attr_reader :parsers
|
20
|
+
|
21
|
+
def initialize
|
22
|
+
@parsers = []
|
23
|
+
end
|
24
|
+
|
25
|
+
def build
|
26
|
+
if @parsers.length > 1
|
27
|
+
Hammer::Parser.sequence(*@parsers)
|
28
|
+
else
|
29
|
+
@parsers.first
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
# can call it either as ParserBuiler.new.sequence(parser1, parser2, parser3)
|
35
|
+
# or as Parser.build { sequence { call parser1; call parser2; call parser3 } }
|
36
|
+
def sequence(*parsers, &block)
|
37
|
+
@parsers += parsers
|
38
|
+
@parsers << Docile.dsl_eval(ParserBuilder.new, &block).build if block_given?
|
39
|
+
return self
|
40
|
+
end
|
41
|
+
|
42
|
+
def choice(*parsers, &block)
|
43
|
+
if block_given?
|
44
|
+
parsers += Docile.dsl_eval(ParserBuilder.new, &block).parsers
|
45
|
+
end
|
46
|
+
@parsers << Hammer::Parser.choice(*parsers)
|
47
|
+
return self
|
48
|
+
end
|
49
|
+
|
50
|
+
def call(parser)
|
51
|
+
@parsers << parser
|
52
|
+
return self
|
53
|
+
end
|
54
|
+
|
55
|
+
# modifies previous parser
|
56
|
+
def action(&block)
|
57
|
+
parser = @parsers.last
|
58
|
+
raise RuntimeError, 'need a parser before action' if parser.nil?
|
59
|
+
@parsers << Hammer::Parser.action(parser, &block)
|
60
|
+
return self
|
61
|
+
end
|
62
|
+
|
63
|
+
# Defines a parser constructor with the given name.
|
64
|
+
def self.define_parser(name, options = {})
|
65
|
+
define_method name do |*args|
|
66
|
+
# TODO: This is wrong!! Needs to accept a block for nested parsers!
|
67
|
+
@parsers << Hammer::Parser.send(name, *args)
|
68
|
+
return self
|
69
|
+
end
|
70
|
+
end
|
71
|
+
private_class_method :define_parser
|
72
|
+
|
73
|
+
define_parser :token
|
74
|
+
define_parser :ch
|
75
|
+
define_parser :int64
|
76
|
+
define_parser :int32
|
77
|
+
define_parser :int16
|
78
|
+
define_parser :int8
|
79
|
+
define_parser :uint64
|
80
|
+
define_parser :uint32
|
81
|
+
define_parser :uint16
|
82
|
+
define_parser :uint8
|
83
|
+
define_parser :whitespace
|
84
|
+
define_parser :left
|
85
|
+
define_parser :right
|
86
|
+
define_parser :middle
|
87
|
+
define_parser :end_p
|
88
|
+
define_parser :nothing_p
|
89
|
+
define_parser :butnot
|
90
|
+
define_parser :difference
|
91
|
+
define_parser :xor
|
92
|
+
define_parser :many
|
93
|
+
define_parser :many1
|
94
|
+
define_parser :optional
|
95
|
+
define_parser :ignore
|
96
|
+
define_parser :sepBy
|
97
|
+
define_parser :sepBy1
|
98
|
+
define_parser :epsilon_p
|
99
|
+
define_parser :length_value
|
100
|
+
define_parser :and
|
101
|
+
define_parser :not
|
102
|
+
|
103
|
+
# At least indirect must return the parser instead of the builder, so it can be stored in a variable.
|
104
|
+
# Other possible solution:
|
105
|
+
# Make indirect take a name parameter, and use the name to bind it later.
|
106
|
+
# Example:
|
107
|
+
# p = Hammer::Parser.build { indirect(:the_name) }
|
108
|
+
# p.bind(:the_name, inner_parser)
|
109
|
+
# (store names and parsers in hash in the builder,
|
110
|
+
# when building merge hashes from sub builders and store everything in the resulting sequence or choice.
|
111
|
+
# make Parser#bind take and optional symbol. if it is given, the name is looked up in the table.)
|
112
|
+
# TODO:
|
113
|
+
# Think about this more.
|
114
|
+
# Do we need to be able to build parsers by chaining function calls? DSL should be sufficient.
|
115
|
+
# If yes, the parser methods in this class should not return "self", but the Hammer::Parser object they create.
|
116
|
+
def indirect
|
117
|
+
parser = Hammer::Parser.indirect
|
118
|
+
@parsers << parser
|
119
|
+
return parser
|
120
|
+
end
|
121
|
+
|
122
|
+
end # class ParserBuilder
|
123
|
+
|
124
|
+
end # module Hammer
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Minitest
|
2
|
+
|
3
|
+
module Assertions
|
4
|
+
HAMMER_JUST_PARSE = Object.new
|
5
|
+
def assert_parse_ok(parser, probe, expected=HAMMER_JUST_PARSE)
|
6
|
+
refute_nil parser, "Parser must not be nil (this is a problem with your test)"
|
7
|
+
parse_result = parser.parse(probe)
|
8
|
+
refute_nil parse_result, "Parse failed"
|
9
|
+
if HAMMER_JUST_PARSE != expected
|
10
|
+
if parse_result.ast == nil
|
11
|
+
assert_nil expected, "Parser returned nil AST; expected #{expected}"
|
12
|
+
else
|
13
|
+
assert_equal parse_result.ast.unmarshal, expected
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def refute_parse_ok(parser, probe)
|
19
|
+
refute_nil parser, "Parser must not be nil (this is a problem with your test)"
|
20
|
+
parse_result = parser.parse(probe)
|
21
|
+
|
22
|
+
if not parse_result.nil?
|
23
|
+
assert_nil parse_result, "Parse succeeded unexpectedly with " + parse_result.ast.inspect
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
#def self.plugin_hammer-parser_init(options)
|
30
|
+
end
|
31
|
+
|
@@ -0,0 +1,755 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require 'minitest/autorun'
|
3
|
+
require 'hammer-parser'
|
4
|
+
class TestToken < Minitest::Test
|
5
|
+
def setup
|
6
|
+
super
|
7
|
+
h = Hammer::Parser
|
8
|
+
@parser_1 = h.token("95\xa2")
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_1
|
12
|
+
assert_parse_ok @parser_1, "95\xa2", "95\xa2"
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_2
|
16
|
+
refute_parse_ok @parser_1, "95\xa3"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class TestCh < Minitest::Test
|
21
|
+
def setup
|
22
|
+
super
|
23
|
+
h = Hammer::Parser
|
24
|
+
@parser_1 = h.ch(0xa2)
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_1
|
28
|
+
assert_parse_ok @parser_1, "\xa2", 0xa2.chr
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_2
|
32
|
+
refute_parse_ok @parser_1, "\xa3"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class TestChRange < Minitest::Test
|
37
|
+
def setup
|
38
|
+
super
|
39
|
+
h = Hammer::Parser
|
40
|
+
@parser_1 = h.ch_range(0x61, 0x63)
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_1
|
44
|
+
assert_parse_ok @parser_1, "b", 0x62.chr
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_2
|
48
|
+
refute_parse_ok @parser_1, "d"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class TestInt64 < Minitest::Test
|
53
|
+
def setup
|
54
|
+
super
|
55
|
+
h = Hammer::Parser
|
56
|
+
@parser_1 = h.int64
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_1
|
60
|
+
assert_parse_ok @parser_1, "\xff\xff\xff\xfe\x00\x00\x00\x00", -0x200000000
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_2
|
64
|
+
refute_parse_ok @parser_1, "\xff\xff\xff\xfe\x00\x00\x00"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
class TestInt32 < Minitest::Test
|
69
|
+
def setup
|
70
|
+
super
|
71
|
+
h = Hammer::Parser
|
72
|
+
@parser_1 = h.int32
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_1
|
76
|
+
assert_parse_ok @parser_1, "\xff\xfe\x00\x00", -0x20000
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_2
|
80
|
+
refute_parse_ok @parser_1, "\xff\xfe\x00"
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_3
|
84
|
+
assert_parse_ok @parser_1, "\x00\x02\x00\x00", 0x20000
|
85
|
+
end
|
86
|
+
|
87
|
+
def test_4
|
88
|
+
refute_parse_ok @parser_1, "\x00\x02\x00"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
class TestInt16 < Minitest::Test
|
93
|
+
def setup
|
94
|
+
super
|
95
|
+
h = Hammer::Parser
|
96
|
+
@parser_1 = h.int16
|
97
|
+
end
|
98
|
+
|
99
|
+
def test_1
|
100
|
+
assert_parse_ok @parser_1, "\xfe\x00", -0x200
|
101
|
+
end
|
102
|
+
|
103
|
+
def test_2
|
104
|
+
refute_parse_ok @parser_1, "\xfe"
|
105
|
+
end
|
106
|
+
|
107
|
+
def test_3
|
108
|
+
assert_parse_ok @parser_1, "\x02\x00", 0x200
|
109
|
+
end
|
110
|
+
|
111
|
+
def test_4
|
112
|
+
refute_parse_ok @parser_1, "\x02"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
class TestInt8 < Minitest::Test
|
117
|
+
def setup
|
118
|
+
super
|
119
|
+
h = Hammer::Parser
|
120
|
+
@parser_1 = h.int8
|
121
|
+
end
|
122
|
+
|
123
|
+
def test_1
|
124
|
+
assert_parse_ok @parser_1, "\x88", -0x78
|
125
|
+
end
|
126
|
+
|
127
|
+
def test_2
|
128
|
+
refute_parse_ok @parser_1, ""
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
class TestUint64 < Minitest::Test
|
133
|
+
def setup
|
134
|
+
super
|
135
|
+
h = Hammer::Parser
|
136
|
+
@parser_1 = h.uint64
|
137
|
+
end
|
138
|
+
|
139
|
+
def test_1
|
140
|
+
assert_parse_ok @parser_1, "\x00\x00\x00\x02\x00\x00\x00\x00", 0x200000000
|
141
|
+
end
|
142
|
+
|
143
|
+
def test_2
|
144
|
+
refute_parse_ok @parser_1, "\x00\x00\x00\x02\x00\x00\x00"
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
class TestUint32 < Minitest::Test
|
149
|
+
def setup
|
150
|
+
super
|
151
|
+
h = Hammer::Parser
|
152
|
+
@parser_1 = h.uint32
|
153
|
+
end
|
154
|
+
|
155
|
+
def test_1
|
156
|
+
assert_parse_ok @parser_1, "\x00\x02\x00\x00", 0x20000
|
157
|
+
end
|
158
|
+
|
159
|
+
def test_2
|
160
|
+
refute_parse_ok @parser_1, "\x00\x02\x00"
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
class TestUint16 < Minitest::Test
|
165
|
+
def setup
|
166
|
+
super
|
167
|
+
h = Hammer::Parser
|
168
|
+
@parser_1 = h.uint16
|
169
|
+
end
|
170
|
+
|
171
|
+
def test_1
|
172
|
+
assert_parse_ok @parser_1, "\x02\x00", 0x200
|
173
|
+
end
|
174
|
+
|
175
|
+
def test_2
|
176
|
+
refute_parse_ok @parser_1, "\x02"
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
class TestUint8 < Minitest::Test
|
181
|
+
def setup
|
182
|
+
super
|
183
|
+
h = Hammer::Parser
|
184
|
+
@parser_1 = h.uint8
|
185
|
+
end
|
186
|
+
|
187
|
+
def test_1
|
188
|
+
assert_parse_ok @parser_1, "x", 0x78
|
189
|
+
end
|
190
|
+
|
191
|
+
def test_2
|
192
|
+
refute_parse_ok @parser_1, ""
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
class TestIntRange < Minitest::Test
|
197
|
+
def setup
|
198
|
+
super
|
199
|
+
h = Hammer::Parser
|
200
|
+
@parser_1 = h.int_range(h.uint8, 0x3, 0xa)
|
201
|
+
end
|
202
|
+
|
203
|
+
def test_1
|
204
|
+
assert_parse_ok @parser_1, "\x05", 0x5
|
205
|
+
end
|
206
|
+
|
207
|
+
def test_2
|
208
|
+
refute_parse_ok @parser_1, "\x0b"
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
class TestWhitespace < Minitest::Test
|
213
|
+
def setup
|
214
|
+
super
|
215
|
+
h = Hammer::Parser
|
216
|
+
@parser_1 = h.whitespace(h.ch(0x61))
|
217
|
+
@parser_2 = h.whitespace(h.end_p)
|
218
|
+
end
|
219
|
+
|
220
|
+
def test_1
|
221
|
+
assert_parse_ok @parser_1, "a", 0x61.chr
|
222
|
+
end
|
223
|
+
|
224
|
+
def test_2
|
225
|
+
assert_parse_ok @parser_1, " a", 0x61.chr
|
226
|
+
end
|
227
|
+
|
228
|
+
def test_3
|
229
|
+
assert_parse_ok @parser_1, " a", 0x61.chr
|
230
|
+
end
|
231
|
+
|
232
|
+
def test_4
|
233
|
+
assert_parse_ok @parser_1, "\x09a", 0x61.chr
|
234
|
+
end
|
235
|
+
|
236
|
+
def test_5
|
237
|
+
refute_parse_ok @parser_1, "_a"
|
238
|
+
end
|
239
|
+
|
240
|
+
def test_6
|
241
|
+
assert_parse_ok @parser_2, "", nil
|
242
|
+
end
|
243
|
+
|
244
|
+
def test_7
|
245
|
+
assert_parse_ok @parser_2, " ", nil
|
246
|
+
end
|
247
|
+
|
248
|
+
def test_8
|
249
|
+
refute_parse_ok @parser_2, " x"
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
class TestLeft < Minitest::Test
|
254
|
+
def setup
|
255
|
+
super
|
256
|
+
h = Hammer::Parser
|
257
|
+
@parser_1 = h.left(h.ch(0x61), h.ch(0x20))
|
258
|
+
end
|
259
|
+
|
260
|
+
def test_1
|
261
|
+
assert_parse_ok @parser_1, "a ", 0x61.chr
|
262
|
+
end
|
263
|
+
|
264
|
+
def test_2
|
265
|
+
refute_parse_ok @parser_1, "a"
|
266
|
+
end
|
267
|
+
|
268
|
+
def test_3
|
269
|
+
refute_parse_ok @parser_1, " "
|
270
|
+
end
|
271
|
+
|
272
|
+
def test_4
|
273
|
+
refute_parse_ok @parser_1, "ba"
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
class TestMiddle < Minitest::Test
|
278
|
+
def setup
|
279
|
+
super
|
280
|
+
h = Hammer::Parser
|
281
|
+
@parser_1 = h.middle(h.ch(0x20.chr), h.ch(0x61.chr), h.ch(0x20.chr))
|
282
|
+
end
|
283
|
+
|
284
|
+
def test_1
|
285
|
+
assert_parse_ok @parser_1, " a ", 0x61.chr
|
286
|
+
end
|
287
|
+
|
288
|
+
def test_2
|
289
|
+
refute_parse_ok @parser_1, "a"
|
290
|
+
end
|
291
|
+
|
292
|
+
def test_3
|
293
|
+
refute_parse_ok @parser_1, " a"
|
294
|
+
end
|
295
|
+
|
296
|
+
def test_4
|
297
|
+
refute_parse_ok @parser_1, "a "
|
298
|
+
end
|
299
|
+
|
300
|
+
def test_5
|
301
|
+
refute_parse_ok @parser_1, " b "
|
302
|
+
end
|
303
|
+
|
304
|
+
def test_6
|
305
|
+
refute_parse_ok @parser_1, "ba "
|
306
|
+
end
|
307
|
+
|
308
|
+
def test_7
|
309
|
+
refute_parse_ok @parser_1, " ab"
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
class TestIn < Minitest::Test
|
314
|
+
def setup
|
315
|
+
super
|
316
|
+
h = Hammer::Parser
|
317
|
+
@parser_1 = h.in("abc")
|
318
|
+
end
|
319
|
+
|
320
|
+
def test_1
|
321
|
+
assert_parse_ok @parser_1, "b", 0x62.chr
|
322
|
+
end
|
323
|
+
|
324
|
+
def test_2
|
325
|
+
refute_parse_ok @parser_1, "d"
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
class TestNotIn < Minitest::Test
|
330
|
+
def setup
|
331
|
+
super
|
332
|
+
h = Hammer::Parser
|
333
|
+
@parser_1 = h.not_in("abc")
|
334
|
+
end
|
335
|
+
|
336
|
+
def test_1
|
337
|
+
assert_parse_ok @parser_1, "d", 0x64.chr
|
338
|
+
end
|
339
|
+
|
340
|
+
def test_2
|
341
|
+
refute_parse_ok @parser_1, "a"
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
class TestEndP < Minitest::Test
|
346
|
+
def setup
|
347
|
+
super
|
348
|
+
h = Hammer::Parser
|
349
|
+
@parser_1 = h.sequence(h.ch(0x61.chr), h.end_p)
|
350
|
+
end
|
351
|
+
|
352
|
+
def test_1
|
353
|
+
assert_parse_ok @parser_1, "a", [0x61.chr]
|
354
|
+
end
|
355
|
+
|
356
|
+
def test_2
|
357
|
+
refute_parse_ok @parser_1, "aa"
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
class TestNothingP < Minitest::Test
|
362
|
+
def setup
|
363
|
+
super
|
364
|
+
h = Hammer::Parser
|
365
|
+
@parser_1 = h.nothing_p
|
366
|
+
end
|
367
|
+
|
368
|
+
def test_1
|
369
|
+
refute_parse_ok @parser_1, "a"
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
class TestSequence < Minitest::Test
|
374
|
+
def setup
|
375
|
+
super
|
376
|
+
h = Hammer::Parser
|
377
|
+
@parser_1 = h.sequence(h.ch(0x61.chr), h.ch(0x62.chr))
|
378
|
+
@parser_2 = h.sequence(h.ch(0x61.chr), h.whitespace(h.ch(0x62.chr)))
|
379
|
+
end
|
380
|
+
|
381
|
+
def test_1
|
382
|
+
assert_parse_ok @parser_1, "ab", [0x61.chr, 0x62.chr]
|
383
|
+
end
|
384
|
+
|
385
|
+
def test_2
|
386
|
+
refute_parse_ok @parser_1, "a"
|
387
|
+
end
|
388
|
+
|
389
|
+
def test_3
|
390
|
+
refute_parse_ok @parser_1, "b"
|
391
|
+
end
|
392
|
+
|
393
|
+
def test_4
|
394
|
+
assert_parse_ok @parser_2, "ab", [0x61.chr, 0x62.chr]
|
395
|
+
end
|
396
|
+
|
397
|
+
def test_5
|
398
|
+
assert_parse_ok @parser_2, "a b", [0x61.chr, 0x62.chr]
|
399
|
+
end
|
400
|
+
|
401
|
+
def test_6
|
402
|
+
assert_parse_ok @parser_2, "a b", [0x61.chr, 0x62.chr]
|
403
|
+
end
|
404
|
+
end
|
405
|
+
|
406
|
+
class TestChoice < Minitest::Test
|
407
|
+
def setup
|
408
|
+
super
|
409
|
+
h = Hammer::Parser
|
410
|
+
@parser_1 = h.choice(h.ch(0x61.chr), h.ch(0x62.chr))
|
411
|
+
end
|
412
|
+
|
413
|
+
def test_1
|
414
|
+
assert_parse_ok @parser_1, "a", 0x61.chr
|
415
|
+
end
|
416
|
+
|
417
|
+
def test_2
|
418
|
+
assert_parse_ok @parser_1, "b", 0x62.chr
|
419
|
+
end
|
420
|
+
|
421
|
+
def test_3
|
422
|
+
assert_parse_ok @parser_1, "ab", 0x61.chr
|
423
|
+
end
|
424
|
+
|
425
|
+
def test_4
|
426
|
+
refute_parse_ok @parser_1, "c"
|
427
|
+
end
|
428
|
+
end
|
429
|
+
|
430
|
+
class TestButnot < Minitest::Test
|
431
|
+
def setup
|
432
|
+
super
|
433
|
+
h = Hammer::Parser
|
434
|
+
@parser_1 = h.butnot(h.ch(0x61.chr), h.token("ab"))
|
435
|
+
@parser_2 = h.butnot(h.ch_range(0x30.chr, 0x39.chr), h.ch(0x36.chr))
|
436
|
+
end
|
437
|
+
|
438
|
+
def test_1
|
439
|
+
assert_parse_ok @parser_1, "a", 0x61.chr
|
440
|
+
end
|
441
|
+
|
442
|
+
def test_2
|
443
|
+
refute_parse_ok @parser_1, "ab"
|
444
|
+
end
|
445
|
+
|
446
|
+
def test_3
|
447
|
+
assert_parse_ok @parser_1, "aa", 0x61.chr
|
448
|
+
end
|
449
|
+
|
450
|
+
def test_4
|
451
|
+
assert_parse_ok @parser_2, "5", 0x35.chr
|
452
|
+
end
|
453
|
+
|
454
|
+
def test_5
|
455
|
+
refute_parse_ok @parser_2, "6"
|
456
|
+
end
|
457
|
+
end
|
458
|
+
|
459
|
+
class TestDifference < Minitest::Test
|
460
|
+
def setup
|
461
|
+
super
|
462
|
+
h = Hammer::Parser
|
463
|
+
@parser_1 = h.difference(h.token("ab"), h.ch(0x61.chr))
|
464
|
+
end
|
465
|
+
|
466
|
+
def test_1
|
467
|
+
assert_parse_ok @parser_1, "ab", "ab"
|
468
|
+
end
|
469
|
+
|
470
|
+
def test_2
|
471
|
+
refute_parse_ok @parser_1, "a"
|
472
|
+
end
|
473
|
+
end
|
474
|
+
|
475
|
+
class TestXor < Minitest::Test
|
476
|
+
def setup
|
477
|
+
super
|
478
|
+
h = Hammer::Parser
|
479
|
+
@parser_1 = h.xor(h.ch_range(0x30.chr, 0x36.chr), h.ch_range(0x35.chr, 0x39.chr))
|
480
|
+
end
|
481
|
+
|
482
|
+
def test_1
|
483
|
+
assert_parse_ok @parser_1, "0", 0x30.chr
|
484
|
+
end
|
485
|
+
|
486
|
+
def test_2
|
487
|
+
assert_parse_ok @parser_1, "9", 0x39.chr
|
488
|
+
end
|
489
|
+
|
490
|
+
def test_3
|
491
|
+
refute_parse_ok @parser_1, "5"
|
492
|
+
end
|
493
|
+
|
494
|
+
def test_4
|
495
|
+
refute_parse_ok @parser_1, "a"
|
496
|
+
end
|
497
|
+
end
|
498
|
+
|
499
|
+
class TestMany < Minitest::Test
|
500
|
+
def setup
|
501
|
+
super
|
502
|
+
h = Hammer::Parser
|
503
|
+
@parser_1 = h.many(h.choice(h.ch(0x61.chr), h.ch(0x62.chr)))
|
504
|
+
end
|
505
|
+
|
506
|
+
def test_1
|
507
|
+
assert_parse_ok @parser_1, "", []
|
508
|
+
end
|
509
|
+
|
510
|
+
def test_2
|
511
|
+
assert_parse_ok @parser_1, "a", [0x61.chr]
|
512
|
+
end
|
513
|
+
|
514
|
+
def test_3
|
515
|
+
assert_parse_ok @parser_1, "b", [0x62.chr]
|
516
|
+
end
|
517
|
+
|
518
|
+
def test_4
|
519
|
+
assert_parse_ok @parser_1, "aabbaba", [0x61.chr, 0x61.chr, 0x62.chr, 0x62.chr, 0x61.chr, 0x62.chr, 0x61.chr]
|
520
|
+
end
|
521
|
+
end
|
522
|
+
|
523
|
+
class TestMany1 < Minitest::Test
|
524
|
+
def setup
|
525
|
+
super
|
526
|
+
h = Hammer::Parser
|
527
|
+
@parser_1 = h.many1(h.choice(h.ch(0x61.chr), h.ch(0x62.chr)))
|
528
|
+
end
|
529
|
+
|
530
|
+
def test_1
|
531
|
+
refute_parse_ok @parser_1, ""
|
532
|
+
end
|
533
|
+
|
534
|
+
def test_2
|
535
|
+
assert_parse_ok @parser_1, "a", [0x61.chr]
|
536
|
+
end
|
537
|
+
|
538
|
+
def test_3
|
539
|
+
assert_parse_ok @parser_1, "b", [0x62.chr]
|
540
|
+
end
|
541
|
+
|
542
|
+
def test_4
|
543
|
+
assert_parse_ok @parser_1, "aabbaba", [0x61.chr, 0x61.chr, 0x62.chr, 0x62.chr, 0x61.chr, 0x62.chr, 0x61.chr]
|
544
|
+
end
|
545
|
+
|
546
|
+
def test_5
|
547
|
+
refute_parse_ok @parser_1, "daabbabadef"
|
548
|
+
end
|
549
|
+
end
|
550
|
+
|
551
|
+
class TestRepeatN < Minitest::Test
|
552
|
+
def setup
|
553
|
+
super
|
554
|
+
h = Hammer::Parser
|
555
|
+
@parser_1 = h.repeat_n(h.choice(h.ch(0x61.chr), h.ch(0x62.chr)), 0x2)
|
556
|
+
end
|
557
|
+
|
558
|
+
def test_1
|
559
|
+
refute_parse_ok @parser_1, "adef"
|
560
|
+
end
|
561
|
+
|
562
|
+
def test_2
|
563
|
+
assert_parse_ok @parser_1, "abdef", [0x61.chr, 0x62.chr]
|
564
|
+
end
|
565
|
+
|
566
|
+
def test_3
|
567
|
+
refute_parse_ok @parser_1, "dabdef"
|
568
|
+
end
|
569
|
+
end
|
570
|
+
|
571
|
+
class TestOptional < Minitest::Test
|
572
|
+
def setup
|
573
|
+
super
|
574
|
+
h = Hammer::Parser
|
575
|
+
@parser_1 = h.sequence(h.ch(0x61.chr), h.optional(h.choice(h.ch(0x62.chr), h.ch(0x63.chr))), h.ch(0x64.chr))
|
576
|
+
end
|
577
|
+
|
578
|
+
def test_1
|
579
|
+
assert_parse_ok @parser_1, "abd", [0x61.chr, 0x62.chr, 0x64.chr]
|
580
|
+
end
|
581
|
+
|
582
|
+
def test_2
|
583
|
+
assert_parse_ok @parser_1, "acd", [0x61.chr, 0x63.chr, 0x64.chr]
|
584
|
+
end
|
585
|
+
|
586
|
+
def test_3
|
587
|
+
assert_parse_ok @parser_1, "ad", [0x61.chr, nil, 0x64.chr]
|
588
|
+
end
|
589
|
+
|
590
|
+
def test_4
|
591
|
+
refute_parse_ok @parser_1, "aed"
|
592
|
+
end
|
593
|
+
|
594
|
+
def test_5
|
595
|
+
refute_parse_ok @parser_1, "ab"
|
596
|
+
end
|
597
|
+
|
598
|
+
def test_6
|
599
|
+
refute_parse_ok @parser_1, "ac"
|
600
|
+
end
|
601
|
+
end
|
602
|
+
|
603
|
+
class TestIgnore < Minitest::Test
|
604
|
+
def setup
|
605
|
+
super
|
606
|
+
h = Hammer::Parser
|
607
|
+
@parser_1 = h.sequence(h.ch(0x61.chr), h.ignore(h.ch(0x62.chr)), h.ch(0x63.chr))
|
608
|
+
end
|
609
|
+
|
610
|
+
def test_1
|
611
|
+
assert_parse_ok @parser_1, "abc", [0x61.chr, 0x63.chr]
|
612
|
+
end
|
613
|
+
|
614
|
+
def test_2
|
615
|
+
refute_parse_ok @parser_1, "ac"
|
616
|
+
end
|
617
|
+
end
|
618
|
+
|
619
|
+
class TestSepBy < Minitest::Test
|
620
|
+
def setup
|
621
|
+
super
|
622
|
+
h = Hammer::Parser
|
623
|
+
@parser_1 = h.sepBy(h.choice(h.ch(0x31.chr), h.ch(0x32.chr), h.ch(0x33.chr)), h.ch(0x2c.chr))
|
624
|
+
end
|
625
|
+
|
626
|
+
def test_1
|
627
|
+
assert_parse_ok @parser_1, "1,2,3", [0x31.chr, 0x32.chr, 0x33.chr]
|
628
|
+
end
|
629
|
+
|
630
|
+
def test_2
|
631
|
+
assert_parse_ok @parser_1, "1,3,2", [0x31.chr, 0x33.chr, 0x32.chr]
|
632
|
+
end
|
633
|
+
|
634
|
+
def test_3
|
635
|
+
assert_parse_ok @parser_1, "1,3", [0x31.chr, 0x33.chr]
|
636
|
+
end
|
637
|
+
|
638
|
+
def test_4
|
639
|
+
assert_parse_ok @parser_1, "3", [0x33.chr]
|
640
|
+
end
|
641
|
+
|
642
|
+
def test_5
|
643
|
+
assert_parse_ok @parser_1, "", []
|
644
|
+
end
|
645
|
+
end
|
646
|
+
|
647
|
+
class TestSepBy1 < Minitest::Test
|
648
|
+
def setup
|
649
|
+
super
|
650
|
+
h = Hammer::Parser
|
651
|
+
@parser_1 = h.sepBy1(h.choice(h.ch(0x31.chr), h.ch(0x32.chr), h.ch(0x33.chr)), h.ch(0x2c.chr))
|
652
|
+
end
|
653
|
+
|
654
|
+
def test_1
|
655
|
+
assert_parse_ok @parser_1, "1,2,3", [0x31.chr, 0x32.chr, 0x33.chr]
|
656
|
+
end
|
657
|
+
|
658
|
+
def test_2
|
659
|
+
assert_parse_ok @parser_1, "1,3,2", [0x31.chr, 0x33.chr, 0x32.chr]
|
660
|
+
end
|
661
|
+
|
662
|
+
def test_3
|
663
|
+
assert_parse_ok @parser_1, "1,3", [0x31.chr, 0x33.chr]
|
664
|
+
end
|
665
|
+
|
666
|
+
def test_4
|
667
|
+
assert_parse_ok @parser_1, "3", [0x33.chr]
|
668
|
+
end
|
669
|
+
|
670
|
+
def test_5
|
671
|
+
refute_parse_ok @parser_1, ""
|
672
|
+
end
|
673
|
+
end
|
674
|
+
|
675
|
+
class TestAnd < Minitest::Test
|
676
|
+
def setup
|
677
|
+
super
|
678
|
+
h = Hammer::Parser
|
679
|
+
@parser_1 = h.sequence(h.and(h.ch(0x30.chr)), h.ch(0x30.chr))
|
680
|
+
@parser_2 = h.sequence(h.and(h.ch(0x30.chr)), h.ch(0x31.chr))
|
681
|
+
@parser_3 = h.sequence(h.ch(0x31.chr), h.and(h.ch(0x32.chr)))
|
682
|
+
end
|
683
|
+
|
684
|
+
def test_1
|
685
|
+
assert_parse_ok @parser_1, "0", [0x30.chr]
|
686
|
+
end
|
687
|
+
|
688
|
+
def test_2
|
689
|
+
refute_parse_ok @parser_1, "1"
|
690
|
+
end
|
691
|
+
|
692
|
+
def test_3
|
693
|
+
refute_parse_ok @parser_2, "0"
|
694
|
+
end
|
695
|
+
|
696
|
+
def test_4
|
697
|
+
refute_parse_ok @parser_2, "1"
|
698
|
+
end
|
699
|
+
|
700
|
+
def test_5
|
701
|
+
assert_parse_ok @parser_3, "12", [0x31.chr]
|
702
|
+
end
|
703
|
+
|
704
|
+
def test_6
|
705
|
+
refute_parse_ok @parser_3, "13"
|
706
|
+
end
|
707
|
+
end
|
708
|
+
|
709
|
+
class TestNot < Minitest::Test
|
710
|
+
def setup
|
711
|
+
super
|
712
|
+
h = Hammer::Parser
|
713
|
+
@parser_1 = h.sequence(h.ch(0x61.chr), h.choice(h.token("+"), h.token("++")), h.ch(0x62.chr))
|
714
|
+
@parser_2 = h.sequence(h.ch(0x61.chr), h.choice(h.sequence(h.token("+"), h.not(h.ch(0x2b.chr))), h.token("++")), h.ch(0x62.chr))
|
715
|
+
end
|
716
|
+
|
717
|
+
def test_1
|
718
|
+
assert_parse_ok @parser_1, "a+b", [0x61.chr, "+", 0x62.chr]
|
719
|
+
end
|
720
|
+
|
721
|
+
def test_2
|
722
|
+
refute_parse_ok @parser_1, "a++b"
|
723
|
+
end
|
724
|
+
|
725
|
+
def test_3
|
726
|
+
assert_parse_ok @parser_2, "a+b", [0x61.chr, ["+"], 0x62.chr]
|
727
|
+
end
|
728
|
+
|
729
|
+
def test_4
|
730
|
+
assert_parse_ok @parser_2, "a++b", [0x61.chr, "++", 0x62.chr]
|
731
|
+
end
|
732
|
+
end
|
733
|
+
|
734
|
+
class TestRightrec < Minitest::Test
|
735
|
+
def setup
|
736
|
+
super
|
737
|
+
h = Hammer::Parser
|
738
|
+
@sp_rr = h.indirect
|
739
|
+
@sp_rr.bind h.choice(h.sequence(h.ch(0x61.chr), @sp_rr), h.epsilon_p)
|
740
|
+
@parser_1 = @sp_rr
|
741
|
+
end
|
742
|
+
|
743
|
+
def test_1
|
744
|
+
assert_parse_ok @parser_1, "a", [0x61.chr]
|
745
|
+
end
|
746
|
+
|
747
|
+
def test_2
|
748
|
+
assert_parse_ok @parser_1, "aa", [0x61.chr, [0x61.chr]]
|
749
|
+
end
|
750
|
+
|
751
|
+
def test_3
|
752
|
+
assert_parse_ok @parser_1, "aaa", [0x61.chr, [0x61.chr, [0x61.chr]]]
|
753
|
+
end
|
754
|
+
end
|
755
|
+
|