optimizations
Some checks failed
Build / build (push) Successful in 9m47s
Publish Image / publish (push) Failing after 49s
Test / build (push) Failing after 6m19s

This commit is contained in:
Chuck Smith
2024-04-02 14:32:03 -04:00
parent 07fd82b261
commit 88e3330856
18 changed files with 510 additions and 31 deletions

View File

@@ -127,6 +127,7 @@ func (b *Boolean) expressionNode() {}
func (b *Boolean) TokenLiteral() string { return b.Token.Literal }
func (b *Boolean) String() string { return b.Token.Literal }
// IntegerLiteral represents a literal integer number and holds an int64 value
type IntegerLiteral struct {
Token token.Token
Value int64
@@ -136,6 +137,20 @@ func (il *IntegerLiteral) expressionNode() {}
func (il *IntegerLiteral) TokenLiteral() string { return il.Token.Literal }
func (il *IntegerLiteral) String() string { return il.Token.Literal }
// FloatLiteral represents a literal floating point number and holds an float64 value
type FloatLiteral struct {
Token token.Token
Value float64
}
func (fl *FloatLiteral) expressionNode() {}
// TokenLiteral prints the literal value of the token associated with this node
func (fl *FloatLiteral) TokenLiteral() string { return fl.Token.Literal }
// String returns a stringified version of the AST for debugging
func (fl *FloatLiteral) String() string { return fl.Token.Literal }
type PrefixExpression struct {
Token token.Token // The prefix token, e.g. !
Operator string

View File

@@ -23,6 +23,9 @@ func Len(ctx context.Context, args ...object.Object) object.Object {
case *object.Hash:
return object.Integer{Value: int64(obj.Len())}
default:
if obj == nil {
return newError("TypeError: object is nil")
}
return newError("TypeError: object of type '%s' has no len()", args[0].Type())
}
}

View File

@@ -173,8 +173,12 @@ func (c *Compiler) Compile(node ast.Node) error {
}
case *ast.IntegerLiteral:
integer := object.Integer{Value: node.Value}
c.emit(code.OpConstant, c.addConstant(integer))
i := object.Integer{Value: node.Value}
c.emit(code.OpConstant, c.addConstant(i))
case *ast.FloatLiteral:
f := object.Float{Value: node.Value}
c.emit(code.OpConstant, c.addConstant(f))
case *ast.Null:
c.emit(code.OpNull)

View File

@@ -165,6 +165,78 @@ func TestIntegerArithmetic(t *testing.T) {
runCompilerTests(t, tests)
}
func TestFloatingPointArithmetic(t *testing.T) {
tests := []compilerTestCase{
{
input: "1.2 + 2.3",
expectedConstants: []interface{}{1.2, 2.3},
expectedInstructions: []code.Instructions{
code.Make(code.OpConstant, 0),
code.Make(code.OpConstant, 1),
code.Make(code.OpAdd),
code.Make(code.OpPop),
code.Make(code.OpHalt),
},
},
{
input: "1.2; 2.3",
expectedConstants: []interface{}{1.2, 2.3},
expectedInstructions: []code.Instructions{
code.Make(code.OpConstant, 0),
code.Make(code.OpPop),
code.Make(code.OpConstant, 1),
code.Make(code.OpPop),
code.Make(code.OpHalt),
},
},
{
input: "2.5 - 0.5",
expectedConstants: []interface{}{2.5, 0.5},
expectedInstructions: []code.Instructions{
code.Make(code.OpConstant, 0),
code.Make(code.OpConstant, 1),
code.Make(code.OpSub),
code.Make(code.OpPop),
code.Make(code.OpHalt),
},
},
{
input: "1.2 * 2.3",
expectedConstants: []interface{}{1.2, 2.3},
expectedInstructions: []code.Instructions{
code.Make(code.OpConstant, 0),
code.Make(code.OpConstant, 1),
code.Make(code.OpMul),
code.Make(code.OpPop),
code.Make(code.OpHalt),
},
},
{
input: "5.0 / 2.0",
expectedConstants: []interface{}{5.0, 2.0},
expectedInstructions: []code.Instructions{
code.Make(code.OpConstant, 0),
code.Make(code.OpConstant, 1),
code.Make(code.OpDiv),
code.Make(code.OpPop),
code.Make(code.OpHalt),
},
},
{
input: "-2.5",
expectedConstants: []interface{}{2.5},
expectedInstructions: []code.Instructions{
code.Make(code.OpConstant, 0),
code.Make(code.OpMinus),
code.Make(code.OpPop),
code.Make(code.OpHalt),
},
},
}
runCompilerTests(t, tests)
}
func TestBooleanExpressions(t *testing.T) {
tests := []compilerTestCase{
{

View File

@@ -133,6 +133,9 @@ func Eval(ctx context.Context, node ast.Node, env *object.Environment) object.Ob
case *ast.IntegerLiteral:
return object.Integer{Value: node.Value}
case *ast.FloatLiteral:
return object.Float{Value: node.Value}
case *ast.Boolean:
return nativeBoolToBooleanObject(node.Value)
@@ -401,6 +404,8 @@ func evalInfixExpression(operator string, left, right object.Object) object.Obje
return evalBooleanInfixExpression(operator, left, right)
case left.Type() == object.IntegerType && right.Type() == object.IntegerType:
return evalIntegerInfixExpression(operator, left, right)
case left.Type() == right.Type() && left.Type() == object.FloatType:
return evalFloatInfixExpression(operator, left, right)
case left.Type() == object.StringType && right.Type() == object.StringType:
return evalStringInfixExpression(operator, left, right)
@@ -409,6 +414,36 @@ func evalInfixExpression(operator string, left, right object.Object) object.Obje
}
}
func evalFloatInfixExpression(operator string, left, right object.Object) object.Object {
leftVal := left.(object.Float).Value
rightVal := right.(object.Float).Value
switch operator {
case "+":
return object.Float{Value: leftVal + rightVal}
case "-":
return object.Float{Value: leftVal - rightVal}
case "*":
return object.Float{Value: leftVal * rightVal}
case "/":
return object.Float{Value: leftVal / rightVal}
case "<":
return nativeBoolToBooleanObject(leftVal < rightVal)
case "<=":
return nativeBoolToBooleanObject(leftVal <= rightVal)
case ">":
return nativeBoolToBooleanObject(leftVal > rightVal)
case ">=":
return nativeBoolToBooleanObject(leftVal >= rightVal)
case "==":
return nativeBoolToBooleanObject(leftVal == rightVal)
case "!=":
return nativeBoolToBooleanObject(leftVal != rightVal)
default:
return NULL
}
}
func evalBooleanInfixExpression(operator string, left, right object.Object) object.Object {
leftVal := left.(object.Boolean).Value
rightVal := right.(object.Boolean).Value

View File

@@ -57,7 +57,7 @@ func assertEvaluated(t *testing.T, expected interface{}, actual object.Object) {
func TestEvalExpressions(t *testing.T) {
tests := []struct {
input string
expected interface{}
expected any
}{
{"5", 5},
{"10", 10},
@@ -84,12 +84,15 @@ func TestEvalExpressions(t *testing.T) {
{`4 * " "`, " "},
{"1 << 2", 4},
{"4 >> 2", 1},
{"5.0 / 2.0", 2.5},
}
for _, tt := range tests {
evaluated := testEval(tt.input)
if expected, ok := tt.expected.(int64); ok {
testIntegerObject(t, evaluated, expected)
} else if expected, ok := tt.expected.(float64); ok {
testFloatObject(t, evaluated, expected)
} else if expected, ok := tt.expected.(bool); ok {
testBooleanObject(t, evaluated, expected)
}
@@ -405,17 +408,45 @@ func TestClosures(t *testing.T) {
testIntegerObject(t, testEval(input), 4)
}
func TestIntegerLiteral(t *testing.T) {
input := `2`
evaluated := testEval(input)
obj, ok := evaluated.(object.Integer)
if !ok {
t.Fatalf("object is not Integer. got=%T (%+v)", evaluated, evaluated)
}
if obj.Value != 2 {
t.Errorf("Integer has wrong value. got=%q", obj.Value)
}
}
func TestFloatLiteral(t *testing.T) {
input := `2.5`
evaluated := testEval(input)
obj, ok := evaluated.(object.Float)
if !ok {
t.Fatalf("object is not Float. got=%T (%+v)", evaluated, evaluated)
}
if obj.Value != 2.5 {
t.Errorf("Float has wrong value. got=%f", obj.Value)
}
}
func TestStringLiteral(t *testing.T) {
input := `"Hello World!"`
evaluated := testEval(input)
str, ok := evaluated.(object.String)
obj, ok := evaluated.(object.String)
if !ok {
t.Fatalf("object is not String. got=%T (+%v)", evaluated, evaluated)
}
if str.Value != "Hello World!" {
t.Errorf("String has wrong value. got=%q", str.Value)
if obj.Value != "Hello World!" {
t.Errorf("String has wrong value. got=%q", obj.Value)
}
}
@@ -803,6 +834,20 @@ func testEval(input string) object.Object {
return Eval(context.New(), program, env)
}
func testFloatObject(t *testing.T, obj object.Object, expected float64) bool {
result, ok := obj.(object.Float)
if !ok {
t.Errorf("object is not Float. got=%T (%+v)", obj, obj)
return false
}
if result.Value != expected {
t.Errorf("object has wrong value. got=%f, want=%f",
result.Value, expected)
return false
}
return true
}
func testIntegerObject(t *testing.T, obj object.Object, expected int64) bool {
result, ok := obj.(object.Integer)
if !ok {

View File

@@ -182,9 +182,7 @@ func (l *Lexer) NextToken() token.Token {
tok.Type = token.LookupIdent(tok.Literal)
return tok
} else if isDigit(l.ch) {
tok.Type = token.INT
tok.Literal = l.readNumber()
return tok
return l.readNumber()
} else {
tok = newToken(token.ILLEGAL, l.ch)
}
@@ -209,12 +207,22 @@ func (l *Lexer) readIdentifier() string {
return l.input[position:l.position]
}
func (l *Lexer) readNumber() string {
func (l *Lexer) readNumber() token.Token {
position := l.position
for isDigit(l.ch) {
l.readChar()
}
return l.input[position:l.position]
intPart := l.input[position:l.position]
if l.ch == '.' {
l.readChar()
position := l.position
for isDigit(l.ch) {
l.readChar()
}
fracPart := l.input[position:l.position]
return token.Token{Type: token.FLOAT, Literal: intPart + "." + fracPart}
}
return token.Token{Type: token.INT, Literal: intPart}
}
func isLetter(ch byte) bool {

View File

@@ -210,3 +210,35 @@ c := "\r\n\t"
}
}
func TestFloats(t *testing.T) {
input := `2.5
3.14
`
tests := []struct {
expectedType token.TokenType
expectedLiteral string
}{
{token.FLOAT, "2.5"},
{token.FLOAT, "3.14"},
{token.EOF, ""},
}
lexer := New(input)
for i, test := range tests {
token := lexer.NextToken()
if token.Type != test.expectedType {
t.Fatalf("tests[%d] - token type wrong. expected=%q, got=%q",
i, test.expectedType, token.Type)
}
if token.Literal != test.expectedLiteral {
t.Fatalf("tests[%d] - literal wrong. expected=%q, got=%q",
i, test.expectedLiteral, token.Literal)
}
}
}

111
internal/object/float.go Normal file
View File

@@ -0,0 +1,111 @@
package object
import (
"fmt"
"math"
)
// Float is the floating-point number type used to represent float literals and holds
// an internal float64 value
type Float struct {
Value float64
}
func (f Float) Bool() bool {
return f.Value != 0
}
func (f Float) Add(other Object) (Object, error) {
switch obj := other.(type) {
case Float:
return Float{f.Value + obj.Value}, nil
default:
return nil, NewBinaryOpError(f, other, "+")
}
}
func (f Float) Sub(other Object) (Object, error) {
switch obj := other.(type) {
case Float:
return Float{f.Value - obj.Value}, nil
default:
return nil, NewBinaryOpError(f, other, "-")
}
}
func (f Float) Mul(other Object) (Object, error) {
switch obj := other.(type) {
case Float:
return Float{f.Value * obj.Value}, nil
case String:
return obj.Mul(f)
case *Array:
return obj.Mul(f)
default:
return nil, NewBinaryOpError(f, other, "*")
}
}
func (f Float) Div(other Object) (Object, error) {
switch obj := other.(type) {
case Float:
if obj.Value == 0 {
return nil, NewDivisionByZeroError(f)
}
return Float{f.Value / obj.Value}, nil
default:
return nil, NewBinaryOpError(f, other, "/")
}
}
func (f Float) Mod(other Object) (Object, error) {
switch obj := other.(type) {
case Float:
return Float{math.Mod(f.Value, obj.Value)}, nil
default:
return nil, NewBinaryOpError(f, other, "%")
}
}
func (f Float) LogicalNot() Object {
return Boolean{!f.Bool()}
}
func (f Float) Negate() Object {
return Float{-f.Value}
}
func (f Float) Compare(other Object) int {
switch obj := other.(type) {
case Float:
if f.Value < obj.Value {
return -1
}
if f.Value > obj.Value {
return 1
}
return 0
default:
return -1
}
}
func (f Float) String() string {
return f.Inspect()
}
// Copy implements the Copyable interface
func (i Float) Copy() Object {
return Float{Value: i.Value}
}
// Hash implements the Hasher interface
func (i Float) Hash() HashKey {
return HashKey{Type: i.Type(), Value: uint64(i.Value)}
}
// Type returns the type of the object
func (i Float) Type() Type { return FloatType }
// Inspect returns a stringified version of the object for debugging
func (i Float) Inspect() string { return fmt.Sprintf("%f", i.Value) }

View File

@@ -11,6 +11,7 @@ type Type int
const (
NullType = iota
IntegerType
FloatType
StringType
BooleanType
ReturnType
@@ -30,6 +31,8 @@ func (t Type) String() string {
return "null"
case IntegerType:
return "int"
case FloatType:
return "float"
case StringType:
return "str"
case BooleanType:

View File

@@ -82,6 +82,7 @@ func New(fn string, l *lexer.Lexer) *Parser {
p.prefixParseFns = make(map[token.TokenType]prefixParseFn)
p.registerPrefix(token.IDENT, p.parseIdentifier)
p.registerPrefix(token.INT, p.parseIntegerLiteral)
p.registerPrefix(token.FLOAT, p.parseFloatLiteral)
p.registerPrefix(token.MINUS, p.parsePrefixExpression)
p.registerPrefix(token.TRUE, p.parseBoolean)
p.registerPrefix(token.FALSE, p.parseBoolean)
@@ -622,6 +623,21 @@ func (p *Parser) parseImportExpression() ast.Expression {
return expression
}
func (p *Parser) parseFloatLiteral() ast.Expression {
lit := &ast.FloatLiteral{Token: p.curToken}
value, err := strconv.ParseFloat(p.curToken.Literal, 64)
if err != nil {
msg := fmt.Sprintf("could not parse %q as float", p.curToken.Literal)
p.errors = append(p.errors, msg)
return nil
}
lit.Value = value
return lit
}
// Parse parses the input source into a top-level AST for either evaluation
// or compilation to bytecode. The parameter fn denotes the filename the source
// originated from.

View File

@@ -126,6 +126,37 @@ func TestIntegerLiteralExpression(t *testing.T) {
}
}
func TestFloatLiteralExpression(t *testing.T) {
input := "2.5;"
l := lexer.New(input)
p := New("<test>", l)
program := p.ParseProgram()
checkParserErrors(t, p)
if len(program.Statements) != 1 {
t.Fatalf("program has not enough statements. got=%d",
len(program.Statements))
}
stmt, ok := program.Statements[0].(*ast.ExpressionStatement)
if !ok {
t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T",
program.Statements[0])
}
literal, ok := stmt.Expression.(*ast.FloatLiteral)
if !ok {
t.Fatalf("exp not *ast.FloatLiteral. got=%T", stmt.Expression)
}
if literal.Value != 2.5 {
t.Errorf("literal.Value not %f. got=%f", 2.5, literal.Value)
}
if literal.TokenLiteral() != "2.5" {
t.Errorf("literal.TokenLiteral not %s. got=%s", "2.5",
literal.TokenLiteral())
}
}
func TestParsingPrefixExpressions(t *testing.T) {
prefixTests := []struct {
input string

View File

@@ -17,6 +17,8 @@ const (
// Identifiers + literals
IDENT = "IDENT" // add, foobar, x, y
INT = "INT" // 123456
// FLOAT is a floating point, e.g: 1.24
FLOAT = "FLOAT"
STRING = "STRING"
// Operators

View File

@@ -55,7 +55,3 @@ func (f *frame) ReadUint16() uint16 {
f.ip += 2
return n
}
func (f frame) Instructions() code.Instructions {
return f.cl.Fn.Instructions
}

View File

@@ -2,6 +2,7 @@ package vm
import (
"fmt"
"log"
"monkey/internal/builtins"
"monkey/internal/code"
"monkey/internal/compiler"
@@ -12,6 +13,8 @@ import (
"monkey/internal/utils"
"os"
"path/filepath"
"sort"
"time"
"unicode"
)
@@ -335,6 +338,12 @@ func (vm *VM) executeAdd() error {
return err
}
return vm.push(val)
case object.Float:
val, err := obj.Add(right)
if err != nil {
return err
}
return vm.push(val)
case object.String:
val, err := obj.Add(right)
if err != nil {
@@ -368,6 +377,12 @@ func (vm *VM) executeSub() error {
return err
}
return vm.push(val)
case object.Float:
val, err := obj.Sub(right)
if err != nil {
return err
}
return vm.push(val)
default:
return fmt.Errorf("unsupported types for unary operation: -%s", left.Type())
}
@@ -389,6 +404,12 @@ func (vm *VM) executeMul() error {
return err
}
return vm.push(val)
case object.Float:
val, err := obj.Mul(right)
if err != nil {
return err
}
return vm.push(val)
case object.String:
val, err := obj.Mul(right)
if err != nil {
@@ -410,6 +431,12 @@ func (vm *VM) executeDiv() error {
return err
}
return vm.push(val)
case object.Float:
val, err := obj.Div(right)
if err != nil {
return err
}
return vm.push(val)
default:
return object.NewBinaryOpError(left, right, "/")
}
@@ -591,6 +618,8 @@ func (vm *VM) executeNot() error {
return vm.push(obj.LogicalNot())
case object.Integer:
return vm.push(obj.LogicalNot())
case object.Float:
return vm.push(obj.LogicalNot())
case object.Null:
return vm.push(obj.LogicalNot())
default:
@@ -604,6 +633,8 @@ func (vm *VM) executeMinus() error {
switch obj := left.(type) {
case object.Integer:
return vm.push(obj.Negate())
case object.Float:
return vm.push(obj.Negate())
default:
return fmt.Errorf("unsupported types for unary operation: -%s", left.Type())
}
@@ -714,6 +745,7 @@ func (vm *VM) callClosure(cl *object.Closure, numArgs int) error {
func (vm *VM) callBuiltin(builtin object.Builtin, numArgs int) error {
args := vm.stack[vm.sp-numArgs : vm.sp]
log.Printf("args: %+v", args)
result := builtin.Fn(vm.ctx, args...)
vm.sp = vm.sp - numArgs - 1
@@ -764,10 +796,29 @@ func (vm *VM) LastPoppedStackElem() object.Object {
}
func (vm *VM) Run() (err error) {
start := time.Now()
freqs := make([]int, 255)
for err == nil {
op := vm.currentFrame().ReadNextOp()
if vm.debug {
freqs[op]++
}
if vm.trace {
log.Printf(
"%-25s M:%-20s\n",
fmt.Sprintf(
"%04d %s", vm.currentFrame().ip, op,
),
fmt.Sprintf(
"[ip=%02d fp=%02d, sp=%02d]",
vm.currentFrame().ip, vm.fp-1, vm.sp,
),
)
}
switch op {
case code.OpConstant:
constIndex := vm.currentFrame().ReadUint16()
@@ -925,6 +976,45 @@ func (vm *VM) Run() (err error) {
default:
err = fmt.Errorf("unhandled opcode: %s", op)
}
if vm.trace {
log.Printf(
"%-25s [ip=%02d fp=%02d, sp=%02d]",
"", vm.currentFrame().ip, vm.fp-1, vm.sp,
)
var globals []object.Object
for _, obj := range vm.state.Globals {
if obj != nil {
globals = append(globals, obj)
}
}
log.Printf("%-25s G:%+v", "", globals)
log.Printf("%-25s S:%+v", "", vm.stack[:vm.sp])
log.Printf("%-25s E:%+v", "", err)
}
}
if vm.debug {
end := time.Now().Sub(start)
total := 0
opcodes := make([]code.Opcode, 0, len(freqs))
for i, count := range freqs {
opcode := code.Opcode(i)
opcodes = append(opcodes, opcode)
total += count
}
sort.SliceStable(opcodes, func(i, j int) bool {
return freqs[opcodes[i]] > freqs[opcodes[j]]
})
log.Printf("%d instructions executed in %s %d/µs", total, end, total/int(end.Microseconds()))
log.Print("Top Instructions:")
for _, opcode := range opcodes[:min(len(opcodes), 10)] {
log.Printf("%10d %s", freqs[opcode], opcode)
}
}
return

View File

@@ -242,6 +242,21 @@ func TestIntegerArithmetic(t *testing.T) {
runVmTests(t, tests)
}
func TestFloatingPointArithmetic(t *testing.T) {
tests := []vmTestCase{
{"1.2", 1.2},
{"2.3", 2.3},
{"1.2 + 2.3", 3.5},
{"1.2 - 2.4", -1.2},
{"1.2 * 2.4", 2.88},
{"5.0 / 2.0", 2.5},
{"-2.5", -2.5},
{"!1.0", false},
}
runVmTests(t, tests)
}
func TestBooleanExpressions(t *testing.T) {
tests := []vmTestCase{
{"true", true},

View File

@@ -7,7 +7,6 @@ import (
"fmt"
"log"
"monkey/internal/compiler"
"monkey/internal/object"
"monkey/internal/parser"
"monkey/internal/vm"
"os"
@@ -24,7 +23,7 @@ fib := fn(n) {
return fib(n-1) + fib(n-2)
}
print(fib(%d))`
println(fib(%d))`
func checkErr(err error) {
if err != nil {
@@ -39,13 +38,12 @@ func readFile(path string) string {
return string(b)
}
func fileOrDefault() string {
func fileOrDefault() (string, []string) {
if flag.NArg() > 0 {
log.Printf("Using %s as program input...", flag.Arg(0))
object.Args = flag.Args()[1:]
return readFile(flag.Arg(0))
return readFile(flag.Arg(0)), flag.Args()[1:]
}
return defaultCode
return defaultCode, flag.Args()[:]
}
func main() {
@@ -60,7 +58,7 @@ func main() {
log.Printf("Using %d as n", *n)
code := fileOrDefault()
code, args := fileOrDefault()
if strings.Count(code, "%d") > 0 {
code = fmt.Sprintf(code, *n)
}
@@ -78,7 +76,7 @@ func main() {
checkErr(c.Compile(tree))
checkErr(pprof.StartCPUProfile(cpuf))
defer pprof.StopCPUProfile()
mvm := vm.New("<profiler>", c.Bytecode())
mvm := vm.New("<profiler>", c.Bytecode(), vm.WithContext(context.New(context.WithArgs(args))))
checkErr(mvm.Run())
runtime.GC()
checkErr(pprof.WriteHeapProfile(heapf))

15
repl.go
View File

@@ -7,6 +7,7 @@ import (
"io"
"log"
"os"
"runtime/debug"
"strings"
"golang.org/x/term"
@@ -32,10 +33,11 @@ func REPL(opts *Options) error {
return fmt.Errorf("error opening terminal: %w", err)
}
defer func() {
if err := recover(); err != nil {
log.Printf("recovered from panic: %s", err)
}
term.Restore(0, initState)
if err := recover(); err != nil {
log.Printf("recovered from panic: %s\n", err)
log.Printf("stack trace:\n%s", debug.Stack())
}
}()
atexit.Register(func() {
term.Restore(0, initState)
@@ -83,7 +85,7 @@ func REPL(opts *Options) error {
log.Printf("Bytecode:\n%s\n", c.Bytecode())
}
mvm := vm.New("<stdin>", c.Bytecode(), opts.ToVMOptions()...)
mvm := vm.New("<stdin>", c.Bytecode(), append(opts.ToVMOptions(), vm.WithState(state))...)
if err := mvm.Run(); err != nil {
fmt.Fprintf(t, "runtime error: %v\n", err)
@@ -151,7 +153,8 @@ func SimpleREPL(opts *Options) {
defer func() {
if err := recover(); err != nil {
log.Printf("recovered from panic: %s", err)
log.Printf("recovered from panic: %s\n", err)
log.Printf("stack trace: %s\n", debug.Stack())
}
}()
@@ -196,7 +199,7 @@ func SimpleREPL(opts *Options) {
log.Printf("Bytecode:\n%s\n", c.Bytecode())
}
mvm := vm.New("<stdin>", c.Bytecode(), opts.ToVMOptions()...)
mvm := vm.New("<stdin>", c.Bytecode(), append(opts.ToVMOptions(), vm.WithState(state))...)
if err := mvm.Run(); err != nil {
fmt.Printf("runtime error: %v\n", err)