Refactor to make Opcode the base class

This commit is contained in:
Reid 'arrdem' McKenzie 2022-07-15 19:33:32 -06:00
parent 6d9d9b3fd5
commit bd880213de
9 changed files with 358 additions and 269 deletions

View file

@ -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

View file

@ -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(),
] ]
) )

View file

@ -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:

View file

@ -1,13 +1,24 @@
"""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)
class Opcode(ABC):
pass
@dataclass
class GOTO(Opcode):
"""() -> () """() -> ()
Branch to another point within the same bytecode segment. The target MUST be within the same module range as the Branch to another point within the same bytecode segment. The target MUST be within the same module range as the
@ -17,14 +28,15 @@ class Opcode:
target: int target: int
#################################################################################################### ####################################################################################################
# Stack manipulation # Stack manipulation
#################################################################################################### ####################################################################################################
# https://wiki.laptop.org/go/Forth_stack_operators # https://wiki.laptop.org/go/Forth_stack_operators
# https://www.forth.com/starting-forth/2-stack-manipulation-operators-arithmetic/ # 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 # https://docs.oracle.com/javase/specs/jvms/se18/html/jvms-6.html#jvms-6.5.swap
class DUP(t.NamedTuple): @dataclass
class DUP(Opcode):
"""(A, B, ...) -> (A, B, ...) """(A, B, ...) -> (A, B, ...)
Duplicate the top N items of the stack. Duplicate the top N items of the stack.
@ -33,7 +45,9 @@ class Opcode:
nargs: int = 1 nargs: int = 1
class ROT(t.NamedTuple):
@dataclass
class ROT(Opcode):
"""(A, B, ... Z) -> (Z, A, B, ...) """(A, B, ... Z) -> (Z, A, B, ...)
Rotate the top N elements of the stack. Rotate the top N elements of the stack.
@ -42,7 +56,8 @@ class Opcode:
nargs: int = 2 nargs: int = 2
class DROP(t.NamedTuple): @dataclass
class DROP(Opcode):
"""(*) -> () """(*) -> ()
Drop the top N items of the stack. Drop the top N items of the stack.
@ -51,7 +66,9 @@ class Opcode:
nargs: int = 1 nargs: int = 1
class SLOT(t.NamedTuple):
@dataclass
class SLOT(Opcode):
"""(..., A) -> (A, ..., A) """(..., A) -> (A, ..., A)
Copy the Nth (counting up from 0 at the bottom of the stack) item to the top of the stack. Copy the Nth (counting up from 0 at the bottom of the stack) item to the top of the stack.
@ -61,11 +78,12 @@ class Opcode:
target: int target: int
#################################################################################################### ####################################################################################################
# Functional abstraction # Functional abstraction
#################################################################################################### ####################################################################################################
class IDENTIFIERC(t.NamedTuple): @dataclass
class IDENTIFIERC(Opcode):
"""() -> (IDENTIFIER) """() -> (IDENTIFIER)
An inline constant which produces an identifier to the stack. An inline constant which produces an identifier to the stack.
@ -77,14 +95,17 @@ class Opcode:
val: str val: str
class FUNREF(t.NamedTuple):
@dataclass
class FUNREF(Opcode):
"""(IDENTIFIER) -> (`FUNREF<... A to ... B>`) """(IDENTIFIER) -> (`FUNREF<... A to ... B>`)
Construct a reference to a static codepoint. Construct a reference to a static codepoint.
""" """
class CALLF(t.NamedTuple): @dataclass
class CALLF(Opcode):
"""(`FUNREF<... A to ... B>`, ... A) -> (... B) """(`FUNREF<... A to ... B>`, ... A) -> (... B)
Call [funref] Call [funref]
@ -99,7 +120,8 @@ class Opcode:
nargs: int = 0 nargs: int = 0
class RETURN(t.NamedTuple): @dataclass
class RETURN(Opcode):
"""(... A) -> () """(... A) -> ()
Return to the source of the last `CALL`. The returnee will see the top `nargs` values of the present stack Return to the source of the last `CALL`. The returnee will see the top `nargs` values of the present stack
@ -114,7 +136,8 @@ class Opcode:
nargs: int nargs: int
class CLOSUREF(t.NamedTuple): @dataclass
class CLOSUREF(Opcode):
"""(`FUNREF<A, ... B to ... C>`, A) -> (`CLOSURE<... B to ... C>`) """(`FUNREF<A, ... B to ... C>`, A) -> (`CLOSURE<... B to ... C>`)
Construct a closure over the function reference at the top of the stack. This may produce nullary closures. Construct a closure over the function reference at the top of the stack. This may produce nullary closures.
@ -123,7 +146,9 @@ class Opcode:
nargs: int = 0 nargs: int = 0
class CLOSUREC(t.NamedTuple):
@dataclass
class CLOSUREC(Opcode):
"""(`CLOSURE<A, ... B to ... C>`, A) -> (`CLOSURE<... B to ... C>`) """(`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. Further close over the closure at the top of the stack. This may produce nullary closures.
@ -132,7 +157,8 @@ class Opcode:
nargs: int = 0 nargs: int = 0
class CALLC(t.NamedTuple):
class CALLC(Opcode):
"""(`CLOSURE<... A to ... B>`, ... A) -> (... B) """(`CLOSURE<... A to ... B>`, ... A) -> (... B)
Call [closure] Call [closure]
@ -147,27 +173,32 @@ class Opcode:
nargs: int = 0 nargs: int = 0
#################################################################################################### ####################################################################################################
# Structures # Structures
#################################################################################################### ####################################################################################################
# FIXME: This lacks any sort of way to do dynamic type/field references # FIXME: This lacks any sort of way to do dynamic type/field references
class TYPEREF(t.NamedTuple): @dataclass
class TYPEREF(Opcode):
"""(IDENTIFIER) -> (TYPEREF) """(IDENTIFIER) -> (TYPEREF)
Produces a TYPEREF to the type named by the provided IDENTIFIER. Produces a TYPEREF to the type named by the provided IDENTIFIER.
""" """
class VARIANTREF(t.NamedTuple):
@dataclass
class VARIANTREF(Opcode):
"""(IDENTIFIER, TYPEREF) -> (VARIANTREF) """(IDENTIFIER, TYPEREF) -> (VARIANTREF)
Produce a VARIANTREF to an 'arm' of the given variant type. Produce a VARIANTREF to an 'arm' of the given variant type.
""" """
class VARIANT(t.NamedTuple):
@dataclass
class VARIANT(Opcode):
"""(VARIANTREF<a ⊢ A ⊂ B>, ...) -> (B) """(VARIANTREF<a ⊢ A ⊂ B>, ...) -> (B)
Construct an instance of an 'arm' of a variant. Construct an instance of an 'arm' of a variant.
@ -180,7 +211,9 @@ class Opcode:
nargs: int = 0 nargs: int = 0
class VTEST(t.NamedTuple):
@dataclass
class VTEST(Opcode):
"""(VARIANTREF<a ⊢ A ⊂ B>, B) -> () """(VARIANTREF<a ⊢ A ⊂ B>, B) -> ()
Test whether B is a given arm of a variant A . Test whether B is a given arm of a variant A .
@ -191,7 +224,9 @@ class Opcode:
target: int target: int
class VLOAD(t.NamedTuple):
@dataclass
class VLOAD(Opcode):
"""(VARIANTREF<a ⊢ A ⊂ B>, B) -> (A) """(VARIANTREF<a ⊢ A ⊂ B>, B) -> (A)
Load the value of the variant arm. Load the value of the variant arm.
@ -200,6 +235,8 @@ class Opcode:
""" """
class BREAK(t.NamedTuple):
@dataclass
class BREAK(Opcode):
"""Abort the interpreter.""" """Abort the interpreter."""
pass pass

View file

@ -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])

View file

@ -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),
] ]

View file

@ -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

View file

@ -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])

View file

@ -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