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 dataclasses import dataclass
|
||||||
from random import choices
|
from random import choices
|
||||||
from string import ascii_lowercase, digits
|
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
|
def gensym(prefix = None) -> isa.Label:
|
||||||
class Label(object):
|
frag = ''.join(choices(ascii_lowercase + digits, k=8))
|
||||||
name: str
|
return isa.Label(f"{prefix or 'gensym'}_{frag}")
|
||||||
|
|
||||||
def __hash__(self):
|
|
||||||
return hash(self.name)
|
|
||||||
|
|
||||||
class FuncBuilder(object):
|
class FuncBuilder(object):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self._opcodes = []
|
self._opcodes = []
|
||||||
|
|
||||||
def write(self, op: Opcode):
|
def _write(self, op):
|
||||||
self._opcodes.append(op)
|
self._opcodes.append(op)
|
||||||
|
|
||||||
def make_label(self, prefix=""):
|
def write(self, op: Union[isa.Opcode, isa.Label, Sequence[isa.Opcode]]):
|
||||||
frag = ''.join(choices(ascii_lowercase + digits, k=8))
|
|
||||||
return Label(f"{prefix or 'gensym'}_{frag}")
|
|
||||||
|
|
||||||
def set_label(self, label: Label):
|
def flatten(o):
|
||||||
self._opcodes.append(label)
|
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."""
|
"""Assemble the written body into fully resolved opcodes."""
|
||||||
|
|
||||||
# The trivial two-pass assembler. First pass removes labels from the
|
# The trivial two-pass assembler. First pass removes labels from the
|
||||||
|
@ -39,8 +48,8 @@ class FuncBuilder(object):
|
||||||
unassembled = []
|
unassembled = []
|
||||||
for op in self._opcodes:
|
for op in self._opcodes:
|
||||||
match op:
|
match op:
|
||||||
case Label(_) as l:
|
case isa.Label(_) as l:
|
||||||
assert l not in labels # Label marks must be unique.
|
assert l not in labels # isa.Label marks must be unique.
|
||||||
labels[l] = len(unassembled)
|
labels[l] = len(unassembled)
|
||||||
case o:
|
case o:
|
||||||
unassembled.append(o)
|
unassembled.append(o)
|
||||||
|
@ -50,13 +59,36 @@ class FuncBuilder(object):
|
||||||
assembled = []
|
assembled = []
|
||||||
for op in unassembled:
|
for op in unassembled:
|
||||||
match op:
|
match op:
|
||||||
case Opcode.GOTO(Label(_) as l):
|
case isa.GOTO(isa.Label(_) as l):
|
||||||
assembled.append(Opcode.GOTO(labels[l]))
|
assembled.append(isa.GOTO(labels[l]))
|
||||||
|
|
||||||
case Opcode.VTEST(Label(_) as l):
|
case isa.VTEST(isa.Label(_) as l):
|
||||||
assembled.append(Opcode.VTEST(labels[l]))
|
assembled.append(isa.VTEST(labels[l]))
|
||||||
|
|
||||||
case o:
|
case o:
|
||||||
assembled.append(o)
|
assembled.append(o)
|
||||||
|
|
||||||
return assembled
|
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.
|
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.state import Module, Variant
|
||||||
from ichor.assembler import FuncBuilder
|
from ichor.assembler import FuncBuilder
|
||||||
|
|
||||||
|
@ -22,59 +22,59 @@ NOT1 = BOOTSTRAP.define_function(
|
||||||
f";not;{BOOL};{BOOL}",
|
f";not;{BOOL};{BOOL}",
|
||||||
[
|
[
|
||||||
# a: Bool
|
# a: Bool
|
||||||
Opcode.IDENTIFIERC("bool"),
|
isa.IDENTIFIERC("bool"),
|
||||||
Opcode.TYPEREF(), # <typeref bool> a
|
isa.TYPEREF(), # <typeref bool> a
|
||||||
Opcode.DUP(),
|
isa.DUP(),
|
||||||
Opcode.IDENTIFIERC("true"),
|
isa.IDENTIFIERC("true"),
|
||||||
Opcode.VARIANTREF(), # <variantref true:bool> <typeref bool> a
|
isa.VARIANTREF(), # <variantref true:bool> <typeref bool> a
|
||||||
Opcode.DUP(),
|
isa.DUP(),
|
||||||
Opcode.SLOT(0),
|
isa.SLOT(0),
|
||||||
Opcode.ROT(2),
|
isa.ROT(2),
|
||||||
Opcode.VTEST(11),
|
isa.VTEST(11),
|
||||||
|
|
||||||
Opcode.VARIANT(0),
|
isa.VARIANT(0),
|
||||||
Opcode.RETURN(1),
|
isa.RETURN(1),
|
||||||
|
|
||||||
Opcode.DROP(1),
|
isa.DROP(1),
|
||||||
Opcode.IDENTIFIERC("false"),
|
isa.IDENTIFIERC("false"),
|
||||||
Opcode.VARIANTREF(),
|
isa.VARIANTREF(),
|
||||||
Opcode.VARIANT(0),
|
isa.VARIANT(0),
|
||||||
Opcode.RETURN(1),
|
isa.RETURN(1),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
OR2 = BOOTSTRAP.define_function(
|
OR2 = BOOTSTRAP.define_function(
|
||||||
f";or;{BOOL},{BOOL};{BOOL}",
|
f";or;{BOOL},{BOOL};{BOOL}",
|
||||||
[
|
[
|
||||||
Opcode.BREAK(),
|
isa.BREAK(),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
OR3 = BOOTSTRAP.define_function(
|
OR3 = BOOTSTRAP.define_function(
|
||||||
f";or;{BOOL},{BOOL},{BOOL};{BOOL}",
|
f";or;{BOOL},{BOOL},{BOOL};{BOOL}",
|
||||||
[
|
[
|
||||||
Opcode.BREAK(),
|
isa.BREAK(),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
AND2 = BOOTSTRAP.define_function(
|
AND2 = BOOTSTRAP.define_function(
|
||||||
f";and;{BOOL},{BOOL};{BOOL}",
|
f";and;{BOOL},{BOOL};{BOOL}",
|
||||||
[
|
[
|
||||||
Opcode.BREAK(),
|
isa.BREAK(),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
AND3 = BOOTSTRAP.define_function(
|
AND3 = BOOTSTRAP.define_function(
|
||||||
f";and;{BOOL},{BOOL},{BOOL};{BOOL}",
|
f";and;{BOOL},{BOOL},{BOOL};{BOOL}",
|
||||||
[
|
[
|
||||||
Opcode.BREAK(),
|
isa.BREAK(),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
XOR2 = BOOTSTRAP.define_function(
|
XOR2 = BOOTSTRAP.define_function(
|
||||||
f";xor;{BOOL},{BOOL};{BOOL}",
|
f";xor;{BOOL},{BOOL};{BOOL}",
|
||||||
[
|
[
|
||||||
Opcode.BREAK(),
|
isa.BREAK(),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -82,6 +82,6 @@ XOR3 = BOOTSTRAP.define_function(
|
||||||
f";xor;{BOOL},{BOOL},{BOOL};{BOOL}",
|
f";xor;{BOOL},{BOOL},{BOOL};{BOOL}",
|
||||||
[
|
[
|
||||||
# A^B|B^C
|
# A^B|B^C
|
||||||
Opcode.BREAK(),
|
isa.BREAK(),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
|
@ -13,7 +13,7 @@ from copy import deepcopy
|
||||||
import typing as t
|
import typing as t
|
||||||
from textwrap import indent
|
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
|
from ichor.state import Closure, FunctionRef, Identifier, Module, Function, Type, TypeRef, VariantRef, Variant, Stackframe
|
||||||
|
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ class Interpreter(object):
|
||||||
b = []
|
b = []
|
||||||
b.append(f"clock {clock}:")
|
b.append(f"clock {clock}:")
|
||||||
b.append(" stack:")
|
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" {offset: <3} {it}")
|
||||||
b.append(f" op: {op}")
|
b.append(f" op: {op}")
|
||||||
print(indent("\n".join(b), " " * stackframe.depth))
|
print(indent("\n".join(b), " " * stackframe.depth))
|
||||||
|
@ -64,7 +64,7 @@ class Interpreter(object):
|
||||||
clock += 1
|
clock += 1
|
||||||
|
|
||||||
match op:
|
match op:
|
||||||
case Opcode.IDENTIFIERC(name):
|
case isa.IDENTIFIERC(name):
|
||||||
if not (name in mod.functions
|
if not (name in mod.functions
|
||||||
or name in mod.types
|
or name in mod.types
|
||||||
or any(name in t.constructors for t in mod.types.values())):
|
or any(name in t.constructors for t in mod.types.values())):
|
||||||
|
@ -72,7 +72,7 @@ class Interpreter(object):
|
||||||
|
|
||||||
stackframe.push(Identifier(name))
|
stackframe.push(Identifier(name))
|
||||||
|
|
||||||
case Opcode.TYPEREF():
|
case isa.TYPEREF():
|
||||||
id = stackframe.pop()
|
id = stackframe.pop()
|
||||||
if not isinstance(id, Identifier):
|
if not isinstance(id, Identifier):
|
||||||
_error("TYPEREF consumes an identifier")
|
_error("TYPEREF consumes an identifier")
|
||||||
|
@ -81,7 +81,7 @@ class Interpreter(object):
|
||||||
|
|
||||||
stackframe.push(TypeRef(id.name))
|
stackframe.push(TypeRef(id.name))
|
||||||
|
|
||||||
case Opcode.VARIANTREF():
|
case isa.VARIANTREF():
|
||||||
id: Identifier = stackframe.pop()
|
id: Identifier = stackframe.pop()
|
||||||
if not isinstance(id, Identifier):
|
if not isinstance(id, Identifier):
|
||||||
_error("VARIANTREF consumes an identifier and a typeref")
|
_error("VARIANTREF consumes an identifier and a typeref")
|
||||||
|
@ -96,7 +96,7 @@ class Interpreter(object):
|
||||||
|
|
||||||
stackframe.push(VariantRef(t, id.name))
|
stackframe.push(VariantRef(t, id.name))
|
||||||
|
|
||||||
case Opcode.VARIANT(n):
|
case isa.VARIANT(n):
|
||||||
armref: VariantRef = stackframe.pop()
|
armref: VariantRef = stackframe.pop()
|
||||||
if not isinstance(armref, VariantRef):
|
if not isinstance(armref, VariantRef):
|
||||||
_error("VARIANT must be given a valid constructor reference")
|
_error("VARIANT must be given a valid constructor reference")
|
||||||
|
@ -114,7 +114,7 @@ class Interpreter(object):
|
||||||
stackframe.drop(n)
|
stackframe.drop(n)
|
||||||
stackframe.push(v)
|
stackframe.push(v)
|
||||||
|
|
||||||
case Opcode.VTEST(n):
|
case isa.VTEST(n):
|
||||||
armref: VariantRef = stackframe.pop()
|
armref: VariantRef = stackframe.pop()
|
||||||
if not isinstance(armref, VariantRef):
|
if not isinstance(armref, VariantRef):
|
||||||
_error("VTEST must be given a variant reference")
|
_error("VTEST must be given a variant reference")
|
||||||
|
@ -127,38 +127,38 @@ class Interpreter(object):
|
||||||
stackframe.goto(n)
|
stackframe.goto(n)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
case Opcode.GOTO(n):
|
case isa.GOTO(n):
|
||||||
if (n < 0):
|
if (n < 0):
|
||||||
_error("Illegal branch target")
|
_error("Illegal branch target")
|
||||||
stackframe.goto(n)
|
stackframe.goto(n)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
case Opcode.DUP(n):
|
case isa.DUP(n):
|
||||||
if (n > len(stackframe)):
|
if (n > len(stackframe)):
|
||||||
_error("Stack size violation")
|
_error("Stack size violation")
|
||||||
|
|
||||||
stackframe.dup(n)
|
stackframe.dup(n)
|
||||||
|
|
||||||
case Opcode.ROT(n):
|
case isa.ROT(n):
|
||||||
if (n > len(stackframe)):
|
if (n > len(stackframe)):
|
||||||
_error("Stack size violation")
|
_error("Stack size violation")
|
||||||
|
|
||||||
stackframe.rot(n)
|
stackframe.rot(n)
|
||||||
|
|
||||||
case Opcode.DROP(n):
|
case isa.DROP(n):
|
||||||
if (n > len(stackframe)):
|
if (n > len(stackframe)):
|
||||||
_error("Stack size violation")
|
_error("Stack size violation")
|
||||||
|
|
||||||
stackframe.drop(n)
|
stackframe.drop(n)
|
||||||
|
|
||||||
case Opcode.SLOT(n):
|
case isa.SLOT(n):
|
||||||
if (n < 0):
|
if (n < 0):
|
||||||
_error("SLOT must have a positive reference")
|
_error("SLOT must have a positive reference")
|
||||||
if (n > len(stackframe) - 1):
|
if (n > len(stackframe) - 1):
|
||||||
_error("SLOT reference out of range")
|
_error("SLOT reference out of range")
|
||||||
stackframe.slot(n)
|
stackframe.slot(n)
|
||||||
|
|
||||||
case Opcode.FUNREF():
|
case isa.FUNREF():
|
||||||
id = stackframe.pop()
|
id = stackframe.pop()
|
||||||
if not isinstance(id, Identifier):
|
if not isinstance(id, Identifier):
|
||||||
_error("FUNREF consumes an IDENTIFIER")
|
_error("FUNREF consumes an IDENTIFIER")
|
||||||
|
@ -168,7 +168,7 @@ class Interpreter(object):
|
||||||
except:
|
except:
|
||||||
_error("Invalid function ref")
|
_error("Invalid function ref")
|
||||||
|
|
||||||
case Opcode.CALLF(n):
|
case isa.CALLF(n):
|
||||||
sig = stackframe.pop()
|
sig = stackframe.pop()
|
||||||
if not isinstance(sig, FunctionRef):
|
if not isinstance(sig, FunctionRef):
|
||||||
_error("CALLF requires a funref at top of stack")
|
_error("CALLF requires a funref at top of stack")
|
||||||
|
@ -186,7 +186,7 @@ class Interpreter(object):
|
||||||
stackframe = stackframe.call(fun, ip)
|
stackframe = stackframe.call(fun, ip)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
case Opcode.RETURN(n):
|
case isa.RETURN(n):
|
||||||
if (n > len(stackframe)):
|
if (n > len(stackframe)):
|
||||||
_error("Stack size violation")
|
_error("Stack size violation")
|
||||||
|
|
||||||
|
@ -199,7 +199,7 @@ class Interpreter(object):
|
||||||
stackframe = stackframe.ret(n)
|
stackframe = stackframe.ret(n)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
case Opcode.CLOSUREF(n):
|
case isa.CLOSUREF(n):
|
||||||
sig = stackframe.pop()
|
sig = stackframe.pop()
|
||||||
if not isinstance(sig, FunctionRef):
|
if not isinstance(sig, FunctionRef):
|
||||||
_error("CLOSUREF requires a funref at top of stack")
|
_error("CLOSUREF requires a funref at top of stack")
|
||||||
|
@ -216,7 +216,7 @@ class Interpreter(object):
|
||||||
stackframe.drop(n)
|
stackframe.drop(n)
|
||||||
stackframe.push(c)
|
stackframe.push(c)
|
||||||
|
|
||||||
case Opcode.CLOSUREC(n):
|
case isa.CLOSUREC(n):
|
||||||
c = stackframe.pop()
|
c = stackframe.pop()
|
||||||
if not isinstance(c, Closure):
|
if not isinstance(c, Closure):
|
||||||
_error("CLOSUREC requires a closure at top of stack")
|
_error("CLOSUREC requires a closure at top of stack")
|
||||||
|
@ -233,7 +233,7 @@ class Interpreter(object):
|
||||||
stackframe.drop(n)
|
stackframe.drop(n)
|
||||||
stackframe.push(c)
|
stackframe.push(c)
|
||||||
|
|
||||||
case Opcode.CALLC(n):
|
case isa.CALLC(n):
|
||||||
c = stackframe.pop()
|
c = stackframe.pop()
|
||||||
if not isinstance(c, Closure):
|
if not isinstance(c, Closure):
|
||||||
_error("CALLC requires a closure at top of stack")
|
_error("CALLC requires a closure at top of stack")
|
||||||
|
@ -246,7 +246,7 @@ class Interpreter(object):
|
||||||
# Extract the function signature
|
# Extract the function signature
|
||||||
|
|
||||||
# Push the closure's stack fragment
|
# Push the closure's stack fragment
|
||||||
stackframe._stack = stackframe._stack.extendleft(c.frag)
|
stackframe._stack = c.frag + stackframe._stack
|
||||||
|
|
||||||
# Perform a "normal" funref call
|
# Perform a "normal" funref call
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -1,205 +1,242 @@
|
||||||
"""The instruction set for Shogoth."""
|
"""The instruction set for Shogoth."""
|
||||||
|
|
||||||
|
from abc import ABC
|
||||||
|
from dataclasses import dataclass
|
||||||
import typing as t
|
import typing as t
|
||||||
|
|
||||||
|
|
||||||
class Opcode:
|
@dataclass
|
||||||
# Note that there's no IF, TRUE or FALSE required if bool is a builtin.
|
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
|
|
||||||
|
|
||||||
####################################################################################################
|
@dataclass
|
||||||
# Stack manipulation
|
class GOTO(Opcode):
|
||||||
####################################################################################################
|
"""() -> ()
|
||||||
# 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 DUP(t.NamedTuple):
|
Branch to another point within the same bytecode segment. The target MUST be within the same module range as the
|
||||||
"""(A, B, ...) -> (A, B, ...)
|
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):
|
@dataclass
|
||||||
"""(A, B, ... Z) -> (Z, A, B, ...)
|
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):
|
nargs: int = 2
|
||||||
"""(..., A) -> (A, ..., A)
|
|
||||||
|
|
||||||
Copy the Nth (counting up from 0 at the bottom of the stack) item to the top of the stack.
|
@dataclass
|
||||||
Intended to allow users to emulate (immutable) frame local slots for reused values.
|
class DROP(Opcode):
|
||||||
|
"""(*) -> ()
|
||||||
|
|
||||||
"""
|
Drop the top N items of the stack.
|
||||||
|
|
||||||
target: int
|
"""
|
||||||
|
|
||||||
####################################################################################################
|
nargs: int = 1
|
||||||
# Functional abstraction
|
|
||||||
####################################################################################################
|
|
||||||
|
|
||||||
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.
|
Copy the Nth (counting up from 0 at the bottom of the stack) item to the top of the stack.
|
||||||
They are a VM-internal naming structure with reference to the module.
|
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):
|
Identifiers name functions, fields and types but are not strings.
|
||||||
"""(`FUNREF<... A to ... B>`, ... A) -> (... B)
|
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.
|
val: str
|
||||||
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.
|
|
||||||
|
|
||||||
"""
|
@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
|
@dataclass
|
||||||
appended to theirs. All other values on the stack will be discarded.
|
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):
|
@dataclass
|
||||||
"""(`FUNREF<A, ... B to ... C>`, A) -> (`CLOSURE<... B to ... C>`)
|
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):
|
Construct a closure over the function reference at the top of the stack. This may produce nullary closures.
|
||||||
"""(`CLOSURE<... A to ... B>`, ... A) -> (... B)
|
|
||||||
|
|
||||||
Call [closure]
|
"""
|
||||||
|
|
||||||
Make a dynamic call to the closure at the top of stack.
|
nargs: int = 0
|
||||||
The callee will see a stack containg only the provided `nargs` and closed-overs.
|
|
||||||
A subsequent RETURN will return execution to the next point.
|
|
||||||
|
|
||||||
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):
|
Make a dynamic call to the closure at the top of stack.
|
||||||
"""(IDENTIFIER, TYPEREF) -> (VARIANTREF)
|
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):
|
nargs: int = 0
|
||||||
"""(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.
|
# Structures
|
||||||
|
####################################################################################################
|
||||||
|
|
||||||
The name and module path of the current function MUST match the name and module path of `VARIANTREF`.
|
# FIXME: This lacks any sort of way to do dynamic type/field references
|
||||||
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 TYPEREF(Opcode):
|
||||||
|
"""(IDENTIFIER) -> (TYPEREF)
|
||||||
|
|
||||||
class VTEST(t.NamedTuple):
|
Produces a TYPEREF to the type named by the provided IDENTIFIER.
|
||||||
"""(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 VARIANTREF(Opcode):
|
||||||
|
"""(IDENTIFIER, TYPEREF) -> (VARIANTREF)
|
||||||
|
|
||||||
class VLOAD(t.NamedTuple):
|
Produce a VARIANTREF to an 'arm' of the given variant type.
|
||||||
"""(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.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
class BREAK(t.NamedTuple):
|
@dataclass
|
||||||
"""Abort the interpreter."""
|
class VARIANT(Opcode):
|
||||||
pass
|
"""(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
|
import typing as t
|
||||||
|
|
||||||
from ichor.isa import Opcode
|
from ichor import isa
|
||||||
|
|
||||||
from pyrsistent import pdeque, PDeque
|
from pyrsistent import pdeque, PDeque
|
||||||
from lark import Lark, Transformer, v_args, Token
|
from lark import Lark, Transformer, v_args, Token
|
||||||
|
@ -77,13 +77,13 @@ class Function(t.NamedTuple):
|
||||||
name: str
|
name: str
|
||||||
arguments: t.List[str]
|
arguments: t.List[str]
|
||||||
returns: t.List[str]
|
returns: t.List[str]
|
||||||
instructions: t.List[Opcode]
|
instructions: t.List[isa.Opcode]
|
||||||
typevars: t.List[t.Any] = []
|
typevars: t.List[t.Any] = []
|
||||||
typeconstraints: t.List[t.Any] = []
|
typeconstraints: t.List[t.Any] = []
|
||||||
metadata: dict = {}
|
metadata: dict = {}
|
||||||
|
|
||||||
@classmethod
|
@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)
|
constraints, name, args, rets = FUNC.parse(name)
|
||||||
# FIXME: Constraints probably needs some massaging
|
# FIXME: Constraints probably needs some massaging
|
||||||
# FIXME: Need to get typevars from somewhere
|
# FIXME: Need to get typevars from somewhere
|
||||||
|
@ -188,18 +188,18 @@ class Module(t.NamedTuple):
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def translate(start: int, end: int, i: Opcode):
|
def translate(start: int, end: int, i: isa.Opcode):
|
||||||
# FIXME: Consolidate bounds checks somehow
|
# FIXME: Consolidate bounds checks somehow
|
||||||
match i:
|
match i:
|
||||||
case Opcode.VTEST(t):
|
case isa.VTEST(t):
|
||||||
d = t + start
|
d = t + start
|
||||||
assert start <= d < end
|
assert start <= d < end
|
||||||
return Opcode.VTEST(d)
|
return isa.VTEST(d)
|
||||||
|
|
||||||
case Opcode.GOTO(t):
|
case isa.GOTO(t):
|
||||||
d = t + start
|
d = t + start
|
||||||
assert start <= d < end
|
assert start <= d < end
|
||||||
return Opcode.GOTO(d)
|
return isa.GOTO(d)
|
||||||
|
|
||||||
case _:
|
case _:
|
||||||
return i
|
return i
|
||||||
|
@ -279,7 +279,10 @@ class Stackframe(object):
|
||||||
self._stack = self._stack[:-nargs]
|
self._stack = self._stack[:-nargs]
|
||||||
|
|
||||||
def rot(self, 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):
|
def slot(self, n):
|
||||||
self.push(self._stack[n])
|
self.push(self._stack[n])
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
from ichor import FuncBuilder, Opcode
|
from ichor import FuncBuilder, isa
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
@ -11,37 +11,37 @@ def builder() -> FuncBuilder:
|
||||||
|
|
||||||
def test_forwards_label(builder: FuncBuilder):
|
def test_forwards_label(builder: FuncBuilder):
|
||||||
l = builder.make_label()
|
l = builder.make_label()
|
||||||
builder.write(Opcode.GOTO(l))
|
builder.write(isa.GOTO(l))
|
||||||
builder.write(Opcode.DROP(0)) # no-op
|
builder.write(isa.DROP(0)) # no-op
|
||||||
builder.set_label(l)
|
builder.write(l)
|
||||||
builder.write(Opcode.DROP(0)) # no-op
|
builder.write(isa.DROP(0)) # no-op
|
||||||
instrs = builder.build()
|
instrs = builder.build()
|
||||||
assert instrs == [
|
assert instrs == [
|
||||||
Opcode.GOTO(2),
|
isa.GOTO(2),
|
||||||
Opcode.DROP(0),
|
isa.DROP(0),
|
||||||
Opcode.DROP(0),
|
isa.DROP(0),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def test_backwards_label(builder: FuncBuilder):
|
def test_backwards_label(builder: FuncBuilder):
|
||||||
l = builder.make_label()
|
l = builder.make_label()
|
||||||
builder.set_label(l)
|
builder.write(l)
|
||||||
builder.write(Opcode.DROP(0)) # no-op
|
builder.write(isa.DROP(0)) # no-op
|
||||||
builder.write(Opcode.GOTO(l))
|
builder.write(isa.GOTO(l))
|
||||||
instrs = builder.build()
|
instrs = builder.build()
|
||||||
assert instrs == [
|
assert instrs == [
|
||||||
Opcode.DROP(0),
|
isa.DROP(0),
|
||||||
Opcode.GOTO(0),
|
isa.GOTO(0),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def test_self_label(builder: FuncBuilder):
|
def test_self_label(builder: FuncBuilder):
|
||||||
l = builder.make_label()
|
l = builder.make_label()
|
||||||
builder.write(Opcode.DROP(0)) # no-op
|
builder.write(isa.DROP(0)) # no-op
|
||||||
builder.set_label(l)
|
builder.write(l)
|
||||||
builder.write(Opcode.GOTO(l))
|
builder.write(isa.GOTO(l))
|
||||||
instrs = builder.build()
|
instrs = builder.build()
|
||||||
assert instrs == [
|
assert instrs == [
|
||||||
Opcode.DROP(0),
|
isa.DROP(0),
|
||||||
Opcode.GOTO(1),
|
isa.GOTO(1),
|
||||||
]
|
]
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
from .fixtures import * # noqa
|
from .fixtures import * # noqa
|
||||||
|
|
||||||
from ichor import *
|
from ichor import isa, TRUE, FALSE, NOT1
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@ -12,10 +12,10 @@ import pytest
|
||||||
])
|
])
|
||||||
def test_not(vm, stack, ret):
|
def test_not(vm, stack, ret):
|
||||||
assert vm.run([
|
assert vm.run([
|
||||||
Opcode.IDENTIFIERC(NOT1),
|
isa.IDENTIFIERC(NOT1),
|
||||||
Opcode.FUNREF(),
|
isa.FUNREF(),
|
||||||
Opcode.CALLF(1),
|
isa.CALLF(1),
|
||||||
Opcode.RETURN(1)
|
isa.RETURN(1)
|
||||||
], stack = stack) == ret
|
], stack = stack) == ret
|
||||||
|
|
||||||
|
|
||||||
|
@ -27,10 +27,10 @@ def test_not(vm, stack, ret):
|
||||||
# ])
|
# ])
|
||||||
# def test_or(vm, stack, ret):
|
# def test_or(vm, stack, ret):
|
||||||
# assert vm.run([
|
# assert vm.run([
|
||||||
# Opcode.IDENTIFIERC(OR2),
|
# isa.IDENTIFIERC(OR2),
|
||||||
# Opcode.FUNREF(),
|
# isa.FUNREF(),
|
||||||
# Opcode.CALLF(2),
|
# isa.CALLF(2),
|
||||||
# Opcode.RETURN(1)
|
# isa.RETURN(1)
|
||||||
# ], stack = stack) == ret
|
# ], stack = stack) == ret
|
||||||
|
|
||||||
|
|
||||||
|
@ -42,10 +42,10 @@ def test_not(vm, stack, ret):
|
||||||
# ])
|
# ])
|
||||||
# def test_and(vm, stack, ret):
|
# def test_and(vm, stack, ret):
|
||||||
# assert vm.run([
|
# assert vm.run([
|
||||||
# Opcode.IDENTIFIERC(AND2),
|
# isa.IDENTIFIERC(AND2),
|
||||||
# Opcode.FUNREF(),
|
# isa.FUNREF(),
|
||||||
# Opcode.CALLF(2),
|
# isa.CALLF(2),
|
||||||
# Opcode.RETURN(1)
|
# isa.RETURN(1)
|
||||||
# ], stack = stack) == ret
|
# ], stack = stack) == ret
|
||||||
|
|
||||||
|
|
||||||
|
@ -57,10 +57,10 @@ def test_not(vm, stack, ret):
|
||||||
# ])
|
# ])
|
||||||
# def test_xor2(vm, stack, ret):
|
# def test_xor2(vm, stack, ret):
|
||||||
# assert vm.run([
|
# assert vm.run([
|
||||||
# Opcode.IDENTIFIERC(XOR2),
|
# isa.IDENTIFIERC(XOR2),
|
||||||
# Opcode.FUNREF(),
|
# isa.FUNREF(),
|
||||||
# Opcode.CALLF(2),
|
# isa.CALLF(2),
|
||||||
# Opcode.RETURN(1)
|
# isa.RETURN(1)
|
||||||
# ], stack = stack) == ret
|
# ], stack = stack) == ret
|
||||||
|
|
||||||
|
|
||||||
|
@ -75,10 +75,10 @@ def test_not(vm, stack, ret):
|
||||||
# ])
|
# ])
|
||||||
# def test_xor3(vm, stack, ret):
|
# def test_xor3(vm, stack, ret):
|
||||||
# assert vm.run([
|
# assert vm.run([
|
||||||
# Opcode.IDENTIFIERC(XOR3),
|
# isa.IDENTIFIERC(XOR3),
|
||||||
# Opcode.FUNREF(),
|
# isa.FUNREF(),
|
||||||
# Opcode.CALLF(3),
|
# isa.CALLF(3),
|
||||||
# Opcode.RETURN(1)
|
# isa.RETURN(1)
|
||||||
# ], stack = stack) == ret
|
# ], stack = stack) == ret
|
||||||
|
|
||||||
|
|
||||||
|
@ -87,9 +87,9 @@ def test_not(vm, stack, ret):
|
||||||
# ])
|
# ])
|
||||||
# def test_funref(vm, stack, ret):
|
# def test_funref(vm, stack, ret):
|
||||||
# assert vm.run([
|
# assert vm.run([
|
||||||
# Opcode.IDENTIFIERC(NOT1),
|
# isa.IDENTIFIERC(NOT1),
|
||||||
# Opcode.FUNREF(),
|
# isa.FUNREF(),
|
||||||
# Opcode.RETURN(1)
|
# isa.RETURN(1)
|
||||||
# ], stack = stack) == ret
|
# ], stack = stack) == ret
|
||||||
|
|
||||||
|
|
||||||
|
@ -98,10 +98,10 @@ def test_not(vm, stack, ret):
|
||||||
# ])
|
# ])
|
||||||
# def test_callf(vm, stack, ret):
|
# def test_callf(vm, stack, ret):
|
||||||
# assert vm.run([
|
# assert vm.run([
|
||||||
# Opcode.IDENTIFIERC(NOT1),
|
# isa.IDENTIFIERC(NOT1),
|
||||||
# Opcode.FUNREF(),
|
# isa.FUNREF(),
|
||||||
# Opcode.CALLF(1),
|
# isa.CALLF(1),
|
||||||
# Opcode.RETURN(1)
|
# isa.RETURN(1)
|
||||||
# ], stack = stack) == ret
|
# ], stack = stack) == ret
|
||||||
|
|
||||||
|
|
||||||
|
@ -113,11 +113,11 @@ def test_not(vm, stack, ret):
|
||||||
# ])
|
# ])
|
||||||
# def test_callc(vm, stack, ret):
|
# def test_callc(vm, stack, ret):
|
||||||
# assert vm.run([
|
# assert vm.run([
|
||||||
# Opcode.IDENTIFIERC(XOR2),
|
# isa.IDENTIFIERC(XOR2),
|
||||||
# Opcode.FUNREF(),
|
# isa.FUNREF(),
|
||||||
# Opcode.CLOSUREF(1),
|
# isa.CLOSUREF(1),
|
||||||
# Opcode.CALLC(1),
|
# isa.CALLC(1),
|
||||||
# Opcode.RETURN(1),
|
# isa.RETURN(1),
|
||||||
# ], stack = stack) == ret
|
# ], stack = stack) == ret
|
||||||
|
|
||||||
|
|
||||||
|
@ -132,10 +132,10 @@ def test_not(vm, stack, ret):
|
||||||
# ])
|
# ])
|
||||||
# def test_closurec(vm, stack, ret):
|
# def test_closurec(vm, stack, ret):
|
||||||
# assert vm.run([
|
# assert vm.run([
|
||||||
# Opcode.IDENTIFIERC(XOR3),
|
# isa.IDENTIFIERC(XOR3),
|
||||||
# Opcode.FUNREF(),
|
# isa.FUNREF(),
|
||||||
# Opcode.CLOSUREF(1),
|
# isa.CLOSUREF(1),
|
||||||
# Opcode.CLOSUREC(1),
|
# isa.CLOSUREC(1),
|
||||||
# Opcode.CALLC(1),
|
# isa.CALLC(1),
|
||||||
# Opcode.RETURN(1),
|
# isa.RETURN(1),
|
||||||
# ], stack = stack) == ret
|
# ], stack = stack) == ret
|
||||||
|
|
|
@ -4,59 +4,64 @@ Tests coverign the VM interpreter
|
||||||
|
|
||||||
from .fixtures import * # noqa
|
from .fixtures import * # noqa
|
||||||
|
|
||||||
from ichor import *
|
from ichor import isa, InterpreterError, TRUE, FALSE
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
def test_return(vm):
|
def test_return(vm):
|
||||||
assert vm.run([Opcode.RETURN(0)], stack=[TRUE, FALSE]) == []
|
assert vm.run([isa.RETURN(0)], stack=[TRUE, FALSE]) == []
|
||||||
assert vm.run([Opcode.RETURN(1)], stack=[TRUE, FALSE]) == [TRUE]
|
assert vm.run([isa.RETURN(1)], stack=[TRUE, FALSE]) == [TRUE]
|
||||||
assert vm.run([Opcode.RETURN(2)], stack=[TRUE, FALSE]) == [TRUE, FALSE]
|
assert vm.run([isa.RETURN(2)], stack=[TRUE, FALSE]) == [TRUE, FALSE]
|
||||||
|
|
||||||
|
|
||||||
def test_dup(vm):
|
def test_dup(vm):
|
||||||
assert vm.run([Opcode.DUP(1), Opcode.RETURN(3)], stack=[FALSE, TRUE]) == [FALSE, TRUE, TRUE]
|
assert vm.run([isa.DUP(1), isa.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(2), isa.RETURN(4)], stack=[FALSE, TRUE]) == [FALSE, TRUE, FALSE, TRUE]
|
||||||
|
|
||||||
|
|
||||||
def test_rot(vm):
|
def test_rot(vm):
|
||||||
assert vm.run([
|
assert vm.run([
|
||||||
Opcode.ROT(2),
|
isa.ROT(2),
|
||||||
Opcode.RETURN(2)
|
isa.RETURN(2)
|
||||||
], stack=[FALSE, TRUE]) == [TRUE, FALSE]
|
], stack=[FALSE, TRUE]) == [TRUE, FALSE]
|
||||||
|
|
||||||
assert vm.run([
|
assert vm.run([
|
||||||
Opcode.ROT(3),
|
isa.ROT(2),
|
||||||
Opcode.RETURN(3)
|
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]
|
], stack=[FALSE, TRUE, FALSE]) == [FALSE, FALSE, TRUE]
|
||||||
|
|
||||||
|
|
||||||
def test_drop(vm):
|
def test_drop(vm):
|
||||||
assert vm.run([
|
assert vm.run([
|
||||||
Opcode.DROP(1),
|
isa.DROP(1),
|
||||||
Opcode.RETURN(1)
|
isa.RETURN(1)
|
||||||
], stack=[TRUE, FALSE]) == [TRUE]
|
], stack=[TRUE, FALSE]) == [TRUE]
|
||||||
|
|
||||||
|
|
||||||
def test_dup_too_many(vm):
|
def test_dup_too_many(vm):
|
||||||
with pytest.raises(InterpreterError):
|
with pytest.raises(InterpreterError):
|
||||||
vm.run([Opcode.DUP(1)])
|
vm.run([isa.DUP(1)])
|
||||||
|
|
||||||
with pytest.raises(InterpreterError):
|
with pytest.raises(InterpreterError):
|
||||||
vm.run([Opcode.FALSE(), Opcode.DUP(2)])
|
vm.run([isa.DUP(2)], stack=[FALSE])
|
||||||
|
|
||||||
|
|
||||||
def test_rot_too_many(vm):
|
def test_rot_too_many(vm):
|
||||||
with pytest.raises(InterpreterError):
|
with pytest.raises(InterpreterError):
|
||||||
vm.run([Opcode.ROT(1)])
|
vm.run([isa.ROT(1)])
|
||||||
|
|
||||||
with pytest.raises(InterpreterError):
|
with pytest.raises(InterpreterError):
|
||||||
vm.run([Opcode.TRUE(), Opcode.ROT(2)])
|
vm.run([isa.ROT(2)], stack=[FALSE])
|
||||||
|
|
||||||
|
|
||||||
def test_drop_too_many(vm):
|
def test_drop_too_many(vm):
|
||||||
with pytest.raises(InterpreterError):
|
with pytest.raises(InterpreterError):
|
||||||
vm.run([Opcode.DROP(1)])
|
vm.run([isa.DROP(1)])
|
||||||
|
|
||||||
with pytest.raises(InterpreterError):
|
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)
|
frame.slot(2)
|
||||||
assert frame.pop() == 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