2019-08-11 01:49:17 +00:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
2019-08-09 22:28:32 +00:00
|
|
|
import getopt
|
|
|
|
import sys
|
|
|
|
import re
|
2019-09-02 02:45:08 +00:00
|
|
|
import os
|
|
|
|
import signal
|
|
|
|
|
2019-08-09 22:52:20 +00:00
|
|
|
from functools import reduce
|
2019-08-11 00:32:36 +00:00
|
|
|
import subprocess
|
2019-08-09 22:28:32 +00:00
|
|
|
|
|
|
|
from asm import Assembler
|
|
|
|
|
2019-08-10 21:02:50 +00:00
|
|
|
flag_i = False
|
2019-08-10 22:39:19 +00:00
|
|
|
flag_l = False
|
|
|
|
flag_v = False
|
|
|
|
flag_o = None
|
2019-08-11 00:32:36 +00:00
|
|
|
flag_E = False
|
2019-09-01 20:30:53 +00:00
|
|
|
flag_c = False
|
2019-08-09 22:28:32 +00:00
|
|
|
|
|
|
|
|
2019-08-09 22:52:20 +00:00
|
|
|
def str_to_int(cc):
|
|
|
|
fn = lambda x, y: (x << 8) + ord(y)
|
|
|
|
return reduce(fn, reversed(cc), 0)
|
|
|
|
|
|
|
|
def str_to_print(cc):
|
2019-08-10 22:18:37 +00:00
|
|
|
return "".join([x if (x.isascii() and x.isprintable()) else "." for x in cc])
|
2019-08-09 22:52:20 +00:00
|
|
|
|
|
|
|
def or_mask(cc):
|
2019-08-10 22:18:37 +00:00
|
|
|
fn = lambda x, y: (x << 8) + (0x20 * (y.isascii() and y.islower()))
|
2019-08-09 22:52:20 +00:00
|
|
|
return reduce(fn, reversed(cc), 0)
|
|
|
|
|
2019-08-10 02:29:31 +00:00
|
|
|
def mask_char(asm, short_m, old, new):
|
2019-08-09 22:52:20 +00:00
|
|
|
|
|
|
|
if old == new: return new
|
|
|
|
|
2019-08-10 02:29:31 +00:00
|
|
|
if old & ~new:
|
|
|
|
asm.emit("tya", 1)
|
|
|
|
|
2019-08-11 05:13:49 +00:00
|
|
|
asm.longm = not short_m
|
2019-08-11 03:13:23 +00:00
|
|
|
if short_m:
|
|
|
|
asm.emit("ora #${:02x}".format(new), 2)
|
|
|
|
else:
|
|
|
|
asm.emit("ora #${:04x}".format(new), 3)
|
2019-08-09 22:52:20 +00:00
|
|
|
return new
|
|
|
|
|
|
|
|
|
2019-08-09 22:28:32 +00:00
|
|
|
def generate_asm(asm, d, level):
|
2019-08-10 21:02:50 +00:00
|
|
|
global flag_i
|
2019-08-09 22:28:32 +00:00
|
|
|
|
|
|
|
double = [x for x in d.keys() if len(x) == 2]
|
|
|
|
single = [x for x in d.keys() if len(x) == 1]
|
|
|
|
short_m = single and not double
|
|
|
|
|
2019-08-09 22:52:20 +00:00
|
|
|
mask = 0
|
2019-08-11 05:11:39 +00:00
|
|
|
save_a = False
|
2019-08-10 21:02:50 +00:00
|
|
|
if flag_i:
|
2019-08-09 22:52:20 +00:00
|
|
|
single.sort(key = or_mask)
|
|
|
|
double.sort(key = or_mask)
|
|
|
|
if len(single): mask = or_mask(single[0])
|
|
|
|
if len(double): mask = or_mask(double[0])
|
|
|
|
|
2019-08-10 02:29:31 +00:00
|
|
|
# need special logic for short-m
|
|
|
|
a = set([or_mask(x) for x in double])
|
|
|
|
b = set([or_mask(x) for x in single])
|
|
|
|
|
2019-08-11 05:11:39 +00:00
|
|
|
if (0x2000 in a) and (0x0020 in a): save_a = True
|
|
|
|
if (0x0000 in b) and (0x0020 in a): save_a = True
|
|
|
|
if (0x0000 in b) and (0x2020 in a): save_a = True
|
2019-08-10 02:29:31 +00:00
|
|
|
|
2019-08-09 22:52:20 +00:00
|
|
|
|
2019-08-09 22:28:32 +00:00
|
|
|
count = len(d)
|
|
|
|
if "" in d: count = count - 1
|
|
|
|
|
|
|
|
if count>0:
|
|
|
|
if short_m:
|
2019-08-11 05:13:49 +00:00
|
|
|
asm.longm = False
|
2019-08-09 22:28:32 +00:00
|
|
|
asm.emit("sep #$20", 2)
|
2019-08-11 04:12:10 +00:00
|
|
|
else:
|
2019-08-11 05:13:49 +00:00
|
|
|
asm.longm = True
|
2019-08-10 02:29:31 +00:00
|
|
|
|
|
|
|
if level==0:
|
|
|
|
asm.emit("lda (cp)", 2)
|
|
|
|
else:
|
2019-08-09 22:28:32 +00:00
|
|
|
asm.emit("ldy #{}".format(level * 2), 3)
|
2019-08-10 02:29:31 +00:00
|
|
|
asm.emit("lda (cp),y", 2)
|
|
|
|
|
2019-08-11 05:11:39 +00:00
|
|
|
if save_a: asm.emit("tay", 1)
|
2019-08-09 22:28:32 +00:00
|
|
|
|
2019-08-10 21:02:50 +00:00
|
|
|
if flag_i: mask = mask_char(asm, short_m, 0, mask)
|
2019-08-09 22:28:32 +00:00
|
|
|
|
|
|
|
for k in double:
|
|
|
|
dd = d[k]
|
|
|
|
l = asm.reserve_label()
|
2019-08-10 21:02:50 +00:00
|
|
|
if flag_i: mask = mask_char(asm, short_m, mask, or_mask(k))
|
2019-08-10 21:05:48 +00:00
|
|
|
v = str_to_int(k)
|
2019-08-11 05:13:49 +00:00
|
|
|
asm.longm = True
|
2019-08-10 22:18:37 +00:00
|
|
|
asm.emit("cmp #${:04x}\t; '{}'".format(v, encode_string(k)), 3)
|
2019-08-09 22:28:32 +00:00
|
|
|
asm.bne(l)
|
|
|
|
generate_asm(asm, dd, level+1)
|
|
|
|
asm.emit_label(l)
|
|
|
|
|
|
|
|
if single and double:
|
2019-08-11 05:13:49 +00:00
|
|
|
asm.longm = False
|
2019-08-09 22:28:32 +00:00
|
|
|
asm.emit("sep #$20", 2)
|
|
|
|
short_m = True
|
2019-08-09 22:52:20 +00:00
|
|
|
mask = mask & 0xff
|
2019-08-09 22:28:32 +00:00
|
|
|
|
|
|
|
for k in single:
|
|
|
|
dd = d[k]
|
|
|
|
l = asm.reserve_label()
|
2019-08-10 21:02:50 +00:00
|
|
|
if flag_i: mask = mask_char(asm, short_m, mask, or_mask(k))
|
2019-08-10 21:05:48 +00:00
|
|
|
v = str_to_int(k)
|
2019-08-11 05:13:49 +00:00
|
|
|
asm.longm = False
|
2019-08-10 22:18:37 +00:00
|
|
|
asm.emit("cmp #${:02x}\t; '{}'".format(v, encode_string(k)), 2)
|
2019-08-11 03:13:23 +00:00
|
|
|
|
2019-08-09 22:28:32 +00:00
|
|
|
asm.bne(l)
|
|
|
|
generate_asm(asm, dd, level+1)
|
|
|
|
asm.emit_label(l)
|
|
|
|
|
|
|
|
if "" in d:
|
2019-08-09 22:56:58 +00:00
|
|
|
d = d[""]
|
|
|
|
asm.emit("ldx #{}\t; '{}'".format(d["__value__"], d["__key__"]), 3)
|
2019-08-09 22:28:32 +00:00
|
|
|
asm.rts()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def process(data, name):
|
|
|
|
tree = {}
|
|
|
|
for k in data.keys():
|
|
|
|
|
|
|
|
chunks = [k[i*2:i*2+2] for i in range(0,len(k)+1>>1)]
|
|
|
|
|
|
|
|
current = tree
|
|
|
|
for x in chunks:
|
|
|
|
if x in current:
|
|
|
|
current = current[x]
|
|
|
|
continue
|
|
|
|
tmp = {}
|
|
|
|
current[x] = tmp
|
|
|
|
current = tmp
|
|
|
|
|
2019-08-10 22:18:37 +00:00
|
|
|
current[""] = { "__value__": data[k], "__key__": encode_string(k) }
|
2019-08-09 22:28:32 +00:00
|
|
|
|
|
|
|
# print(tree);
|
|
|
|
asm = Assembler(name)
|
|
|
|
generate_asm(asm, tree, 0)
|
|
|
|
asm.finish(sys.stdout)
|
|
|
|
|
2019-08-10 22:18:37 +00:00
|
|
|
def decode_string(s):
|
|
|
|
global decode_map
|
|
|
|
|
|
|
|
fn = lambda x: decode_map.get(x[1].lower(), '')
|
|
|
|
return re.sub(r"\\([xX][A-Fa-f0-9]{2}|.)", fn, s)
|
|
|
|
|
|
|
|
def encode_string(s):
|
|
|
|
global encode_map
|
|
|
|
return "".join([encode_map.get(x, x) for x in s])
|
|
|
|
|
2019-08-09 22:28:32 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def read_data(f, name):
|
2019-09-01 20:30:53 +00:00
|
|
|
global flag_i, flag_l, flag_c
|
2019-08-09 22:28:32 +00:00
|
|
|
|
|
|
|
data = {}
|
|
|
|
ln = 0
|
2019-09-01 20:45:39 +00:00
|
|
|
errors = 0
|
2019-08-09 22:28:32 +00:00
|
|
|
for line in f:
|
|
|
|
ln = ln + 1
|
|
|
|
line = line.strip()
|
|
|
|
if line == "" : continue
|
2019-08-11 00:35:50 +00:00
|
|
|
if line.startswith("#"): continue
|
|
|
|
if line.startswith("//"): continue
|
2019-08-09 22:28:32 +00:00
|
|
|
|
2019-09-01 20:45:39 +00:00
|
|
|
try:
|
|
|
|
|
|
|
|
m = re.match(r'^"([^"]*)"\s*:\s*(\d+|0x[A-Fa-f0-9]+)$', line)
|
|
|
|
if not m:
|
|
|
|
err = "Syntax error: {}".format(line)
|
|
|
|
raise Exception(err)
|
|
|
|
k = orig_k = m[1]
|
|
|
|
|
|
|
|
k = decode_string(k)
|
|
|
|
if flag_i: k = k.lower()
|
|
|
|
len_k = len(k)
|
|
|
|
if flag_c: k = k + "\x00"
|
|
|
|
tmp = m[2]
|
|
|
|
base = 10
|
|
|
|
if tmp.startswith("0x"): base = 16
|
|
|
|
v = int(tmp, base)
|
|
|
|
|
|
|
|
if v > 65535:
|
|
|
|
err = "Value too large: {}".format(v)
|
|
|
|
raise Exception(err)
|
|
|
|
|
|
|
|
if flag_l:
|
|
|
|
if v > 255 or len_k > 255:
|
|
|
|
err = "Value too large: {}".format(v)
|
|
|
|
raise Exception(err)
|
|
|
|
v = (v << 8) + len_k
|
|
|
|
|
|
|
|
if k in data:
|
|
|
|
err = "Duplicate string: {}".format(orig_k)
|
2019-08-10 22:39:19 +00:00
|
|
|
raise Exception(err)
|
2019-08-09 22:28:32 +00:00
|
|
|
|
2019-09-01 20:45:39 +00:00
|
|
|
data[k] = v
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
errors = errors + 1
|
|
|
|
print("{}:{}".format(name, ln), e, file=sys.stderr, flush=True)
|
2019-08-09 22:28:32 +00:00
|
|
|
|
|
|
|
|
2019-09-02 02:45:08 +00:00
|
|
|
if errors: sys.exit(os.EX_DATAERR)
|
2019-08-09 22:28:32 +00:00
|
|
|
return data
|
|
|
|
|
|
|
|
def read_stdin():
|
|
|
|
return read_data(sys.stdin, "<stdin>")
|
|
|
|
|
|
|
|
def read_file(path):
|
|
|
|
with open(path) as f:
|
|
|
|
return read_data(f, path)
|
|
|
|
|
2019-08-11 00:32:36 +00:00
|
|
|
def read_cpp(infile):
|
|
|
|
args = ["cpp"]
|
|
|
|
if infile: args.append(infile)
|
|
|
|
|
|
|
|
x = subprocess.run(args, stdout=subprocess.PIPE, encoding='ascii')
|
|
|
|
if x.returncode:
|
2019-08-11 01:49:17 +00:00
|
|
|
sys.exit(x.returncode)
|
2019-08-11 00:32:36 +00:00
|
|
|
|
|
|
|
lines = x.stdout.split("\n")
|
|
|
|
return read_data(lines, "<cpp-stdin>")
|
|
|
|
|
2019-08-09 22:28:32 +00:00
|
|
|
|
2019-08-10 22:39:19 +00:00
|
|
|
def init_maps():
|
2019-08-09 22:28:32 +00:00
|
|
|
|
2019-08-10 22:18:37 +00:00
|
|
|
global decode_map
|
|
|
|
global encode_map
|
|
|
|
|
|
|
|
decode_map = {}
|
|
|
|
for i in range(0, 256):
|
|
|
|
decode_map["x{:02x}".format(i)] = chr(i)
|
|
|
|
decode_map['\\'] = '\\'
|
|
|
|
decode_map["'"] = "'"
|
|
|
|
decode_map['"'] = '"'
|
|
|
|
decode_map['?'] = '?'
|
|
|
|
|
|
|
|
decode_map['a'] = chr(7)
|
|
|
|
decode_map['b'] = chr(8)
|
|
|
|
decode_map['f'] = chr(12)
|
|
|
|
decode_map['n'] = chr(10)
|
|
|
|
decode_map['r'] = chr(13)
|
|
|
|
decode_map['t'] = chr(9)
|
|
|
|
decode_map['v'] = chr(11)
|
|
|
|
|
|
|
|
encode_map = {}
|
|
|
|
for i in range(0, 20): encode_map[chr(i)] = "\\x{:02x}".format(i)
|
|
|
|
for i in range(127, 256): encode_map[chr(i)] = "\\x{:02x}".format(i)
|
|
|
|
|
|
|
|
encode_map['\\'] = '\\\\'
|
|
|
|
encode_map[chr(7)] = '\\a'
|
|
|
|
encode_map[chr(8)] = '\\b'
|
|
|
|
encode_map[chr(12)] = '\\f'
|
|
|
|
encode_map[chr(10)] = '\\n'
|
|
|
|
encode_map[chr(13)] = '\\r'
|
|
|
|
encode_map[chr(9)] = '\\t'
|
|
|
|
encode_map[chr(11)] = '\\v'
|
|
|
|
|
2019-09-02 02:45:08 +00:00
|
|
|
def exit_code_for(e):
|
|
|
|
t = type(e)
|
|
|
|
if t == FileNotFoundError: return os.EX_NOINPUT
|
|
|
|
if t == PermissionError: return os.EX_NOINPUT
|
|
|
|
return 1
|
2019-08-10 22:18:37 +00:00
|
|
|
|
2019-09-02 02:45:08 +00:00
|
|
|
def usage(ex):
|
2019-09-01 21:46:18 +00:00
|
|
|
print("Usage: hystricomorph [-cilvE] [-o output_file] function_name [input_file]")
|
|
|
|
print(" -c add implicit 0-terminator to strings")
|
|
|
|
print(" -i case insensitive")
|
|
|
|
print(" -l include string length in lsb of return value")
|
|
|
|
print(" -v be verbose")
|
|
|
|
print(" -E use c pre-processor")
|
|
|
|
print(" -o output_file specify output file")
|
|
|
|
sys.exit(ex)
|
2019-08-09 22:28:32 +00:00
|
|
|
|
2019-08-10 22:39:19 +00:00
|
|
|
def main():
|
2019-09-01 20:30:53 +00:00
|
|
|
global flag_i, flag_l, flag_E, flag_c
|
2019-08-10 22:39:19 +00:00
|
|
|
|
|
|
|
init_maps()
|
|
|
|
|
|
|
|
|
2019-08-09 22:28:32 +00:00
|
|
|
argv = sys.argv[1:]
|
2019-09-02 02:45:08 +00:00
|
|
|
opts, args = getopt.getopt(argv, "hivo:leEc")
|
2019-08-10 22:39:19 +00:00
|
|
|
|
2019-08-09 22:28:32 +00:00
|
|
|
for k, v in opts:
|
2019-09-02 02:45:08 +00:00
|
|
|
if k == '-E': flag_E = True
|
|
|
|
elif k == '-c': flag_c = True
|
|
|
|
elif k == '-h': usage(os.EX_OK)
|
|
|
|
elif k == '-i': flag_i = True
|
|
|
|
elif k == '-l': flag_l = True
|
2019-08-10 22:39:19 +00:00
|
|
|
elif k == '-o': flag_o = v
|
|
|
|
elif k == '-v': flag_v = True
|
2019-08-09 22:28:32 +00:00
|
|
|
else:
|
2019-09-02 02:45:08 +00:00
|
|
|
usage(os.EX_USAGE)
|
2019-08-09 22:28:32 +00:00
|
|
|
|
|
|
|
if len(args) < 1 or len(args) > 2:
|
2019-09-02 02:45:08 +00:00
|
|
|
usage(os.EX_USAGE)
|
2019-08-09 22:28:32 +00:00
|
|
|
|
|
|
|
name = args[0]
|
|
|
|
data = {}
|
|
|
|
|
2019-08-11 00:32:36 +00:00
|
|
|
if len(args) == 2 and args[1] == "-":
|
|
|
|
args.pop()
|
|
|
|
|
|
|
|
if flag_E:
|
|
|
|
infile = None
|
|
|
|
if len(args) == 2:
|
|
|
|
infile = args.pop()
|
|
|
|
data = read_cpp(infile)
|
|
|
|
|
|
|
|
elif len(args) == 2:
|
2019-08-09 22:28:32 +00:00
|
|
|
data = read_file(args[1])
|
2019-08-11 00:32:36 +00:00
|
|
|
else:
|
|
|
|
data = read_stdin()
|
2019-08-09 22:28:32 +00:00
|
|
|
|
|
|
|
process(data, name)
|
|
|
|
|
2019-09-02 02:45:08 +00:00
|
|
|
sys.exit(os.EX_OK)
|
|
|
|
|
|
|
|
try:
|
|
|
|
# prevent ^C from generating a KeyboardInterrupt signal (and backtrace)
|
|
|
|
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
|
|
|
main()
|
|
|
|
except Exception as e:
|
|
|
|
print("hystricomorph:", e, file=sys.stderr, flush=True)
|
|
|
|
sys.exit(exit_code_for(e))
|
2019-08-10 21:02:50 +00:00
|
|
|
|