def main(args=None, emitter=None): """ Entry point when executed from command line. zxbc can be used as python module. If so, bear in mind this function won't be executed unless explicitly called. """ # region [Initialization] config.init() zxbpp.init() zxbparser.init() arch.target.backend.init() arch.target.Translator.reset() asmparse.init() # endregion options = parse_options(args) arch.set_target_arch(OPTIONS.architecture) backend = arch.target.backend args = [ options.PROGRAM ] # Strip out other options, because they're already set in the OPTIONS container input_filename = options.PROGRAM 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_level > 1)) if gl.has_errors: debug.__DEBUG__("exiting due to errors.") return 1 # Exit with errors # Unreachable code removal unreachable_code_visitor = src.api.optimize.UnreachableCodeVisitor() unreachable_code_visitor.visit(zxbparser.ast) # Function calls graph func_call_visitor = src.api.optimize.FunctionGraphVisitor() func_call_visitor.visit(zxbparser.ast) # Optimizations optimizer = src.api.optimize.OptimizerVisitor() optimizer.visit(zxbparser.ast) # Emits intermediate code translator = arch.target.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.target.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() # Signals end of user code translator.ic_inline(';; --- end of user code ---') if OPTIONS.emit_backend: with open_file(OPTIONS.output_filename, '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.target.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_level > 0) asm_output = arch.target.optimizer.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, filename=input_filename) # 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 var_checker = src.api.optimize.VariableVisitor() var_checker.visit(zxbparser.data_ast) translator = arch.target.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 = backend.emit_start() + tmp \ + ['%s:' % backend.DATA_END_LABEL, '%s:' % backend.MAIN_LABEL] \ + asm_output + backend.emit_end() if OPTIONS.output_file_type == FileType.ASM: # Only output assembler file with open_file(OPTIONS.output_filename, '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.output_filename, OPTIONS.output_file_type, 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: if asmparse.MEMORY is not None: with open_file(OPTIONS.memory_map, 'wt', 'utf-8') as f: f.write(asmparse.MEMORY.memory_map) if not gl.has_errors and options.save_config: src.api.config.save_config_into_file( options.save_config, src.api.config.ConfigSections.ZXBC) return gl.has_errors # Exit success
def main(args=None): # Initializes asm parser state src.api.config.init() asmparse.init() zxbpp.init() # Create option parser o_parser = argparse.ArgumentParser() o_parser.add_argument('PROGRAM', type=str, help='ASM program file') o_parser.add_argument("-d", "--debug", action="count", default=OPTIONS.Debug, help="Enable verbosity/debugging output") o_parser.add_argument("-O", "--optimize", type=int, dest="optimization_level", help="Sets optimization level. 0 = None", default=OPTIONS.optimization) o_parser.add_argument( "-o", "--output", type=str, dest="output_file", help="Sets output file. Default is input filename with .bin extension", default=None) o_parser.add_argument("-T", "--tzx", action="store_true", dest="tzx", default=False, help="Sets output format to tzx (default is .bin)") o_parser.add_argument("-t", "--tap", action="store_true", dest="tap", default=False, help="Sets output format to tzx (default is .bin)") o_parser.add_argument( "-B", "--BASIC", action="store_true", dest="basic", default=False, help= "Creates a BASIC loader which load the rest of the CODE. Requires -T ot -t" ) o_parser.add_argument( "-a", "--autorun", action="store_true", default=False, help="Sets the program to auto run once loaded (implies --BASIC)") o_parser.add_argument( "-e", "--errmsg", type=str, dest="stderr", default=OPTIONS.StdErrFileName, help="Error messages file (standard error console by default") o_parser.add_argument("-M", "--mmap", type=str, dest="memory_map", default=None, help="Generate label memory map") o_parser.add_argument( "-b", "--bracket", action="store_true", default=False, help="Allows brackets only for memory access and indirections") o_parser.add_argument('-N', "--zxnext", action="store_true", default=False, help="Enable ZX Next extra ASM opcodes!") o_parser.add_argument("--version", action="version", version="%(prog)s " + VERSION) options = o_parser.parse_args(args) if not os.path.exists(options.PROGRAM): o_parser.error("No such file or directory: '%s'" % options.PROGRAM) sys.exit(2) OPTIONS.Debug = int(options.debug) OPTIONS.inputFileName = options.PROGRAM OPTIONS.outputFileName = options.output_file OPTIONS.optimization = options.optimization_level OPTIONS.use_loader = options.autorun or options.basic OPTIONS.autorun = options.autorun OPTIONS.StdErrFileName = options.stderr OPTIONS.memory_map = options.memory_map OPTIONS.bracket = options.bracket OPTIONS.zxnext = options.zxnext if options.tzx: OPTIONS.output_file_type = 'tzx' elif options.tap: OPTIONS.output_file_type = 'tap' if not OPTIONS.outputFileName: OPTIONS.outputFileName = os.path.splitext( os.path.basename(OPTIONS.inputFileName) )[0] + os.path.extsep + OPTIONS.output_file_type if OPTIONS.StdErrFileName: OPTIONS.stderr = open(OPTIONS.StdErrFileName, 'wt') if int(options.tzx) + int(options.tap) > 1: o_parser.error("Options --tap, --tzx and --asm are mutually exclusive") return 3 if OPTIONS.use_loader and not options.tzx and not options.tap: o_parser.error( 'Option --BASIC and --autorun requires --tzx or tap format') return 4 # Configure the preprocessor to use the asm-preprocessor-lexer zxbpp.setMode('asm') # Now filter them against the preprocessor zxbpp.main([OPTIONS.inputFileName]) # Now output the result asm_output = zxbpp.OUTPUT asmparse.assemble(asm_output) if global_.has_errors: return 1 if not asmparse.MEMORY.memory_bytes: # empty seq. asmparse.warning(0, "Nothing to assemble. Exiting...") return 0 current_org = max(asmparse.MEMORY.memory_bytes.keys() or [0]) + 1 for label, line in asmparse.INITS: expr_label = asmparse.Expr.makenode( asmparse.Container(asmparse.MEMORY.get_label(label, line), line)) asmparse.MEMORY.add_instruction(asmparse.Asm(0, 'CALL NN', expr_label)) if len(asmparse.INITS) > 0: if asmparse.AUTORUN_ADDR is not None: asmparse.MEMORY.add_instruction( asmparse.Asm(0, 'JP NN', asmparse.AUTORUN_ADDR)) else: asmparse.MEMORY.add_instruction( asmparse.Asm(0, 'JP NN', min(asmparse.MEMORY.orgs.keys())) ) # To the beginning of binary asmparse.AUTORUN_ADDR = current_org if OPTIONS.memory_map: with open(OPTIONS.memory_map, 'wt') as f: f.write(asmparse.MEMORY.memory_map) asmparse.generate_binary(OPTIONS.outputFileName, OPTIONS.output_file_type) return global_.has_errors
def main(args=None, emitter=None): """ Entry point when executed from command line. You can use zxbc.py as a module with import, and this function won't be executed. """ # region [Initialization] config.init() zxbpp.init() zxbparser.init() arch.target.backend.init() arch.target.Translator.reset() asmparse.init() # endregion parser = args_parser.parser() options = parser.parse_args(args=args) # ------------------------------------------------------------ # Setting of internal parameters according to command line # ------------------------------------------------------------ OPTIONS.Debug = options.debug OPTIONS.optimization = options.optimize OPTIONS.outputFileName = options.output_file OPTIONS.StdErrFileName = options.stderr OPTIONS.array_base = options.array_base OPTIONS.string_base = options.string_base OPTIONS.Sinclair = options.sinclair OPTIONS.heap_size = options.heap_size OPTIONS.memoryCheck = options.debug_memory OPTIONS.strictBool = options.strict_bool or OPTIONS.Sinclair OPTIONS.arrayCheck = options.debug_array OPTIONS.emitBackend = options.emit_backend OPTIONS.enableBreak = options.enable_break OPTIONS.explicit = options.explicit OPTIONS.memory_map = options.memory_map OPTIONS.strict = options.strict OPTIONS.headerless = options.headerless OPTIONS.zxnext = options.zxnext OPTIONS.expect_warnings = gl.EXPECTED_WARNINGS = options.expect_warnings OPTIONS.hide_warning_codes = options.hide_warning_codes if options.arch not in arch.AVAILABLE_ARCHITECTURES: parser.error(f"Invalid architecture '{options.arch}'") return 2 OPTIONS.architecture = options.arch arch.set_target_arch(options.arch) backend = arch.target.backend # region [Enable/Disable Warnings] enabled_warnings = set(options.enable_warning or []) disabled_warnings = set(options.disable_warning or []) duplicated_options = [f"W{x}" for x in enabled_warnings.intersection(disabled_warnings)] if duplicated_options: parser.error(f"Warning(s) {', '.join(duplicated_options)} cannot be enabled " f"and disabled simultaneously") return 2 for warn_code in enabled_warnings: errmsg.enable_warning(warn_code) for warn_code in disabled_warnings: errmsg.disable_warning(warn_code) # endregion OPTIONS.org = src.api.utils.parse_int(options.org) if OPTIONS.org 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[name] = val zxbpp.ID_TABLE.define(name, value=val, lineno=0) if OPTIONS.Sinclair: OPTIONS.array_base = 1 OPTIONS.string_base = 1 OPTIONS.strictBool = True OPTIONS.case_insensitive = True if options.ignore_case: OPTIONS.case_insensitive = True debug.ENABLED = OPTIONS.Debug 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 = options.basic OPTIONS.autorun = options.autorun if options.tzx: OPTIONS.output_file_type = 'tzx' elif options.tap: OPTIONS.output_file_type = 'tap' elif options.asm: OPTIONS.output_file_type = 'asm' elif options.emit_backend: OPTIONS.output_file_type = '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: OPTIONS.__DEFINES['__MEMORY_CHECK__'] = '' zxbpp.ID_TABLE.define('__MEMORY_CHECK__', lineno=0) if OPTIONS.arrayCheck: OPTIONS.__DEFINES['__CHECK_ARRAY_BOUNDARY__'] = '' zxbpp.ID_TABLE.define('__CHECK_ARRAY_BOUNDARY__', lineno=0) if OPTIONS.enableBreak: OPTIONS.__DEFINES['__ENABLE_BREAK__'] = '' zxbpp.ID_TABLE.define('__ENABLE_BREAK__', lineno=0) OPTIONS.include_path = options.include_path OPTIONS.inputFileName = zxbparser.FILENAME = \ os.path.basename(args[0]) if not OPTIONS.outputFileName: OPTIONS.outputFileName = \ os.path.splitext(os.path.basename(OPTIONS.inputFileName))[0] + os.path.extsep + \ OPTIONS.output_file_type if OPTIONS.StdErrFileName: OPTIONS.stderr = open_file(OPTIONS.StdErrFileName, '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 > 1)) if gl.has_errors: debug.__DEBUG__("exiting due to errors.") return 1 # Exit with errors # Unreachable code removal unreachable_code_visitor = src.api.optimize.UnreachableCodeVisitor() unreachable_code_visitor.visit(zxbparser.ast) # Function calls graph func_call_visitor = src.api.optimize.FunctionGraphVisitor() func_call_visitor.visit(zxbparser.ast) # Optimizations optimizer = src.api.optimize.OptimizerVisitor() optimizer.visit(zxbparser.ast) # Emits intermediate code translator = arch.target.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.target.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() # Signals end of user code translator.ic_inline(';; --- end of user code ---') if OPTIONS.emitBackend: with open_file(OPTIONS.outputFileName, '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.target.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 > 0) asm_output = arch.target.optimizer.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 var_checker = src.api.optimize.VariableVisitor() var_checker.visit(zxbparser.data_ast) translator = arch.target.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 = backend.emit_start() + tmp \ + ['%s:' % backend.DATA_END_LABEL, '%s:' % backend.MAIN_LABEL] \ + asm_output + backend.emit_end() if options.asm: # Only output assembler file with open_file(OPTIONS.outputFileName, '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, OPTIONS.output_file_type, 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: if asmparse.MEMORY is not None: with open_file(OPTIONS.memory_map, 'wt', 'utf-8') as f: f.write(asmparse.MEMORY.memory_map) return gl.has_errors # Exit success