Slang/compilers/sbc/__init__.py

290 lines
6.3 KiB
Python

#!/usr/bin/python3
# Slang Bytecode (SBC) compiler target
from . import *
from .. import *
from ...ast import *
from utils import *
NOP = 0x00
POP = 0x01
DUP = 0x02
RET = 0x03
CODE = 0x04
IF = 0x05
LOOP = 0x06
ELSE = 0x07
END = 0x08
CALL = 0x09
ASGN = 0x0A
BLTIN = 0x0B
CONST = 0x0C
SGET = 0x0D
SSET = 0x0E
def readVarInt(s):
r = int()
i = int()
while (True):
b = s.recv(1)[0]
r |= (b & (1 << 7)-1) << (7*i)
if (not b & (1 << 7)): break
i += 1
return r
def writeVarInt(v):
assert v >= 0
r = bytearray()
while (True):
c = v & (1 << 7)-1
v >>= 7
if (v): c |= (1 << 7)
r.append(c)
if (not v): break
return bytes(r)
class Instrs:
unopmap = {
'!': 'not',
'+': 'abs',
'-': 'neg',
'~': 'inv',
'++': 'inc',
'--': 'dec',
'**': 'sqr',
'not': 'not',
}
binopmap = {
'+': 'add',
'-': 'sub',
'*': 'mul',
'/': 'div',
'//': 'idiv',
'%': 'mod',
'**': 'pow',
'<<': 'ls',
'>>': 'rs',
'&': 'and',
'^': 'xor',
'|': 'or',
'&&': 'and',
'^^': 'xor',
'||': 'or',
'==': 'eq',
'!=': 'ne',
'<': 'lt',
'>': 'gt',
'<=': 'le',
'>=': 'ge',
'is': 'is',
'is not': 'isnot',
'in': 'in',
'not in': 'notin',
'isof': 'isof',
'and': 'and',
'but': 'and',
'xor': 'xor',
'or': 'or',
'to': 'range',
}
@init_defaults
def __init__(self, *, name, ns, filename, scpcells: indexset):
self.name, self.ns, self.filename, self.scpcells = name, ns, filename, scpcells
self.instrs = bytearray()
self.opmap = dict()
def compile(self):
return bytes(self.instrs)
@dispatch
def add(self, opcode: int, *args: int):
self.instrs.append(opcode)
if (args): self.instrs += bytes(args)
@dispatch
def add(self, x: ASTRootNode):
self.add(x.code)
@dispatch
def add(self, x: ASTBlockNode):
self.add(x.code)
@dispatch
def add(self, x: ASTCodeNode):
for i in x.nodes:
self.add(i)
@dispatch
def add(self, x: ASTValueNode):
self.add(x.value)
@dispatch
def add(self, x: ASTVardefNode):
if (x.value is not None):
self.load(x.value, sig=Signature.build(x.type, ns=self.ns))
self.store(x.name)
@dispatch
def add(self, x: ASTFunccallNode):
self.load(x)
self.add(POP)
@dispatch
def add(self, x: ASTKeywordDefNode):
if (x.keyword.keyword == 'main'):
name = '<main>'
code_ns = self.ns.derive(name)
f_instrs = Instrs(name=name, ns=code_ns, filename=self.filename)
f_instrs.add(x.code)
self.add(CODE)
self.instrs += f_instrs.instrs
self.add(END)
self.add(CALL, 0)
self.add(POP)
else: raise NotImplementedError(x.keyword)
@dispatch
def add(self, x: ASTConditionalNode):
self.load(x.condition)
self.add(IF)
self.add(x.code)
self.add(END)
@dispatch
def add(self, x: ASTForLoopNode):
self.load(x.iterable)
self.builtin('iter', 1)
self.store(x.name)
self.add(DUP, 0)
self.add(LOOP)
self.add(x.code)
self.builtin('iter', 1)
self.store(x.name)
self.add(DUP, 0)
self.add(END)
@dispatch
def add(self, x: ASTWhileLoopNode):
self.load(x.condition)
self.add(LOOP)
self.add(x.code)
self.load(x.condition)
self.add(END)
@dispatch
def add(self, x: ASTElseClauseNode):
assert (self.instrs[-1] == END)
end = self.instrs.pop()
self.add(ELSE)
self.add(x.code)
self.add(end)
@dispatch
def load(self, x: ASTLiteralNode, *, sig=None):
if (sig is None): sig = Signature.build(x, ns=self.ns)
if (hasattr(sig, 'fmt')):
t, v = sig.__class__.__name__, struct.pack(sig.fmt, int(x.literal))
elif (isinstance(sig, stdlib.int)):
t, v = 'i', writeVarInt(eval_literal(x))
elif (isinstance(sig, stdlib.str)):
t, v = 's', eval_literal(x).encode('utf-8')
elif (isinstance(sig, stdlib.char)):
t, v = 'c', eval_literal(x).encode('utf-8') # TODO
else: raise NotImplementedError(sig)
self.add(CONST)
self.instrs += t.encode('utf-8')+b'\0' + writeVarInt(len(v)) + v
@dispatch
def load(self, x: ASTIdentifierNode, **kwargs):
self.load(x.identifier, **kwargs)
@dispatch
def load(self, x: ASTValueNode, **kwargs):
self.load(x.value, **kwargs)
@dispatch
def load(self, x: ASTFunccallNode):
nargs = int()
for i in x.callargs.callargs:
self.load(i)
nargs += 1
if (x.callargs.starargs): raise NotImplementedError()
if (x.callkwargs.callkwargs): raise NotImplementedError()
if (x.callkwargs.starkwargs): raise NotImplementedError()
if (isinstance(x.callable, ASTValueNode) and isinstance(x.callable.value, ASTIdentifierNode) and x.callable.value.identifier in self.ns.signatures):
raise NotImplementedError()
#name = f"{x.callable.value.identifier}({CallArguments.build(x, self.ns)})"
#self.load(name)
#self.load(name+'.len')
#self.add(EXEC)
else:
self.load(x.callable)
self.add(CALL, nargs)
@dispatch
def load(self, x: ASTAttrgetNode):
if (str(x.value) != 'stdio'): raise NotImplementedError(x)
if (x.optype.special != '.'): raise NotImplementedError(x)
self.builtin(str(x.attr))
@dispatch
def load(self, x: ASTBinaryExprNode, **kwargs):
self.load(x.lvalue, **kwargs)
self.load(x.rvalue, **kwargs)
self.builtin(self.binopmap[x.operator.operator], 2)
@dispatch
def load(self, x: str, **kwargs):
self.add(SGET, self.scpcells[x])
@dispatch
def store(self, x: ASTIdentifierNode):
self.store(x.identifier)
@dispatch
def store(self, x: str):
self.add(SSET, self.scpcells[x])
@dispatch
def assign(self, builtin: str,
nargs_code: lambda nargs_code: isinstance(nargs_code, int) and 0 <= nargs_code < 4,
nargs_stack: lambda nargs_stack: isinstance(nargs_stack, int) and 0 <= nargs_stack < 64,
opcode: int = None):
self.builtin(builtin)
if (opcode is None): opcode = first(sorted(set(range(0x10, 0x100)) - set(self.opmap.values())))
self.add(ASGN, opcode, (nargs_code << 6) | nargs_stack)
self.opmap[builtin, nargs_code, nargs_stack] = opcode
@dispatch
def builtin(self, builtin: str):
self.add(BLTIN)
self.instrs += builtin.encode('ascii')+b'\0'
@dispatch
def builtin(self, builtin: str, nargs: int):
if (nargs is not None and len(self.opmap) < 0xf0): self.assign(builtin, 0, nargs)
if ((builtin, 0, nargs) in self.opmap): self.add(self.opmap[builtin, 0, nargs])
else: self.builtin(builtin); self.add(CALL, nargs)
class SBCCompiler(Compiler):
ext = '.sbc'
@classmethod
def compile_ast(cls, ast, ns, *, filename):
instrs = Instrs(name='<module>', ns=ns, filename=filename)
instrs.add(ast)
code = instrs.compile()
return code
compiler = SBCCompiler
# by Sdore, 2020