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