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