User:1hals/subleq.py

From Esolang
Jump to navigation Jump to search

subleq.py is my Python script to compile and emulate Subleq assembler code.

#!/usr/bin/env python3

import argparse, sys
p = argparse.ArgumentParser()
p.add_argument('file', help="the program file to parse")
p.add_argument('-emulate', action='store_true', help="emulate the program")
p.add_argument('-memory', type=int, default=0, help="additional memory when evaluating")
p.add_argument('-1', dest='one', action='store_true', help="emit output on one line")
args = p.parse_args()
with open(args.file, "r") as file:
    text = file.read()

def error (msg):
    print('error:', msg, file=sys.stderr)
    sys.exit(-1)

# preprocess labels and comments
lines = text.split('\n')
labels = dict() # map labels to addresses
code = list() # source code
for i, line in enumerate(lines):
    htag = line.find('#')
    if htag >= 0: # remove comments
        line = line[:htag]
    if not line: continue
    abc = line.split()
    if len(abc) not in (2, 3):
        adj = 'many' if len(abc) > 3 else 'few'
        error('too %s instructions one line %d' %(adj, i))
    for op in abc: # get labels and instructions
        colon = op.find(':')
        if colon > -1: # no label
            if colon == 0: # invalid label
                error('empty label')
            label = op[:colon]
            op = op[colon+1:]
            labels[label] = len(code)
        code.append(op)
    if len(abc) == 2: # default 3rd argument is the next instruction
        code.append(len(code) + 1)

# resolve numbers, the question mark, and labels
for i, op in enumerate(code):
    n = 0 # final result number
    if op[0] == '?': # question mark means the current instruction address
        if op[1] == '+': # relative offset
            n += i
            op = op[2:]
        elif op[1] == '-': # relative offset
            n -= i
            op = op[2:]
        else: # no offset
            code[i] = i
            continue
    try: # number
        n += int(op)
    except ValueError: # label
        try:
            n += labels[op]
        except KeyError:
            error('undefined label "%s"' % op)
    code[i] = n

if args.emulate: # emulate the program
    pc = 0 # program counter
    mem = code + ([0] * args.memory) # program & memory
    while pc >= 0: # branch to negative address halts the program
        a, b, c = mem[pc:pc+3]
        if b == -1: # memory map address -1 to output
            sys.stdout.write(chr(mem[a]))
            pc += 3
        # note: you can add more memory-mapped special cases here
        else: # subleq
            r = mem[b] - mem[a]
            mem[b] = r
            if r > 0:
                pc +=3
            else:
                pc = c
    sys.exit(0)

# just emit the assembled output
sep2 = ' ' if args.one else '\n'
for i in range(0, len(code), 3):
    print(' '.join(str(x) for x in code[i:i+3]), end=sep2)