/lib/metasm/metasm/parse_c.rb
Ruby | 4043 lines | 3600 code | 229 blank | 214 comment | 1162 complexity | c00ccb6d0a1677da4a9e23c86e7331a8 MD5 | raw file
Possible License(s): BSD-3-Clause, Apache-2.0, GPL-3.0, LGPL-2.1, GPL-2.0
Large files files are truncated, but you can click here to view the full file
- # This file is part of Metasm, the Ruby assembly manipulation suite
- # Copyright (C) 2006-2009 Yoann GUILLOT
- #
- # Licence is LGPL, see LICENCE in the top-level directory
- require 'metasm/main'
- require 'metasm/preprocessor'
- module Metasm
- # c parser
- # inspired from http://www.math.grin.edu/~stone/courses/languages/C-syntax.xhtml
- module C
- Keyword = %w[struct union enum if else for while do switch goto
- register extern auto static typedef const volatile
- void int float double char signed unsigned long short
- case continue break return default __attribute__
- asm __asm __asm__ sizeof typeof
- __declspec __cdecl __stdcall __fastcall __noreturn
- inline __inline __inline__ __volatile__
- __int8 __int16 __int32 __int64
- __builtin_offsetof
- ].inject({}) { |h, w| h.update w => true }
- class Statement
- end
- module Typed # allows quick testing whether an object is an CExpr or a Variable
- end
- class Block < Statement
- attr_accessor :symbol # hash name => Type/Variable/enum value
- attr_accessor :struct # hash name => Struct/Union/Enum
- attr_accessor :outer # parent block
- attr_accessor :statements # array of Statement/Declaration
- attr_accessor :anonymous_enums # array of anonymous Enum
- def initialize(outer, statements=[], symbol={}, struct={})
- @outer = outer
- @statements = statements
- @symbol = symbol
- @struct = struct
- end
- def struct_ancestors
- @outer ? @outer.struct_ancestors.merge(@struct) : @struct
- end
- def symbol_ancestors
- @outer ? @outer.symbol_ancestors.merge(@symbol) : @symbol
- end
- end
- module Attributes
- attr_accessor :attributes
- DECLSPECS = %w[cdecl stdcall fastcall inline naked thiscall noreturn]
- # parses a sequence of __attribute__((anything)) into self.attributes (array of string)
- def parse_attributes(parser, allow_declspec = false)
- while tok = parser.skipspaces and tok.type == :string
- case keyword = tok.raw
- when '__attribute__', '__declspec' # synonymous: __attribute__((foo)) == __declspec(foo)
- raise tok || parser if not tok = parser.skipspaces or tok.type != :punct or tok.raw != '('
- raise tok || parser if keyword == '__attribute__' and (not tok = parser.skipspaces or tok.type != :punct or tok.raw != '(')
- nest = 0
- attrib = ''
- loop do
- raise parser if not tok = parser.skipspaces
- if tok.type == :punct and tok.raw == ')'
- if nest == 0
- raise tok || parser if keyword == '__attribute__' and (not tok = parser.skipspaces or tok.type != :punct or tok.raw != ')')
- break
- else
- nest -= 1
- end
- elsif tok.type == :punct and tok.raw == '('
- nest += 1
- elsif nest == 0 and tok.type == :punct and tok.raw == ','
- raise tok || parser if not allow_declspec and DECLSPECS.include? attrib
- add_attribute attrib
- attrib = ''
- next
- end
- attrib << tok.raw
- end
- raise tok || parser, "attr #{attrib.inspect} not allowed here" if not allow_declspec and DECLSPECS.include? attrib
- else
- if allow_declspec and DECLSPECS.include? keyword.gsub('_', '')
- attrib = keyword.gsub('_', '')
- else break
- end
- end
- add_attribute(attrib)
- end
- parser.unreadtok tok
- end
- # checks if the object has an attribute in its attribute list
- def has_attribute(attr)
- attributes.to_a.include? attr
- end
- # adds an attribute to the object attribute list if it is not already in it
- def add_attribute(attr)
- (@attributes ||= []) << attr if not has_attribute(attr)
- end
- # checks if the object has an attributes a la __attribute__((attr(stuff))), returns 'stuff' (raw, no split on ',' or anything)
- def has_attribute_var(attr)
- $1 if attributes.to_a.find { |a| a =~ /^#{attr}\((.*)\)$/ }
- end
- end
- class Type
- include Attributes
- attr_accessor :qualifier # const volatile
- def pointer? ; false end
- def arithmetic? ; false end
- def integral? ; false end
- def float? ; false end
- def void? ; false end
- def base ; self end
- def untypedef ; self end
- def parse_initializer(parser, scope)
- raise parser, 'expr expected' if not ret = CExpression.parse(parser, scope, false)
- p, i = pointer?, integral?
- r = ret.reduce(parser) if p or i
- if (not p and not i) or (i and not r.kind_of? ::Integer) or (p and r != 0)
- parser.check_compatible_type(parser, ret.type, self)
- end
- ret
- end
- def parse_initializer_designator(parser, scope, value, idx, root=true)
- if not root and (not nt = parser.skipspaces or nt.type != :punct or nt.raw != '=')
- raise nt || parser, '"=" expected'
- end
- value[idx] = parse_initializer(parser, scope)
- idx + 1
- end
- end
- class BaseType < Type
- attr_accessor :name # :int :long :longlong :short :double :longdouble :float :char :void :__int8/16/32/64
- attr_accessor :specifier # sign specifier only
- def arithmetic? ; @name != :void end
- def integral? ; [:char, :short, :int, :long, :longlong, :ptr,
- :__int8, :__int16, :__int32, :__int64].include? @name end
- def signed? ; specifier != :unsigned end
- def float? ; [:float, :double, :longdouble].include? @name end
- def void? ; @name == :void end
- def align(parser) @name == :double ? 4 : parser.typesize[@name] end
- def initialize(name, *specs)
- @name = name
- specs.each { |s|
- case s
- when :const, :volatile; (@qualifier ||= []) << s
- when :signed, :unsigned; @specifier = s
- when nil
- else raise "internal error, got #{name.inspect} #{specs.inspect}"
- end
- }
- end
- def ==(o)
- o.object_id == self.object_id or
- (o.class == self.class and o.name == self.name and o.specifier == self.specifier and o.attributes == self.attributes)
- end
- end
- class TypeDef < Type
- attr_accessor :name
- attr_accessor :type
- attr_accessor :backtrace
- def initialize(name, type, backtrace)
- @name, @type, @backtrace = name, type, backtrace
- end
- def parse_initializer(parser, scope)
- @type.parse_initializer(parser, scope)
- end
- def pointer? ; @type.pointer? end
- def arithmetic? ; @type.arithmetic? end
- def integral? ; @type.integral? end
- def signed? ; @type.signed? end # relevant only if integral? returns true
- def float? ; @type.float? end
- def void? ; @type.void? end
- def untypedef ; @type.untypedef end
- def align(parser) @type.align(parser) end # XXX __attribute__ ?
- def pointed ; @type.pointed end
- end
- class Function < Type
- attr_accessor :type # return type
- attr_accessor :args # [name, Variable]
- attr_accessor :varargs # true/false
- def initialize(type=nil, args=nil)
- @type = type
- @args = args if args
- end
- def base ; @type.base ; end
- end
- class Union < Type
- attr_accessor :members # [Variable]
- attr_accessor :bits # [bits] or nil
- attr_accessor :name
- attr_accessor :backtrace
- attr_accessor :fldoffset, :fldbitoffset, :fldlist
- def align(parser) @members.to_a.map { |m| m.type.align(parser) }.max end
- # there is only one instance of a given named struct per parser
- # so we just compare struct names here
- # for comparison between parsers, see #compare_deep
- def ==(o)
- o.object_id == self.object_id or
- (o.class == self.class and o.name == self.name and ((o.name and true) or compare_deep(o)))
- end
- # compare to another structure, comparing members recursively (names and type)
- # returns true if the self is same as o
- def compare_deep(o, seen = [])
- return true if o.object_id == self.object_id
- return if o.class != self.class or o.name != self.name or o.attributes != self.attributes
- o.members.to_a.zip(self.members.to_a).each { |om, sm|
- return if om.name != sm.name
- return if om.type != sm.type
- if om.type.pointer?
- ot = om.type
- st = sm.type
- 500.times { # limit for selfpointers (shouldnt happen)
- break if not ot.pointer?
- ot = ot.pointed.untypedef
- st = st.pointed.untypedef
- }
- if ot.kind_of?(C::Union) and ot.name and not seen.include?(ot)
- return if not st.compare_deep(ot, seen+[ot])
- end
- end
- }
- true
- end
- def findmember(name, igncase=false)
- raise 'undefined structure' if not members
- return @fldlist[name] if fldlist and @fldlist[name]
- name = name.downcase if igncase
- if m = @members.find { |m_| (n = m_.name) and (igncase ? n.downcase : n) == name }
- return m
- else
- @members.each { |m_|
- if t = m_.type.untypedef and t.kind_of? Union and mm = t.findmember(name, igncase)
- return mm
- end
- }
- end
- nil
- end
- def offsetof(parser, name)
- raise parser, 'undefined structure' if not members
- update_member_cache(parser) if not fldlist
- return 0 if @fldlist[name]
- if name.kind_of?(Variable)
- return 0 if @members.include? name
- raise ParseError, 'unknown union member'
- end
- raise parser, 'unknown union member' if not findmember(name)
- @members.find { |m|
- m.type.untypedef.kind_of? Union and m.type.untypedef.findmember(name)
- }.type.untypedef.offsetof(parser, name)
- end
- def bitoffsetof(parser, name)
- raise parser, 'undefined structure' if not members
- update_member_cache(parser) if not fldlist
- return if @fldlist[name] or @members.include?(name)
- raise parser, 'undefined union' if not @members
- raise parser, 'unknown union member' if not findmember(name)
- @members.find { |m|
- m.type.untypedef.kind_of? Union and m.type.untypedef.findmember(name)
- }.type.untypedef.bitoffsetof(parser, name)
- end
- def parse_members(parser, scope)
- @fldlist = nil if fldlist # invalidate fld offset cache
- @members = []
- # parse struct/union members in definition
- loop do
- raise parser if not tok = parser.skipspaces
- break if tok.type == :punct and tok.raw == '}'
- parser.unreadtok tok
- raise tok, 'invalid struct member type' if not basetype = Variable.parse_type(parser, scope)
- loop do
- member = basetype.dup
- member.parse_declarator(parser, scope)
- member.type.length ||= 0 if member.type.kind_of?(Array) # struct { char blarg[]; };
- raise member.backtrace, 'member redefinition' if member.name and @members.find { |m| m.name == member.name }
- @members << member
- raise tok || parser if not tok = parser.skipspaces or tok.type != :punct
- if tok.raw == ':' # bits
- raise tok, 'bad type for bitslice' if not member.type.integral?
- bits = nil
- raise tok, "bad bit count #{bits.inspect}" if not bits = CExpression.parse(parser, scope, false) or
- not bits.constant? or !(bits = bits.reduce(parser)).kind_of? ::Integer
- #raise tok, 'need more bits' if bits > 8*parser.sizeof(member)
- # WORD wReserved:17; => yay windows.h
- (@bits ||= [])[@members.length-1] = bits
- raise tok || parser, '"," or ";" expected' if not tok = parser.skipspaces or tok.type != :punct
- end
- case tok.raw
- when ';'; break
- when ','
- when '}'; parser.unreadtok(tok); break
- else raise tok, '"," or ";" expected'
- end
- end
- end
- parse_attributes(parser)
- end
- # updates the @fldoffset / @fldbitoffset hash storing the offset of members
- def update_member_cache(parser)
- @fldlist = {}
- @members.to_a.each { |m|
- @fldlist[m.name] = m if m.name
- }
- end
- def parse_initializer(parser, scope)
- if tok = parser.skipspaces and tok.type == :punct and tok.raw == '{'
- # struct x toto = { 1, .4, .member[0][6].bla = 12 };
- raise tok, 'undefined struct' if not @members
- ret = []
- if tok = parser.skipspaces and (tok.type != :punct or tok.raw != '}')
- parser.unreadtok tok
- idx = 0
- loop do
- idx = parse_initializer_designator(parser, scope, ret, idx, true)
- raise tok || parser, '"," or "}" expected' if not tok = parser.skipspaces or tok.type != :punct or (tok.raw != '}' and tok.raw != ',')
- break if tok.raw == '}'
- raise tok, 'struct is smaller than that' if idx >= @members.length
- end
- end
- ret
- else
- parser.unreadtok tok
- super(parser, scope)
- end
- end
- # parses a designator+initializer eg '.toto = 4' or '.tutu[42][12].bla = 16' or (root ? '4' : '=4')
- def parse_initializer_designator(parser, scope, value, idx, root=true)
- if nt = parser.skipspaces and nt.type == :punct and nt.raw == '.' and
- nnt = parser.skipspaces and nnt.type == :string and
- findmember(nnt.raw)
- raise nnt, 'unhandled indirect initializer' if not nidx = @members.index(@members.find { |m| m.name == nnt.raw }) # TODO
- if not root
- value[idx] ||= [] # AryRecorder may change [] to AryRec.new, can't do v = v[i] ||= []
- value = value[idx]
- end
- idx = nidx
- @members[idx].type.untypedef.parse_initializer_designator(parser, scope, value, idx, false)
- else
- parser.unreadtok nnt
- if root
- parser.unreadtok nt
- value[idx] = @members[idx].type.parse_initializer(parser, scope)
- else
- raise nt || parser, '"=" expected' if not nt or nt.type != :punct or nt.raw != '='
- value[idx] = parse_initializer(parser, scope)
- end
- end
- idx + 1
- end
- # resolve structptr + offset into 'str.membername'
- # handles 'var.substruct1.array[12].foo'
- # updates str
- # returns the final member type itself
- # works for Struct/Union/Array
- def expand_member_offset(c_parser, off, str)
- # XXX choose in members, check sizeof / prefer structs
- m = @members.first
- str << '.' << m.name if m.name
- if m.type.respond_to?(:expand_member_offset)
- m.type.expand_member_offset(c_parser, off, str)
- else
- m.type
- end
- end
- end
- class Struct < Union
- attr_accessor :pack
- def align(parser) [@members.to_a.map { |m| m.type.align(parser) }.max || 1, (pack || 8)].min end
- def offsetof(parser, name)
- raise parser, 'undefined structure' if not members
- update_member_cache(parser) if not fldlist
- return @fldoffset[name] if @fldoffset[name]
- return @fldoffset[name.name] if name.respond_to?(:name) and @fldoffset[name.name]
- # this is almost never reached, only for <struct>.offsetof(anonymoussubstructmembername)
- raise parser, 'unknown structure member' if (name.kind_of?(::String) ? !findmember(name) : !@members.include?(name))
- indirect = true if name.kind_of?(::String) and not @fldlist[name]
- al = align(parser)
- off = 0
- bit_off = 0
- isz = nil
- @members.each_with_index { |m, i|
- if bits and b = @bits[i]
- if not isz
- mal = [m.type.align(parser), al].min
- off = (off + mal - 1) / mal * mal
- end
- isz = parser.sizeof(m)
- if b == 0 or (bit_off > 0 and bit_off + b > 8*isz)
- bit_off = 0
- mal = [m.type.align(parser), al].min
- off = (off + isz + mal - 1) / mal * mal
- end
- break if m.name == name or m == name
- bit_off += b
- else
- if isz
- off += isz
- bit_off = 0
- isz = nil
- end
- mal = [m.type.align(parser), al].min
- off = (off + mal - 1) / mal * mal
- if m.name == name or m == name
- break
- elsif indirect and m.type.untypedef.kind_of? Union and m.type.untypedef.findmember(name)
- off += m.type.untypedef.offsetof(parser, name)
- break
- else
- off += parser.sizeof(m)
- end
- end
- }
- off
- end
- # returns the [bitoffset, bitlength] of the field if it is a bitfield
- # this should be added to the offsetof(field)
- def bitoffsetof(parser, name)
- raise parser, 'undefined structure' if not members
- update_member_cache(parser) if not fldlist
- return @fldbitoffset[name] if fldbitoffset and @fldbitoffset[name]
- return @fldbitoffset[name.name] if fldbitoffset and name.respond_to?(:name) and @fldbitoffset[name.name]
- return if @fldlist[name] or @members.include?(name)
- raise parser, 'undefined union' if not @members
- raise parser, 'unknown union member' if not findmember(name)
- @members.find { |m|
- m.type.untypedef.kind_of?(Union) and m.type.untypedef.findmember(name)
- }.type.untypedef.bitoffsetof(parser, name)
- end
- # returns the @member element that has offsetof(m) == off
- def findmember_atoffset(parser, off)
- return if not members
- update_member_cache(parser) if not fldlist
- if m = @fldoffset.index(off)
- @fldlist[m]
- end
- end
- def parse_members(parser, scope)
- super(parser, scope)
- if has_attribute 'packed'
- @pack = 1
- elsif p = has_attribute_var('pack')
- @pack = p[/\d+/].to_i
- raise parser, "illegal struct pack(#{p})" if @pack == 0
- end
- end
- # updates the @fldoffset / @fldbitoffset hash storing the offset of members
- def update_member_cache(parser)
- super(parser)
- @fldoffset = {}
- @fldbitoffset = {} if fldbitoffset
- al = align(parser)
- off = 0
- bit_off = 0
- isz = nil
- @members.each_with_index { |m, i|
- if bits and b = @bits[i]
- if not isz
- mal = [m.type.align(parser), al].min
- off = (off + mal - 1) / mal * mal
- end
- isz = parser.sizeof(m)
- if b == 0 or (bit_off > 0 and bit_off + b > 8*isz)
- bit_off = 0
- mal = [m.type.align(parser), al].min
- off = (off + isz + mal - 1) / mal * mal
- end
- if m.name
- @fldoffset[m.name] = off
- @fldbitoffset ||= {}
- @fldbitoffset[m.name] = [bit_off, b]
- end
- bit_off += b
- else
- if isz
- off += isz
- bit_off = 0
- isz = nil
- end
- mal = [m.type.align(parser), al].min
- off = (off + mal - 1) / mal * mal
- @fldoffset[m.name] = off if m.name
- off += parser.sizeof(m)
- end
- }
- end
- # see Union#expand_member_offset
- def expand_member_offset(c_parser, off, str)
- members.to_a.each { |m|
- mo = offsetof(c_parser, m)
- if mo == off or mo + c_parser.sizeof(m) > off
- if bitoffsetof(c_parser, m)
- # ignore bitfields
- str << "+#{off}" if off > 0
- return self
- end
- str << '.' << m.name if m.name
- if m.type.respond_to?(:expand_member_offset)
- return m.type.expand_member_offset(c_parser, off-mo, str)
- else
- return m.type
- end
- elsif mo > off
- break
- end
- }
- # XXX that works only for pointer-style str
- str << "+#{off}" if off > 0
- nil
- end
- end
- class Enum < Type
- # name => value
- attr_accessor :members
- attr_accessor :name
- attr_accessor :backtrace
- def align(parser) BaseType.new(:int).align(parser) end
- def arithmetic?; true end
- def integral?; true end
- def signed?; false end
- def parse_members(parser, scope)
- val = -1
- @members = {}
- loop do
- raise parser if not tok = parser.skipspaces
- break if tok.type == :punct and tok.raw == '}'
- name = tok.raw
- raise tok, 'bad enum name' if tok.type != :string or Keyword[name] or (?0..?9).include?(name[0])
- raise parser if not tok = parser.skipspaces
- if tok.type == :punct and tok.raw == '='
- raise tok || parser if not val = CExpression.parse(parser, scope, false) or not val = val.reduce(parser) or not tok = parser.skipspaces
- else
- val += 1
- end
- raise tok, "enum value #{name} redefinition" if scope.symbol[name] and scope.symbol[name] != val
- @members[name] = val
- scope.symbol[name] = val
- if tok.type == :punct and tok.raw == '}'
- break
- elsif tok.type == :punct and tok.raw == ','
- else raise tok, '"," or "}" expected'
- end
- end
- parse_attributes(parser)
- end
- def compare_deep(o)
- return true if o.object_id == self.object_id
- return if o.class != self.class or o.name != self.name or o.attributes != self.attributes
- members == o.members
- end
- end
- class Pointer < Type
- attr_accessor :type
- def initialize(type=nil)
- @type = type
- end
- def pointer? ; true ; end
- def arithmetic? ; true ; end
- def base ; @type.base ; end
- def align(parser) BaseType.new(:ptr).align(parser) end
- def pointed ; @type end
- def ==(o)
- o.class == self.class and o.type == self.type
- end
- end
- class Array < Pointer
- attr_accessor :length
- def initialize(type=nil, length=nil)
- super(type)
- @length = length if length
- end
- def align(parser) @type.align(parser) end
- def parse_initializer(parser, scope)
- raise parser, 'cannot initialize dynamic array' if @length.kind_of? CExpression
- if tok = parser.skipspaces and tok.type == :punct and tok.raw == '{'
- # struct x foo[] = { { 4 }, [12].tutu = 2 };
- ret = []
- if tok = parser.skipspaces and (tok.type != :punct or tok.raw != '}')
- parser.unreadtok tok
- idx = 0
- loop do
- idx = parse_initializer_designator(parser, scope, ret, idx, true)
- raise tok || parser, '"," or "}" expected' if not tok = parser.skipspaces or tok.type != :punct or (tok.raw != '}' and tok.raw != ',')
- break if tok.raw == '}'
- # allow int x[] = {1, 2, 3, };
- break if tok = parser.skipspaces and tok.type == :punct and tok.raw == '}'
- parser.unreadtok tok
- raise tok, 'array is smaller than that' if length and idx >= @length
- end
- end
- ret
- else
- parser.unreadtok tok
- i = super(parser, scope)
- if i.kind_of? CExpression and not i.op and i.rexpr.kind_of? String and @length and i.rexpr.length > @length
- puts tok.exception("initializer is too long (#{i.rexpr.length} for #@length)").message if $VERBOSE
- i.rexpr = i.rexpr[0, @length]
- end
- i
- end
- end
- # this class is a hack to support [1 ... 4] array initializer
- # it stores the effects of subsequent initializers (eg [1 ... 4].toto[48].bla[2 ... 57] = 12)
- # which are later played back on the range
- class AryRecorder
- attr_accessor :log
- def initialize
- @log = []
- end
- def []=(idx, val)
- val = self.class.new if val == []
- @log[idx] = val
- end
- def [](idx)
- @log[idx]
- end
- def playback_idx(i)
- case v = @log[i]
- when self.class; v.playback
- else v
- end
- end
- def playback(ary=[])
- @log.each_with_index { |v, i| ary[i] = playback_idx(i) }
- ary
- end
- end
- # parses a designator+initializer eg '[12] = 4' or '[42].bla = 16' or '[3 ... 8] = 28'
- def parse_initializer_designator(parser, scope, value, idx, root=true)
- # root = true for 1st invocation (x = { 4 }) => immediate value allowed
- # or false for recursive invocations (x = { .y = 4 }) => need '=' sign before immediate
- if nt = parser.skipspaces and nt.type == :punct and nt.raw == '['
- if not root
- value[idx] ||= [] # AryRecorder may change [] to AryRec.new, can't do v = v[i] ||= []
- value = value[idx]
- end
- raise nt, 'const expected' if not idx = CExpression.parse(parser, scope) or not idx.constant? or not idx = idx.reduce(parser) or not idx.kind_of? ::Integer
- nt = parser.skipspaces
- if nt and nt.type == :punct and nt.raw == '.' # range
- raise nt || parser, '".." expected' if not nt = parser.skipspaces or nt.type != :punct or nt.raw != '.'
- raise nt || parser, '"." expected' if not nt = parser.skipspaces or nt.type != :punct or nt.raw != '.'
- raise nt, 'const expected' if not eidx = CExpression.parse(parser, scope) or not eidx.constant? or not eidx = eidx.reduce(parser) or not eidx.kind_of? ::Integer
- raise nt, 'bad range' if eidx < idx
- nt = parser.skipspaces
- realvalue = value
- value = AryRecorder.new
- end
- raise nt || parser, '"]" expected' if not nt or nt.type != :punct or nt.raw != ']'
- raise nt, 'array is smaller than that' if length and (eidx||idx) >= @length
- @type.untypedef.parse_initializer_designator(parser, scope, value, idx, false)
- if eidx
- (idx..eidx).each { |i| realvalue[i] = value.playback_idx(idx) }
- idx = eidx # next default value = eidx+1 (eg int x[] = { [1 ... 3] = 4, 5 } => x[4] = 5)
- end
- else
- if root
- parser.unreadtok nt
- value[idx] = @type.parse_initializer(parser, scope)
- else
- raise nt || parser, '"=" expected' if not nt or nt.type != :punct or nt.raw != '='
- value[idx] = parse_initializer(parser, scope)
- end
- end
- idx + 1
- end
- # see Union#expand_member_offset
- def expand_member_offset(c_parser, off, str)
- tsz = c_parser.sizeof(@type)
- str << "[#{off/tsz}]"
- if @type.respond_to?(:expand_member_offset)
- @type.expand_member_offset(c_parser, off%tsz, str)
- else
- @type
- end
- end
- end
- class Variable
- include Attributes
- include Typed
- attr_accessor :type
- attr_accessor :initializer # CExpr / Block (for Functions)
- attr_accessor :name
- attr_accessor :storage # auto register static extern typedef
- attr_accessor :backtrace # definition backtrace info (the name token)
- def initialize(name=nil, type=nil)
- @name, @type = name, type
- end
- end
- # found in a block's Statements, used to know the initialization order
- # eg { int i; i = 4; struct foo { int k; } toto = {i}; }
- class Declaration
- attr_accessor :var
- def initialize(var)
- @var = var
- end
- end
- class If < Statement
- attr_accessor :test # expression
- attr_accessor :bthen, :belse # statements
- def initialize(test, bthen, belse=nil)
- @test = test
- @bthen = bthen
- @belse = belse if belse
- end
- def self.parse(parser, scope, nest)
- tok = nil
- raise tok || self, '"(" expected' if not tok = parser.skipspaces or tok.type != :punct or tok.raw != '('
- raise tok, 'expr expected' if not expr = CExpression.parse(parser, scope) or not expr.type.arithmetic?
- raise tok || self, '")" expected' if not tok = parser.skipspaces or tok.type != :punct or tok.raw != ')'
- bthen = parser.parse_statement scope, nest
- if tok = parser.skipspaces and tok.type == :string and tok.raw == 'else'
- belse = parser.parse_statement scope, nest
- else
- parser.unreadtok tok
- end
- new expr, bthen, belse
- end
- end
- class For < Statement
- attr_accessor :init, :test, :iter # CExpressions, init may be Block
- attr_accessor :body
- def initialize(init, test, iter, body)
- @init, @test, @iter, @body = init, test, iter, body
- end
- def self.parse(parser, scope, nest)
- tok = nil
- raise tok || parser, '"(" expected' if not tok = parser.skipspaces or tok.type != :punct or tok.raw != '('
- init = forscope = Block.new(scope)
- if not parser.parse_definition(forscope)
- forscope = scope
- init = CExpression.parse(parser, forscope)
- raise tok || parser, '";" expected' if not tok = parser.skipspaces or tok.type != :punct or tok.raw != ';'
- end
- test = CExpression.parse(parser, forscope)
- raise tok || parser, '";" expected' if not tok = parser.skipspaces or tok.type != :punct or tok.raw != ';'
- raise tok, 'bad test expression in for loop' if test and not test.type.arithmetic?
- iter = CExpression.parse(parser, forscope)
- raise tok || parser, '")" expected' if not tok = parser.skipspaces or tok.type != :punct or tok.raw != ')'
- new init, test, iter, parser.parse_statement(forscope, nest + [:loop])
- end
- end
- class While < Statement
- attr_accessor :test
- attr_accessor :body
- def initialize(test, body)
- @test = test
- @body = body
- end
- def self.parse(parser, scope, nest)
- tok = nil
- raise tok || parser, '"(" expected' if not tok = parser.skipspaces or tok.type != :punct or tok.raw != '('
- raise tok, 'expr expected' if not expr = CExpression.parse(parser, scope) or not expr.type.arithmetic?
- raise tok || parser, '")" expected' if not tok = parser.skipspaces or tok.type != :punct or tok.raw != ')'
- new expr, parser.parse_statement(scope, nest + [:loop])
- end
- end
- class DoWhile < While
- def self.parse(parser, scope, nest)
- body = parser.parse_statement(scope, nest + [:loop])
- tok = nil
- raise tok || parser, '"while" expected' if not tok = parser.skipspaces or tok.type != :string or tok.raw != 'while'
- raise tok || parser, '"(" expected' if not tok = parser.skipspaces or tok.type != :punct or tok.raw != '('
- raise tok, 'expr expected' if not expr = CExpression.parse(parser, scope) or not expr.type.arithmetic?
- raise tok || parser, '")" expected' if not tok = parser.skipspaces or tok.type != :punct or tok.raw != ')'
- parser.checkstatementend(tok)
- new expr, body
- end
- end
- class Switch < Statement
- attr_accessor :test, :body
- def initialize(test, body)
- @test = test
- @body = body
- end
- def self.parse(parser, scope, nest)
- raise tok || parser, '"(" expected' if not tok = parser.skipspaces or tok.type != :punct or tok.raw != '('
- raise tok, 'expr expected' if not expr = CExpression.parse(parser, scope) or not expr.type.integral?
- raise tok || parser, '")" expected' if not tok = parser.skipspaces or tok.type != :punct or tok.raw != ')'
- new expr, parser.parse_statement(scope, nest + [:switch])
- end
- end
- class Continue < Statement
- end
- class Break < Statement
- end
- class Goto < Statement
- attr_accessor :target
- def initialize(target)
- @target = target
- end
- end
- class Return < Statement
- attr_accessor :value
- def initialize(value)
- @value = value
- end
- end
- class Label < Statement
- attr_accessor :name
- attr_accessor :statement
- def initialize(name, statement=nil)
- @name, @statement = name, statement
- end
- end
- class Case < Label
- attr_accessor :expr, :exprup # exprup if range, expr may be 'default'
- def initialize(expr, exprup, statement)
- @expr, @statement = expr, statement
- @exprup = exprup if exprup
- end
- def self.parse(parser, scope, nest)
- raise parser, 'invalid case' if not expr = CExpression.parse(parser, scope) or not expr.constant? or not expr.type.integral?
- raise tok || parser, '":" or "..." expected' if not tok = parser.skipspaces or tok.type != :punct or (tok.raw != ':' and tok.raw != '.')
- if tok.raw == '.'
- raise tok || parser, '".." expected' if not tok = parser.skipspaces or tok.type != :punct or tok.raw != '.'
- raise tok || parser, '"." expected' if not tok = parser.skipspaces or tok.type != :punct or tok.raw != '.'
- raise tok, 'invalid case range' if not exprup = CExpression.parse(parser, scope) or not exprup.constant? or not exprup.type.integral?
- raise tok || parser, '":" expected' if not tok = parser.skipspaces or tok.type != :punct or tok.raw != ':'
- end
- body = parser.parse_statement scope, nest
- new expr, exprup, body
- end
- end
- # inline asm statement
- class Asm < Statement
- include Attributes
- attr_accessor :body # asm source (::String)
- attr_accessor :output, :input, :clobber # I/O, gcc-style (::Array)
- attr_accessor :backtrace # body Token
- attr_accessor :volatile
- def initialize(body, backtrace, output=nil, input=nil, clobber=nil, volatile=nil)
- @body, @backtrace, @output, @input, @clobber, @volatile = body, backtrace, output, input, clobber, volatile
- end
- def self.parse(parser, scope)
- if tok = parser.skipspaces and tok.type == :string and (tok.raw == 'volatile' or tok.raw == '__volatile__')
- volatile = true
- tok = parser.skipspaces
- end
- if not tok or tok.type != :punct or tok.raw != '('
- # detect MS-style inline asm: "__asm .* __asm .*" or "asm { [\s.]* }"
- ftok = tok
- body = ''
- if tok.type == :punct and tok.raw == '{'
- loop do
- raise ftok, 'unterminated asm block' if not tok = parser.lexer.readtok
- break if tok.type == :punct and tok.raw == '}'
- case tok.type
- when :space; body << ' '
- when :eol; body << "\n"
- when :punct; body << tok.raw
- when :quoted; body << CExpression.string_inspect(tok.value) # concat adjacent c strings
- when :string
- body << \
- case tok.raw
- when 'asm', '__asm', '__asm__'; "\n"
- when '_emit'; 'db'
- else tok.raw
- end
- end
- end
- # allow shell-style heredoc: asm <<EOS\n<asm>\nEOS
- elsif tok.type == :punct and tok.raw == '<'
- raise ftok, 'bad asm heredoc' if not tok = parser.lexer.readtok or tok.type != :punct or tok.raw != '<'
- delimiter = parser.lexer.readtok
- if delimiter.type == :punct and delimiter.raw == '-'
- skipspc = true
- delimiter = parser.lexer.readtok
- end
- raise ftok, 'bad asm heredoc delim' if delimiter.type != :string or not tok = parser.lexer.readtok or tok.type != :eol
- nl = true
- loop do
- raise ftok, 'unterminated heredoc' if not tok = parser.lexer.readtok
- break if nl and tok.raw == delimiter.raw
- raw = tok.raw
- raw = "\n" if skipspc and tok.type == :eol
- body << raw
- nl = (tok.type == :eol and (raw[-1] == ?\n or raw[-1] == ?\r))
- end
- # MS single-instr: asm inc eax;
- # also allow asm "foo bar\nbaz";
- else
- parser.lexer.unreadtok tok
- loop do
- break if not tok = parser.lexer.readtok or tok.type == :eol
- case tok.type
- when :space; body << ' '
- when :punct
- case tok.raw
- when '}'
- parser.lexer.unreadtok tok
- break
- else body << tok.raw
- end
- when :quoted; body << (body.empty? ? tok.value : CExpression.string_inspect(tok.value)) # asm "pop\nret" VS asm add al, 'z'
- when :string
- body << \
- case tok.raw
- when 'asm', '__asm', '__asm__'; "\n"
- when '_emit'; 'db'
- else tok.raw
- end
- end
- end
- end
- return new(body, ftok, nil, nil, nil, volatile)
- end
- raise tok || parser, '"(" expected' if not tok or tok.type != :punct or tok.raw != '('
- raise tok || parser, 'qstring expected' if not tok = parser.skipspaces or tok.type != :quoted
- body = tok
- ret = new body.value, body
- tok = parser.skipspaces
- raise tok || parser, '":" or ")" expected' if not tok or tok.type != :punct or (tok.raw != ':' and tok.raw != ')')
- if tok.raw == ':'
- ret.output = []
- raise parser if not tok = parser.skipspaces
- while tok.type == :quoted
- type = tok.value
- raise tok, 'expr expected' if not var = CExpression.parse_value(parser, scope)
- ret.output << [type, var]
- raise tok || parser, '":" or "," or ")" expected' if not tok = parser.skipspaces or tok.type != :punct or (tok.raw != ',' and tok.raw != ')' and tok.raw != ':')
- break if tok.raw == ':' or tok.raw == ')'
- raise tok || parser, 'qstring expected' if not tok = parser.skipspaces or tok.type != :quoted
- end
- end
- if tok.raw == ':'
- ret.input = []
- raise parser if not tok = parser.skipspaces
- while tok.type == :quoted
- type = tok.value
- raise tok, 'expr expected' if not var = CExpression.parse_value(parser, scope)
- ret.input << [type, var]
- raise tok || parser, '":" or "," or ")" expected' if not tok = parser.skipspaces or tok.type != :punct or (tok.raw != ',' and tok.raw != ')' and tok.raw != ':')
- break if tok.raw == ':' or tok.raw == ')'
- raise tok || parser, 'qstring expected' if not tok = parser.skipspaces or tok.type != :quoted
- end
- end
- if tok.raw == ':'
- ret.clobber = []
- raise parser if not tok = parser.skipspaces
- while tok.type == :quoted
- ret.clobber << tok.value
- raise tok || parser, '"," or ")" expected' if not tok = parser.skipspaces or tok.type != :punct or (tok.raw != ',' and tok.raw != ')')
- break if tok.raw == ')'
- raise tok || parser, 'qstring expected' if not tok = parser.skipspaces or tok.type != :quoted
- end
- end
- raise tok || parser, '")" expected' if not tok or tok.type != :punct or tok.raw != ')'
- ret.parse_attributes(parser)
- parser.checkstatementend(tok)
- ret
- end
- end
- class CExpression < Statement
- include Typed
- # may be :,, :., :'->', :funcall (function, [arglist]), :[] (array indexing), nil (cast)
- attr_accessor :op
- # nil/CExpr/Variable/Label/::String( = :quoted/struct member name)/::Integer/::Float/Block
- attr_accessor :lexpr, :rexpr
- # a Type
- attr_accessor :type
- def initialize(l, o, r, t)
- raise "invalid CExpr #{[l, o, r, t].inspect}" if (o and not o.kind_of? ::Symbol) or not t.kind_of? Type
- @lexpr, @op, @rexpr, @type = l, o, r, t
- end
- # overwrites @lexpr @op @rexpr @type from the arg
- def replace(o)
- @lexpr, @op, @rexpr, @type = o.lexpr, o.op, o.rexpr, o.type
- self
- end
- # deep copy of the object
- # recurses only within CExpressions, anything else is copied by reference
- def deep_dup
- n = dup
- n.lexpr = n.lexpr.deep_dup if n.lexpr.kind_of? CExpression
- n.rexpr = n.rexpr.deep_dup if n.rexpr.kind_of? CExpression
- n.rexpr = n.rexpr.map { |e| e.kind_of?(CExpression) ? e.deep_dup : e } if n.rexpr.kind_of? ::Array
- n
- end
- # recursive constructor with automatic type inference
- # e.g. CExpression[foo, :+, [:*, bar]]
- # assumes root args are correctly typed (eg *foo => foo must be a pointer)
- # take care to use [int] with immediates, e.g. CExpression[foo, :+, [2]]
- # CExpr[some_cexpr] returns some_cexpr
- def self.[](*args)
- # sub-arrays in args are to be passed to self.[] recursively (syntaxic sugar)
- splat = lambda { |e| e.kind_of?(::Array) ? self[*e] : e }
- args.shift while args.first == nil # CExpr[nil, :&, bla] => CExpr[:&, bla]
- case args.length
- when 4
- op = args[1]
- if op == :funcall or op == :'?:'
- x2 = args[2].map { |a| splat[a] } if args[2]
- else
- x2 = splat[args[2]]
- end
- new(splat[args[0]], op, x2, args[3])
- when 3
- op = args[1]
- x1 = splat[args[0]]
- if op == :funcall or op == :'?:'
- x2 = args[2].map { |a| splat[a] } if args[2]
- else
- x2 = splat[args[2]]
- end
- case op
- when :funcall
- rt = x1.type.untypedef
- rt = rt.type.untypedef if rt.pointer?
- new(x1, op, x2, rt.type)
- when :[]; new(x1, op, x2, x1.type.untypedef.type)
- when :+; new(x1, op, x2, (x2.type.pointer? ? x2.type : x1.type))
- when :-; new(x1, op, x2, ((x1.type.pointer? and x2.type.pointer?) ? BaseType.new(:int) : x2.type.pointer? ? x2.type : x1.type))
- when :'&&', :'||', :==, :'!=', :>, :<, :<=, :>=; new(x1, op, x2, BaseType.new(:int))
- when :'.', :'->'
- t = x1.type.untypedef
- t = t.type.untypedef if op == :'->' and x1.type.pointer?
- raise "parse error: #{t} has no member #{x2}" if not t.kind_of? Union or not m = t.findmember(x2)
- new(x1, op, x2, m.type)
- when :'?:'; new(x1, op, x2, x2[0].type)
- when :','; new(x1, op, x2, x2.type)
- else new(x1, op, x2, x1.type)
- end
- when 2
- x0 = splat[args[0]]
- x1 = splat[args[1]]
- x0, x1 = x1, x0 if x0.kind_of? Type
- if x1.kind_of? Type; new(nil, nil, x0, x1) # (cast)r
- elsif x0 == :*; new(nil, x0, x1, x1.type.untypedef.type) # *r
- elsif x0 == :& and x1.kind_of? CExpression and x1.type.kind_of? C::Array; new(nil, nil, x1, Pointer.new(x1.type.type))
- elsif x0 == :&; new(nil, x0, x1, Pointer.new(x1.type)) # &r
- elsif x0 == :'!'; new(nil, x0, x1, BaseType.new(:int)) # &r
- elsif x1.kind_of? ::Symbol; new(x0, x1, nil, x0.type) # l++
- else new(nil, x0, x1, x1.type) # +r
- end
- when 1
- x = splat[args[0]]
- case x
- when CExpression; x
- when ::Integer; new(nil, nil, x, BaseType.new(:int)) # XXX range => __int64 ?
- when ::Float; new(nil, nil, x, BaseType.new(:double))
- when ::String; new(nil, nil, x, Pointer.new(BaseType.new(:char)))
- else new(nil, nil, x, x.type)
- end
- else raise "parse error CExpr[*#{args.inspect}]"
- end
- end
- end
- class Parser
- # creates a new CParser, parses all top-level statements
- def self.parse(text)
- new.parse text
- end
- # parses the current lexer content (or the text arg) for toplevel definitions
- def parse(text=nil, filename='<unk>', lineno=1)
- @lexer.feed text, filename, lineno if text
- nil while not @lexer.eos? and (parse_definition(@toplevel) or parse_toplevel_statement(@toplevel))
- raise @lexer.readtok || self, 'invalid definition' if not @lexer.eos?
- sanity_checks
- self
- end
- # parses a C file
- def parse_file(file)
- parse(File.read(file), file)
- end
- attr_accessor :lexer, :toplevel, :typesize, :pragma_pack
- attr_accessor :endianness
- attr_accessor :allow_bad_c
- attr_accessor :program
- # allowed arguments: ExeFormat, CPU, Preprocessor, Symbol (for the data model)
- def initialize(*args)
- model = args.grep(Symbol).first || :ilp32
- lexer = args.grep(Preprocessor).first || Preprocessor.new
- @program = args.grep(ExeFormat).first
- cpu = args.grep(CPU).first
- cpu ||= @program.cpu if @program
- @lexer = lexer
- @prev_pragma_callback = @lexer.pragma_callback
- @lexer.pragma_callback = lambda { |tok| parse_pragma_callback(tok) }
- @toplevel = Block.new(nil)
- @unreadtoks = []
- @endianness = cpu ? cpu.endianness : :big
- @typesize = { :void => 1, :__int8 => 1, :__int16 => 2, :__int32 => 4, :__int64 => 8,
- :char => 1, :float => 4, :double => 8, :longdouble => 12 }
- send model
- cpu.tune_cparser(self) if cpu
- @program.tune_cparser(self) if @program
- end
- def ilp16
- @typesize.update :short => 2, :ptr => 2,
- :int => 2, :long => 4, :longlong => 4
- end
- def lp32
- @typesize.update :short => 2, :ptr => 4,
- :int => 2, :long => 4, :longlong => 8
- end
- def ilp32
- @typesize.update :short => 2, :ptr => 4,
- :int => 4, :long => 4, :longlong => 8
- end
- def llp64
- @typesize.update :short => 2, :ptr => 8,
- :int => 4, :long => 4, :longlong => 8
- end
- def lp64
- @typesize.update :short => 2, :ptr => 8,
- :int => 4, :long => 8, :longlong => 8
- end
- def ilp64
- @typesize.update :short => 2, :ptr => 8,
- :int => 8, :long => 8, :longlong => 8
- end
- def parse_pragma_callback(otok)
- case otok.raw
- when 'pack'
- nil while lp = @lexer.readtok and lp.type == :space
- nil while rp = @lexer.readtok and rp.type == :space
- if not rp or rp.type != :punct or rp.raw != ')'
- v1 = rp
- nil while rp = @lexer.readtok and rp.type == :space
- end
- if rp and rp.type == :punct and rp.raw == ','
- nil while v2 = @lexer.readtok and v2.type == :space
- nil while rp = @lexer.readtok and rp.type == :space
- end
- raise otok if not rp or lp.type != :punct or rp.type != :punct or lp.raw != '(' or rp.raw != ')'
- raise otok if (v1 and v1.type != :string) or (v2 and (v2.type != :string or v2.raw =~ /[^\d]/))
- if not v1
- @pragma_pack = nil
- elsif v1.raw == 'push'
- @pragma_pack_stack ||= []
- @pragma_pack_stack << pragma_pack
- @pragma_pack = v2.raw.to_i if v2
- raise v2, 'bad pack value' if pragma_pack == 0
- elsif v1.raw == 'pop'
- @pragma_pack_stack ||= []
- raise v1, 'pack stack empty' if @pragma_pack_stack.empty?
- @pragma_pack = @pragma_pack_stack.pop
- @pragma_pack = v2.raw.to_i if v2 and v2.raw # #pragma pack(pop, 4) => pop stack, but use 4 as pack value (imho)
- raise v2, 'bad pack value' if @pragma_pack == 0
- elsif v1.raw =~ /^\d+$/
- raise v2, '2nd arg unexpected' if v2
- @pragma_pack = v1.raw.to_i
- raise v1, 'bad pack value' if @pragma_pack == 0
- else raise otok
- end
- # the caller checks for :eol
- when 'warning'
- if $DEBUG
- @prev_pragma_callback[otok]
- else
- # silent discard
- nil while tok = @lexer.readtok_nopp and tok.type != :eol
- @lexer.unreadtok tok
- end
- when 'prepare_visualstudio'
- prepare_visualstudio
- when 'prepare_gcc'
- prepare_gcc
- when 'data_model' # XXX use carefully, should be the very first thing parsed
- nil while lp = @lexer.readtok and lp.type == :space
- if lp.type != :string or lp.raw !~ /^s?[il]?lp(16|32|64)$/ or not respond_to? lp.raw
- raise lp, "invalid data model (use lp32/lp64/llp64/ilp64)"
- else
- send lp.raw
- end
- else @prev_pragma_callback[otok]
- end
- end
- def prepare_visualstudio
- @lexer.define_weak('_WIN32')
- @lexer.define_weak('_WIN32_WINNT', 0x500)
- @lexer.define_weak('_INTEGRAL_MAX_BITS', 64)
- @lexer.define_weak('__w64')
- @lexer.define_weak('_cdecl', '__cdecl') # typo ? seen in winreg.h
- @lexer.define_weak('_fastcall', '__fastcall') # typo ? seen in ntddk.h
- @lexer.define_weak('_MSC_VER', 1300) # handle '#pragma once' and _declspec(noreturn)
- @lexer.define_weak('__forceinline', '__inline')
- @lexer.define_weak('__ptr32') # needed with msc_ver 1300, don't understand their use
- @lexer.define_weak('__ptr64')
- end
- def prepare_gcc
- @lexer.define_weak('__GNUC__', 2) # otherwise __attribute__ is defined to void..
- @lexer.define_weak('__STDC__')
- @lexer.define_weak('__const', 'const')
- @lexer.define_weak('__signed', 'signed')
- @lexer.define_weak('__signed__', 'signed')
- @lexer.define_weak('__volatile', 'volatile')
- if not @lexer.definition['__builtin_constant_p']
- # magic macro to check if its arg is an immediate value
- @lexer.define_weak('__builtin_constant_p', '0')
- @lexer.definition['__builtin_constant_p'].args = [Preprocessor::Token.new([])]
- end
- @lexer.nodefine_strong('alloca') # TODO __builtin_alloca
- @lexer.hooked_include['stddef.h'] = <<EOH
- /* simplified, define all at first invocation. may break things... */
- #undef __need_ptrdiff_t
- #undef __need_size_t
- #undef __need_wint_t
- #undef __need_wchar_t
- #undef __need_NULL
- #undef NULL
- #if !defined (_STDDEF_H)
- #define _STDDEF_H
- #define __PTRDIFF_TYPE__ long int
- typedef __PTRDIFF_TYPE__ ptrdiff_t;
- #define __SIZE_TYPE__ long unsigned int
- typedef __SIZE_TYPE__ size_t;
- #define __WINT_TYPE__ unsigned int
- typedef __WINT_TYPE__ wint_t;
- #define __WCHAR_TYPE__ int
- typedef __WCHAR_TYPE__ wchar_t;
- #define NULL 0
- #define offsetof(TYPE, MEMBER) __builtin_offsetof (TYPE, MEMBER)
- #endif
- EOH
- # TODO va_args
- @lexer.hooked_include['stdarg.h'] = <<EOH
- // TODO
- typedef void* __gnuc_va_list;
- /*
- typedef void* va_list;
- #define va_start(v, l)
- #define va_end(v)
- #define va_arg(v, l)
- #define va_copy(d, s)
- */
- EOH
- @lexer.hooked_include['limits.h'] = <<EOH
- #define CHAR_BIT 8
- #define SCHAR_MIN (-128)
- #define SCHAR_MAX 127
- #define UCHAR_MAX 255
- #ifdef __CHAR_UNSIGNED__
- # define CHAR_MIN 0
- # define CHAR_MAX UCHAR_MAX
- #else
- # define CHAR_MIN SCHAR_MIN
- # define CHAR_MAX SCHAR_MAX
- #endif
- #define UINT_MAX #{(1 << (8*@typesize[:int]))-1}U
- #define INT_MAX (UINT_MAX >> 1)
- #define INT_MIN (-INT_MAX - 1)
- #define ULONG_MAX #{(1 << (8*@typesize[:long]))-1}UL
- #define LONG_MAX (ULONG_MAX >> 1L)
- #define LONG_MIN (-LONG_MAX - 1L)
- EOH
- end
- # C sanity checks
- def sanity_checks
- return if not $VERBOSE
- # TODO
- end
- # checks that the types are compatible (variable predeclaration, function argument..)
- # strict = false for func call/assignment (eg…
Large files files are truncated, but you can click here to view the full file