def p_def_label(p): """ line : ID EQU expr NEWLINE | ID EQU pexpr NEWLINE """ p[0] = None __DEBUG__("Declaring '%s%s' in %i" % (NAMESPACE, p[1], p.lineno(1))) MEMORY.declare_label(p[1], p.lineno(1), p[3])
def p_namespace(p): """ asm : NAMESPACE ID """ global NAMESPACE NAMESPACE = normalize_namespace(p[2]) __DEBUG__('Setting namespace to ' + (NAMESPACE.rstrip(DOT) or DOT), level=1)
def exit_proc(self, lineno): """ Exits current procedure. Local labels are transferred to global scope unless they have been marked as local ones. Raises an error if no current local context (stack underflow) """ __DEBUG__('Exiting current scope from lineno %i' % lineno) if len(self.local_labels) <= 1: error(lineno, 'ENDP in global scope (with no PROC)') return for label in self.local_labels[-1].values(): if label.local: if not label.defined: error(lineno, "Undefined LOCAL label '%s'" % label.name) return continue name = label.name _lineno = label.lineno value = label.value if name not in self.global_labels.keys(): self.global_labels[name] = label else: self.global_labels[name].define(value, _lineno) self.local_labels.pop() # Removes current context self.scopes.pop()
def p_line_label_asm(p): """ line : LABEL asms NEWLINE """ p[0] = p[2] __DEBUG__("Declaring '%s%s' (value %04Xh) in %i" % (NAMESPACE, p[1], MEMORY.org, p.lineno(1))) MEMORY.declare_label(p[1], p.lineno(1))
def p_program_line(p): """ program : program line """ if p[2] is not None: __DEBUG__('%04Xh [%04Xh] ASM: %s' % (MEMORY.org, MEMORY.org - MEMORY.ORG, p[2].asm)) MEMORY.add_instruction(p[2])
def update_next_block(self): """ If the last instruction of this block is a JP, JR or RET (with no conditions) then goes_to set contains just a single block """ last = self.mem[-1] if last.inst not in { 'djnz', 'jp', 'jr', 'call', 'ret', 'reti', 'retn', 'rst' }: return if last.inst in {'reti', 'retn'}: if self.next is not None: self.next.delete_comes_from(self) return if self.next is not None and last.condition_flag is None: # jp NNN, call NNN, rst, jr NNNN, ret self.next.delete_comes_from(self) if last.inst == 'ret': return if last.opers[0] not in LABELS.keys(): __DEBUG__( "INFO: %s is not defined. No optimization is done." % last.opers[0], 2) LABELS[last.opers[0]] = LabelInfo( last.opers[0], 0, DummyBasicBlock(ALL_REGS, ALL_REGS)) n_block = LABELS[last.opers[0]].basic_block self.add_goes_to(n_block)
def enter_proc(self, lineno): """ Enters (pushes) a new context """ self.local_labels.append({}) # Add a new context self.scopes.append(lineno) __DEBUG__('Entering scope level %i at line %i' % (len(self.scopes), lineno))
def p_line_label(p): """ line : LABEL NEWLINE """ p[0] = None # Nothing to append __DEBUG__("Declaring '%s%s' (value %04Xh) in %i" % (NAMESPACE, p[1], MEMORY.org, p.lineno(1))) MEMORY.declare_label(p[1], p.lineno(1))
def p_LOCAL(p): """ asm : LOCAL id_list """ p[0] = None for label, line in p[2]: __DEBUG__("Setting label '%s' as local at line %i" % (label, line)) MEMORY.set_label(label, line, local=True)
def _visit(self, node): self.norm_attr() if isinstance(node, Symbol): __DEBUG__('Visiting {}'.format(node.token), 1) if node.token in self.ATTR_TMP: return self.visit_ATTR_TMP(node) return TranslatorInstVisitor._visit(self, node)
def _visit(self, node): if node.obj is None: return None __DEBUG__("Optimizer: Visiting node {}".format(str(node.obj)), 1) methname = 'visit_' + node.obj.token meth = getattr(self, methname, None) if meth is None: meth = self.generic_visit return meth(node.obj)
def add_instruction(self, instr): """ This will insert an asm instruction at the current memory position in a t-uple as (mnemonic, params). It will also insert the opcodes at the memory_bytes """ if gl.has_errors: return __DEBUG__('%04Xh [%04Xh] ASM: %s' % (self.org, self.org - self.ORG, instr.asm)) self.set_memory_slot() self.orgs[self.org] += (instr,) for byte in instr.bytes(): self.__set_byte(byte, instr.lineno)
def p_asm_label(p): """ asm : ID """ __DEBUG__("Declaring '%s%s' (value %04Xh) in %i" % (NAMESPACE, p[1], MEMORY.org, p.lineno(1))) MEMORY.declare_label(p[1], p.lineno(1))
def visit_BLOCK(self, node): __DEBUG__('BLOCK', 2) for child in node.children: yield child
def emit(*args): """ Convert the given args to a Quad (3 address code) instruction """ quad = Quad(*args) __DEBUG__('EMIT ' + str(quad)) MEMORY.append(quad)
def main(args=None, emitter=None): """ Entry point when executed from command line. You can use zxb.py as a module with import, and this function won't be executed. """ api.config.init() zxbpp.init() zxbparser.init() arch.zx48k.backend.init() arch.zx48k.Translator.reset() asmparse.init() # ------------------------------------------------------------ # Command line parsing # ------------------------------------------------------------ parser = argparse.ArgumentParser() parser.add_argument('PROGRAM', type=str, help='BASIC program file') parser.add_argument( '-d', '--debug', dest='debug', default=OPTIONS.Debug.value, action='count', help= 'Enable verbosity/debugging output. Additional -d increase verbosity/debug level' ) parser.add_argument('-O', '--optimize', type=int, default=OPTIONS.optimization.value, help='Sets optimization level. ' '0 = None (default level is {0})'.format( OPTIONS.optimization.value)) parser.add_argument( '-o', '--output', type=str, dest='output_file', default=None, help='Sets output file. Default is input filename with .bin extension') parser.add_argument('-T', '--tzx', action='store_true', help="Sets output format to tzx (default is .bin)") parser.add_argument('-t', '--tap', action='store_true', help="Sets output format to tap (default is .bin)") parser.add_argument( '-B', '--BASIC', action='store_true', dest='basic', help= "Creates a BASIC loader which loads the rest of the CODE. Requires -T ot -t" ) parser.add_argument('-a', '--autorun', action='store_true', help="Sets the program to be run once loaded") parser.add_argument('-A', '--asm', action='store_true', help="Sets output format to asm") parser.add_argument('-S', '--org', type=str, default=str(OPTIONS.org.value), help="Start of machine code. By default %i" % OPTIONS.org.value) parser.add_argument( '-e', '--errmsg', type=str, dest='stderr', default=OPTIONS.StdErrFileName.value, help='Error messages file (standard error console by default)') parser.add_argument( '--array-base', type=int, default=OPTIONS.array_base.value, help='Default lower index for arrays ({0} by default)'.format( OPTIONS.array_base.value)) parser.add_argument( '--string-base', type=int, default=OPTIONS.string_base.value, help='Default lower index for strings ({0} by default)'.format( OPTIONS.array_base.value)) parser.add_argument( '-Z', '--sinclair', action='store_true', help= 'Enable by default some more original ZX Spectrum Sinclair BASIC features: ATTR, SCREEN$, ' 'POINT') parser.add_argument( '-H', '--heap-size', type=int, default=OPTIONS.heap_size.value, help='Sets heap size in bytes (default {0} bytes)'.format( OPTIONS.heap_size.value)) parser.add_argument('--debug-memory', action='store_true', help='Enables out-of-memory debug') parser.add_argument('--debug-array', action='store_true', help='Enables array boundary checking') parser.add_argument('--strict-bool', action='store_true', help='Enforce boolean values to be 0 or 1') parser.add_argument('--enable-break', action='store_true', help='Enables program execution BREAK detection') parser.add_argument('-E', '--emit-backend', action='store_true', help='Emits backend code instead of ASM or binary') parser.add_argument( '--explicit', action='store_true', help='Requires all variables and functions to be declared before used') parser.add_argument( '-D', '--define', type=str, dest='defines', action='append', help='Defines de given macro. Eg. -D MYDEBUG or -D NAME=Value') parser.add_argument('-M', '--mmap', type=str, dest='memory_map', default=None, help='Generate label memory map') parser.add_argument( '-i', '--ignore-case', action='store_true', help='Ignore case. Makes variable names are case insensitive') parser.add_argument( '-I', '--include-path', type=str, default='', help= 'Add colon separated list of directories to add to include path. e.g. -I dir1:dir2' ) parser.add_argument( '--strict', action='store_true', help='Enables strict mode. Force explicit type declaration') parser.add_argument( '--headerless', action='store_true', help='Header-less mode: omit asm prologue and epilogue') parser.add_argument('--version', action='version', version='%(prog)s {0}'.format(VERSION)) parser.add_argument( '--parse-only', action='store_true', help='Only parses to check for syntax and semantic errors') parser.add_argument( '--append-binary', default=[], action='append', help='Appends binary to tape file (only works with -t or -T)') parser.add_argument( '--append-headless-binary', default=[], action='append', help='Appends binary to tape file (only works with -t or -T)') parser.add_argument('-N', '--zxnext', action='store_true', help='Enables ZX Next asm extended opcodes') options = parser.parse_args(args=args) # ------------------------------------------------------------ # Setting of internal parameters according to command line # ------------------------------------------------------------ OPTIONS.Debug.value = options.debug OPTIONS.optimization.value = options.optimize OPTIONS.outputFileName.value = options.output_file OPTIONS.StdErrFileName.value = options.stderr OPTIONS.array_base.value = options.array_base OPTIONS.string_base.value = options.string_base OPTIONS.Sinclair.value = options.sinclair OPTIONS.heap_size.value = options.heap_size OPTIONS.memoryCheck.value = options.debug_memory OPTIONS.strictBool.value = options.strict_bool or OPTIONS.Sinclair.value OPTIONS.arrayCheck.value = options.debug_array OPTIONS.emitBackend.value = options.emit_backend OPTIONS.enableBreak.value = options.enable_break OPTIONS.explicit.value = options.explicit OPTIONS.memory_map.value = options.memory_map OPTIONS.strict.value = options.strict OPTIONS.headerless.value = options.headerless OPTIONS.zxnext.value = options.zxnext OPTIONS.org.value = api.utils.parse_int(options.org) if OPTIONS.org.value is None: parser.error("Invalid --org option '{}'".format(options.org)) if options.defines: for i in options.defines: macro = list(i.split('=', 1)) name = macro[0] val = ''.join(macro[1:]) OPTIONS.__DEFINES.value[name] = val zxbpp.ID_TABLE.define(name, value=val, lineno=0) if OPTIONS.Sinclair.value: OPTIONS.array_base.value = 1 OPTIONS.string_base.value = 1 OPTIONS.strictBool.value = True OPTIONS.case_insensitive.value = True if options.ignore_case: OPTIONS.case_insensitive.value = True debug.ENABLED = OPTIONS.Debug.value if int(options.tzx) + int(options.tap) + int(options.asm) + int(options.emit_backend) + \ int(options.parse_only) > 1: parser.error( "Options --tap, --tzx, --emit-backend, --parse-only and --asm are mutually exclusive" ) return 3 if options.basic and not options.tzx and not options.tap: parser.error( 'Option --BASIC and --autorun requires --tzx or tap format') return 4 if options.append_binary and not options.tzx and not options.tap: parser.error('Option --append-binary needs either --tap or --tzx') return 5 if options.asm and options.memory_map: parser.error('Option --asm and --mmap cannot be used together') return 6 OPTIONS.use_loader.value = options.basic OPTIONS.autorun.value = options.autorun if options.tzx: OPTIONS.output_file_type.value = 'tzx' elif options.tap: OPTIONS.output_file_type.value = 'tap' elif options.asm: OPTIONS.output_file_type.value = 'asm' elif options.emit_backend: OPTIONS.output_file_type.value = 'ic' args = [options.PROGRAM] if not os.path.exists(options.PROGRAM): parser.error("No such file or directory: '%s'" % args[0]) return 2 if OPTIONS.memoryCheck.value: OPTIONS.__DEFINES.value['__MEMORY_CHECK__'] = '' zxbpp.ID_TABLE.define('__MEMORY_CHECK__', lineno=0) if OPTIONS.arrayCheck.value: OPTIONS.__DEFINES.value['__CHECK_ARRAY_BOUNDARY__'] = '' zxbpp.ID_TABLE.define('__CHECK_ARRAY_BOUNDARY__', lineno=0) if OPTIONS.enableBreak.value: OPTIONS.__DEFINES.value['__ENABLE_BREAK__'] = '' zxbpp.ID_TABLE.define('__ENABLE_BREAK__', lineno=0) OPTIONS.include_path.value = options.include_path OPTIONS.inputFileName.value = zxbparser.FILENAME = \ os.path.basename(args[0]) if not OPTIONS.outputFileName.value: OPTIONS.outputFileName.value = \ os.path.splitext(os.path.basename(OPTIONS.inputFileName.value))[0] + os.path.extsep + \ OPTIONS.output_file_type.value if OPTIONS.StdErrFileName.value: OPTIONS.stderr.value = open_file(OPTIONS.StdErrFileName.value, 'wt', 'utf-8') zxbpp.setMode('basic') zxbpp.main(args) if gl.has_errors: debug.__DEBUG__("exiting due to errors.") return 1 # Exit with errors input_ = zxbpp.OUTPUT zxbparser.parser.parse(input_, lexer=zxblex.lexer, tracking=True, debug=(OPTIONS.Debug.value > 1)) if gl.has_errors: debug.__DEBUG__("exiting due to errors.") return 1 # Exit with errors # Optimizations optimizer = api.optimize.OptimizerVisitor() optimizer.visit(zxbparser.ast) # Emits intermediate code translator = arch.zx48k.Translator() translator.visit(zxbparser.ast) if gl.DATA_IS_USED: gl.FUNCTIONS.extend(gl.DATA_FUNCTIONS) # This will fill MEMORY with pending functions func_visitor = arch.zx48k.FunctionTranslator(gl.FUNCTIONS) func_visitor.start() # Emits data lines translator.emit_data_blocks() # Emits default constant strings translator.emit_strings() # Emits jump tables translator.emit_jump_tables() if OPTIONS.emitBackend.value: with open_file(OPTIONS.outputFileName.value, 'wt', 'utf-8') as output_file: for quad in translator.dumpMemory(backend.MEMORY): output_file.write(str(quad) + '\n') backend.MEMORY[:] = [] # Empties memory # This will fill MEMORY with global declared variables translator = arch.zx48k.VarTranslator() translator.visit(zxbparser.data_ast) for quad in translator.dumpMemory(backend.MEMORY): output_file.write(str(quad) + '\n') return 0 # Exit success # Join all lines into a single string and ensures an INTRO at end of file asm_output = backend.emit(backend.MEMORY, optimize=OPTIONS.optimization.value > 0) asm_output = optimize(asm_output) + '\n' # invoke the -O3 asm_output = asm_output.split('\n') for i in range(len(asm_output)): tmp = backend.ASMS.get(asm_output[i], None) if tmp is not None: asm_output[i] = '\n'.join(tmp) asm_output = '\n'.join(asm_output) # Now filter them against the preprocessor again zxbpp.setMode('asm') zxbpp.OUTPUT = '' zxbpp.filter_(asm_output, args[0]) # Now output the result asm_output = zxbpp.OUTPUT.split('\n') get_inits(asm_output) # Find out remaining inits backend.MEMORY[:] = [] # This will fill MEMORY with global declared variables translator = arch.zx48k.VarTranslator() translator.visit(zxbparser.data_ast) if gl.has_errors: debug.__DEBUG__("exiting due to errors.") return 1 # Exit with errors tmp = [ x for x in backend.emit(backend.MEMORY, optimize=False) if x.strip()[0] != '#' ] asm_output += tmp asm_output = backend.emit_start() + asm_output asm_output += backend.emit_end() if options.asm: # Only output assembler file with open_file(OPTIONS.outputFileName.value, 'wt', 'utf-8') as output_file: output(asm_output, output_file) elif not options.parse_only: fout = StringIO() output(asm_output, fout) asmparse.assemble(fout.getvalue()) fout.close() asmparse.generate_binary( OPTIONS.outputFileName.value, OPTIONS.output_file_type.value, binary_files=options.append_binary, headless_binary_files=options.append_headless_binary, emitter=emitter) if gl.has_errors: return 5 # Error in assembly if OPTIONS.memory_map.value: if asmparse.MEMORY is not None: with open_file(OPTIONS.memory_map.value, 'wt', 'utf-8') as f: f.write(asmparse.MEMORY.memory_map) return gl.has_errors # Exit success
def update_goes_and_comes(self): """ Once the block is a Basic one, check the last instruction and updates goes_to and comes_from set of the receivers. Note: jp, jr and ret are already done in update_next_block() """ if not len(self): return last = self.mem[-1] inst = last.inst oper = last.opers cond = last.condition_flag if not last.is_ender: return if cond is None: self.delete_goes_to(self.next) if last.inst in {'ret', 'reti', 'retn'} and cond is None: return # subroutine returns are updated from CALLer blocks if oper and oper[0]: if oper[0] not in LABELS: __DEBUG__( "INFO: %s is not defined. No optimization is done." % oper[0], 1) LABELS[oper[0]] = LabelInfo( oper[0], 0, DummyBasicBlock(ALL_REGS, ALL_REGS)) LABELS[oper[0]].used_by.add(self) self.add_goes_to(LABELS[oper[0]].basic_block) if inst in {'djnz', 'jp', 'jr'}: return assert inst in ('call', 'rst') if self.next is None: raise OptimizerError("Unexpected NULL next block") final_blk = self.next # The block all the final returns should go to stack = [LABELS[oper[0]].basic_block] bbset = IdentitySet() while stack: bb = stack.pop(0) while True: if bb is None: DummyBasicBlock(ALL_REGS, ALL_REGS) if bb in bbset: break bbset.add(bb) if isinstance(bb, DummyBasicBlock): bb.add_goes_to(final_blk) break if bb: bb1 = bb[-1] if bb1.inst in {'ret', 'reti', 'retn'}: bb.add_goes_to(final_blk) if bb1.condition_flag is None: # 'ret' break elif bb1.inst in ( 'jp', 'jr' ) and bb1.condition_flag is not None: # jp/jr nc/nz/.. LABEL if bb1.opers[ 0] in LABELS: # some labels does not exist (e.g. immediate numeric addresses) stack.append(LABELS[bb1.opers[0]].basic_block) else: raise OptimizerError( "Unknown block label '{}'".format( bb1.opers[0])) bb = bb.next # next contiguous block
def get_basic_blocks(block): """ If a block is not partitionable, returns a list with the same block. Otherwise, returns a list with the resulting blocks, recursively. """ result = [] EDP = END_PROGRAM_LABEL + ':' new_block = block while new_block: block = new_block new_block = None for i, mem in enumerate(block): if i and mem.code == EDP: # END_PROGRAM label always starts a basic block block, new_block = block_partition(block, i - 1) LABELS[END_PROGRAM_LABEL].basic_block = new_block break if mem.is_ender: block, new_block = block_partition(block, i) if not mem.condition_flag: block.delete_goes_to(new_block) for l in mem.opers: if l in LABELS: JUMP_LABELS.add(l) block.label_goes.append(l) break if mem.is_label and mem.code[:-1] not in LABELS: raise OptimizerError( "Missing label '{}' in labels list".format(mem.code[:-1])) if mem.code in arch.zx48k.backend.ASMS: # An inline ASM block block, new_block = block_partition(block, max(0, i - 1)) break result.append(block) for label in JUMP_LABELS: blk = LABELS[label].basic_block if isinstance(blk, DummyBasicBlock): continue must_partition = False # This label must point to the beginning of blk, just before the code # Otherwise we must partition it (must_partition = True) for i, cell in enumerate(blk): if cell.inst == label: break # already starts with this label if cell.is_label: continue # It's another label if cell.is_ender: raise OptimizerInvalidBasicBlockError(blk) must_partition = True else: __DEBUG__("Label {} not found in BasicBlock {}".format( label, blk.id)) continue if must_partition: j = result.index(blk) block_, new_block_ = block_partition(blk, i - 1) LABELS[label].basic_block = new_block_ result.pop(j) result.insert(j, block_) result.insert(j + 1, new_block_) for b in result: b.update_goes_and_comes() return result
def __call__(self, table): __DEBUG__("evaluating id '%s'" % self.name, DEBUG_LEVEL) if self.value is None: __DEBUG__("undefined (null) value. BUG?", DEBUG_LEVEL) return '' result = '' for token in self.value: __DEBUG__("evaluating token '%s'" % str(token), DEBUG_LEVEL) if isinstance(token, MacroCall): __DEBUG__( "token '%s'(%s) is a MacroCall" % (token.id_, str(token)), DEBUG_LEVEL) if table.defined(token.id_): tmp = table[token.id_] __DEBUG__( "'%s' is defined in the symbol table as '%s'" % (token.id_, tmp.name), DEBUG_LEVEL) if isinstance(tmp, ID) and not tmp.hasArgs: __DEBUG__("'%s' is an ID" % tmp.name, DEBUG_LEVEL) token = copy.deepcopy(token) token.id_ = tmp(table) __DEBUG__("'%s' is the new id" % token.id_, DEBUG_LEVEL) __DEBUG__("executing MacroCall '%s'" % token.id_, DEBUG_LEVEL) tmp = token(table) else: if isinstance(token, ID): __DEBUG__("token '%s' is an ID" % token.id_, DEBUG_LEVEL) token = token(table) tmp = token __DEBUG__("token got value '%s'" % tmp, DEBUG_LEVEL) result += tmp return result
def optimize(initial_memory): """ This will remove useless instructions """ global BLOCKS global PROC_COUNTER del MEMORY[:] PROC_COUNTER = 0 cleanupmem(initial_memory) if OPTIONS.optimization.value <= 2: # if -O2 or lower, do nothing and return return '\n'.join(x for x in initial_memory if not RE_PRAGMA.match(x)) basicblock.BasicBlock.clean_asm_args = OPTIONS.optimization.value > 3 bb = basicblock.BasicBlock(initial_memory) cleanup_local_labels(bb) initialize_memory(bb) BLOCKS = basic_blocks = basicblock.get_basic_blocks( bb) # 1st partition the Basic Blocks for b in basic_blocks: __DEBUG__('--- BASIC BLOCK: {} ---'.format(b.id), 1) __DEBUG__('Code:\n' + '\n'.join(' {}'.format(x) for x in b.code), 1) __DEBUG__('Requires: {}'.format(b.requires()), 1) __DEBUG__('Destroys: {}'.format(b.destroys()), 1) __DEBUG__('Label goes: {}'.format(b.label_goes), 1) __DEBUG__('Comes from: {}'.format([x.id for x in b.comes_from]), 1) __DEBUG__('Goes to: {}'.format([x.id for x in b.goes_to]), 1) __DEBUG__('Next: {}'.format(b.next.id if b.next is not None else None), 1) __DEBUG__('Size: {} Time: {}'.format(b.sizeof, b.max_tstates), 1) __DEBUG__('--- END ---', 1) LABELS['*START*'].basic_block.add_goes_to(basic_blocks[0]) LABELS['*START*'].basic_block.next = basic_blocks[0] basic_blocks[0].prev = LABELS['*START*'].basic_block LABELS[END_PROGRAM_LABEL].basic_block.add_goes_to( LABELS['*__END_PROGRAM*'].basic_block) # In O3 we simplify the graph by reducing jumps over jumps for label in JUMP_LABELS: block = LABELS[label].basic_block if isinstance(block, DummyBasicBlock): continue # The instruction that starts this block must be one of jr / jp first = block.get_next_exec_instruction() if first is None or first.inst not in ('jp', 'jr'): continue for blk in list(LABELS[label].used_by): if not first.condition_flag or blk[ -1].condition_flag == first.condition_flag: new_label = first.opers[0] blk[-1].asm = blk[-1].code.replace(label, new_label) block.delete_comes_from(blk) LABELS[label].used_by.remove(blk) LABELS[new_label].used_by.add(blk) blk.add_goes_to(LABELS[new_label].basic_block) for x in basic_blocks: x.compute_cpu_state() filtered_patterns_list = [ p for p in engine.PATTERNS if OPTIONS.optimization.value >= p.level >= 3 ] for x in basic_blocks: x.optimize(filtered_patterns_list) for x in basic_blocks: if x.comes_from == [] and len( [y for y in JUMP_LABELS if x is LABELS[y].basic_block]): x.ignored = True return '\n'.join([ y for y in flatten_list([x.code for x in basic_blocks if not x.ignored]) if not RE_PRAGMA.match(y) ])
def __call__(self, symbolTable=None): """ Execute the macro call using LAZY evaluation """ __DEBUG__("evaluating '%s'" % self.id_, DEBUG_LEVEL) if symbolTable is None: symbolTable = self.table # The macro is not defined => returned as is if not self.is_defined(symbolTable): __DEBUG__("macro '%s' not defined" % self.id_, DEBUG_LEVEL) tmp = self.id_ if self.callargs is not None: tmp += str(self.callargs) __DEBUG__("evaluation result: %s" % tmp, DEBUG_LEVEL) return tmp # The macro is defined __DEBUG__("macro '%s' defined" % self.id_, DEBUG_LEVEL) TABLE = copy.deepcopy(symbolTable) ID = TABLE[self.id_] # Get the defined macro if ID.hasArgs and self.callargs is None: return self.id_ # If no args passed, returned as is if self.callargs: # has args. Evaluate them removing spaces __DEBUG__("'%s' has args defined" % self.id_, DEBUG_LEVEL) __DEBUG__( "evaluating %i arg(s) for '%s'" % (len(self.callargs), self.id_), DEBUG_LEVEL) args = [x(TABLE).strip() for x in self.callargs] __DEBUG__( "macro call: %s%s" % (self.id_, '(' + ', '.join(args) + ')'), DEBUG_LEVEL) if not ID.hasArgs: # The macro doesn't need args __DEBUG__("'%s' has no args defined" % self.id_, DEBUG_LEVEL) tmp = ID(TABLE) # If no args passed, returned as is if self.callargs is not None: tmp += '(' + ', '.join(args) + ')' __DEBUG__("evaluation result: %s" % tmp, DEBUG_LEVEL) return tmp # Now ensure both args and callargs have the same length if len(self.callargs) != len(ID.args): raise PreprocError( 'Macro "%s" expected %i params, got %i' % (str(self.id_), len(ID.args), len(self.callargs)), self.lineno) # Carry out unification __DEBUG__('carrying out args unification', DEBUG_LEVEL) for i in range(len(self.callargs)): __DEBUG__("arg '%s' = '%s'" % (ID.args[i].name, args[i]), DEBUG_LEVEL) TABLE.set(ID.args[i].name, self.lineno, args[i]) tmp = ID(TABLE) if '\n' in tmp: tmp += '\n#line %i\n' % self.lineno return tmp