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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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