Refactor to make Opcode the base class
This commit is contained in:
parent
7e7c557dea
commit
eb128d8b54
9 changed files with 358 additions and 269 deletions
|
@ -3,33 +3,42 @@
|
|||
from dataclasses import dataclass
|
||||
from random import choices
|
||||
from string import ascii_lowercase, digits
|
||||
from typing import List
|
||||
from typing import Generator, List, Union, Optional, Sequence
|
||||
|
||||
from ichor.isa import Opcode
|
||||
from ichor import isa
|
||||
|
||||
|
||||
@dataclass
|
||||
class Label(object):
|
||||
name: str
|
||||
def gensym(prefix = None) -> isa.Label:
|
||||
frag = ''.join(choices(ascii_lowercase + digits, k=8))
|
||||
return isa.Label(f"{prefix or 'gensym'}_{frag}")
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.name)
|
||||
|
||||
class FuncBuilder(object):
|
||||
def __init__(self) -> None:
|
||||
self._opcodes = []
|
||||
|
||||
def write(self, op: Opcode):
|
||||
def _write(self, op):
|
||||
self._opcodes.append(op)
|
||||
|
||||
def make_label(self, prefix=""):
|
||||
frag = ''.join(choices(ascii_lowercase + digits, k=8))
|
||||
return Label(f"{prefix or 'gensym'}_{frag}")
|
||||
def write(self, op: Union[isa.Opcode, isa.Label, Sequence[isa.Opcode]]):
|
||||
|
||||
def set_label(self, label: Label):
|
||||
self._opcodes.append(label)
|
||||
def flatten(o):
|
||||
for e in o:
|
||||
if isinstance(e, (isa.Opcode, isa.Label)):
|
||||
yield e
|
||||
else:
|
||||
yield from flatten(e)
|
||||
|
||||
def build(self) -> List[Opcode]:
|
||||
if isinstance(op, (isa.Opcode, isa.Label)):
|
||||
self._opcodes.append(op)
|
||||
else:
|
||||
for e in op:
|
||||
self.write(e)
|
||||
|
||||
def make_label(self, prefix: Optional[str] = ""):
|
||||
return gensym(prefix)
|
||||
|
||||
def build(self) -> List[isa.Opcode]:
|
||||
"""Assemble the written body into fully resolved opcodes."""
|
||||
|
||||
# The trivial two-pass assembler. First pass removes labels from the
|
||||
|
@ -39,8 +48,8 @@ class FuncBuilder(object):
|
|||
unassembled = []
|
||||
for op in self._opcodes:
|
||||
match op:
|
||||
case Label(_) as l:
|
||||
assert l not in labels # Label marks must be unique.
|
||||
case isa.Label(_) as l:
|
||||
assert l not in labels # isa.Label marks must be unique.
|
||||
labels[l] = len(unassembled)
|
||||
case o:
|
||||
unassembled.append(o)
|
||||
|
@ -50,13 +59,36 @@ class FuncBuilder(object):
|
|||
assembled = []
|
||||
for op in unassembled:
|
||||
match op:
|
||||
case Opcode.GOTO(Label(_) as l):
|
||||
assembled.append(Opcode.GOTO(labels[l]))
|
||||
case isa.GOTO(isa.Label(_) as l):
|
||||
assembled.append(isa.GOTO(labels[l]))
|
||||
|
||||
case Opcode.VTEST(Label(_) as l):
|
||||
assembled.append(Opcode.VTEST(labels[l]))
|
||||
case isa.VTEST(isa.Label(_) as l):
|
||||
assembled.append(isa.VTEST(labels[l]))
|
||||
|
||||
case o:
|
||||
assembled.append(o)
|
||||
|
||||
return assembled
|
||||
|
||||
|
||||
def assemble(builder_cls=FuncBuilder, /, **opcodes: List[isa.Opcode]) -> List[isa.Opcode]:
|
||||
builder = builder_cls()
|
||||
for o in opcodes:
|
||||
builder.write(o)
|
||||
return builder.build()
|
||||
|
||||
|
||||
class LocalBuilder(FuncBuilder):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._stack = 0
|
||||
self._labels = {}
|
||||
|
||||
def _write(self, op):
|
||||
pass
|
||||
|
||||
def write_local(self, label: isa.Label):
|
||||
pass
|
||||
|
||||
def get_local(self, label: isa.Label):
|
||||
pass
|
||||
|
|
|
@ -4,7 +4,7 @@ Some utterly trivial functions and types that allow me to begin testing the VM.
|
|||
Hopefully no "real" interpreter ever uses this code, since it's obviously replaceable.
|
||||
"""
|
||||
|
||||
from ichor.isa import Opcode
|
||||
from ichor import isa
|
||||
from ichor.state import Module, Variant
|
||||
from ichor.assembler import FuncBuilder
|
||||
|
||||
|
@ -22,59 +22,59 @@ NOT1 = BOOTSTRAP.define_function(
|
|||
f";not;{BOOL};{BOOL}",
|
||||
[
|
||||
# a: Bool
|
||||
Opcode.IDENTIFIERC("bool"),
|
||||
Opcode.TYPEREF(), # <typeref bool> a
|
||||
Opcode.DUP(),
|
||||
Opcode.IDENTIFIERC("true"),
|
||||
Opcode.VARIANTREF(), # <variantref true:bool> <typeref bool> a
|
||||
Opcode.DUP(),
|
||||
Opcode.SLOT(0),
|
||||
Opcode.ROT(2),
|
||||
Opcode.VTEST(11),
|
||||
isa.IDENTIFIERC("bool"),
|
||||
isa.TYPEREF(), # <typeref bool> a
|
||||
isa.DUP(),
|
||||
isa.IDENTIFIERC("true"),
|
||||
isa.VARIANTREF(), # <variantref true:bool> <typeref bool> a
|
||||
isa.DUP(),
|
||||
isa.SLOT(0),
|
||||
isa.ROT(2),
|
||||
isa.VTEST(11),
|
||||
|
||||
Opcode.VARIANT(0),
|
||||
Opcode.RETURN(1),
|
||||
isa.VARIANT(0),
|
||||
isa.RETURN(1),
|
||||
|
||||
Opcode.DROP(1),
|
||||
Opcode.IDENTIFIERC("false"),
|
||||
Opcode.VARIANTREF(),
|
||||
Opcode.VARIANT(0),
|
||||
Opcode.RETURN(1),
|
||||
isa.DROP(1),
|
||||
isa.IDENTIFIERC("false"),
|
||||
isa.VARIANTREF(),
|
||||
isa.VARIANT(0),
|
||||
isa.RETURN(1),
|
||||
],
|
||||
)
|
||||
|
||||
OR2 = BOOTSTRAP.define_function(
|
||||
f";or;{BOOL},{BOOL};{BOOL}",
|
||||
[
|
||||
Opcode.BREAK(),
|
||||
isa.BREAK(),
|
||||
],
|
||||
)
|
||||
|
||||
OR3 = BOOTSTRAP.define_function(
|
||||
f";or;{BOOL},{BOOL},{BOOL};{BOOL}",
|
||||
[
|
||||
Opcode.BREAK(),
|
||||
isa.BREAK(),
|
||||
]
|
||||
)
|
||||
|
||||
AND2 = BOOTSTRAP.define_function(
|
||||
f";and;{BOOL},{BOOL};{BOOL}",
|
||||
[
|
||||
Opcode.BREAK(),
|
||||
isa.BREAK(),
|
||||
],
|
||||
)
|
||||
|
||||
AND3 = BOOTSTRAP.define_function(
|
||||
f";and;{BOOL},{BOOL},{BOOL};{BOOL}",
|
||||
[
|
||||
Opcode.BREAK(),
|
||||
isa.BREAK(),
|
||||
],
|
||||
)
|
||||
|
||||
XOR2 = BOOTSTRAP.define_function(
|
||||
f";xor;{BOOL},{BOOL};{BOOL}",
|
||||
[
|
||||
Opcode.BREAK(),
|
||||
isa.BREAK(),
|
||||
],
|
||||
)
|
||||
|
||||
|
@ -82,6 +82,6 @@ XOR3 = BOOTSTRAP.define_function(
|
|||
f";xor;{BOOL},{BOOL},{BOOL};{BOOL}",
|
||||
[
|
||||
# A^B|B^C
|
||||
Opcode.BREAK(),
|
||||
isa.BREAK(),
|
||||
]
|
||||
)
|
||||
|
|
|
@ -13,7 +13,7 @@ from copy import deepcopy
|
|||
import typing as t
|
||||
from textwrap import indent
|
||||
|
||||
from ichor.isa import Opcode
|
||||
from ichor import isa
|
||||
from ichor.state import Closure, FunctionRef, Identifier, Module, Function, Type, TypeRef, VariantRef, Variant, Stackframe
|
||||
|
||||
|
||||
|
@ -52,7 +52,7 @@ class Interpreter(object):
|
|||
b = []
|
||||
b.append(f"clock {clock}:")
|
||||
b.append(" stack:")
|
||||
for offset, it in zip(range(len(stackframe), 0, -1), stackframe):
|
||||
for offset, it in zip(range(0, len(stackframe), 1), stackframe):
|
||||
b.append(f" {offset: <3} {it}")
|
||||
b.append(f" op: {op}")
|
||||
print(indent("\n".join(b), " " * stackframe.depth))
|
||||
|
@ -64,7 +64,7 @@ class Interpreter(object):
|
|||
clock += 1
|
||||
|
||||
match op:
|
||||
case Opcode.IDENTIFIERC(name):
|
||||
case isa.IDENTIFIERC(name):
|
||||
if not (name in mod.functions
|
||||
or name in mod.types
|
||||
or any(name in t.constructors for t in mod.types.values())):
|
||||
|
@ -72,7 +72,7 @@ class Interpreter(object):
|
|||
|
||||
stackframe.push(Identifier(name))
|
||||
|
||||
case Opcode.TYPEREF():
|
||||
case isa.TYPEREF():
|
||||
id = stackframe.pop()
|
||||
if not isinstance(id, Identifier):
|
||||
_error("TYPEREF consumes an identifier")
|
||||
|
@ -81,7 +81,7 @@ class Interpreter(object):
|
|||
|
||||
stackframe.push(TypeRef(id.name))
|
||||
|
||||
case Opcode.VARIANTREF():
|
||||
case isa.VARIANTREF():
|
||||
id: Identifier = stackframe.pop()
|
||||
if not isinstance(id, Identifier):
|
||||
_error("VARIANTREF consumes an identifier and a typeref")
|
||||
|
@ -96,7 +96,7 @@ class Interpreter(object):
|
|||
|
||||
stackframe.push(VariantRef(t, id.name))
|
||||
|
||||
case Opcode.VARIANT(n):
|
||||
case isa.VARIANT(n):
|
||||
armref: VariantRef = stackframe.pop()
|
||||
if not isinstance(armref, VariantRef):
|
||||
_error("VARIANT must be given a valid constructor reference")
|
||||
|
@ -114,7 +114,7 @@ class Interpreter(object):
|
|||
stackframe.drop(n)
|
||||
stackframe.push(v)
|
||||
|
||||
case Opcode.VTEST(n):
|
||||
case isa.VTEST(n):
|
||||
armref: VariantRef = stackframe.pop()
|
||||
if not isinstance(armref, VariantRef):
|
||||
_error("VTEST must be given a variant reference")
|
||||
|
@ -127,38 +127,38 @@ class Interpreter(object):
|
|||
stackframe.goto(n)
|
||||
continue
|
||||
|
||||
case Opcode.GOTO(n):
|
||||
case isa.GOTO(n):
|
||||
if (n < 0):
|
||||
_error("Illegal branch target")
|
||||
stackframe.goto(n)
|
||||
continue
|
||||
|
||||
case Opcode.DUP(n):
|
||||
case isa.DUP(n):
|
||||
if (n > len(stackframe)):
|
||||
_error("Stack size violation")
|
||||
|
||||
stackframe.dup(n)
|
||||
|
||||
case Opcode.ROT(n):
|
||||
case isa.ROT(n):
|
||||
if (n > len(stackframe)):
|
||||
_error("Stack size violation")
|
||||
|
||||
stackframe.rot(n)
|
||||
|
||||
case Opcode.DROP(n):
|
||||
case isa.DROP(n):
|
||||
if (n > len(stackframe)):
|
||||
_error("Stack size violation")
|
||||
|
||||
stackframe.drop(n)
|
||||
|
||||
case Opcode.SLOT(n):
|
||||
case isa.SLOT(n):
|
||||
if (n < 0):
|
||||
_error("SLOT must have a positive reference")
|
||||
if (n > len(stackframe) - 1):
|
||||
_error("SLOT reference out of range")
|
||||
stackframe.slot(n)
|
||||
|
||||
case Opcode.FUNREF():
|
||||
case isa.FUNREF():
|
||||
id = stackframe.pop()
|
||||
if not isinstance(id, Identifier):
|
||||
_error("FUNREF consumes an IDENTIFIER")
|
||||
|
@ -168,7 +168,7 @@ class Interpreter(object):
|
|||
except:
|
||||
_error("Invalid function ref")
|
||||
|
||||
case Opcode.CALLF(n):
|
||||
case isa.CALLF(n):
|
||||
sig = stackframe.pop()
|
||||
if not isinstance(sig, FunctionRef):
|
||||
_error("CALLF requires a funref at top of stack")
|
||||
|
@ -186,7 +186,7 @@ class Interpreter(object):
|
|||
stackframe = stackframe.call(fun, ip)
|
||||
continue
|
||||
|
||||
case Opcode.RETURN(n):
|
||||
case isa.RETURN(n):
|
||||
if (n > len(stackframe)):
|
||||
_error("Stack size violation")
|
||||
|
||||
|
@ -199,7 +199,7 @@ class Interpreter(object):
|
|||
stackframe = stackframe.ret(n)
|
||||
continue
|
||||
|
||||
case Opcode.CLOSUREF(n):
|
||||
case isa.CLOSUREF(n):
|
||||
sig = stackframe.pop()
|
||||
if not isinstance(sig, FunctionRef):
|
||||
_error("CLOSUREF requires a funref at top of stack")
|
||||
|
@ -216,7 +216,7 @@ class Interpreter(object):
|
|||
stackframe.drop(n)
|
||||
stackframe.push(c)
|
||||
|
||||
case Opcode.CLOSUREC(n):
|
||||
case isa.CLOSUREC(n):
|
||||
c = stackframe.pop()
|
||||
if not isinstance(c, Closure):
|
||||
_error("CLOSUREC requires a closure at top of stack")
|
||||
|
@ -233,7 +233,7 @@ class Interpreter(object):
|
|||
stackframe.drop(n)
|
||||
stackframe.push(c)
|
||||
|
||||
case Opcode.CALLC(n):
|
||||
case isa.CALLC(n):
|
||||
c = stackframe.pop()
|
||||
if not isinstance(c, Closure):
|
||||
_error("CALLC requires a closure at top of stack")
|
||||
|
@ -246,7 +246,7 @@ class Interpreter(object):
|
|||
# Extract the function signature
|
||||
|
||||
# Push the closure's stack fragment
|
||||
stackframe._stack = stackframe._stack.extendleft(c.frag)
|
||||
stackframe._stack = c.frag + stackframe._stack
|
||||
|
||||
# Perform a "normal" funref call
|
||||
try:
|
||||
|
|
|
@ -1,205 +1,242 @@
|
|||
"""The instruction set for Shogoth."""
|
||||
|
||||
|
||||
from abc import ABC
|
||||
from dataclasses import dataclass
|
||||
import typing as t
|
||||
|
||||
|
||||
class Opcode:
|
||||
# Note that there's no IF, TRUE or FALSE required if bool is a builtin.
|
||||
@dataclass
|
||||
class Label(object):
|
||||
name: str
|
||||
|
||||
class GOTO(t.NamedTuple):
|
||||
"""() -> ()
|
||||
def __hash__(self):
|
||||
return hash(self.name)
|
||||
|
||||
Branch to another point within the same bytecode segment. The target MUST be within the same module range as the
|
||||
current function. Branching does NOT update the name or module of the current function.
|
||||
|
||||
"""
|
||||
class Opcode(ABC):
|
||||
pass
|
||||
|
||||
target: int
|
||||
|
||||
####################################################################################################
|
||||
# Stack manipulation
|
||||
####################################################################################################
|
||||
# https://wiki.laptop.org/go/Forth_stack_operators
|
||||
# https://www.forth.com/starting-forth/2-stack-manipulation-operators-arithmetic/
|
||||
# https://docs.oracle.com/javase/specs/jvms/se18/html/jvms-6.html#jvms-6.5.swap
|
||||
@dataclass
|
||||
class GOTO(Opcode):
|
||||
"""() -> ()
|
||||
|
||||
class DUP(t.NamedTuple):
|
||||
"""(A, B, ...) -> (A, B, ...)
|
||||
Branch to another point within the same bytecode segment. The target MUST be within the same module range as the
|
||||
current function. Branching does NOT update the name or module of the current function.
|
||||
|
||||
Duplicate the top N items of the stack.
|
||||
"""
|
||||
|
||||
"""
|
||||
target: int
|
||||
|
||||
nargs: int = 1
|
||||
####################################################################################################
|
||||
# Stack manipulation
|
||||
####################################################################################################
|
||||
# https://wiki.laptop.org/go/Forth_stack_operators
|
||||
# https://www.forth.com/starting-forth/2-stack-manipulation-operators-arithmetic/
|
||||
# https://docs.oracle.com/javase/specs/jvms/se18/html/jvms-6.html#jvms-6.5.swap
|
||||
|
||||
class ROT(t.NamedTuple):
|
||||
"""(A, B, ... Z) -> (Z, A, B, ...)
|
||||
@dataclass
|
||||
class DUP(Opcode):
|
||||
"""(A, B, ...) -> (A, B, ...)
|
||||
|
||||
Rotate the top N elements of the stack.
|
||||
Duplicate the top N items of the stack.
|
||||
|
||||
"""
|
||||
"""
|
||||
|
||||
nargs: int = 2
|
||||
nargs: int = 1
|
||||
|
||||
class DROP(t.NamedTuple):
|
||||
"""(*) -> ()
|
||||
|
||||
Drop the top N items of the stack.
|
||||
@dataclass
|
||||
class ROT(Opcode):
|
||||
"""(A, B, ... Z) -> (Z, A, B, ...)
|
||||
|
||||
"""
|
||||
Rotate the top N elements of the stack.
|
||||
|
||||
nargs: int = 1
|
||||
"""
|
||||
|
||||
class SLOT(t.NamedTuple):
|
||||
"""(..., A) -> (A, ..., A)
|
||||
nargs: int = 2
|
||||
|
||||
Copy the Nth (counting up from 0 at the bottom of the stack) item to the top of the stack.
|
||||
Intended to allow users to emulate (immutable) frame local slots for reused values.
|
||||
@dataclass
|
||||
class DROP(Opcode):
|
||||
"""(*) -> ()
|
||||
|
||||
"""
|
||||
Drop the top N items of the stack.
|
||||
|
||||
target: int
|
||||
"""
|
||||
|
||||
####################################################################################################
|
||||
# Functional abstraction
|
||||
####################################################################################################
|
||||
nargs: int = 1
|
||||
|
||||
class IDENTIFIERC(t.NamedTuple):
|
||||
"""() -> (IDENTIFIER)
|
||||
|
||||
An inline constant which produces an identifier to the stack.
|
||||
@dataclass
|
||||
class SLOT(Opcode):
|
||||
"""(..., A) -> (A, ..., A)
|
||||
|
||||
Identifiers name functions, fields and types but are not strings.
|
||||
They are a VM-internal naming structure with reference to the module.
|
||||
Copy the Nth (counting up from 0 at the bottom of the stack) item to the top of the stack.
|
||||
Intended to allow users to emulate (immutable) frame local slots for reused values.
|
||||
|
||||
"""
|
||||
"""
|
||||
|
||||
val: str
|
||||
target: int
|
||||
|
||||
class FUNREF(t.NamedTuple):
|
||||
"""(IDENTIFIER) -> (`FUNREF<... A to ... B>`)
|
||||
####################################################################################################
|
||||
# Functional abstraction
|
||||
####################################################################################################
|
||||
|
||||
Construct a reference to a static codepoint.
|
||||
@dataclass
|
||||
class IDENTIFIERC(Opcode):
|
||||
"""() -> (IDENTIFIER)
|
||||
|
||||
"""
|
||||
An inline constant which produces an identifier to the stack.
|
||||
|
||||
class CALLF(t.NamedTuple):
|
||||
"""(`FUNREF<... A to ... B>`, ... A) -> (... B)
|
||||
Identifiers name functions, fields and types but are not strings.
|
||||
They are a VM-internal naming structure with reference to the module.
|
||||
|
||||
Call [funref]
|
||||
"""
|
||||
|
||||
Make a dynamic call to the function reference at the top of stack.
|
||||
The callee will see a stack containg only the provided `nargs`.
|
||||
A subsequent RETURN will return execution to the next point.
|
||||
val: str
|
||||
|
||||
Executing a `CALL` pushes the name and module path of the current function.
|
||||
|
||||
"""
|
||||
@dataclass
|
||||
class FUNREF(Opcode):
|
||||
"""(IDENTIFIER) -> (`FUNREF<... A to ... B>`)
|
||||
|
||||
nargs: int = 0
|
||||
Construct a reference to a static codepoint.
|
||||
|
||||
class RETURN(t.NamedTuple):
|
||||
"""(... A) -> ()
|
||||
"""
|
||||
|
||||
Return to the source of the last `CALL`. The returnee will see the top `nargs` values of the present stack
|
||||
appended to theirs. All other values on the stack will be discarded.
|
||||
@dataclass
|
||||
class CALLF(Opcode):
|
||||
"""(`FUNREF<... A to ... B>`, ... A) -> (... B)
|
||||
|
||||
Executing a `RETURN` pops (restores) the name and module path of the current function back to that of the caller.
|
||||
Call [funref]
|
||||
|
||||
If the call stack is empty, `RETURN` will exit the interpreter.
|
||||
Make a dynamic call to the function reference at the top of stack.
|
||||
The callee will see a stack containg only the provided `nargs`.
|
||||
A subsequent RETURN will return execution to the next point.
|
||||
|
||||
"""
|
||||
Executing a `CALL` pushes the name and module path of the current function.
|
||||
|
||||
nargs: int
|
||||
"""
|
||||
|
||||
nargs: int = 0
|
||||
|
||||
class CLOSUREF(t.NamedTuple):
|
||||
"""(`FUNREF<A, ... B to ... C>`, A) -> (`CLOSURE<... B to ... C>`)
|
||||
@dataclass
|
||||
class RETURN(Opcode):
|
||||
"""(... A) -> ()
|
||||
|
||||
Construct a closure over the function reference at the top of the stack. This may produce nullary closures.
|
||||
Return to the source of the last `CALL`. The returnee will see the top `nargs` values of the present stack
|
||||
appended to theirs. All other values on the stack will be discarded.
|
||||
|
||||
"""
|
||||
Executing a `RETURN` pops (restores) the name and module path of the current function back to that of the caller.
|
||||
|
||||
nargs: int = 0
|
||||
If the call stack is empty, `RETURN` will exit the interpreter.
|
||||
|
||||
class CLOSUREC(t.NamedTuple):
|
||||
"""(`CLOSURE<A, ... B to ... C>`, A) -> (`CLOSURE<... B to ... C>`)
|
||||
"""
|
||||
|
||||
Further close over the closure at the top of the stack. This may produce nullary closures.
|
||||
nargs: int
|
||||
|
||||
"""
|
||||
|
||||
nargs: int = 0
|
||||
@dataclass
|
||||
class CLOSUREF(Opcode):
|
||||
"""(`FUNREF<A, ... B to ... C>`, A) -> (`CLOSURE<... B to ... C>`)
|
||||
|
||||
class CALLC(t.NamedTuple):
|
||||
"""(`CLOSURE<... A to ... B>`, ... A) -> (... B)
|
||||
Construct a closure over the function reference at the top of the stack. This may produce nullary closures.
|
||||
|
||||
Call [closure]
|
||||
"""
|
||||
|
||||
Make a dynamic call to the closure at the top of stack.
|
||||
The callee will see a stack containg only the provided `nargs` and closed-overs.
|
||||
A subsequent RETURN will return execution to the next point.
|
||||
nargs: int = 0
|
||||
|
||||
Executing a `CALL` pushes the name and module path of the current function.
|
||||
|
||||
"""
|
||||
@dataclass
|
||||
class CLOSUREC(Opcode):
|
||||
"""(`CLOSURE<A, ... B to ... C>`, A) -> (`CLOSURE<... B to ... C>`)
|
||||
|
||||
nargs: int = 0
|
||||
Further close over the closure at the top of the stack. This may produce nullary closures.
|
||||
|
||||
####################################################################################################
|
||||
# Structures
|
||||
####################################################################################################
|
||||
"""
|
||||
|
||||
# FIXME: This lacks any sort of way to do dynamic type/field references
|
||||
nargs: int = 0
|
||||
|
||||
class TYPEREF(t.NamedTuple):
|
||||
"""(IDENTIFIER) -> (TYPEREF)
|
||||
|
||||
Produces a TYPEREF to the type named by the provided IDENTIFIER.
|
||||
class CALLC(Opcode):
|
||||
"""(`CLOSURE<... A to ... B>`, ... A) -> (... B)
|
||||
|
||||
"""
|
||||
Call [closure]
|
||||
|
||||
class VARIANTREF(t.NamedTuple):
|
||||
"""(IDENTIFIER, TYPEREF) -> (VARIANTREF)
|
||||
Make a dynamic call to the closure at the top of stack.
|
||||
The callee will see a stack containg only the provided `nargs` and closed-overs.
|
||||
A subsequent RETURN will return execution to the next point.
|
||||
|
||||
Produce a VARIANTREF to an 'arm' of the given variant type.
|
||||
Executing a `CALL` pushes the name and module path of the current function.
|
||||
|
||||
"""
|
||||
"""
|
||||
|
||||
class VARIANT(t.NamedTuple):
|
||||
"""(VARIANTREF<a ⊢ A ⊂ B>, ...) -> (B)
|
||||
nargs: int = 0
|
||||
|
||||
Construct an instance of an 'arm' of a variant.
|
||||
The type of the 'arm' is considered to be the type of the whole variant.
|
||||
####################################################################################################
|
||||
# Structures
|
||||
####################################################################################################
|
||||
|
||||
The name and module path of the current function MUST match the name and module path of `VARIANTREF`.
|
||||
The arity of this opcode MUST match the arity of the arm.
|
||||
The signature of the arm MUST match the signature fo the top N of the stack.
|
||||
"""
|
||||
# FIXME: This lacks any sort of way to do dynamic type/field references
|
||||
|
||||
nargs: int = 0
|
||||
@dataclass
|
||||
class TYPEREF(Opcode):
|
||||
"""(IDENTIFIER) -> (TYPEREF)
|
||||
|
||||
class VTEST(t.NamedTuple):
|
||||
"""(VARIANTREF<a ⊢ A ⊂ B>, B) -> ()
|
||||
Produces a TYPEREF to the type named by the provided IDENTIFIER.
|
||||
|
||||
Test whether B is a given arm of a variant A .
|
||||
If it is, branch to the given target.
|
||||
Otherwise fall through.
|
||||
"""
|
||||
|
||||
"""
|
||||
|
||||
target: int
|
||||
@dataclass
|
||||
class VARIANTREF(Opcode):
|
||||
"""(IDENTIFIER, TYPEREF) -> (VARIANTREF)
|
||||
|
||||
class VLOAD(t.NamedTuple):
|
||||
"""(VARIANTREF<a ⊢ A ⊂ B>, B) -> (A)
|
||||
Produce a VARIANTREF to an 'arm' of the given variant type.
|
||||
|
||||
Load the value of the variant arm.
|
||||
VLOAD errors (undefined) if B is not within the variant.
|
||||
VLOAD errors (undefined) if the value in B is not an A - use VTEST as needed.
|
||||
"""
|
||||
|
||||
"""
|
||||
|
||||
class BREAK(t.NamedTuple):
|
||||
"""Abort the interpreter."""
|
||||
pass
|
||||
@dataclass
|
||||
class VARIANT(Opcode):
|
||||
"""(VARIANTREF<a ⊢ A ⊂ B>, ...) -> (B)
|
||||
|
||||
Construct an instance of an 'arm' of a variant.
|
||||
The type of the 'arm' is considered to be the type of the whole variant.
|
||||
|
||||
The name and module path of the current function MUST match the name and module path of `VARIANTREF`.
|
||||
The arity of this opcode MUST match the arity of the arm.
|
||||
The signature of the arm MUST match the signature fo the top N of the stack.
|
||||
"""
|
||||
|
||||
nargs: int = 0
|
||||
|
||||
|
||||
@dataclass
|
||||
class VTEST(Opcode):
|
||||
"""(VARIANTREF<a ⊢ A ⊂ B>, B) -> ()
|
||||
|
||||
Test whether B is a given arm of a variant A .
|
||||
If it is, branch to the given target.
|
||||
Otherwise fall through.
|
||||
|
||||
"""
|
||||
|
||||
target: int
|
||||
|
||||
|
||||
@dataclass
|
||||
class VLOAD(Opcode):
|
||||
"""(VARIANTREF<a ⊢ A ⊂ B>, B) -> (A)
|
||||
|
||||
Load the value of the variant arm.
|
||||
VLOAD errors (undefined) if B is not within the variant.
|
||||
VLOAD errors (undefined) if the value in B is not an A - use VTEST as needed.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@dataclass
|
||||
class BREAK(Opcode):
|
||||
"""Abort the interpreter."""
|
||||
pass
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
import typing as t
|
||||
|
||||
from ichor.isa import Opcode
|
||||
from ichor import isa
|
||||
|
||||
from pyrsistent import pdeque, PDeque
|
||||
from lark import Lark, Transformer, v_args, Token
|
||||
|
@ -77,13 +77,13 @@ class Function(t.NamedTuple):
|
|||
name: str
|
||||
arguments: t.List[str]
|
||||
returns: t.List[str]
|
||||
instructions: t.List[Opcode]
|
||||
instructions: t.List[isa.Opcode]
|
||||
typevars: t.List[t.Any] = []
|
||||
typeconstraints: t.List[t.Any] = []
|
||||
metadata: dict = {}
|
||||
|
||||
@classmethod
|
||||
def build(cls, name: str, instructions: t.List[Opcode]):
|
||||
def build(cls, name: str, instructions: t.List[isa.Opcode]):
|
||||
constraints, name, args, rets = FUNC.parse(name)
|
||||
# FIXME: Constraints probably needs some massaging
|
||||
# FIXME: Need to get typevars from somewhere
|
||||
|
@ -188,18 +188,18 @@ class Module(t.NamedTuple):
|
|||
)
|
||||
|
||||
@staticmethod
|
||||
def translate(start: int, end: int, i: Opcode):
|
||||
def translate(start: int, end: int, i: isa.Opcode):
|
||||
# FIXME: Consolidate bounds checks somehow
|
||||
match i:
|
||||
case Opcode.VTEST(t):
|
||||
case isa.VTEST(t):
|
||||
d = t + start
|
||||
assert start <= d < end
|
||||
return Opcode.VTEST(d)
|
||||
return isa.VTEST(d)
|
||||
|
||||
case Opcode.GOTO(t):
|
||||
case isa.GOTO(t):
|
||||
d = t + start
|
||||
assert start <= d < end
|
||||
return Opcode.GOTO(d)
|
||||
return isa.GOTO(d)
|
||||
|
||||
case _:
|
||||
return i
|
||||
|
@ -279,7 +279,10 @@ class Stackframe(object):
|
|||
self._stack = self._stack[:-nargs]
|
||||
|
||||
def rot(self, nargs):
|
||||
self._stack[nargs:].extend(rotate(self._stack[:nargs]))
|
||||
frag = self._stack[-nargs:]
|
||||
base = self._stack[:-nargs]
|
||||
rotated = rotate(frag)
|
||||
self._stack = base + rotated
|
||||
|
||||
def slot(self, n):
|
||||
self.push(self._stack[n])
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
from ichor import FuncBuilder, Opcode
|
||||
from ichor import FuncBuilder, isa
|
||||
|
||||
import pytest
|
||||
|
||||
|
@ -11,37 +11,37 @@ def builder() -> FuncBuilder:
|
|||
|
||||
def test_forwards_label(builder: FuncBuilder):
|
||||
l = builder.make_label()
|
||||
builder.write(Opcode.GOTO(l))
|
||||
builder.write(Opcode.DROP(0)) # no-op
|
||||
builder.set_label(l)
|
||||
builder.write(Opcode.DROP(0)) # no-op
|
||||
builder.write(isa.GOTO(l))
|
||||
builder.write(isa.DROP(0)) # no-op
|
||||
builder.write(l)
|
||||
builder.write(isa.DROP(0)) # no-op
|
||||
instrs = builder.build()
|
||||
assert instrs == [
|
||||
Opcode.GOTO(2),
|
||||
Opcode.DROP(0),
|
||||
Opcode.DROP(0),
|
||||
isa.GOTO(2),
|
||||
isa.DROP(0),
|
||||
isa.DROP(0),
|
||||
]
|
||||
|
||||
|
||||
def test_backwards_label(builder: FuncBuilder):
|
||||
l = builder.make_label()
|
||||
builder.set_label(l)
|
||||
builder.write(Opcode.DROP(0)) # no-op
|
||||
builder.write(Opcode.GOTO(l))
|
||||
builder.write(l)
|
||||
builder.write(isa.DROP(0)) # no-op
|
||||
builder.write(isa.GOTO(l))
|
||||
instrs = builder.build()
|
||||
assert instrs == [
|
||||
Opcode.DROP(0),
|
||||
Opcode.GOTO(0),
|
||||
isa.DROP(0),
|
||||
isa.GOTO(0),
|
||||
]
|
||||
|
||||
|
||||
def test_self_label(builder: FuncBuilder):
|
||||
l = builder.make_label()
|
||||
builder.write(Opcode.DROP(0)) # no-op
|
||||
builder.set_label(l)
|
||||
builder.write(Opcode.GOTO(l))
|
||||
builder.write(isa.DROP(0)) # no-op
|
||||
builder.write(l)
|
||||
builder.write(isa.GOTO(l))
|
||||
instrs = builder.build()
|
||||
assert instrs == [
|
||||
Opcode.DROP(0),
|
||||
Opcode.GOTO(1),
|
||||
isa.DROP(0),
|
||||
isa.GOTO(1),
|
||||
]
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
from .fixtures import * # noqa
|
||||
|
||||
from ichor import *
|
||||
from ichor import isa, TRUE, FALSE, NOT1
|
||||
import pytest
|
||||
|
||||
|
||||
|
@ -12,10 +12,10 @@ import pytest
|
|||
])
|
||||
def test_not(vm, stack, ret):
|
||||
assert vm.run([
|
||||
Opcode.IDENTIFIERC(NOT1),
|
||||
Opcode.FUNREF(),
|
||||
Opcode.CALLF(1),
|
||||
Opcode.RETURN(1)
|
||||
isa.IDENTIFIERC(NOT1),
|
||||
isa.FUNREF(),
|
||||
isa.CALLF(1),
|
||||
isa.RETURN(1)
|
||||
], stack = stack) == ret
|
||||
|
||||
|
||||
|
@ -27,10 +27,10 @@ def test_not(vm, stack, ret):
|
|||
# ])
|
||||
# def test_or(vm, stack, ret):
|
||||
# assert vm.run([
|
||||
# Opcode.IDENTIFIERC(OR2),
|
||||
# Opcode.FUNREF(),
|
||||
# Opcode.CALLF(2),
|
||||
# Opcode.RETURN(1)
|
||||
# isa.IDENTIFIERC(OR2),
|
||||
# isa.FUNREF(),
|
||||
# isa.CALLF(2),
|
||||
# isa.RETURN(1)
|
||||
# ], stack = stack) == ret
|
||||
|
||||
|
||||
|
@ -42,10 +42,10 @@ def test_not(vm, stack, ret):
|
|||
# ])
|
||||
# def test_and(vm, stack, ret):
|
||||
# assert vm.run([
|
||||
# Opcode.IDENTIFIERC(AND2),
|
||||
# Opcode.FUNREF(),
|
||||
# Opcode.CALLF(2),
|
||||
# Opcode.RETURN(1)
|
||||
# isa.IDENTIFIERC(AND2),
|
||||
# isa.FUNREF(),
|
||||
# isa.CALLF(2),
|
||||
# isa.RETURN(1)
|
||||
# ], stack = stack) == ret
|
||||
|
||||
|
||||
|
@ -57,10 +57,10 @@ def test_not(vm, stack, ret):
|
|||
# ])
|
||||
# def test_xor2(vm, stack, ret):
|
||||
# assert vm.run([
|
||||
# Opcode.IDENTIFIERC(XOR2),
|
||||
# Opcode.FUNREF(),
|
||||
# Opcode.CALLF(2),
|
||||
# Opcode.RETURN(1)
|
||||
# isa.IDENTIFIERC(XOR2),
|
||||
# isa.FUNREF(),
|
||||
# isa.CALLF(2),
|
||||
# isa.RETURN(1)
|
||||
# ], stack = stack) == ret
|
||||
|
||||
|
||||
|
@ -75,10 +75,10 @@ def test_not(vm, stack, ret):
|
|||
# ])
|
||||
# def test_xor3(vm, stack, ret):
|
||||
# assert vm.run([
|
||||
# Opcode.IDENTIFIERC(XOR3),
|
||||
# Opcode.FUNREF(),
|
||||
# Opcode.CALLF(3),
|
||||
# Opcode.RETURN(1)
|
||||
# isa.IDENTIFIERC(XOR3),
|
||||
# isa.FUNREF(),
|
||||
# isa.CALLF(3),
|
||||
# isa.RETURN(1)
|
||||
# ], stack = stack) == ret
|
||||
|
||||
|
||||
|
@ -87,9 +87,9 @@ def test_not(vm, stack, ret):
|
|||
# ])
|
||||
# def test_funref(vm, stack, ret):
|
||||
# assert vm.run([
|
||||
# Opcode.IDENTIFIERC(NOT1),
|
||||
# Opcode.FUNREF(),
|
||||
# Opcode.RETURN(1)
|
||||
# isa.IDENTIFIERC(NOT1),
|
||||
# isa.FUNREF(),
|
||||
# isa.RETURN(1)
|
||||
# ], stack = stack) == ret
|
||||
|
||||
|
||||
|
@ -98,10 +98,10 @@ def test_not(vm, stack, ret):
|
|||
# ])
|
||||
# def test_callf(vm, stack, ret):
|
||||
# assert vm.run([
|
||||
# Opcode.IDENTIFIERC(NOT1),
|
||||
# Opcode.FUNREF(),
|
||||
# Opcode.CALLF(1),
|
||||
# Opcode.RETURN(1)
|
||||
# isa.IDENTIFIERC(NOT1),
|
||||
# isa.FUNREF(),
|
||||
# isa.CALLF(1),
|
||||
# isa.RETURN(1)
|
||||
# ], stack = stack) == ret
|
||||
|
||||
|
||||
|
@ -113,11 +113,11 @@ def test_not(vm, stack, ret):
|
|||
# ])
|
||||
# def test_callc(vm, stack, ret):
|
||||
# assert vm.run([
|
||||
# Opcode.IDENTIFIERC(XOR2),
|
||||
# Opcode.FUNREF(),
|
||||
# Opcode.CLOSUREF(1),
|
||||
# Opcode.CALLC(1),
|
||||
# Opcode.RETURN(1),
|
||||
# isa.IDENTIFIERC(XOR2),
|
||||
# isa.FUNREF(),
|
||||
# isa.CLOSUREF(1),
|
||||
# isa.CALLC(1),
|
||||
# isa.RETURN(1),
|
||||
# ], stack = stack) == ret
|
||||
|
||||
|
||||
|
@ -132,10 +132,10 @@ def test_not(vm, stack, ret):
|
|||
# ])
|
||||
# def test_closurec(vm, stack, ret):
|
||||
# assert vm.run([
|
||||
# Opcode.IDENTIFIERC(XOR3),
|
||||
# Opcode.FUNREF(),
|
||||
# Opcode.CLOSUREF(1),
|
||||
# Opcode.CLOSUREC(1),
|
||||
# Opcode.CALLC(1),
|
||||
# Opcode.RETURN(1),
|
||||
# isa.IDENTIFIERC(XOR3),
|
||||
# isa.FUNREF(),
|
||||
# isa.CLOSUREF(1),
|
||||
# isa.CLOSUREC(1),
|
||||
# isa.CALLC(1),
|
||||
# isa.RETURN(1),
|
||||
# ], stack = stack) == ret
|
||||
|
|
|
@ -4,59 +4,64 @@ Tests coverign the VM interpreter
|
|||
|
||||
from .fixtures import * # noqa
|
||||
|
||||
from ichor import *
|
||||
from ichor import isa, InterpreterError, TRUE, FALSE
|
||||
import pytest
|
||||
|
||||
|
||||
def test_return(vm):
|
||||
assert vm.run([Opcode.RETURN(0)], stack=[TRUE, FALSE]) == []
|
||||
assert vm.run([Opcode.RETURN(1)], stack=[TRUE, FALSE]) == [TRUE]
|
||||
assert vm.run([Opcode.RETURN(2)], stack=[TRUE, FALSE]) == [TRUE, FALSE]
|
||||
assert vm.run([isa.RETURN(0)], stack=[TRUE, FALSE]) == []
|
||||
assert vm.run([isa.RETURN(1)], stack=[TRUE, FALSE]) == [TRUE]
|
||||
assert vm.run([isa.RETURN(2)], stack=[TRUE, FALSE]) == [TRUE, FALSE]
|
||||
|
||||
|
||||
def test_dup(vm):
|
||||
assert vm.run([Opcode.DUP(1), Opcode.RETURN(3)], stack=[FALSE, TRUE]) == [FALSE, TRUE, TRUE]
|
||||
assert vm.run([Opcode.DUP(2), Opcode.RETURN(4)], stack=[FALSE, TRUE]) == [FALSE, TRUE, FALSE, TRUE]
|
||||
assert vm.run([isa.DUP(1), isa.RETURN(3)], stack=[FALSE, TRUE]) == [FALSE, TRUE, TRUE]
|
||||
assert vm.run([isa.DUP(2), isa.RETURN(4)], stack=[FALSE, TRUE]) == [FALSE, TRUE, FALSE, TRUE]
|
||||
|
||||
|
||||
def test_rot(vm):
|
||||
assert vm.run([
|
||||
Opcode.ROT(2),
|
||||
Opcode.RETURN(2)
|
||||
isa.ROT(2),
|
||||
isa.RETURN(2)
|
||||
], stack=[FALSE, TRUE]) == [TRUE, FALSE]
|
||||
|
||||
assert vm.run([
|
||||
Opcode.ROT(3),
|
||||
Opcode.RETURN(3)
|
||||
isa.ROT(2),
|
||||
isa.RETURN(5)
|
||||
], stack=[TRUE, TRUE, TRUE, FALSE, TRUE]) == [TRUE, TRUE, TRUE, TRUE, FALSE]
|
||||
|
||||
assert vm.run([
|
||||
isa.ROT(3),
|
||||
isa.RETURN(3)
|
||||
], stack=[FALSE, TRUE, FALSE]) == [FALSE, FALSE, TRUE]
|
||||
|
||||
|
||||
def test_drop(vm):
|
||||
assert vm.run([
|
||||
Opcode.DROP(1),
|
||||
Opcode.RETURN(1)
|
||||
isa.DROP(1),
|
||||
isa.RETURN(1)
|
||||
], stack=[TRUE, FALSE]) == [TRUE]
|
||||
|
||||
|
||||
def test_dup_too_many(vm):
|
||||
with pytest.raises(InterpreterError):
|
||||
vm.run([Opcode.DUP(1)])
|
||||
vm.run([isa.DUP(1)])
|
||||
|
||||
with pytest.raises(InterpreterError):
|
||||
vm.run([Opcode.FALSE(), Opcode.DUP(2)])
|
||||
vm.run([isa.DUP(2)], stack=[FALSE])
|
||||
|
||||
|
||||
def test_rot_too_many(vm):
|
||||
with pytest.raises(InterpreterError):
|
||||
vm.run([Opcode.ROT(1)])
|
||||
vm.run([isa.ROT(1)])
|
||||
|
||||
with pytest.raises(InterpreterError):
|
||||
vm.run([Opcode.TRUE(), Opcode.ROT(2)])
|
||||
vm.run([isa.ROT(2)], stack=[FALSE])
|
||||
|
||||
|
||||
def test_drop_too_many(vm):
|
||||
with pytest.raises(InterpreterError):
|
||||
vm.run([Opcode.DROP(1)])
|
||||
vm.run([isa.DROP(1)])
|
||||
|
||||
with pytest.raises(InterpreterError):
|
||||
vm.run([Opcode.TRUE(), Opcode.DROP(2)])
|
||||
vm.run([isa.DROP(2)], stack=[FALSE])
|
||||
|
|
|
@ -70,3 +70,15 @@ def test_stackframe_slot(frame):
|
|||
|
||||
frame.slot(2)
|
||||
assert frame.pop() == 2
|
||||
|
||||
|
||||
def test_stackframe_rot(frame):
|
||||
frame.push(0)
|
||||
frame.push(1)
|
||||
frame.push(2)
|
||||
frame.push(3)
|
||||
frame.push(4)
|
||||
|
||||
frame.rot(2)
|
||||
assert frame.pop() == 3
|
||||
assert frame.pop() == 4
|
||||
|
|
Loading…
Reference in a new issue