From fb0cebaf5645dddfc2d21a109b5df08cc6dc0b97 Mon Sep 17 00:00:00 2001 From: Chuck Smith Date: Thu, 28 Mar 2024 17:19:23 -0400 Subject: [PATCH] bunch of changes --- .gitignore | 4 +- cmd/monkey/main.go | 16 ++- examples/fib.c | 33 +++++ examples/fib.go | 25 ++++ examples/fib.monkey | 2 +- examples/fib.py | 22 ++++ examples/fib.tau | 10 ++ go.mod | 6 +- go.sum | 7 + internal/ast/ast.go | 8 +- internal/object/state.go | 2 +- internal/vm/cache.go | 270 --------------------------------------- internal/vm/frame.go | 15 +-- internal/vm/vm.go | 18 +-- monkey.go | 27 +++- repl.go | 29 ++++- 16 files changed, 174 insertions(+), 320 deletions(-) create mode 100644 examples/fib.c create mode 100644 examples/fib.go create mode 100644 examples/fib.py create mode 100644 examples/fib.tau delete mode 100644 internal/vm/cache.go diff --git a/.gitignore b/.gitignore index fcae1b9..4025deb 100644 --- a/.gitignore +++ b/.gitignore @@ -84,10 +84,12 @@ fabric.properties *.out *.swo *.swn +*.pyc *.prof **/.DS_Store /dist /.vscode -/monkey \ No newline at end of file +/monkey +/examples/fib \ No newline at end of file diff --git a/cmd/monkey/main.go b/cmd/monkey/main.go index 6aead28..4463234 100644 --- a/cmd/monkey/main.go +++ b/cmd/monkey/main.go @@ -12,27 +12,29 @@ func main() { compile bool version bool simple bool + debug bool ) - flag.BoolVar(&compile, "c", false, "Compile a monkey file into a '.monkeyc' bytecode file.") - flag.BoolVar(&simple, "s", false, "Use simple REPL instead of opening a terminal.") - flag.BoolVar(&version, "v", false, "Print Monkey version information.") + flag.BoolVar(&compile, "c", false, "Compile a monkey file into a '.mc' bytecode file") + flag.BoolVar(&simple, "s", false, "Use simple REPL instead of opening a terminal") + flag.BoolVar(&version, "v", false, "Print Monkey version information") + flag.BoolVar(&debug, "d", false, "Enable debug mode") flag.Parse() switch { case compile: - monkey.CompileFiles(flag.Args()) + monkey.CompileFiles(flag.Args(), debug) case version: monkey.PrintVersionInfo(os.Stdout) case flag.NArg() > 0: - monkey.ExecFileVM(flag.Arg(0)) + monkey.ExecFileVM(flag.Arg(0), flag.Args()[1:], debug) case simple: - monkey.SimpleVmREPL() + monkey.SimpleVmREPL(flag.Args(), debug) default: - monkey.VmREPL() + monkey.VmREPL(flag.Args(), debug) } } diff --git a/examples/fib.c b/examples/fib.c new file mode 100644 index 0000000..50554f4 --- /dev/null +++ b/examples/fib.c @@ -0,0 +1,33 @@ +#include +#include +#include +#include + +int n = 35; + +int fib(int x) { + if (x < 2) { + return x; + } + return fib(x - 1) + fib(x - 2); +} + +int main(int argc, char *argv[]) { + if (argc > 1) { + char*p; + + errno = 0; + long conv = strtol(argv[1], &p, 10); + + // Check for errors: e.g., the string does not represent an integer + // or the integer is larger than int + if (errno != 0 || *p != '\0' || conv > INT_MAX || conv < INT_MIN) { + printf("invalid argument: %s\n", p); + exit(1); + } + + n = conv; + } + + printf("%d\n", fib(n)); +} \ No newline at end of file diff --git a/examples/fib.go b/examples/fib.go new file mode 100644 index 0000000..003f817 --- /dev/null +++ b/examples/fib.go @@ -0,0 +1,25 @@ +//go:build ignore + +package main + +import ( + "fmt" + "os" + "strconv" +) + +func fib(x int) int { + if x < 2 { + return x + } + return fib(x-1) + fib(x-2) +} + +var n = 35 + +func main() { + if len(os.Args) > 2 { + n, _ = strconv.Atoi(os.Args[1]) + } + fmt.Println(fib(n)) +} diff --git a/examples/fib.monkey b/examples/fib.monkey index f20d3c4..9999a60 100644 --- a/examples/fib.monkey +++ b/examples/fib.monkey @@ -7,7 +7,7 @@ fib := fn(x) { N := 35 -if (len(args()) == 1) { +if (len(args()) > 0) { N = int(args()[0]) } diff --git a/examples/fib.py b/examples/fib.py new file mode 100644 index 0000000..fb8879c --- /dev/null +++ b/examples/fib.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 + +import sys +print(sys.argv) + +n = 35 + +def fib(x): + if (x < 2): + return x + return fib(x-1) + fib(x-2) + +def main(): + global n + + if len(sys.argv) > 1: + n = int(sys.argv[1]) + + print("{}\n".format(fib(n))) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/examples/fib.tau b/examples/fib.tau new file mode 100644 index 0000000..a2d8b75 --- /dev/null +++ b/examples/fib.tau @@ -0,0 +1,10 @@ +#!/usr/bin/env tau + +fib = fn(n) { + if n < 2 { + return n + } + fib(n-1) + fib(n-2) +} + +println(fib(35)) \ No newline at end of file diff --git a/go.mod b/go.mod index a612a10..557e906 100644 --- a/go.mod +++ b/go.mod @@ -3,13 +3,15 @@ module monkey go 1.21 require ( - github.com/stretchr/testify v1.8.4 github.com/pkg/profile v1.7.0 - golang.org/x/term v0.15.0 + github.com/stretchr/testify v1.8.4 + github.com/tebeka/atexit v0.3.0 + golang.org/x/term v0.18.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + golang.org/x/sys v0.18.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 5b3a470..574f0b8 100644 --- a/go.sum +++ b/go.sum @@ -12,14 +12,21 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/tebeka/atexit v0.3.0/go.mod h1:WJmSUSmMT7WoR7etUOaGBVXk+f5/ZJ+67qwuedq7Fbs= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/ast/ast.go b/internal/ast/ast.go index 174be7f..f5ae49b 100644 --- a/internal/ast/ast.go +++ b/internal/ast/ast.go @@ -38,13 +38,13 @@ func (p *Program) TokenLiteral() string { } func (p *Program) String() string { - var out bytes.Buffer + var s strings.Builder - for _, s := range p.Statements { - out.WriteString(s.String()) + for _, stmt := range p.Statements { + s.WriteString(fmt.Sprintf("%s\n", stmt.String())) } - return out.String() + return s.String() } type ReturnStatement struct { diff --git a/internal/object/state.go b/internal/object/state.go index 288d1c4..0b18f7e 100644 --- a/internal/object/state.go +++ b/internal/object/state.go @@ -6,7 +6,7 @@ import ( ) var ( - Args []string = os.Args + Args []string = os.Args[1:] // Skip the monkey binary itself Stdin io.Reader = os.Stdin Stdout io.Writer = os.Stdout Stderr io.Writer = os.Stderr diff --git a/internal/vm/cache.go b/internal/vm/cache.go deleted file mode 100644 index cf09c32..0000000 --- a/internal/vm/cache.go +++ /dev/null @@ -1,270 +0,0 @@ -package vm - -import ( - "container/list" - "fmt" - "sync" -) - -type cachedFrame struct { - Key uint // pointer to key - Value *Frame // pointer to value - Frequency uint // frequency of access -} - -type frequencyNode struct { - count uint // frequency count - never decreases - valuesList *list.List // valuesList contains pointer to the head of values linked list - inner *list.Element // actual content of the next element -} - -// creates a new frequency list node with the given count -func newFrequencyNode(count uint) *frequencyNode { - return &frequencyNode{ - count: count, - valuesList: list.New(), - inner: nil, - } -} - -type keyRefNode struct { - inner *list.Element // contains the actual value wrapped by a list element - parentFreqNode *list.Element // contains reference to the frequency node element - keyRef uint // contains pointer to the key - valueRef *Frame // value -} - -// creates a new KeyRef node which is used to represent the value in linked list -func newKeyRefNode(keyRef uint, valueRef *Frame, parent *list.Element) *keyRefNode { - return &keyRefNode{ - inner: nil, - parentFreqNode: parent, - keyRef: keyRef, - valueRef: valueRef, - } -} - -// FrameCache implements all the methods and data-structures required for a LFU cache for caching frames. -type FrameCache struct { - rwLock sync.RWMutex // rwLock is a read-write mutex which provides concurrent reads but exclusive writes - lookupTable map[uint]*keyRefNode // a hash table of for quick reference of values based on keys - frequencies *list.List // internal linked list that contains frequency mapping - maxSize uint // maxSize represents the maximum number of elements that can be in the cache before eviction -} - -// MaxSize returns the maximum size of the cache at that point in time -func (lfu *FrameCache) MaxSize() uint { - lfu.rwLock.RLock() - defer lfu.rwLock.RUnlock() - - return lfu.maxSize -} - -// CurrentSize returns the number of elements in that cache -func (lfu *FrameCache) CurrentSize() uint { - lfu.rwLock.RLock() - defer lfu.rwLock.RUnlock() - - return uint(len(lfu.lookupTable)) -} - -// IsFull checks if the LFU cache is full -func (lfu *FrameCache) IsFull() bool { - lfu.rwLock.RLock() - defer lfu.rwLock.RUnlock() - - return uint(len(lfu.lookupTable)) == lfu.maxSize -} - -// SetMaxSize updates the max size of the LFU cache -func (lfu *FrameCache) SetMaxSize(size uint) { - lfu.rwLock.Lock() - defer lfu.rwLock.Unlock() - - lfu.maxSize = size -} - -// evict the least recently used element from the cache, this function is unsafe to be called externally -// because it doesn't provide locking mechanism. -func (lfu *FrameCache) unsafeEvict() error { - // WARNING: This function assumes that a write lock has been held by the caller already - - // get the head node of the list - headFreq := lfu.frequencies.Front() - if headFreq == nil { - // list is empty, this is a very unusual condition - return fmt.Errorf("internal error: failed to evict, empty frequency list") - } - - headFreqInner := (headFreq.Value).(*frequencyNode) - - if headFreqInner.valuesList.Len() == 0 { - // again this is a very unusual condition - return fmt.Errorf("internal error: failed to evict, empty values list") - } - - headValuesList := headFreqInner.valuesList - // pop the head of this this values list - headValueNode := headValuesList.Front() - removeResult := headValuesList.Remove(headValueNode).(*keyRefNode) - - // update the values list - headFreqInner.valuesList = headValuesList - - if headFreqInner.valuesList.Len() == 0 && headFreqInner.count > 1 { - // this node can be removed from the frequency list - freqList := lfu.frequencies - freqList.Remove(headFreq) - lfu.frequencies = freqList - } - - // remove the key from lookup table - key := removeResult.keyRef - delete(lfu.lookupTable, key) - return nil -} - -// Put method inserts a `` to the LFU cache and updates internal -// data structures to keep track of access frequencies, if the cache is full, it evicts the -// least frequently used value from the cache. -func (lfu *FrameCache) Put(key uint, value *Frame) error { - // get write lock - lfu.rwLock.Lock() - defer lfu.rwLock.Unlock() - - if _, ok := lfu.lookupTable[key]; ok { - // update the cache value - lfu.lookupTable[key].valueRef = value - return nil - } - - if lfu.maxSize == uint(len(lfu.lookupTable)) { - lfu.unsafeEvict() - } - - valueNode := newKeyRefNode(key, value, nil) - - head := lfu.frequencies.Front() - if head == nil { - // fresh linked list - freqNode := newFrequencyNode(1) - head = lfu.frequencies.PushFront(freqNode) - freqNode.inner = head - - } else { - node := head.Value.(*frequencyNode) - if node.count != 1 { - freqNode := newFrequencyNode(1) - head = lfu.frequencies.PushFront(freqNode) - freqNode.inner = head - } - } - - valueNode.parentFreqNode = head - node := head.Value.(*frequencyNode) - head = node.valuesList.PushBack(valueNode) - valueNode.inner = head - - lfu.lookupTable[key] = valueNode - return nil -} - -// Evict can be called to manually perform eviction -func (lfu *FrameCache) Evict() error { - lfu.rwLock.Lock() - defer lfu.rwLock.Unlock() - - return lfu.unsafeEvict() -} - -func (lfu *FrameCache) unsafeUpdateFrequency(valueNode *keyRefNode) { - parentFreqNode := valueNode.parentFreqNode - currentNode := parentFreqNode.Value.(*frequencyNode) - nextParentFreqNode := parentFreqNode.Next() - - var newParent *list.Element = nil - - if nextParentFreqNode == nil { - // this is the last node - // create a new node with frequency + 1 - newFreqNode := newFrequencyNode(currentNode.count + 1) - lfu.frequencies.PushBack(newFreqNode) - newParent = parentFreqNode.Next() - - } else { - nextNode := nextParentFreqNode.Value.(*frequencyNode) - if nextNode.count == (currentNode.count + 1) { - newParent = nextParentFreqNode - } else { - // insert a node in between - newFreqNode := newFrequencyNode(currentNode.count + 1) - - lfu.frequencies.InsertAfter(newFreqNode, parentFreqNode) - newParent = parentFreqNode.Next() - } - } - - // remove from the existing list - currentNode.valuesList.Remove(valueNode.inner) - - newParentNode := newParent.Value.(*frequencyNode) - valueNode.parentFreqNode = newParent - newValueNode := newParentNode.valuesList.PushBack(valueNode) - valueNode.inner = newValueNode - - // check if the current node is empty - if currentNode.valuesList.Len() == 0 { - // remove the current node - lfu.frequencies.Remove(parentFreqNode) - } -} - -// Get can be called to obtain the value for given key -func (lfu *FrameCache) Get(key uint) (*Frame, bool) { - lfu.rwLock.Lock() - defer lfu.rwLock.Unlock() - - // check if data is in the map - valueNode, found := lfu.lookupTable[key] - if !found { - return nil, false - } - - lfu.unsafeUpdateFrequency(valueNode) - - return valueNode.valueRef, true -} - -// Delete removes the specified entry from LFU cache -func (lfu *FrameCache) Delete(key uint) error { - lfu.rwLock.Lock() - defer lfu.rwLock.Unlock() - - // check if the key is in the map - valueNode, found := lfu.lookupTable[key] - if !found { - return fmt.Errorf("key %v not found", key) - } - - parentFreqNode := valueNode.parentFreqNode - - currentNode := (parentFreqNode.Value).(*frequencyNode) - currentNode.valuesList.Remove(valueNode.inner) - - if currentNode.valuesList.Len() == 0 { - lfu.frequencies.Remove(parentFreqNode) - } - - delete(lfu.lookupTable, key) - return nil -} - -// NewFrameCache ... -func NewFrameCache(maxSize uint) *FrameCache { - return &FrameCache{ - rwLock: sync.RWMutex{}, - lookupTable: make(map[uint]*keyRefNode), - maxSize: maxSize, - frequencies: list.New(), - } -} diff --git a/internal/vm/frame.go b/internal/vm/frame.go index 6d57b12..6e6b57d 100644 --- a/internal/vm/frame.go +++ b/internal/vm/frame.go @@ -3,11 +3,8 @@ package vm import ( "monkey/internal/code" "monkey/internal/object" - "unsafe" ) -var cache = NewFrameCache(32) - type Frame struct { cl *object.Closure ip int @@ -15,21 +12,11 @@ type Frame struct { } func NewFrame(cl *object.Closure, basePointer int) *Frame { - key := uint(uintptr(unsafe.Pointer(cl))) + uint(basePointer) - if frame, ok := cache.Get(key); ok { - frame.Reset() - return frame - } - - frame := &Frame{ + return &Frame{ cl: cl, ip: -1, basePointer: basePointer, } - - cache.Put(key, frame) - - return frame } // NextOp ... diff --git a/internal/vm/vm.go b/internal/vm/vm.go index 5fe42ac..5c9db07 100644 --- a/internal/vm/vm.go +++ b/internal/vm/vm.go @@ -13,6 +13,7 @@ import ( "os" "path/filepath" "strings" + "time" "unicode" ) @@ -201,22 +202,16 @@ func (vm *VM) LastPoppedStackElem() object.Object { } func (vm *VM) Run() error { + var n int var ip int var ins code.Instructions var op code.Opcode if vm.Debug { - log.Printf( - "%-25s %-20s\n", - fmt.Sprintf( - "%04d %s", ip, - strings.Split(ins[ip:].String(), "\n")[0][4:], - ), - fmt.Sprintf( - "[ip=%02d fp=%02d, sp=%02d]", - ip, vm.fp-1, vm.sp, - ), - ) + start := time.Now() + defer func() { + log.Printf("%d instructions executed in %s", n, time.Now().Sub(start)) + }() } for vm.frame.ip < len(vm.frame.Instructions())-1 { @@ -487,6 +482,7 @@ func (vm *VM) Run() error { } if vm.Debug { + n++ log.Printf( "%-25s [ip=%02d fp=%02d, sp=%02d]", "", ip, vm.fp-1, vm.sp, diff --git a/monkey.go b/monkey.go index 7b3e307..c0e6585 100644 --- a/monkey.go +++ b/monkey.go @@ -4,6 +4,8 @@ import ( "errors" "fmt" "io" + "log" + "monkey/internal/object" "os" "path/filepath" "runtime" @@ -42,7 +44,7 @@ func decode(path string) (*compiler.Bytecode, error) { return compiler.Decode(b), nil } -func compile(path string) (bc *compiler.Bytecode, err error) { +func compile(path string, debug bool) (bc *compiler.Bytecode, err error) { input := string(mustReadFile(path)) res, errs := parser.Parse(path, input) if len(errs) > 0 { @@ -55,7 +57,12 @@ func compile(path string) (bc *compiler.Bytecode, err error) { return nil, errors.New(buf.String()) } + if debug { + log.Printf("AST:\n%s\n", res) + } + c := compiler.New() + c.Debug = debug c.SetFileInfo(path, input) if err = c.Compile(res); err != nil { return @@ -64,13 +71,15 @@ func compile(path string) (bc *compiler.Bytecode, err error) { return c.Bytecode(), nil } -func ExecFileVM(f string) (err error) { +func ExecFileVM(f string, args []string, debug bool) (err error) { var bytecode *compiler.Bytecode - if filepath.Ext(f) == ".monkeyc" { + object.Args = args + + if filepath.Ext(f) == ".mc" { bytecode, err = decode(f) } else { - bytecode, err = compile(f) + bytecode, err = compile(f, debug) } if err != nil { @@ -79,6 +88,7 @@ func ExecFileVM(f string) (err error) { } mvm := vm.New(f, bytecode) + mvm.Debug = debug if err = mvm.Run(); err != nil { fmt.Println(err) return @@ -87,7 +97,7 @@ func ExecFileVM(f string) (err error) { return } -func CompileFiles(files []string) error { +func CompileFiles(files []string, debug bool) error { for _, f := range files { b := mustReadFile(f) @@ -99,7 +109,12 @@ func CompileFiles(files []string) error { return ErrParseError } + if debug { + log.Printf("AST:\n%s\n", res) + } + c := compiler.New() + c.Debug = debug if err := c.Compile(res); err != nil { fmt.Println(err) continue @@ -112,7 +127,7 @@ func CompileFiles(files []string) error { } ext := filepath.Ext(f) - writeFile(f[:len(f)-len(ext)]+".monkeyc", cnt) + writeFile(f[:len(f)-len(ext)]+".mc", cnt) } return nil diff --git a/repl.go b/repl.go index d6d6dbe..2d910c0 100644 --- a/repl.go +++ b/repl.go @@ -3,7 +3,9 @@ package monkey import ( "bufio" "fmt" + "github.com/tebeka/atexit" "io" + "log" "os" "strings" @@ -18,19 +20,25 @@ import ( // Package repl implements the Read-Eval-Print-Loop or interactive console // by lexing, parsing and evaluating the input in the interpreter -func VmREPL() error { +func VmREPL(args []string, debug bool) error { var state = vm.NewVMState() + object.Args = args + initState, err := term.MakeRaw(0) if err != nil { fmt.Println(err) return fmt.Errorf("error opening terminal: %w", err) } defer term.Restore(0, initState) + atexit.Register(func() { + term.Restore(0, initState) + }) t := term.NewTerminal(os.Stdin, ">>> ") t.AutoCompleteCallback = autoComplete object.Stdout = t + object.ExitFunction = atexit.Exit PrintVersionInfo(t) for { @@ -51,13 +59,20 @@ func VmREPL() error { continue } + if debug { + log.Printf("AST:\n%s\n", res) + } + c := compiler.NewWithState(state.Symbols, &state.Constants) + c.Debug = debug + c.SetFileInfo("", input) if err := c.Compile(res); err != nil { fmt.Fprintln(t, err) continue } mvm := vm.NewWithState("", c.Bytecode(), state) + mvm.Debug = debug if err := mvm.Run(); err != nil { fmt.Fprintf(t, "runtime error: %v\n", err) continue @@ -114,12 +129,14 @@ func acceptUntil(t *term.Terminal, start, end string) (string, error) { return buf.String(), nil } -func SimpleVmREPL() { +func SimpleVmREPL(args []string, debug bool) { var ( state = vm.NewVMState() reader = bufio.NewReader(os.Stdin) ) + object.Args = args + PrintVersionInfo(os.Stdout) for { fmt.Print(">>> ") @@ -140,14 +157,20 @@ func SimpleVmREPL() { continue } + if debug { + log.Printf("AST:\n%s\n", res) + } + c := compiler.NewWithState(state.Symbols, &state.Constants) + c.Debug = debug c.SetFileInfo("", input) if err := c.Compile(res); err != nil { fmt.Println(err) continue } - mvm := vm.NewWithState("", c.Bytecode(), state) + mvm := vm.NewWithState("", c.Bytecode(), state) + mvm.Debug = debug if err := mvm.Run(); err != nil { fmt.Printf("runtime error: %v\n", err) continue