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,13 +1,24 @@
"""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)
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
@ -24,7 +35,8 @@ class Opcode:
# 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):
@dataclass
class DUP(Opcode):
"""(A, B, ...) -> (A, B, ...)
Duplicate the top N items of the stack.
@ -33,7 +45,9 @@ class Opcode:
nargs: int = 1
class ROT(t.NamedTuple):
@dataclass
class ROT(Opcode):
"""(A, B, ... Z) -> (Z, A, B, ...)
Rotate the top N elements of the stack.
@ -42,7 +56,8 @@ class Opcode:
nargs: int = 2
class DROP(t.NamedTuple):
@dataclass
class DROP(Opcode):
"""(*) -> ()
Drop the top N items of the stack.
@ -51,7 +66,9 @@ class Opcode:
nargs: int = 1
class SLOT(t.NamedTuple):
@dataclass
class SLOT(Opcode):
"""(..., A) -> (A, ..., A)
Copy the Nth (counting up from 0 at the bottom of the stack) item to the top of the stack.
@ -65,7 +82,8 @@ class Opcode:
# Functional abstraction
####################################################################################################
class IDENTIFIERC(t.NamedTuple):
@dataclass
class IDENTIFIERC(Opcode):
"""() -> (IDENTIFIER)
An inline constant which produces an identifier to the stack.
@ -77,14 +95,17 @@ class Opcode:
val: str
class FUNREF(t.NamedTuple):
@dataclass
class FUNREF(Opcode):
"""(IDENTIFIER) -> (`FUNREF<... A to ... B>`)
Construct a reference to a static codepoint.
"""
class CALLF(t.NamedTuple):
@dataclass
class CALLF(Opcode):
"""(`FUNREF<... A to ... B>`, ... A) -> (... B)
Call [funref]
@ -99,7 +120,8 @@ class Opcode:
nargs: int = 0
class RETURN(t.NamedTuple):
@dataclass
class RETURN(Opcode):
"""(... A) -> ()
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
class CLOSUREF(t.NamedTuple):
@dataclass
class CLOSUREF(Opcode):
"""(`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.
@ -123,7 +146,9 @@ class Opcode:
nargs: int = 0
class CLOSUREC(t.NamedTuple):
@dataclass
class CLOSUREC(Opcode):
"""(`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.
@ -132,7 +157,8 @@ class Opcode:
nargs: int = 0
class CALLC(t.NamedTuple):
class CALLC(Opcode):
"""(`CLOSURE<... A to ... B>`, ... A) -> (... B)
Call [closure]
@ -153,21 +179,26 @@ class Opcode:
# FIXME: This lacks any sort of way to do dynamic type/field references
class TYPEREF(t.NamedTuple):
@dataclass
class TYPEREF(Opcode):
"""(IDENTIFIER) -> (TYPEREF)
Produces a TYPEREF to the type named by the provided IDENTIFIER.
"""
class VARIANTREF(t.NamedTuple):
@dataclass
class VARIANTREF(Opcode):
"""(IDENTIFIER, TYPEREF) -> (VARIANTREF)
Produce a VARIANTREF to an 'arm' of the given variant type.
"""
class VARIANT(t.NamedTuple):
@dataclass
class VARIANT(Opcode):
"""(VARIANTREF<a ⊢ A ⊂ B>, ...) -> (B)
Construct an instance of an 'arm' of a variant.
@ -180,7 +211,9 @@ class Opcode:
nargs: int = 0
class VTEST(t.NamedTuple):
@dataclass
class VTEST(Opcode):
"""(VARIANTREF<a ⊢ A ⊂ B>, B) -> ()
Test whether B is a given arm of a variant A .
@ -191,7 +224,9 @@ class Opcode:
target: int
class VLOAD(t.NamedTuple):
@dataclass
class VLOAD(Opcode):
"""(VARIANTREF<a ⊢ A ⊂ B>, B) -> (A)
Load the value of the variant arm.
@ -200,6 +235,8 @@ class Opcode:
"""
class BREAK(t.NamedTuple):
@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