Class: Hammer::Parser

Inherits:
Object
  • Object
show all
Defined in:
lib/hammer/parser.rb,
lib/hammer/parser_builder.rb

Constant Summary collapse

@@saved_objects =
Hammer::Internal::DynamicVariable.new nil, "Hammer parse-time pins"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name, h_parser, dont_gc = []) ⇒ Parser

Don’t create new instances with Hammer::Parser.new, use the constructor methods instead (i.e. Hammer::Parser.int64 etc.)

name: Name of the parser. Should be a symbol. h_parser: The pointer to the parser as returned by hammer. 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
15
16
17
18
19
20
# File 'lib/hammer/parser.rb', line 14

def initialize(name, h_parser, dont_gc=[])
  @name = name
  @h_parser = h_parser
  # Always store as array, so we can easily add stuff later on
  dont_gc = [dont_gc] unless dont_gc.is_a? Array
  @dont_gc = dont_gc.dup
end

Instance Attribute Details

#dont_gcObject (readonly)

Returns the value of attribute dont_gc.



25
26
27
# File 'lib/hammer/parser.rb', line 25

def dont_gc
  @dont_gc
end

#h_parserObject (readonly)

Returns the value of attribute h_parser.



24
25
26
# File 'lib/hammer/parser.rb', line 24

def h_parser
  @h_parser
end

#nameObject (readonly)

dont_gc is required to build a fuzzer from the declaration of Hammer::Parser object.



23
24
25
# File 'lib/hammer/parser.rb', line 23

def name
  @name
end

Class Method Details

.action(parser, action = nil, &block) ⇒ Object

Can pass the action either as a Proc in second parameter, or as block.

Raises:

  • (ArgumentError)


59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/hammer/parser.rb', line 59

def self.action(parser, action=nil, &block)
  action = block if action.nil?
  raise ArgumentError, 'no action' if action.nil?

  real_action = Proc.new {|hpr|
                      ret = action.call(hpr.ast)
                      # Pin the result
                      @@saved_objects.value << ret
                      hpt = hpr.arena_alloc(Hammer::Internal::HParsedToken)
                      unless hpr.ast.nil?
                        hpt[:index] = hpr[:ast][:index]
                        hpt[:bit_offset] = hpr[:ast][:bit_offset]
                      end
                      hpt[:token_type] = :"com.upstandinghackers.hammer.ruby.object"
                      hpt[:data][:uint] = ret.object_id
                      hpt
                    }

  h_parser = Hammer::Internal.h_action(parser.h_parser, real_action)
  return Hammer::Parser.new(:action, h_parser, [parser, action, real_action])
end

.attr_bool(parser, predicate = nil, &block) ⇒ Object

Can pass the predicate either as a Proc in second parameter, or as block.

Raises:

  • (ArgumentError)


82
83
84
85
86
87
88
89
90
# File 'lib/hammer/parser.rb', line 82

def self.attr_bool(parser, predicate=nil, &block)
  predicate = block if predicate.nil?
  raise ArgumentError, 'no predicate' if predicate.nil?

  real_pred = Proc.new {|hpr| predicate.call hpr.ast}

  h_parser = Hammer::Internal.h_attr_bool(parser.h_parser, real_pred)
  return Hammer::Parser.new(:attr_bool, h_parser, [parser, predicate, real_pred])
end

.build(&block) ⇒ Object



9
10
11
# File 'lib/hammer/parser_builder.rb', line 9

def self.build(&block)
  ParserBuilder.new.sequence(&block).build
end

.build_choice(&block) ⇒ Object



13
14
15
# File 'lib/hammer/parser_builder.rb', line 13

def self.build_choice(&block)
  ParserBuilder.new.choice(&block).build
end

.ch(ch) ⇒ Object



130
131
132
133
134
135
# File 'lib/hammer/parser.rb', line 130

def self.ch(ch)
  num = marshal_ch_arg(ch)
  h_parser = Hammer::Internal.h_ch(num)

  return ch_parser_wrapper(Hammer::Parser.new(:ch, h_parser, nil))
end

.ch_parser_wrapper(parser) ⇒ Object



126
127
128
# File 'lib/hammer/parser.rb', line 126

def self.ch_parser_wrapper(parser)
  return Hammer::Parser.action(parser) {|x| x.data.chr}
end

.ch_range(ch1, ch2) ⇒ Object



137
138
139
140
141
142
# File 'lib/hammer/parser.rb', line 137

def self.ch_range(ch1, ch2)
  ch1 = marshal_ch_arg(ch1)
  ch2 = marshal_ch_arg(ch2)
  h_parser = Hammer::Internal.h_ch_range(ch1, ch2)
  return ch_parser_wrapper(Hammer::Parser.new(:ch_range, h_parser, nil))
