183 lines
4.7 KiB
Python
183 lines
4.7 KiB
Python
# Copyright (c) 2023 Peter McGoron <code@mcgoron.com>
|
|
#
|
|
# Permission to use, copy, modify, and/or distribute this software for any
|
|
# purpose with or without fee is hereby granted, provided that the above
|
|
# copyright notice and this permission notice appear in all copies.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
from ctypes import *
|
|
import creole
|
|
from enum import Enum
|
|
dll = CDLL("./libcreole.so")
|
|
|
|
class CompileRet(Enum):
|
|
OK = 0
|
|
OPCODE_READ_ERROR = 1
|
|
OPCODE_MALFORMED = 2
|
|
ARG_READ_ERROR = 3
|
|
ARG_MALFORMED = 4
|
|
LAST_READ_ERROR = 5
|
|
LAST_MALFORMED = 6
|
|
DATA_OVERFLOW = 7
|
|
TYPE_ERROR = 8
|
|
PROGRAM_OVERFLOW = 9
|
|
|
|
class RunRet(Enum):
|
|
CONTINUE = 0
|
|
SYSCALL = 1
|
|
STOP = 2
|
|
STACK_OVERFLOW = 3
|
|
STACK_UNDERFLOW = 4
|
|
DECODE_ERROR = 5
|
|
REGISTER_OVERFLOW = 6
|
|
UNKNOWN_OPCODE = 7
|
|
DIVIDE_BY_ZERO = 8
|
|
HIGH_BIT_MALFORMED = 9
|
|
JUMP_OVERFLOW = 10
|
|
|
|
def is_halt(self):
|
|
return not (self == RunRet.CONTINUE or self == RunRet.SYSCALL)
|
|
|
|
class CWord(Structure):
|
|
_fields_ = [("len", c_int),
|
|
("high_bits", c_int),
|
|
("word", c_uint)]
|
|
|
|
class CReader(Structure):
|
|
_fields_ = [("p", POINTER(c_ubyte)),
|
|
("left", c_size_t)]
|
|
class Reader:
|
|
def __init__(self, s):
|
|
self.buf = (c_ubyte * len(s))()
|
|
self.buf[:] = s[:]
|
|
self.rd = CReader(self.buf, len(s))
|
|
|
|
class CEnv(Structure):
|
|
_fields_ = [
|
|
("dats", POINTER(POINTER(c_ubyte))),
|
|
("datlen", c_size_t),
|
|
("reg", POINTER(c_uint)),
|
|
("reglen", c_size_t),
|
|
("stk", POINTER(c_uint)),
|
|
("stkptr", c_size_t),
|
|
("stklen", c_size_t),
|
|
("r_current", CReader),
|
|
("r_start", CReader)
|
|
]
|
|
|
|
class RegisterOverflowError(Exception):
|
|
def __init__(self, reg):
|
|
self.reg = reg
|
|
class StackOverflowError(Exception):
|
|
def __init__(self, pos):
|
|
self.pos = pos
|
|
class InvalidSyscallError(Exception):
|
|
def __init__(self, sc):
|
|
self.sc = sc
|
|
class CompileError(Exception):
|
|
def __init__(self, r):
|
|
self.r = r
|
|
class DataOverflowError(Exception):
|
|
pass
|
|
|
|
class Environment:
|
|
def getreg(self, reg, signed=False):
|
|
""" Get the value at the register.
|
|
|
|
:param reg: Register number.
|
|
:param signed: If the register value should be interpreted
|
|
as signed.
|
|
"""
|
|
if reg >= self.cenv.reglen or reg < 0:
|
|
raise RegisterOverflowError(r)
|
|
if signed:
|
|
return creole.from_2c(self.cenv.reg[reg])
|
|
else:
|
|
return self.cenv.reg[reg]
|
|
def getstk(self, stk, signed=False):
|
|
""" Get the value at the stack position.
|
|
|
|
:param reg: Register number.
|
|
:param signed: If the stack value should be interpreted
|
|
as signed.
|
|
"""
|
|
if stk >= self.cenv.stklen or stk < 0:
|
|
raise StackOverflowError(r)
|
|
if signed:
|
|
return creole.from_2c(self.cenv.stk[stk])
|
|
else:
|
|
return self.cenv.stk[stk]
|
|
def getdat(self, n):
|
|
if n >= self.cenv.datlen or n < 0:
|
|
raise DataOverflowError(n)
|
|
rdr = CReader()
|
|
rdr.p = self.cenv.dats[n]
|
|
# Python does not allow for direct pointer arithmetic
|
|
rdr_p_v = addressof(rdr.p.contents)
|
|
r_start_p_v = addressof(self.cenv.r_start.p)
|
|
|
|
rdr.left = self.cenv.r_start.left - (rdr_p_v - r_start_p_v)
|
|
|
|
l = []
|
|
w = CWord()
|
|
while dll.creole_decode(byref(rdr), byref(w)) == 1:
|
|
if w.word == 0 and w.len == 1:
|
|
break
|
|
l.append(w.word)
|
|
return l
|
|
|
|
def pop(self):
|
|
if stk == 0:
|
|
return None
|
|
stk = stk - 1
|
|
return self.cenv.stk[stk]
|
|
|
|
def reset(self):
|
|
self.cenv.r_current = self.cenv.r_start
|
|
|
|
def __init__(self, prog=None, reglen=32, datlen=32, stklen=4096, prglen=4096):
|
|
cenv = CEnv()
|
|
cenv.dats = (POINTER(c_ubyte) * datlen)()
|
|
cenv.datlen = datlen
|
|
|
|
cenv.reglen = reglen
|
|
cenv.reg = (c_uint * reglen)()
|
|
|
|
cenv.stklen = stklen
|
|
cenv.stk = (c_uint * stklen)()
|
|
cenv.stkptr = 0
|
|
|
|
self.cenv = cenv
|
|
if prog is not None:
|
|
if type(prog) is creole.Program:
|
|
prog = prog()
|
|
r = self.load(prog)
|
|
if r is not CompileRet.OK:
|
|
raise CompileError(r)
|
|
|
|
def load(self, prog):
|
|
self.reader = Reader(prog)
|
|
self.cenv.r_current = self.reader.rd
|
|
self.cenv.r_start = self.cenv.r_current
|
|
ret = dll.creole_compile(byref(self.cenv), byref(self.cenv.r_current))
|
|
return CompileRet(ret)
|
|
|
|
def syscall(self, sc):
|
|
raise InvalidSyscallError(sc)
|
|
def __call__(self, debug=False):
|
|
sc = c_size_t()
|
|
ret = RunRet.CONTINUE
|
|
while not ret.is_halt():
|
|
ret = RunRet(dll.creole_step(byref(self.cenv), byref(sc)))
|
|
if ret == RunRet.SYSCALL:
|
|
self.syscall(sc)
|
|
if debug:
|
|
print(self.cenv.reg[0])
|
|
return ret
|