class Metasm::Preprocessor::Macro
a preprocessor macro
Attributes
args[RW]
array of tokens of formal arguments
body[RW]
array of tokens of macro body
name[RW]
the token holding the name used in the macro definition
varargs[RW]
bool
Public Class Methods
new(name)
click to toggle source
# File metasm/preprocessor.rb, line 77 def initialize(name) @name = name @body = [] end
parse_arglist(lexer, list=nil)
click to toggle source
parses an argument list from the lexer or from a list of tokens modifies the list, returns an array of list of tokens/nil handles nesting
# File metasm/preprocessor.rb, line 86 def self.parse_arglist(lexer, list=nil) readtok = lambda { list ? list.shift : lexer.readtok_nopp } unreadtok = lambda { |t| list ? (list.unshift(t) if t) : lexer.unreadtok(t) } tok = nil unreadlist = [] unreadlist << tok while tok = readtok[] and tok.type == :space if not tok or tok.type != :punct or tok.raw != '(' unreadtok[tok] unreadlist.reverse_each { |t| unreadtok[t] } return nil end args = [] # each argument is any token sequence # if it includes an '(' then find the matching ')', whatever is inside (handle nesting) # arg cannot include ',' in the top-level # args are parsed with no macro expansion # convert any space/eol sequence to a single space, strips them at begin/end of argument loop do arg = [] nest = 0 loop do raise lexer, 'unterminated arg list' if not tok = readtok[] case tok.type when :eol, :space next if arg.last and arg.last.type == :space tok = tok.dup tok.type = :space tok.raw = ' ' when :punct case tok.raw when ','; break if nest == 0 when ')'; break if nest == 0 ; nest -= 1 when '('; nest += 1 end end arg << tok end arg.pop if arg.last and arg.last.type == :space args << arg if not arg.empty? or args.length > 0 or tok.raw != ')' break if tok.raw == ')' end args end
Public Instance Methods
apply(lexer, name, args, list=nil)
click to toggle source
applies a preprocessor macro parses arguments if needed macros are lazy fills tokens.expanded_from returns an array of tokens
# File metasm/preprocessor.rb, line 135 def apply(lexer, name, args, list=nil) expfrom = name.expanded_from.to_a + [name] if args # hargs is a hash argname.raw => array of tokens hargs = @args.zip(args).inject({}) { |h, (af, ar)| h.update af.raw => ar } if not varargs raise name, 'invalid argument count' if args.length != @args.length else raise name, 'invalid argument count' if args.length < @args.length virg = name.dup # concat remaining args in __VA_ARGS__ virg.type = :punct virg.raw = ',' va = args[@args.length..-1].map { |a| a + [virg.dup] }.flatten va.pop hargs['__VA_ARGS__'] = va end else hargs = {} end res = [] b = @body.map { |t| t = t.dup ; t.expanded_from = expfrom ; t } while t = b.shift if a = hargs[t.raw] # expand macros a = a.dup while at = a.shift margs = nil if at.type == :string and am = lexer.definition[at.raw] and not at.expanded_from.to_a.find { |ef| ef.raw == @name.raw } and ((am.args and margs = Macro.parse_arglist(lexer, a)) or not am.args) toks = am.apply(lexer, at, margs, a) a = toks + a # reroll else res << at.dup if not res.last or res.last.type != :space or at.type != :space end end elsif t.type == :punct and t.raw == '##' # the '##' operator: concat the next token to the last in body nil while t = b.shift and t.type == :space res.pop while res.last and res.last.type == :space if not a = hargs[t.raw] a = [t] end if varargs and t.raw == '__VA_ARGS__' and res.last and res.last.type == :punct and res.last.raw == ',' if args.length == @args.length # pop last , if no vararg passed # XXX poof(1, 2,) != poof(1, 2) res.pop else # allow merging with ',' without warning res.concat a end else a = a[1..-1] if a.first and a.first.type == :space if not res.last or res.last.type != :string or not a.first or a.first.type != :string puts name.exception("cannot merge token #{res.last.raw} with #{a.first ? a.first.raw : 'nil'}").message if not a.first or (a.first.raw != '.' and res.last.raw != '.') if $VERBOSE res.concat a else res[-1] = res[-1].dup res.last.raw << a.first.raw res.concat a[1..-1] end end elsif args and t.type == :punct and t.raw == '#' # map an arg to a qstring nil while t = b.shift and t.type == :space t.type = :quoted t.value = hargs[t.raw].map { |aa| aa.raw }.join t.value = t.value[1..-1] if t.value[0] == ?\ # delete leading space t.raw = t.value.inspect res << t else res << t end end res end
dump(comment = true)
click to toggle source
# File metasm/preprocessor.rb, line 308 def dump(comment = true) str = '' str << "\n// from #{@name.backtrace[-2, 2] * ':'}\n" if comment str << "#define #{@name.raw}" if args str << '(' << (@args.map { |t| t.raw } + (varargs ? ['...'] : [])).join(', ') << ')' end str << ' ' << @body.map { |t| t.raw }.join end
parse_definition(lexer)
click to toggle source
parses the argument list and the body from lexer converts # + # to ## in body
# File metasm/preprocessor.rb, line 212 def parse_definition(lexer) varg = nil if tok = lexer.readtok_nopp and tok.type == :punct and tok.raw == '(' @args = [] loop do nil while tok = lexer.readtok_nopp and tok.type == :space # check '...' if tok and tok.type == :punct and tok.raw == '.' t1 = lexer.readtok_nopp t2 = lexer.readtok_nopp t3 = lexer.readtok_nopp t3 = lexer.readtok_nopp while t3 and t3.type == :space raise @name, 'booh' if not t1 or t1.type != :punct or t1.raw != '.' or not t2 or t2.type != :punct or t2.raw != '.' or not t3 or t3.type != :punct or t3.raw != ')' @varargs = true break end break if tok and tok.type == :punct and tok.raw == ')' and @args.empty? # allow empty list raise @name, 'invalid arg definition' if not tok or tok.type != :string @args << tok nil while tok = lexer.readtok_nopp and tok.type == :space # check '...' if tok and tok.type == :punct and tok.raw == '.' t1 = lexer.readtok_nopp t2 = lexer.readtok_nopp t3 = lexer.readtok_nopp t3 = lexer.readtok_nopp while t3 and t3.type == :space raise @name, 'booh' if not t1 or t1.type != :punct or t1.raw != '.' or not t2 or t2.type != :punct or t2.raw != '.' or not t3 or t3.type != :punct or t3.raw != ')' @varargs = true varg = @args.pop.raw break end raise @name, 'invalid arg separator' if not tok or tok.type != :punct or (tok.raw != ')' and tok.raw != ',') break if tok.raw == ')' end else lexer.unreadtok tok end nil while tok = lexer.readtok_nopp and tok.type == :space lexer.unreadtok tok while tok = lexer.readtok_nopp tok = tok.dup case tok.type when :eol lexer.unreadtok tok break when :space next if @body.last and @body.last.type == :space tok.raw = ' ' when :string tok.raw = '__VA_ARGS__' if varg and tok.raw == varg when :punct if tok.raw == '#' ntok = lexer.readtok_nopp if ntok and ntok.type == :punct and ntok.raw == '#' tok.raw << '#' else lexer.unreadtok ntok end end end @body << tok end @body.pop if @body.last and @body.last.type == :space # check macro is correct invalid_body = nil if (@body[-1] and @body[-1].raw == '##') or (@body[0] and @body[0].raw == '##') invalid_body ||= 'cannot have ## at begin or end of macro body' end if args if @args.map { |a| a.raw }.uniq.length != @args.length invalid_body ||= 'duplicate macro parameter' end @body.each_with_index { |tok_, i| if tok_.type == :punct and tok_.raw == '#' a = @body[i+1] a = @body[i+2] if not a or a.type == :space if not a.type == :string or (not @args.find { |aa| aa.raw == a.raw } and (not varargs or a.raw != '__VA_ARGS__')) invalid_body ||= 'cannot have # followed by non-argument' end end } end if invalid_body puts "W: #{lexer.filename}:#{lexer.lineno}, in #{@name.raw}: #{invalid_body}" if $VERBOSE false else true end end