hammer-parser 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+