acme/contrib/toacme/masm2acme.py
2021-02-14 21:32:32 +00:00

278 lines
8.3 KiB
Python
Executable File

#!/usr/bin/env python3
import sys
def line_preprocess(line):
"split line into comment, strings and everything else"
result = []
part = ""
comment = None
quotes = None
for char in line:
# are we inside comment?
if comment:
comment += char
continue
# are we inside quotes?
if quotes:
part += char
if char == quotes:
# end of quotes
# if previous part was also quoted, we have to combine them,
# because "a""b" really means a"b
if result and result[-1][-1] == quotes:
part = result[-1][:-1] + "\\" + part
result.pop()
# use singlequotes for 1-char strings
if len(part) == 3:
part = "'" + part[1] + "'"
# escape backslash, singlequote, doublequote
if part == "'\\'":
part = "'\\\\'"
elif part == "'''":
part = "'\\''"
elif part == '"\\""':
part = "'\"'"
result.append(part) # move quoted string to result list
part = ""
quotes = None
continue
# not in quotes
if char == '"' or char == "'":
# new quotes, so finish old part
if part and part != ' ':
result.append(part)
# ...and start new one
part = char
quotes = char
continue
# comment?
if char == ';':
# finish old part
if part and part != ' ':
result.append(part)
part = ""
# ...and start comment
comment = char
continue
## tab-to-space:
#if char == '\t':
# char = ' '
# skip blanks after blank
if part.endswith(' ') and char == ' ':
pass
else:
# all other characters:
part += char
# quotes still open at end of line?
if quotes:
raise Exception("Unterminated string constant in input data")
# append last part
if part:
result.append(part)
return result, comment
def single_out(items, substring):
"split any item containing substring into first part, substring, second part. empty parts are dropped."
result = []
for i in items:
while substring in i:
parts = i.partition(substring)
if parts[0]:
result.append(parts[0])
result.append(substring)
i = parts[2]
if i:
result.append(i)
return result
def unquoted_tokenize(part):
"split part into tokens (so do not pass string literals!)"
# split at spaces (and throw away all spaces)
items = part.split()
# split at commas, braces, ...
items = single_out(items, ',')
items = single_out(items, '/')
items = single_out(items, '=')
items = single_out(items, '+')
items = single_out(items, '-')
items = single_out(items, '*')
return items
opcodes_to_keep = [
# std 6502:
"brk", "rti", "rts", "nop",
"php", "plp", "pha", "pla",
"bpl", "bmi", "bvc", "bvs", "bcc", "bcs", "bne", "beq",
"clc", "sec", "cli", "sei", "clv", "cld", "sed",
"dex", "dey", "inx", "iny",
"tax", "tay", "txa", "tya", "tsx", "txs",
# new in 65c02:
"phx", "plx", "phy", "ply", "bra" # inc, dec
]
opcodes_with_arg = [
# std 6502:
"ora", "and", "eor", "adc", "sta", "lda", "cmp", "sbc",
"asl", "rol", "lsr", "ror", "dec", "inc",
"ldx", "stx", "cpx", "ldy", "sty", "cpy",
"jsr", "jmp", "bit",
# new in 65c02:
"tsb", "trb", "stz"
]
token_substitutions = {
".": "*", # program counter
":not:": "not", # operator
":eor:": "xor", # operator
":msb:": ">", # operator?
}
opcodes_to_replace = {
"org": "*=",
"cpu": ";!cpu", # TODO: support properly!
"=": "!tx",
"$": "!wo", # actually & instead of $, but substitution was done earlier
"end": "!eof",
"assert": "+assert",
"lnk": ";!source", # TODO: support properly!
"asla": "\tasl",
"lsra": "\tlsr",
"rola": "\trol",
"rora": "\tror",
"dea": "\tdec", # 65c02
"ina": "\tinc", # 65c02
}
opcodes_to_rename = {
"clr": "stz" # 65c02
}
def convert_opcodes(parts):
"convert mnemonics and pseudo opcodes"
if not parts:
return parts
op = parts[0]
parts = parts[1:]
if op in opcodes_to_keep:
return ["\t" + op] + parts
if op in opcodes_to_replace:
return [opcodes_to_replace[op]] + parts
# wtf?!
if op == "jmi":
op = "jmpi"
elif op == "jmix":
op = "jmpxi"
# convert addressing modes
if len(op) > 3:
oldop = op
am = op[3:]
op = op[:3]
if am == "im":
if parts[0] == '/':
parts[0] = "#>"
elif parts[0][0] >= 'a' and parts[0][0] <= 'z':
parts[0] = "#< " + parts[0]
else:
parts[0] = "#" + parts[0]
elif am == "ax" or am == "zx":
parts[-1] = parts[-1] + ", x"
elif am == "ay" or am == "zy":
parts[-1] = parts[-1] + ", y"
elif am == "xi":
parts[0] = "(" + parts[0]
parts[-1] = parts[-1] + ", x)"
elif am == "iy":
parts[0] = "(" + parts[0]
parts[-1] = parts[-1] + "), y"
elif am == "i":
parts[0] = "(" + parts[0]
parts[-1] = parts[-1] + ")"
else:
op = oldop
# convert
if op in opcodes_to_rename:
op = opcodes_to_rename[op]
if op in opcodes_with_arg:
return ["\t" + op] + parts
return [op] + parts
def process_code(parts):
"split code parts at special characters"
prefix = ""
# remember if line starts with space
indented = (parts[0][0] == " ")
# because now spaces are dropped
result = []
for part in parts:
# do not process quoted strings any further
if part.startswith("'") or part.startswith('"'):
result.append(part)
continue
# convert to lower case
part = part.lower()
# substitute: & becomes $
part = "$".join(part.split("&"))
# all other parts are split up into tokens
result.extend(unquoted_tokenize(part))
# convert some tokens (string literals are not in danger, as they include quotes)
parts = result
result = []
for part in parts:
if part in token_substitutions:
part = token_substitutions[part]
result.append(part)
# now convert
label = ""
if indented:
# code
result = convert_opcodes(result)
else:
# label or symbol definition
if len(result) > 1 and result[1] == "*":
# symbol definition
symdef = result[0] + "\t="
result = [symdef] + convert_opcodes(result[2:])
else:
# label
label = result[0]
result = convert_opcodes(result[1:])
if result:
label = label + "\t"
return label, result
def process_line(line):
"process a single line of input and return converted version"
# remove line ending, if there is one. don't care if NL or CR or combination
while len(line) != 0 and (line[-1] == "\r" or line[-1] == "\n"):
line = line[:-1]
# step 1: split into strings, comments and everything else
codeparts, comment = line_preprocess(line)
# step 2: if there is anything before comment, process that
if codeparts:
prefix, codeparts = process_code(codeparts)
# reassemble line
line = prefix + " ".join(codeparts)
else:
line = ""
if comment:
line = line + comment
return line + "\n"
def convert_file(input, output):
"convert input file to output file line-by-line"
with open(input, "rt") as infile:
with open(output, "wt") as outfile:
outfile.write(";ACME 0.97\n")
for line in infile:
outfile.write(process_line(line))
if __name__ == '__main__':
if len(sys.argv) != 3:
sys.exit(
"Error: wrong number of arguments\n"
"\n"
"masm2acme.py converts a file from MASM to ACME syntax.\n"
"Usage: masm2acme.py INPUTFILE OUTPUTFILE\n"
)
convert_file(sys.argv[1], sys.argv[2])