def run_all(infile, outfile): "Transforms the source infile to a binary outfile." Err.count = 0 z = Ophis.Frontend.parse(infile) env = Ophis.Environment.Environment() m = Ophis.Passes.ExpandMacros() i = Ophis.Passes.InitLabels() l_basic = Ophis.Passes.UpdateLabels() l = Ophis.Passes.FixPoint("label update", [l_basic], lambda: l_basic.changed == 0) c = Ophis.Passes.Collapse() a = Ophis.Passes.Assembler() passes = [] passes.append(Ophis.Passes.DefineMacros()) passes.append(Ophis.Passes.FixPoint("macro expansion", [m], lambda: m.changed == 0)) passes.append(Ophis.Passes.FixPoint("label initialization", [i], lambda: i.changed == 0)) passes.extend([Ophis.Passes.CircularityCheck(), Ophis.Passes.CheckExprs(), Ophis.Passes.EasyModes()]) passes.append(Ophis.Passes.FixPoint("instruction selection", [l, c], lambda: c.collapsed == 0)) passes.extend([Ophis.Passes.NormalizeModes(), Ophis.Passes.UpdateLabels(), a]) for p in passes: p.go(z, env) if Err.count == 0: try: output = file(outfile, 'wb') output.write("".join(map(chr, a.output))) except IOError: print "Could not write to "+outfile else: Err.report()
def assemble(self, node, mode, env): "A generic instruction called by the visitor methods themselves" (opcode, expr, expr2) = node.data bin_op = Ops.opcodes[opcode][mode] if bin_op is None: Err.log('%s does not have mode "%s"' % (opcode.upper(), Ops.modes[mode])) return inst_bytes = [] self.outputbyte(IR.ConstantExpr(bin_op), env, inst_bytes) arglen = Ops.lengths[mode] val1 = None val2 = None if expr is not None: val1 = expr.value(env) if expr2 is not None: val2 = expr2.value(env) if mode == 15: # ZP Relative mode is wildly nonstandard expr2 = self.relativize(expr2, env, arglen) self.outputbyte(expr, env, inst_bytes) self.outputbyte(expr2, env, inst_bytes) else: if mode == 14: expr = self.relativize(expr, env, arglen) if arglen == 1: self.outputbyte(expr, env, inst_bytes) elif arglen == 2: self.outputword(expr, env, inst_bytes) self.listing.listInstruction(self.listing_string(env.getPC(), inst_bytes, mode, opcode, val1, val2)) env.incPC(1 + arglen) self.code += 1 + arglen
def assemble(self, node, mode, env): "A generic instruction called by the visitor methods themselves" (opcode, expr) = node.data bin_op = Ops.opcodes[opcode][mode] if bin_op is None: Err.log('%s does not have mode "%s"' % (opcode.upper(), Ops.modes[mode])) return self.outputbyte(IR.ConstantExpr(bin_op), env) arglen = Ops.lengths[mode] if mode == 14: # Special handling for relative mode arg = expr.value(env) arg = arg - (env.getPC() + 2) if arg < -128 or arg > 127: Err.log("Branch target out of bounds") arg = 0 if arg < 0: arg += 256 expr = IR.ConstantExpr(arg) if arglen == 1: self.outputbyte(expr, env) if arglen == 2: self.outputword(expr, env) env.incPC(1 + arglen) self.code += 1 + arglen
def pragmaCbmfloat(ppt, line, result): "Parses a string into a CBM BASIC format floating point number" data = [] while True: try: v_str = line.expect("STRING").value v = float(v_str) if v == 0.0: data.extend([0,0,0,0,0]) else: if v < 0.0: sign = 128 v = -v else: sign = 0 expt = math.floor(math.log(v, 2)) if expt >= -128 and expt <= 126: mantissa = v / (2**expt) m1 = (mantissa - 1.0) * 128 + sign m2 = m1 * 256 m3 = m2 * 256 m4 = m3 * 256 data.extend([int(x) % 256 for x in [expt+129,m1,m2,m3,m4]]) else: Err.log("Floating point constant out of range") except ValueError: Err.log("Expected: floating point") next = line.expect(',', 'EOL').type if next == 'EOL': break bytes = [IR.ConstantExpr(x) for x in data] result.append(IR.Node(ppt, "Byte", *bytes))
def expect(self, *tokens): """Reads a token from the ParseLine line and returns it if it's of a type in the sequence tokens. Otherwise, it logs an error.""" token = self.pop() if token.type not in tokens: Err.log('Expected: "'+'", "'.join(tokens)+'"') return token
def pragmaIncbin(ppt, line, result): "Includes a binary file" filename = line.expect("STRING").value offset = IR.ConstantExpr(0) size = None if str(line.lookahead(0)) == ",": line.pop() offset = FE.parse_expr(line) if str(line.lookahead(0)) == ",": line.pop() size = FE.parse_expr(line) line.expect("EOL") if type(filename) == str: try: f = file(os.path.join(FE.context_directory, filename), "rb") if offset.hardcoded and (size is None or size.hardcoded): # We know how big it will be, we can just use the values. # First check to make sure they're sane if offset.value() < 0: Err.log("Offset may not be negative") f.close() return f.seek(0, 2) # Seek to end of file if offset.value() > f.tell(): Err.log("Offset runs past end of file") f.close() return if size is not None: if size.value() < 0: Err.log("Length may not be negative") f.close() return if offset.value() + size.value() > f.tell(): Err.log(".incbin length too long") f.close() return if size is None: size = IR.ConstantExpr(-1) f.seek(offset.value()) bytes = f.read(size.value()) bytes = [IR.ConstantExpr(ord(x)) for x in bytes] result.append(IR.Node(ppt, "Byte", *bytes)) else: # offset or length could change based on label placement. # This seems like an unbelievably bad idea, but since we # don't have constant prop it will happen for any symbolic # alias. Don't use symbolic aliases when extracting tiny # pieces out of humongous files, I guess. bytes = f.read() bytes = [IR.ConstantExpr(ord(x)) for x in bytes] if size is None: size = IR.SequenceExpr([IR.ConstantExpr(len(bytes)), "-", offset]) result.append(IR.Node(ppt, "ByteRange", offset, size, *bytes)) f.close() except IOError: Err.log("Could not read " + filename) return
def __getitem__(self, item): if item[0] == '_': for dict in [self.dicts[i] for i in self.stack]: if item in dict: return dict[item] else: if item in self.dicts[0]: return self.dicts[0][item] Err.log("Unknown label '%s'" % item) return 0
def visitMacroEnd(self, node, env): if self.inDef: Macro.endMacro() node.nodetype = "None" node.data = [] self.inDef = False elif not self.nestedError: Err.log("Unmatched .macend")
def visitMacroBegin(self, node, env): if self.inDef: Err.log("Nested macro definition") self.nestedError = True else: Macro.newMacro(node.data[0]) node.nodetype = "None" node.data = [] self.inDef = True
def reset(self): "Clears out program counter, segment, and scoping information" self.pc = 0 self.segmentdict = {} self.segment = "*text-default*" self.scopecount = 0 if len(self.stack) > 1: Err.log("Unmatched .scope") self.stack = [0]
def add_token(token): "Converts a substring into a single lexeme" if token == "": return if token == "0": result.append(Lexeme("NUM", 0)) return firstchar = token[0] rest = token[1:] if firstchar == '"': result.append(Lexeme("STRING", rest)) return elif firstchar in bases: try: result.append(Lexeme("NUM", long(rest, bases[firstchar][1]))) return except ValueError: Err.log('Invalid ' + bases[firstchar][0] + ' constant: ' + rest) result.append(Lexeme("NUM", 0)) return elif firstchar.isdigit(): try: result.append(Lexeme("NUM", long(token))) except ValueError: Err.log('Identifiers may not begin with a number') result.append(Lexeme("LABEL", "ERROR")) return elif firstchar == "'": if len(rest) == 1: result.append(Lexeme("NUM", ord(rest))) else: Err.log("Invalid character constant '" + rest + "'") result.append(Lexeme("NUM", 0)) return elif firstchar in punctuation: if rest != "": Err.log("Internal lexer error! '" + token + "' can't happen!") result.append(Lexeme(firstchar)) return else: # Label, opcode, or index register id = token.lower() if is_opcode(id): result.append(Lexeme("OPCODE", id)) elif id == "x": result.append(Lexeme("X")) elif id == "y": result.append(Lexeme("Y")) elif id == "z": result.append(Lexeme("Z")) elif id == "sp": result.append(Lexeme("SP")) else: result.append(Lexeme("LABEL", id)) return # should never reach here Err.log("Internal lexer error: add_token fall-through")
def expandMacro(ppt, name, arglist): global macros if name not in macros: Err.log("Undefined macro '%s'" % name) return IR.NullNode argexprs = [IR.Node(ppt, "Label", "_*%d" % i, arg) for (i, arg) in zip(xrange(1, sys.maxint), arglist)] bindexprs = [IR.Node(ppt, "Label", "_%d" % i, IR.LabelExpr("_*%d" % i)) for i in range(1, len(arglist)+1)] body = [IR.Node("%s->%s" % (ppt, node.ppt), node.nodetype, *node.data) for node in macros[name]] invocation = [IR.Node(ppt, "ScopeBegin")] + argexprs + [IR.Node(ppt, "ScopeBegin")] + bindexprs + body + [IR.Node(ppt, "ScopeEnd"), IR.Node(ppt, "ScopeEnd")] return IR.SequenceNode(ppt, invocation)
def endMacro(): global currentname global currentbody global macros if currentname is None: Err.log("Internal error! Ended a non-existent macro!") else: macros[currentname] = currentbody currentname = None currentbody = None
def outputbyte(self, expr, env): 'Outputs a byte, with range checking' if self.writeOK: val = expr.value(env) if val < 0x00 or val > 0xff: Err.log("Byte constant " + str(expr) + " out of range") val = 0 self.output.append(int(val)) else: Err.log("Attempt to write to data segment")
def visitAdvance(self, node, env): pc = env.getPC() target = node.data[0].value(env) if (pc > target): Err.log("Attempted to .advance backwards: $%x to $%x" % (pc, target)) else: zero = IR.ConstantExpr(0) for i in xrange(target-pc): self.outputbyte(zero, env) self.filler += target-pc env.setPC(target)
def visitLabel(self, node, env): (label, val) = node.data fulllabel = "%d:%s" % (env.stack[0], label) if fulllabel in self.labelmap and self.labelmap[fulllabel] is not node: Err.log("Duplicate label definition '%s'" % label) if fulllabel not in self.labelmap: self.labelmap[fulllabel] = node if val.valid(env, self.PCvalid) and label not in env: env[label]=0 self.changed=1
def relativize(self, expr, env, arglen): "Convert an expression into one for use in relative addressing" arg = expr.value(env) arg = arg - (env.getPC() + arglen + 1) if arg < -128 or arg > 127: Err.log("Branch target out of bounds") arg = 0 if arg < 0: arg += 256 return IR.ConstantExpr(arg)
def outputword_be(self, expr, env): 'Outputs a big-endian word, with range checking' if self.writeOK: val = expr.value(env) if val < 0x0000 or val > 0xFFFF: Err.log("Word constant " + str(expr) + " out of range") val = 0 self.output.append(int((val >> 8) & 0xFF)) self.output.append(int(val & 0xFF)) else: Err.log("Attempt to write to data segment")
def go(self, node, env): """Runs this FixPoint's passes, in order, until the fixpoint is true. Always runs the passes at least once.""" for i in xrange(100): if Err.count != 0: break for p in self.passes: p.go(node, env) if Err.count != 0: break if self.fixpoint(): break if Cmd.verbose > 1: print "Fixpoint failed, looping back" else: Err.log("Can't make %s converge! Maybe there's a recursive dependency somewhere?" % self.name)
def newMacro(name): "Start creating a new macro with the specified name." global currentname global currentbody global macros if currentname is not None: Err.log("Internal error! Nested macro attempt!") else: if name in macros: Err.log("Duplicate macro definition '%s'" % name) currentname = name currentbody = []
def pragmaCharmapbin(ppt, line, result): "Load a new character map from a file" global currentcharmap filename = line.expect("STRING").value line.expect("EOL") if type(filename)==str: f = file(filename, "rb") bytes = f.read() f.close() if len(bytes)==256: currentcharmap = bytes else: Err.log("Character map "+filename+" not 256 bytes long")
def outputword(self, expr, env, tee=None): 'Outputs a little-endian word, with range checking' if self.writeOK: val = expr.value(env) if val < 0x0000 or val > 0xFFFF: Err.log("Word constant " + str(expr) + " out of range") val = 0 self.output.append(int(val & 0xFF)) self.output.append(int((val >> 8) & 0xFF)) if tee is not None: tee.extend(self.output[-2:]) else: Err.log("Attempt to write to data segment")
def visitAdvance(self, node, env): pc = env.getPC() target = node.data[0].value(env) if (pc > target): Err.log("Attempted to .advance backwards: $%x to $%x" % (pc, target)) else: created = [] for i in xrange(target - pc): self.outputbyte(node.data[1], env, created) self.filler += target - pc self.registerData(created, env.getPC()) env.setPC(target)
def expandMacro(ppt, name, arglist): global macros if name not in macros: Err.log("Undefined macro '%s'" % name) return IR.NullNode argexprs = [IR.Node(ppt, "Label", "_*%d" % i, arg) for (i, arg) in zip(xrange(1, sys.maxint), arglist)] bindexprs = [IR.Node(ppt, "Label", "_%d" % i, IR.LabelExpr("_*%d" % i)) for i in range(1, len(arglist) + 1)] body = [IR.Node("%s->%s" % (ppt, node.ppt), node.nodetype, *node.data) for node in macros[name]] invocation = [IR.Node(ppt, "ScopeBegin")] + argexprs + \ [IR.Node(ppt, "ScopeBegin")] + bindexprs + body + \ [IR.Node(ppt, "ScopeEnd"), IR.Node(ppt, "ScopeEnd")] return IR.SequenceNode(ppt, invocation)
def outputdword_be(self, expr, env, tee=None): 'Outputs a big-endian dword, with range checking' if self.writeOK: val = expr.value(env) if val < 0x00000000 or val > 0xFFFFFFFFL: Err.log("DWord constant " + str(expr) + " out of range") val = 0 self.output.append(int((val >> 24) & 0xFF)) self.output.append(int((val >> 16) & 0xFF)) self.output.append(int((val >> 8) & 0xFF)) self.output.append(int(val & 0xFF)) if tee is not None: tee.extend(self.output[-4:]) else: Err.log("Attempt to write to data segment")
def expect(self, *tokens): """Reads a token from the ParseLine line and returns it if it's of a type in the sequence tokens. Otherwise, it logs an error.""" token = self.pop() if token.type in tokens: return token if 'LABEL' in tokens: if token.type in ['X', 'Y', 'Z', 'SP']: token.value = token.type.lower() token.type = 'LABEL' return token elif token.type == 'OPCODE': token.type = 'LABEL' return token Err.log('Expected: "' + '", "'.join(tokens) + '"') return token
def pragmaCharmap(ppt, line, result): "Modify the character map." global currentcharmap, basecharmap bytes = readData(line) if len(bytes) == 0: currentcharmap = basecharmap else: try: base = bytes[0].data newsubstr = "".join([chr(x.data) for x in bytes[1:]]) currentcharmap = currentcharmap[:base] + newsubstr + currentcharmap[base+len(newsubstr):] if len(currentcharmap) != 256 or base < 0 or base > 255: Err.log("Charmap replacement out of range") currentcharmap = currentcharmap[:256] except ValueError: Err.log("Illegal character in .charmap directive")
def visitLabel(self, node, env): (label, val) = node.data fulllabel = "%d:%s" % (env.stack[0], label) if fulllabel in self.labelmap and self.labelmap[fulllabel] is not node: Err.log("Duplicate label definition '%s'" % label) if fulllabel not in self.labelmap: self.labelmap[fulllabel] = node if val.valid(env, self.PCvalid) and label not in env: env[label] = 0 self.changed = True if label in ['a', 'x', 'y'] and self.runcount == 1: print>>sys.stderr, str(node.ppt) + ": WARNING: " \ "using register name as label" if label in Ops.opcodes and self.runcount == 1: print>>sys.stderr, str(node.ppt) + ": WARNING: " \ "using opcode name as label"
def parse_file(ppt, filename): "Loads a .P65 source file, and returns an IR list." Err.currentpoint = ppt if Cmd.verbose > 0: print("Loading " + filename) try: f = open(filename, 'r', encoding='utf-8') linelist = f.readlines() f.close() pptlist = ["%s:%d" % (filename, i + 1) for i in range(len(linelist))] lexlist = map(lex, pptlist, linelist) IRlist = map(parse_line, pptlist, lexlist) IRlist = [node for node in IRlist if node is not IR.NullNode] return IR.SequenceNode(ppt, IRlist) except IOError: Err.log("Could not read " + filename) return IR.NullNode
def parse_file(ppt, filename): "Loads a .P65 source file, and returns an IR list." Err.currentpoint = ppt if Cmd.verbose > 0: print "Loading "+filename try: f = file(filename) linelist = f.readlines() f.close() pptlist = ["%s:%d" % (filename, i+1) for i in range(len(linelist))] lexlist = map(lex, pptlist, linelist) IRlist = map(parse_line, pptlist, lexlist) IRlist = [node for node in IRlist if node is not IR.NullNode] return IR.SequenceNode(ppt, IRlist) except IOError: Err.log ("Could not read "+filename) return IR.NullNode
def expect(self, *tokens): """Reads a token from the ParseLine line and returns it if it's of a type in the sequence tokens. Otherwise, it logs an error.""" token = self.pop() if token.type in tokens: return token if "LABEL" in tokens: if token.type in ["X", "Y"]: token.value = token.type.lower() token.type = "LABEL" return token elif token.type == "OPCODE": token.type = "LABEL" return token Err.log('Expected: "' + '", "'.join(tokens) + '"') return token
def go(self, node, env): """Runs this FixPoint's passes, in order, until the fixpoint is true. Always runs the passes at least once.""" for i in xrange(100): if Err.count != 0: break for p in self.passes: p.go(node, env) if Err.count != 0: break if self.fixpoint(): break if Cmd.print_pass: print>>sys.stderr, "Fixpoint failed, looping back" else: Err.log("Can't make %s converge! Maybe there's a recursive " "dependency somewhere?" % self.name)
def pragmaCharmap(ppt, line, result): "Modify the character map." global currentcharmap, basecharmap if str(line.lookahead(0)) == "EOL": currentcharmap = basecharmap else: bytes = readData(line) try: base = bytes[0].data newsubstr = "".join([chr(x.data) for x in bytes[1:]]) currentcharmap = currentcharmap[:base] + newsubstr + \ currentcharmap[base + len(newsubstr):] if len(currentcharmap) != 256 or base < 0 or base > 255: Err.log("Charmap replacement out of range") currentcharmap = currentcharmap[:256] except ValueError: Err.log("Illegal character in .charmap directive")
def pragmaCharmapbin(ppt, line, result): "Load a new character map from a file" global currentcharmap filename = line.expect("STRING").value line.expect("EOL") if type(filename) == str: try: f = open(os.path.join(FE.context_directory, filename), "rb") bytes = f.read() f.close() except IOError: Err.log("Could not read " + filename) return if len(bytes) == 256: currentcharmap = bytes else: Err.log("Character map " + filename + " not 256 bytes long")
def pragmaCharmapbin(ppt, line, result): "Load a new character map from a file" global currentcharmap filename = line.expect("STRING").value line.expect("EOL") if type(filename) == str: try: f = file(os.path.join(FE.context_directory, filename), "rb") bytes = f.read() f.close() except IOError: Err.log("Could not read " + filename) return if len(bytes) == 256: currentcharmap = bytes else: Err.log("Character map " + filename + " not 256 bytes long")
def run_all(infile, outfile): "Transforms the source infile to a binary outfile." Err.count = 0 z = Ophis.Frontend.parse(infile) env = Ophis.Environment.Environment() m = Ophis.Passes.ExpandMacros() i = Ophis.Passes.InitLabels() l_basic = Ophis.Passes.UpdateLabels() l = Ophis.Passes.FixPoint("label update", [l_basic], lambda: l_basic.changed == 0) c = Ophis.Passes.Collapse() a = Ophis.Passes.Assembler() passes = [] passes.append(Ophis.Passes.DefineMacros()) passes.append( Ophis.Passes.FixPoint("macro expansion", [m], lambda: m.changed == 0)) passes.append( Ophis.Passes.FixPoint("label initialization", [i], lambda: i.changed == 0)) passes.extend([ Ophis.Passes.CircularityCheck(), Ophis.Passes.CheckExprs(), Ophis.Passes.EasyModes() ]) passes.append( Ophis.Passes.FixPoint("instruction selection", [l, c], lambda: c.collapsed == 0)) passes.extend( [Ophis.Passes.NormalizeModes(), Ophis.Passes.UpdateLabels(), a]) for p in passes: p.go(z, env) if Err.count == 0: try: output = open(outfile, 'wb') output.write(bytes(a.output)) output.close() except IOError: print("Could not write to " + outfile) else: Err.report()
def parse_file(ppt, filename, load_once=False): "Loads an Ophis source file, and returns an IR list." global context_directory, loadedfiles Err.currentpoint = ppt old_context = context_directory if filename != '-': if context_directory is not None: filename = os.path.abspath( os.path.join(context_directory, filename)) if load_once and filename in loadedfiles: if Cmd.print_loaded_files: print("Skipping " + filename, file=sys.stderr) return IR.NullNode loadedfiles[filename] = True if Cmd.print_loaded_files: if filename != '-': print("Loading " + filename, file=sys.stderr) else: print("Loading from standard input", file=sys.stderr) try: if filename != '-': if context_directory is not None: filename = os.path.join(context_directory, filename) f = open(filename, "rt") linelist = f.readlines() f.close() context_directory = os.path.abspath(os.path.dirname(filename)) else: context_directory = os.getcwd() linelist = sys.stdin.readlines() pptlist = ["%s:%d" % (filename, i + 1) for i in range(len(linelist))] lexlist = list(map(lex, pptlist, linelist)) IRlist = list(map(parse_line, pptlist, lexlist)) IRlist = [node for node in IRlist if node is not IR.NullNode] context_directory = old_context return IR.SequenceNode(ppt, IRlist) except IOError: Err.log("Could not read " + filename) context_directory = old_context return IR.NullNode
def parse_file(ppt, filename, load_once=False): "Loads an Ophis source file, and returns an IR list." global context_directory, loadedfiles Err.currentpoint = ppt old_context = context_directory if filename != '-': if context_directory is not None: filename = os.path.abspath(os.path.join(context_directory, filename)) if load_once and filename in loadedfiles: if Cmd.print_loaded_files: print>>sys.stderr, "Skipping " + filename return IR.NullNode loadedfiles[filename] = True if Cmd.print_loaded_files: if filename != '-': print>>sys.stderr, "Loading " + filename else: print>>sys.stderr, "Loading from standard input" try: if filename != '-': if context_directory is not None: filename = os.path.join(context_directory, filename) f = file(filename) linelist = f.readlines() f.close() context_directory = os.path.abspath(os.path.dirname(filename)) else: context_directory = os.getcwd() linelist = sys.stdin.readlines() pptlist = ["%s:%d" % (filename, i + 1) for i in range(len(linelist))] lexlist = map(lex, pptlist, linelist) IRlist = map(parse_line, pptlist, lexlist) IRlist = [node for node in IRlist if node is not IR.NullNode] context_directory = old_context return IR.SequenceNode(ppt, IRlist) except IOError: Err.log("Could not read " + filename) context_directory = old_context return IR.NullNode
def assemble(self, node, mode, env): "A generic instruction called by the visitor methods themselves" (opcode, expr) = node.data bin_op = Ops.opcodes[opcode][mode] if bin_op is None: Err.log('%s does not have mode "%s"' % (opcode.upper(), Ops.modes[mode])) return self.outputbyte(IR.ConstantExpr(bin_op), env) arglen = Ops.lengths[mode] if mode == 11: # Special handling for relative mode arg = expr.value(env) arg = arg - (env.getPC() + 2) if arg < -128 or arg > 127: Err.log("Branch target out of bounds") arg = 0 if arg < 0: arg += 256 expr = IR.ConstantExpr(arg) if arglen == 1: self.outputbyte(expr, env) if arglen == 2: self.outputword(expr, env) env.incPC(1 + arglen) self.code += 1 + arglen
def atom(): "Parses lowest-priority expression components." global templabelcount next = line.lookahead(0).type if next == "NUM": return IR.ConstantExpr(line.expect("NUM").value) elif next in ["LABEL", "X", "Y", "Z", "SP", "OPCODE"]: return IR.LabelExpr(line.expect("LABEL").value) elif next == "^": line.expect("^") return IR.PCExpr() elif next == "[": line.expect("[") result = parse_expr(line) line.expect("]") return result elif next == "+": offset = 0 while next == "+": offset += 1 line.expect("+") next = line.lookahead(0).type return IR.LabelExpr("*" + str(templabelcount + offset)) elif next == "-": offset = 1 while next == "-": offset -= 1 line.expect("-") next = line.lookahead(0).type return IR.LabelExpr("*" + str(templabelcount + offset)) elif next == ">": line.expect(">") return IR.HighByteExpr(atom()) elif next == "<": line.expect("<") return IR.LowByteExpr(atom()) else: Err.log('Expected: expression')
def assemble(self, node, mode, env): "A generic instruction called by the visitor methods themselves" (opcode, expr, expr2) = node.data bin_op = Ops.opcodes[opcode][mode] if bin_op is None: Err.log('%s does not have mode "%s"' % (opcode.upper(), Ops.modes[mode])) return inst_bytes = [] self.outputbyte(IR.ConstantExpr(bin_op), env, inst_bytes) arglen = Ops.lengths[mode] val1 = None val2 = None if expr is not None: val1 = expr.value(env) if expr2 is not None: val2 = expr2.value(env) if mode == Ops.modes.index("Zero Page, Relative"): expr2 = self.relativize(expr2, env, arglen) self.outputbyte(expr, env, inst_bytes) self.outputbyte(expr2, env, inst_bytes) else: if mode == Ops.modes.index("Relative"): expr = self.relativize(expr, env, arglen) elif mode == Ops.modes.index("RelativeLong"): expr = self.relativizelong(expr, env, arglen) if arglen == 1: self.outputbyte(expr, env, inst_bytes) elif arglen == 2: self.outputword(expr, env, inst_bytes) self.listing.listInstruction(self.listing_string(env.getPC(), inst_bytes, mode, opcode, val1, val2)) env.incPC(1 + arglen) self.code += 1 + arglen
def visitByteRange(self, node, env): offset = node.data[0].value(env) + 2 length = node.data[1].value(env) if offset < 2: Err.log("Negative offset in .incbin") elif offset > len(node.data): Err.log("Offset extends past end of file") elif length < 0: Err.log("Negative length") elif offset + length > len(node.data): Err.log("File too small for .incbin subrange") else: created = [] for expr in node.data[offset:(offset + length)]: self.outputbyte(expr, env, created) self.registerData(created, env.getPC()) env.incPC(length) self.data += length
def aux(): "Accumulates all IR nodes defined by this line." if line.lookahead(0).type == "EOL": pass elif line.lookahead(1).type == ":": newlabel = line.expect("LABEL").value line.expect(":") result.append(IR.Node(ppt, "Label", newlabel, IR.PCExpr())) aux() elif line.lookahead(0).type == "*": global templabelcount templabelcount = templabelcount + 1 result.append( IR.Node(ppt, "Label", "*" + str(templabelcount), IR.PCExpr())) line.expect("*") aux() elif line.lookahead(0).type == "." or line.lookahead(0).type == "`": which = line.expect(".", "`").type if (which == "."): pragma = line.expect("LABEL").value else: pragma = "invoke" pragmaFunction = "pragma" + pragma.title() for mod in pragma_modules: if hasattr(mod, pragmaFunction): getattr(mod, pragmaFunction)(ppt, line, result) break else: Err.log("Unknown pragma " + pragma) else: # Instruction opcode = line.expect("OPCODE").value if line.lookahead(0).type == "#": mode = "Immediate" line.expect("#") arg = parse_expr(line) line.expect("EOL") elif line.lookahead(0).type == "(": line.expect("(") arg = parse_expr(line) if line.lookahead(0).type == ",": mode = "IndirectX" line.expect(",") line.expect("X") line.expect(")") line.expect("EOL") else: line.expect(")") tok = line.expect(",", "EOL").type if tok == "EOL": mode = "Indirect" else: mode = "IndirectY" line.expect("Y") line.expect("EOL") elif line.lookahead(0).type == "EOL": mode = "Implied" arg = None else: arg = parse_expr(line) tok = line.expect("EOL", ",").type if tok == ",": tok = line.expect("X", "Y").type if tok == "X": mode = "MemoryX" else: mode = "MemoryY" line.expect("EOL") else: mode = "Memory" result.append(IR.Node(ppt, mode, opcode, arg))
def visitAdvance(self, node, env): PCvalid = self.PCvalid self.PCvalid = node.data[0].valid(env, self.PCvalid) if not node.data[0].valid(env, PCvalid): Err.log("Undefined or circular reference on .advance")
def postPass(self): if self.inDef: Err.log("Unmatched .macro") elif Cmd.print_ir: print>>sys.stderr, "Macro definitions:" Macro.dump()
def run_all(): """Transforms the source infiles to a binary outfile. Returns a shell-style exit code: 1 if there were errors, 0 if there were no errors. """ Err.count = 0 z = Ophis.Frontend.parse(Ophis.CmdLine.infiles) env = Ophis.Environment.Environment() m = Ophis.Passes.ExpandMacros() i = Ophis.Passes.InitLabels() l_basic = Ophis.Passes.UpdateLabels() l = Ophis.Passes.FixPoint("label update", [l_basic], lambda: not l_basic.changed) # The instruction selector is a bunch of fixpoints, and which # passes run depends on the command line options a bit. c_basic = Ophis.Passes.Collapse() c = Ophis.Passes.FixPoint("instruction selection 1", [l, c_basic], lambda: not c_basic.changed) if Ophis.CmdLine.enable_branch_extend: b = Ophis.Passes.ExtendBranches() instruction_select = Ophis.Passes.FixPoint("instruction selection 2", [c, b], lambda: not b.changed) else: instruction_select = c a = Ophis.Passes.Assembler() passes = [] passes.append(Ophis.Passes.DefineMacros()) passes.append( Ophis.Passes.FixPoint("macro expansion", [m], lambda: not m.changed)) passes.append( Ophis.Passes.FixPoint("label initialization", [i], lambda: not i.changed)) passes.extend([ Ophis.Passes.CircularityCheck(), Ophis.Passes.CheckExprs(), Ophis.Passes.EasyModes() ]) passes.append(instruction_select) passes.extend( [Ophis.Passes.NormalizeModes(), Ophis.Passes.UpdateLabels(), a]) for p in passes: p.go(z, env) if Err.count == 0: try: outfile = Ophis.CmdLine.outfile if outfile == '-': output = sys.stdout if sys.platform == "win32": # We can't dump our binary in text mode; that would be # disastrous. So, we'll do some platform-specific # things here to force our stdout to binary mode. import msvcrt msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) elif outfile is None: output = file('ophis.bin', 'wb') else: output = file(outfile, 'wb') output.write("".join(map(chr, a.output))) output.flush() if outfile != '-': output.close() return 0 except IOError: print >> sys.stderr, "Could not write to " + outfile return 1 else: Err.report() return 1
def visitUnknown(self, node, env): Err.log("Internal error! " + self.name + " cannot understand node type " + node.nodetype)
def visitCheckPC(self, node, env): pc = env.getPC() target = node.data[0].value(env) if (pc > target): Err.log(".checkpc assertion failed: $%x > $%x" % (pc, target))
def aux(): "Accumulates all IR nodes defined by this line." if line.lookahead(0).type == "EOL": pass elif line.lookahead(1).type == ":": newlabel = line.expect("LABEL").value line.expect(":") result.append(IR.Node(ppt, "Label", newlabel, IR.PCExpr())) aux() elif line.lookahead(0).type == "*": global templabelcount templabelcount = templabelcount + 1 result.append( IR.Node(ppt, "Label", "*" + str(templabelcount), IR.PCExpr())) line.expect("*") aux() elif line.lookahead(0).type == "." or line.lookahead(0).type == "`": which = line.expect(".", "`").type if (which == "."): pragma = line.expect("LABEL").value else: pragma = "invoke" pragmaFunction = "pragma" + pragma.title() for mod in pragma_modules: if hasattr(mod, pragmaFunction): getattr(mod, pragmaFunction)(ppt, line, result) break else: Err.log("Unknown pragma " + pragma) else: # Instruction opcode = line.expect("OPCODE").value arg2 = None if line.lookahead(0).type == "#": mode = "Immediate" line.expect("#") arg = parse_expr(line) line.expect("EOL") elif line.lookahead(0).type == "(": line.expect("(") arg = parse_expr(line) if line.lookahead(0).type == ",": line.expect(",") if line.lookahead(0).type == "X": mode = "PointerX" line.expect("X") line.expect(")") line.expect("EOL") else: mode = "PointerSPY" line.expect("SP") line.expect(")") line.expect(",") line.expect("Y") line.expect("EOL") else: line.expect(")") tok = line.expect(",", "EOL").type if tok == "EOL": mode = "Pointer" else: if line.lookahead(0).type == "Y": mode = "PointerY" line.expect("Y") line.expect("EOL") else: mode = "PointerZ" line.expect("Z") line.expect("EOL") elif line.lookahead(0).type == "EOL": mode = "Implied" arg = None else: arg = parse_expr(line) tok = line.expect("EOL", ",").type if tok == ",": # Parser has to special-case the BBXn instructions, # Which uniquely take two addresses if opcode[:3] in ["bbs", "bbr"]: arg2 = parse_expr(line) mode = "Memory2" else: tok = line.expect("X", "Y", "Z").type if tok == "X": mode = "MemoryX" elif tok == "Y": mode = "MemoryY" else: mode = "MemoryZ" line.expect("EOL") else: mode = "Memory" result.append(IR.Node(ppt, mode, opcode, arg, arg2))
def visitLabel(self, node, env): (label, val) = node.data if not val.valid(env, self.PCvalid): Err.log("Undefined or circular dependency for label '%s'" % label)
def visitCheckPC(self, node, env): if not node.data[0].valid(env, self.PCvalid): Err.log("Undefined or circular reference on program counter check")
def endscope(self): "Leaves a scope." if len(self.stack) == 1: Err.log("Unmatched .scend") self.stack.pop(0)
def lex(point, line): """Turns a line of source into a sequence of lexemes.""" Err.currentpoint = point result = [] def is_opcode(op): "Tests whether a string is an opcode or an identifier" return op in Ops.opcodes def add_token(token): "Converts a substring into a single lexeme" if token == "": return if token == "0": result.append(Lexeme("NUM", 0)) return firstchar = token[0] rest = token[1:] if firstchar == '"': result.append(Lexeme("STRING", rest)) return elif firstchar in bases: try: result.append(Lexeme("NUM", int(rest, bases[firstchar][1]))) return except ValueError: Err.log('Invalid ' + bases[firstchar][0] + ' constant: ' + rest) result.append(Lexeme("NUM", 0)) return elif firstchar.isdigit(): try: result.append(Lexeme("NUM", int(token))) except ValueError: Err.log('Identifiers may not begin with a number') result.append(Lexeme("LABEL", "ERROR")) return elif firstchar == "'": if len(rest) == 1: result.append(Lexeme("NUM", ord(rest))) else: Err.log("Invalid character constant '" + rest + "'") result.append(Lexeme("NUM", 0)) return elif firstchar in punctuation: if rest != "": Err.log("Internal lexer error! '" + token + "' can't happen!") result.append(Lexeme(firstchar)) return else: # Label, opcode, or index register id = token.lower() if is_opcode(id): result.append(Lexeme("OPCODE", id)) elif id == "x": result.append(Lexeme("X")) elif id == "y": result.append(Lexeme("Y")) elif id == "z": result.append(Lexeme("Z")) elif id == "sp": result.append(Lexeme("SP")) else: result.append(Lexeme("LABEL", id)) return # should never reach here Err.log("Internal lexer error: add_token fall-through") def add_EOL(): "Adds an end-of-line lexeme" result.append(Lexeme("EOL")) # Actual routine begins here value = "" quotemode = False backslashmode = False for c in line.strip(): if backslashmode: backslashmode = False value = value + c elif c == "\\": backslashmode = True elif quotemode: if c == '"': quotemode = False else: value = value + c elif c == ';': add_token(value) value = "" break elif c == '.' and value != "": value = value + c elif c.isspace(): add_token(value) value = "" elif c in punctuation: add_token(value) add_token(c) value = "" elif c == '"': add_token(value) value = '"' quotemode = True else: value = value + c if backslashmode: Err.log("Backslashed newline") if quotemode: Err.log("Unterminated string constant") add_token(value) add_EOL() return result
def postPass(self): if self.inDef: Err.log("Unmatched .macro") elif Cmd.verbose > 2: print("Macro definitions:") Macro.dump()
def visitSetPC(self, node, env): PCvalid = self.PCvalid self.PCvalid = node.data[0].valid(env, self.PCvalid) if not node.data[0].valid(env, PCvalid): Err.log("Undefined or circular reference on program counter set")