end

.in(charset) ⇒ Object

Raises:

  • (ArgumentError)


149
150
151
152
153
154
# File 'lib/hammer/parser.rb', line 149

def self.in(charset)
  raise ArgumentError, "Expected a String" unless charset.is_a?(String)
  ibuf = FFI::MemoryPointer.from_string(charset)
  h_parser = Hammer::Internal.h_in(ibuf, charset.bytesize)
  return ch_parser_wrapper(Hammer::Parser.new(:in, h_parser, nil))
end

.int_range(parser, i1, i2) ⇒ Object



144
145
146
147
# File 'lib/hammer/parser.rb', line 144

def self.int_range(parser, i1, i2)
  h_parser = Hammer::Internal.h_int_range(parser.h_parser, i1, i2)
  return Hammer::Parser.new(:int_range, h_parser, [parser])
end

.not_in(charset) ⇒ Object

Raises:

  • (ArgumentError)


161
162
163
164
165
166
# File 'lib/hammer/parser.rb', line 161

def self.not_in(charset)
  raise ArgumentError, "Expected a String" unless charset.is_a?(String)
  ibuf = FFI::MemoryPointer.from_string(charset)
  h_parser = Hammer::Internal.h_not_in(ibuf, charset.bytesize)
  return ch_parser_wrapper(Hammer::Parser.new(:not_in, h_parser, nil))
end

.repeat_n(parser, count) ⇒ Object



156
157
158
159
# File 'lib/hammer/parser.rb', line 156

def self.repeat_n(parser, count)
  h_parser = Hammer::Internal.h_repeat_n(parser.h_parser, count)
  return Hammer::Parser.new(:repeat_n, h_parser, nil)
end

.token(string) ⇒ Object



92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/hammer/parser.rb', line 92

def self.token(string)
  # Need to copy string to a memory buffer (not just string.dup)
  # * Original string might be modified, this must not affect existing tokens
  # * We need a constant memory address (Ruby string might be moved around by the Ruby VM)
  buffer = FFI::MemoryPointer.from_string(string)
  h_parser = Hammer::Internal.h_token(buffer, buffer.size-1) # buffer.size includes the null byte at the end
  encoding = string.encoding

  wrapping_action = Proc.new {|hpr|
                          hstr = hpr.arena_alloc(Hammer::Internal::HString)
                          hstr[:content] = hpr[:ast][:data][:bytes]
                          hstr[:encoding] = encoding.object_id

                          hpt = hpr.arena_alloc(Hammer::Internal::HParsedToken)
                          hpt[:token_type] = :"com.upstandinghackers.hammer.ruby.encodedStr"
                          hpt[:data][:user] = hstr.to_ptr
                          hpt[:bit_offset] = hpr[:ast][:bit_offset]
                          hpt[:index] = hpr[:ast][:index]
                          hpt
                        }
  wrapped_parser = Hammer::Internal.h_action(h_parser, wrapping_action)
  return Hammer::Parser.new(:token, wrapped_parser, [buffer, string, encoding, wrapping_action, h_parser])
end

Instance Method Details

#bind(other_parser) ⇒ Object

Binds an indirect parser.

Raises:

  • (RuntimeError)


52
53
54
55
56
# File 'lib/hammer/parser.rb', line 52

def bind(other_parser)
  raise RuntimeError, 'can only bind indirect parsers' unless self.name == :indirect
  Hammer::Internal.h_bind_indirect(self.h_parser, other_parser.h_parser)
  @dont_gc << other_parser
end

#parse(data) ⇒ Object

Parse the given data. Returns the parse result if successful, nil otherwise.

data: A string containing the data to parse.

Raises:

  • (RuntimeError)


30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/hammer/parser.rb', line 30

def parse(data)
  raise RuntimeError, '@h_parser is nil' if @h_parser.nil?
  raise ArgumentError, 'expecting a String' unless data.is_a? String # TODO: Not needed, FFI checks that.

  ibuf = FFI::MemoryPointer.from_string(data)
  @@saved_objects.with([]) do
      result = Hammer::Internal.h_parse(@h_parser, ibuf, data.bytesize) # Don't include the trailing null
      if result.null?
        return nil
      else
        # NOTE:
        # The parse result *must* hold a reference to the parser that created it!
        # Otherwise, the parser might get garbage-collected while the result is still valid.
        # Any pointers to token strings will then be invalid.
        result.instance_variable_set :@parser, self
        result.instance_variable_set :@pins, @@saved_objects.value
        return result
      end
    end
end