creole/asm/creole.py

228 lines
6.0 KiB
Python
Raw Normal View History

2023-02-07 11:38:04 -05:00
#!/usr/bin/python3
from enum import Enum
2023-02-07 12:25:52 -05:00
class MalformedArgument(Exception):
pass
2023-02-11 12:04:17 -05:00
def word_2c(w):
""" Negate a non-negative integer using two's compliment. """
return (~w + 1) & 0xFFFFFFFF
def ti(w):
""" Explicitly transform integer into two's compliment representation. """
return w if w >= 0 else word_wc(-w)
2023-02-11 15:49:39 -05:00
def from_2c(w):
""" Turn two's compliment word into Python integer. """
if (w >> 31) & 1 == 0:
return w
return -word_2c(w)
2023-02-11 12:04:17 -05:00
2023-02-07 11:38:04 -05:00
class ArgType(Enum):
2023-02-11 12:04:17 -05:00
""" Class denoting the type of an argument to an instruction. """
2023-02-07 12:25:52 -05:00
IMM = 1
2023-02-11 12:04:17 -05:00
""" Immediate values are ones that must be numbers (positive or negative). """
2023-02-07 12:25:52 -05:00
REG = 2
2023-02-11 12:04:17 -05:00
""" Type of registers. """
2023-02-07 12:25:52 -05:00
VAL = 3
2023-02-11 12:04:17 -05:00
""" Type that denotes either immediate values or registers. """
2023-02-07 12:25:52 -05:00
LAB = 4
2023-02-11 12:04:17 -05:00
""" Type of labels. """
2023-02-07 11:38:04 -05:00
def gettype(s):
2023-02-11 12:04:17 -05:00
""" Parses the type of the argument represented as a string
and returns a tuple with the first the first element being
the type and the second element being the integer value of
the argument.
"""
2023-02-07 11:38:04 -05:00
if s.isnumeric():
2023-02-07 12:25:52 -05:00
return (ArgType.IMM, int(s))
2023-02-11 12:04:17 -05:00
elif s[0] == "-" and s[1:].isnumeric():
return (ArgType.IMM, word_2c(int(s[1:])))
2023-02-07 11:48:22 -05:00
elif s[0] == 'r' and s[1:].isnumeric():
2023-02-07 12:25:52 -05:00
return (ArgType.REG, int(s[1:]))
2023-02-07 11:48:22 -05:00
elif s[0] == 'l' and s[1:].isnumeric():
2023-02-07 12:25:52 -05:00
return (ArgType.LAB, int(s[1:]))
2023-02-07 11:38:04 -05:00
else:
2023-02-07 12:25:52 -05:00
raise MalformedArgument(s)
2023-02-07 11:38:04 -05:00
def typecheck(self, s):
2023-02-11 12:04:17 -05:00
""" Parses the type of the string and returns it if it fits
the type of the enum value. """
2023-02-07 11:48:22 -05:00
t = ArgType.gettype(s)
2023-02-07 12:25:52 -05:00
if self == ArgType.VAL:
2023-02-08 08:56:39 -05:00
if t[0] == ArgType.REG or t[0] == ArgType.IMM:
return t
else:
return None
elif t[0] == self:
return t
2023-02-07 11:38:04 -05:00
else:
2023-02-08 08:56:39 -05:00
return None
2023-02-07 11:38:04 -05:00
2023-02-07 12:25:52 -05:00
class OpcodeException(Exception):
pass
class TypecheckLenException(Exception):
2023-02-08 08:56:39 -05:00
def __init__(self, opcode, insargs, argtypelen):
self.opcode = opcode
self.insargs = insargs
self.argtypelen = argtypelen
def __str__(self):
return f'arguments {insargs} to opcode {self.opcode} not length {self.argtypelen}'
2023-02-07 12:25:52 -05:00
class TypecheckException(Exception):
2023-02-08 08:56:39 -05:00
def __init__(self, argtype, sarg, i, opcode):
self.argtype = argtype
self.sarg = sarg
self.i = i
self.opcode = opcode
def __str__(self):
return f'opcode {self.opcode} has invalid value {self.sarg} (expected {self.argtype} in position {self.i}'
2023-02-11 12:04:17 -05:00
2023-02-09 11:32:32 -05:00
class Instruction(Enum):
2023-02-11 12:04:17 -05:00
""" Class of microcode instructions. The first number is the opcode
and the suceeding values are the types of each of the
arguments. """
2023-02-09 11:32:32 -05:00
NOP = 0
PUSH = 1, ArgType.VAL
POP = 2, ArgType.REG
2023-02-11 12:11:32 -05:00
ADD = 3, ArgType.REG, ArgType.VAL, ArgType.VAL
MUL = 4, ArgType.REG, ArgType.VAL, ArgType.VAL
DIV = 5, ArgType.REG, ArgType.VAL, ArgType.VAL
2023-02-11 15:51:03 -05:00
SDIV = 6, ArgType.REG, ArgType.VAL, ArgType.VAL
2023-02-11 15:49:39 -05:00
JL = 7, ArgType.LAB, ArgType.VAL, ArgType.VAL
CLB = 8, ArgType.LAB
SYS = 9, ArgType.VAL
2023-02-09 11:32:32 -05:00
def __init__(self, opcode, *args):
2023-02-07 12:25:52 -05:00
if opcode > 0x7F or opcode < 0:
raise OpcodeException(opcode)
2023-02-07 11:38:04 -05:00
self.opcode = opcode
2023-02-09 11:32:32 -05:00
self.argtypes = args
2023-02-11 12:04:17 -05:00
2023-02-07 11:38:04 -05:00
def typecheck(self, sargs):
2023-02-11 12:04:17 -05:00
""" Pass arguments to the instruction and check if the
arguments are correct. """
2023-02-07 11:38:04 -05:00
rargs = []
if len(sargs) != len(self.argtypes):
2023-02-08 08:56:39 -05:00
raise TypecheckLenException(self.opcode, sargs,
len(self.argtypes))
2023-02-07 11:38:04 -05:00
for i in range(0, len(sargs)):
2023-02-08 08:56:39 -05:00
t = self.argtypes[i].typecheck(sargs[i])
if t is None:
2023-02-07 12:25:52 -05:00
raise TypecheckException(self.argtypes[i],
2023-02-08 08:56:39 -05:00
sargs[i],
i, self.opcode)
rargs.append(t)
2023-02-07 11:38:04 -05:00
return rargs
encoding_types = {
# start mask A B
2: (0x7F, 0xC0, 7),
3: (0xFFF, 0xE0, 12),
4: (0x1FFFF, 0xF0, 16),
5: (0x3FFFFF, 0xF8, 21),
6: (0x7FFFFFF, 0xFC, 26),
7: (0xFFFFFFFF, 0xFE, 36),
# A : number of bits in start byte
# B : Total number of bits excluding high bits
}
2023-02-07 12:25:52 -05:00
class InvalidNumberException(Exception):
pass
class InvalidLengthException(Exception):
pass
2023-02-07 11:38:04 -05:00
def encode_pseudo_utf8(n, high_bits, to):
2023-02-07 12:25:52 -05:00
if n < 0:
raise InvalidNumberException(n)
if to is None or to < 0:
for k in sorted(encoding_types):
if n <= encoding_types[k][0]:
to = k
break
if to is None:
raise InvalidNumberException(n)
2023-02-07 11:38:04 -05:00
if to > 8 or to < 0:
2023-02-07 12:25:52 -05:00
raise InvalidLengthException(to)
2023-02-07 11:38:04 -05:00
elif to == 1:
if n < 0x80:
return bytes([n])
else:
2023-02-07 12:25:52 -05:00
raise InvalidNumberException(n,to)
2023-02-07 11:38:04 -05:00
(maxval, start_byte, n_tot) = encoding_types[to]
if n > maxval or high_bits > 15:
2023-02-07 12:25:52 -05:00
raise InvalidNumberException(n, high_bits)
2023-02-07 11:38:04 -05:00
n = n | (high_bits << n_tot)
all_bytes = []
for i in range(0, to - 1):
all_bytes.append(0x80 | (n & 0x3F))
n >>= 6
all_bytes.append(start_byte | n)
return bytes(reversed(all_bytes))
2023-02-07 12:25:52 -05:00
class RangeCheckException(Exception):
pass
2023-02-07 11:38:04 -05:00
class Line:
2023-02-11 12:04:17 -05:00
def __init__(self, opcode, args):
2023-02-07 11:38:04 -05:00
self.opcode = opcode
self.args = args
2023-02-11 12:04:17 -05:00
2023-02-11 12:27:29 -05:00
def check_line(self, lablen, reglen):
2023-02-11 12:04:17 -05:00
for a in self.args:
2023-02-07 12:25:52 -05:00
if a[0] == ArgType.REG:
2023-02-11 12:27:29 -05:00
if a[1] < 0 or a[1] >= reglen:
2023-02-07 12:25:52 -05:00
raise RangeCheckException(a[0],
a[1],
2023-02-11 12:27:29 -05:00
reglen)
2023-02-07 12:25:52 -05:00
elif a[0] == ArgType.LAB:
2023-02-11 12:27:29 -05:00
if a[1] < 0 or a[1] >= lablen:
2023-02-07 12:25:52 -05:00
raise RangeCheckException(a[0],
a[1],
2023-02-11 12:27:29 -05:00
reglen)
2023-02-07 12:25:52 -05:00
2023-02-07 11:48:22 -05:00
def __call__(self):
b = bytes([self.opcode])
2023-02-07 12:25:52 -05:00
for a in self.args:
2023-02-07 12:30:29 -05:00
l = 2 if a[1] < 0x80 else None
2023-02-07 12:25:52 -05:00
if a[0] == ArgType.REG:
2023-02-07 12:30:29 -05:00
b = b + encode_pseudo_utf8(a[1],1,l)
2023-02-07 11:48:22 -05:00
else:
2023-02-07 12:30:29 -05:00
b = b + encode_pseudo_utf8(a[1],0,l)
2023-02-07 11:48:22 -05:00
return b + bytes([0])
2023-02-07 11:38:04 -05:00
2023-02-07 12:25:52 -05:00
class InstructionNotFoundException(Exception):
pass
2023-02-07 11:38:04 -05:00
class Program:
def asm_push_line(self, ins, args):
2023-02-11 12:04:17 -05:00
l = Line(ins, args)
2023-02-11 12:27:29 -05:00
l.check_line(self.lablen, self.reglen)
2023-02-11 12:04:17 -05:00
self.asm.append(l)
2023-02-07 11:38:04 -05:00
def parse_asm_line(self, line):
2023-02-07 11:48:22 -05:00
line = line.split()
2023-02-07 12:25:52 -05:00
line[0] = line[0].casefold()
2023-02-09 11:32:32 -05:00
try:
# TODO: is there no better way to do this in Python?
ins = getattr(Instruction, line[0].upper())
except Exception as e:
2023-02-07 12:25:52 -05:00
raise InstructionNotFoundException(line[0])
2023-02-09 11:32:32 -05:00
args_w_type = ins.typecheck(line[1:])
self.asm_push_line(ins.opcode, args_w_type)
2023-02-07 11:48:22 -05:00
def __call__(self):
b = bytes()
for line in self.asm:
b = b + line()
return b
2023-02-11 12:27:29 -05:00
def __init__(self, lablen=16, reglen=16):
2023-02-07 11:48:22 -05:00
self.asm = []
2023-02-11 12:27:29 -05:00
self.lablen = lablen
self.reglen = reglen
2023-02-11 12:04:17 -05:00