Min 6 anni fa
parent
commit
e0bbee7352
1 ha cambiato i file con 243 aggiunte e 0 eliminazioni
  1. 243 0
      tools/asm_compiler.py

+ 243 - 0
tools/asm_compiler.py

@@ -0,0 +1,243 @@
+#!/usr/bin/python3
+import sys
+import argparse
+
+from os import path
+
+
+def decode_byte(val: str):
+    try:
+        if val.endswith('h'):
+            return int(val[:-1], 16)
+        if val.startswith('0x'):
+            return int(val[2:], 16)
+        if val.startswith('b'):
+            return int(val.replace('_', '')[1:], 2)
+    except ValueError:
+        raise ValueError(f"Invalid binary '{val}'")
+    if val.isdigit():
+        i = int(val)
+        if i > 255 or i < 0:
+            raise ValueError(f"Invalid binary '{val}', unsigned int out of bounds")
+        return i
+    if (val.startswith('+') or val.startswith('-')) and val[1:].isdigit():
+        i = int(val)
+        if i > 127 or i < -128:
+            raise ValueError(f"Invalid binary '{val}', signed int out of bounds")
+        if i < 0:  # convert to unsigned
+            i += 2**8
+        return i
+    if len(val) == 3 and ((val[0] == "'" and val[2] == "'") or (val[0] == '"' and val[2] == '"')):
+        return ord(val[1])
+    raise ValueError(f"Invalid binary '{val}'")
+
+
+def is_reg(r):
+    if r.startswith('$'):
+        r = r[1:]
+    return len(r) == 2 and (r == 'ra' or r == 'rb' or r == 'rc' or r == 're')
+
+
+def decode_reg(r):
+    rl = r.lower()
+    if rl.startswith('$'):
+        rl = rl[1:]
+    if rl == 'ra':
+        return 0
+    if rl == 'rb':
+        return 1
+    if rl == 'rc':
+        return 2
+    if rl == 're':
+        return 3
+    raise ValueError(f"Invalid register name '{r}'")
+
+
+def assemble(file):
+    odata = []
+    afile = open(file, 'r')
+    failed = False
+    refs = dict()
+    for lnum, line in enumerate(afile.readlines()):
+        lnum += 1  # Line numbers start from 1, not 0
+        if '//' in line:
+            line = line[:line.index('//')]
+        if ':' in line:
+            rsplit = line.split(':', 1)
+            ref = rsplit[0]
+            if not ref.isalnum():
+                print(f"{file}:{lnum}: Invalid pointer reference '{ref}'")
+                failed = True
+                continue
+            if ref in refs:
+                print(f"{file}:{lnum}: Pointer reference '{ref}' is duplicated with {file}:{refs[ref][0]}")
+                failed = True
+                continue
+            refs[ref] = [lnum, len(odata)]
+            line = rsplit[1]
+        line = line.replace('\n', '').replace('\r', '').replace('\t', '')
+        line = line.strip(' ')
+        if line == '':
+            continue
+        ops = line.split()
+        instr = ops[0].upper()
+        rops = 3
+        if instr == 'CPY' or instr == 'COPY':
+            iname = 'COPY'
+            inibb = 0
+        elif instr == 'ADD':
+            iname = 'ADD'
+            inibb = 1
+        elif instr == 'SUB':
+            iname = 'SUB'
+            inibb = 2
+        elif instr == 'AND':
+            iname = 'AND'
+            inibb = 3
+        elif instr == 'OR':
+            iname = 'OR'
+            inibb = 4
+        elif instr == 'XOR':
+            iname = 'XOR'
+            inibb = 5
+        elif instr == 'GT' or instr == 'GRT':
+            iname = 'GT'
+            inibb = 6
+        elif instr == 'EX' or instr == 'EXT':
+            iname = 'EXT'
+            inibb = 7
+        elif instr == 'LW':
+            iname = 'LW'
+            inibb = 8
+        elif instr == 'SW':
+            iname = 'SW'
+            inibb = 9
+        elif instr == 'JEQ':
+            iname = 'JEQ'
+            rops = 4
+            inibb = 10
+        elif instr == 'JMP' or instr == 'JUMP':
+            iname = 'JUMP'
+            rops = 2
+            inibb = 11
+        else:
+            if len(ops) == 1:
+                try:
+                    odata.append(decode_byte(ops[0]))
+                    continue
+                except ValueError:
+                    pass
+            print(f"{file}:{lnum}: Instruction '{ops[0]}' not recognised")
+            failed = True
+            continue
+        if len(ops) != rops:
+            print(f"{file}:{lnum}: {iname} instruction requires {rops - 1} arguments")
+            failed = True
+            continue
+        try:
+            if iname == 'JUMP':
+                odata.append(inibb << 4)
+                try:
+                    odata.append(decode_byte(ops[1]))
+                except ValueError:
+                    if not ops[1].isalnum():
+                        print(f"{file}:{lnum}: Invalid pointer reference '{ops[1]}'")
+                        failed = True
+                        continue
+                    if ops[1] in refs:
+                        odata.append(refs[ops[1]][1])
+                    else:
+                        refs[ops[1]] = [lnum, None]
+                        odata.append(ops[1])
+                continue
+
+            rd = decode_reg(ops[1])
+            if iname == 'COPY' and not is_reg(ops[2]):
+                imm = decode_byte(ops[2])
+                odata.append((inibb << 4) | (rd << 2) | rd)
+                odata.append(int(imm))
+                continue
+
+            rs = decode_reg(ops[2])
+            if iname == 'COPY' and rd == rs:
+                print(f"{file}:{lnum}: {iname} cannot copy register to itself")
+                failed = True
+                continue
+
+            odata.append((inibb << 4) | (rd << 2) | rs)
+            if iname == 'JEQ':
+                try:
+                    odata.append(decode_byte(ops[3]))
+                except ValueError:
+                    if not ops[3].isalnum():
+                        print(f"{file}:{lnum}: Invalid pointer reference '{ops[3]}'")
+                        failed = True
+                        continue
+                    if ops[3] in refs:
+                        odata.append(refs[ops[3]][1])
+                    else:
+                        refs[ops[3]] = [lnum, None]
+                        odata.append(ops[3])
+                continue
+        except ValueError as e:
+            print(f"{file}:{lnum}: {e}")
+            failed = True
+            continue
+
+    afile.close()
+    # Convert jumps
+    for i, l in enumerate(odata):
+        if isinstance(l, str):
+            if refs[l][1] is None:
+                print(f"{file}:{refs[l][0]}: Pointer reference '{l}' does not exist!")
+                failed = True
+                continue
+            odata[i] = refs[l][1]
+
+    return not failed, odata
+
+
+if __name__ == '__main__':
+    parser = argparse.ArgumentParser(description='Assembly compiler', add_help=True)
+    parser.add_argument('file', help='Files to compile')
+    parser.add_argument('-t', '--output_type', choices=['bin', 'mem', 'binary'], default='mem', help='Output type')
+    parser.add_argument('-o', '--output', help='Output file')
+    parser.add_argument('-f', '--force', action='store_true', help='Force override output file')
+    args = parser.parse_args(sys.argv[1:])
+    if not path.isfile(args.file):
+        print(f'No file {args.file}!')
+        sys.exit(1)
+
+    output = args.output
+    if not output:
+        opath = path.dirname(args.file)
+        bname = path.basename(args.file).rsplit('.', 1)[0]
+        ext = '.out'
+        if args.output_type == 'mem':
+            ext = '.mem'
+        elif args.output_type == 'bin':
+            ext = '.bin'
+        output = path.join(opath, bname + ext)
+    if not args.force and path.isfile(output):
+        print(f'Output file already exists {output}!')
+        sys.exit(1)
+
+    success, data = assemble(args.file)
+    if success:
+        print(f"Saving {args.output_type} data to {output}")
+        with open(output, 'wb') as of:
+            if args.output_type == 'binary':
+                a = '\n'.join([format(i, '08b') for i in data])
+                of.write(a.encode())
+            elif args.output_type == 'mem':
+                a = [format(i, '02x') for i in data]
+                for i in range(int(len(a)/8)+1):
+                    of.write((' '.join(a[i*8:(i+1)*8]) + '\n').encode())
+            elif args.output_type == 'bin':
+                of.write(bytes(data))
+    else:
+        print(f'Failed to compile {args.file}!')
+        sys.exit(1)
+    sys.exit(0)
+
+