bunch of changes
Some checks failed
Build / build (push) Failing after 5m54s
Publish Image / publish (push) Failing after 35s
Test / build (push) Failing after 5m46s

This commit is contained in:
Chuck Smith
2024-03-28 17:19:23 -04:00
parent 244b71d245
commit fb0cebaf56
16 changed files with 174 additions and 320 deletions

4
.gitignore vendored
View File

@@ -84,10 +84,12 @@ fabric.properties
*.out *.out
*.swo *.swo
*.swn *.swn
*.pyc
*.prof *.prof
**/.DS_Store **/.DS_Store
/dist /dist
/.vscode /.vscode
/monkey /monkey
/examples/fib

View File

@@ -12,27 +12,29 @@ func main() {
compile bool compile bool
version bool version bool
simple bool simple bool
debug bool
) )
flag.BoolVar(&compile, "c", false, "Compile a monkey file into a '.monkeyc' bytecode file.") 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(&simple, "s", false, "Use simple REPL instead of opening a terminal")
flag.BoolVar(&version, "v", false, "Print Monkey version information.") flag.BoolVar(&version, "v", false, "Print Monkey version information")
flag.BoolVar(&debug, "d", false, "Enable debug mode")
flag.Parse() flag.Parse()
switch { switch {
case compile: case compile:
monkey.CompileFiles(flag.Args()) monkey.CompileFiles(flag.Args(), debug)
case version: case version:
monkey.PrintVersionInfo(os.Stdout) monkey.PrintVersionInfo(os.Stdout)
case flag.NArg() > 0: case flag.NArg() > 0:
monkey.ExecFileVM(flag.Arg(0)) monkey.ExecFileVM(flag.Arg(0), flag.Args()[1:], debug)
case simple: case simple:
monkey.SimpleVmREPL() monkey.SimpleVmREPL(flag.Args(), debug)
default: default:
monkey.VmREPL() monkey.VmREPL(flag.Args(), debug)
} }
} }

33
examples/fib.c Normal file
View File

@@ -0,0 +1,33 @@
#include <stdio.h>
#include <errno.h>
#include <limits.h>
#include <stdlib.h>
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));
}

25
examples/fib.go Normal file
View File

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

View File

@@ -7,7 +7,7 @@ fib := fn(x) {
N := 35 N := 35
if (len(args()) == 1) { if (len(args()) > 0) {
N = int(args()[0]) N = int(args()[0])
} }

22
examples/fib.py Normal file
View File

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

10
examples/fib.tau Normal file
View File

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

6
go.mod
View File

@@ -3,13 +3,15 @@ module monkey
go 1.21 go 1.21
require ( require (
github.com/stretchr/testify v1.8.4
github.com/pkg/profile v1.7.0 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 ( require (
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // 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 gopkg.in/yaml.v3 v3.0.1 // indirect
) )

7
go.sum
View File

@@ -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/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.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 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.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.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 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 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.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 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= 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/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.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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -38,13 +38,13 @@ func (p *Program) TokenLiteral() string {
} }
func (p *Program) String() string { func (p *Program) String() string {
var out bytes.Buffer var s strings.Builder
for _, s := range p.Statements { for _, stmt := range p.Statements {
out.WriteString(s.String()) s.WriteString(fmt.Sprintf("%s\n", stmt.String()))
} }
return out.String() return s.String()
} }
type ReturnStatement struct { type ReturnStatement struct {

View File

@@ -6,7 +6,7 @@ import (
) )
var ( var (
Args []string = os.Args Args []string = os.Args[1:] // Skip the monkey binary itself
Stdin io.Reader = os.Stdin Stdin io.Reader = os.Stdin
Stdout io.Writer = os.Stdout Stdout io.Writer = os.Stdout
Stderr io.Writer = os.Stderr Stderr io.Writer = os.Stderr

View File

@@ -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 <KeyType, *ValueType> 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 `<KeyType, ValueType>` 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(),
}
}

View File

@@ -3,11 +3,8 @@ package vm
import ( import (
"monkey/internal/code" "monkey/internal/code"
"monkey/internal/object" "monkey/internal/object"
"unsafe"
) )
var cache = NewFrameCache(32)
type Frame struct { type Frame struct {
cl *object.Closure cl *object.Closure
ip int ip int
@@ -15,21 +12,11 @@ type Frame struct {
} }
func NewFrame(cl *object.Closure, basePointer int) *Frame { func NewFrame(cl *object.Closure, basePointer int) *Frame {
key := uint(uintptr(unsafe.Pointer(cl))) + uint(basePointer) return &Frame{
if frame, ok := cache.Get(key); ok {
frame.Reset()
return frame
}
frame := &Frame{
cl: cl, cl: cl,
ip: -1, ip: -1,
basePointer: basePointer, basePointer: basePointer,
} }
cache.Put(key, frame)
return frame
} }
// NextOp ... // NextOp ...

View File

@@ -13,6 +13,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"time"
"unicode" "unicode"
) )
@@ -201,22 +202,16 @@ func (vm *VM) LastPoppedStackElem() object.Object {
} }
func (vm *VM) Run() error { func (vm *VM) Run() error {
var n int
var ip int var ip int
var ins code.Instructions var ins code.Instructions
var op code.Opcode var op code.Opcode
if vm.Debug { if vm.Debug {
log.Printf( start := time.Now()
"%-25s %-20s\n", defer func() {
fmt.Sprintf( log.Printf("%d instructions executed in %s", n, time.Now().Sub(start))
"%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,
),
)
} }
for vm.frame.ip < len(vm.frame.Instructions())-1 { for vm.frame.ip < len(vm.frame.Instructions())-1 {
@@ -487,6 +482,7 @@ func (vm *VM) Run() error {
} }
if vm.Debug { if vm.Debug {
n++
log.Printf( log.Printf(
"%-25s [ip=%02d fp=%02d, sp=%02d]", "%-25s [ip=%02d fp=%02d, sp=%02d]",
"", ip, vm.fp-1, vm.sp, "", ip, vm.fp-1, vm.sp,

View File

@@ -4,6 +4,8 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"log"
"monkey/internal/object"
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
@@ -42,7 +44,7 @@ func decode(path string) (*compiler.Bytecode, error) {
return compiler.Decode(b), nil 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)) input := string(mustReadFile(path))
res, errs := parser.Parse(path, input) res, errs := parser.Parse(path, input)
if len(errs) > 0 { if len(errs) > 0 {
@@ -55,7 +57,12 @@ func compile(path string) (bc *compiler.Bytecode, err error) {
return nil, errors.New(buf.String()) return nil, errors.New(buf.String())
} }
if debug {
log.Printf("AST:\n%s\n", res)
}
c := compiler.New() c := compiler.New()
c.Debug = debug
c.SetFileInfo(path, input) c.SetFileInfo(path, input)
if err = c.Compile(res); err != nil { if err = c.Compile(res); err != nil {
return return
@@ -64,13 +71,15 @@ func compile(path string) (bc *compiler.Bytecode, err error) {
return c.Bytecode(), nil return c.Bytecode(), nil
} }
func ExecFileVM(f string) (err error) { func ExecFileVM(f string, args []string, debug bool) (err error) {
var bytecode *compiler.Bytecode var bytecode *compiler.Bytecode
if filepath.Ext(f) == ".monkeyc" { object.Args = args
if filepath.Ext(f) == ".mc" {
bytecode, err = decode(f) bytecode, err = decode(f)
} else { } else {
bytecode, err = compile(f) bytecode, err = compile(f, debug)
} }
if err != nil { if err != nil {
@@ -79,6 +88,7 @@ func ExecFileVM(f string) (err error) {
} }
mvm := vm.New(f, bytecode) mvm := vm.New(f, bytecode)
mvm.Debug = debug
if err = mvm.Run(); err != nil { if err = mvm.Run(); err != nil {
fmt.Println(err) fmt.Println(err)
return return
@@ -87,7 +97,7 @@ func ExecFileVM(f string) (err error) {
return return
} }
func CompileFiles(files []string) error { func CompileFiles(files []string, debug bool) error {
for _, f := range files { for _, f := range files {
b := mustReadFile(f) b := mustReadFile(f)
@@ -99,7 +109,12 @@ func CompileFiles(files []string) error {
return ErrParseError return ErrParseError
} }
if debug {
log.Printf("AST:\n%s\n", res)
}
c := compiler.New() c := compiler.New()
c.Debug = debug
if err := c.Compile(res); err != nil { if err := c.Compile(res); err != nil {
fmt.Println(err) fmt.Println(err)
continue continue
@@ -112,7 +127,7 @@ func CompileFiles(files []string) error {
} }
ext := filepath.Ext(f) ext := filepath.Ext(f)
writeFile(f[:len(f)-len(ext)]+".monkeyc", cnt) writeFile(f[:len(f)-len(ext)]+".mc", cnt)
} }
return nil return nil

29
repl.go
View File

@@ -3,7 +3,9 @@ package monkey
import ( import (
"bufio" "bufio"
"fmt" "fmt"
"github.com/tebeka/atexit"
"io" "io"
"log"
"os" "os"
"strings" "strings"
@@ -18,19 +20,25 @@ import (
// Package repl implements the Read-Eval-Print-Loop or interactive console // Package repl implements the Read-Eval-Print-Loop or interactive console
// by lexing, parsing and evaluating the input in the interpreter // by lexing, parsing and evaluating the input in the interpreter
func VmREPL() error { func VmREPL(args []string, debug bool) error {
var state = vm.NewVMState() var state = vm.NewVMState()
object.Args = args
initState, err := term.MakeRaw(0) initState, err := term.MakeRaw(0)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
return fmt.Errorf("error opening terminal: %w", err) return fmt.Errorf("error opening terminal: %w", err)
} }
defer term.Restore(0, initState) defer term.Restore(0, initState)
atexit.Register(func() {
term.Restore(0, initState)
})
t := term.NewTerminal(os.Stdin, ">>> ") t := term.NewTerminal(os.Stdin, ">>> ")
t.AutoCompleteCallback = autoComplete t.AutoCompleteCallback = autoComplete
object.Stdout = t object.Stdout = t
object.ExitFunction = atexit.Exit
PrintVersionInfo(t) PrintVersionInfo(t)
for { for {
@@ -51,13 +59,20 @@ func VmREPL() error {
continue continue
} }
if debug {
log.Printf("AST:\n%s\n", res)
}
c := compiler.NewWithState(state.Symbols, &state.Constants) c := compiler.NewWithState(state.Symbols, &state.Constants)
c.Debug = debug
c.SetFileInfo("<stdin>", input)
if err := c.Compile(res); err != nil { if err := c.Compile(res); err != nil {
fmt.Fprintln(t, err) fmt.Fprintln(t, err)
continue continue
} }
mvm := vm.NewWithState("<stdin>", c.Bytecode(), state) mvm := vm.NewWithState("<stdin>", c.Bytecode(), state)
mvm.Debug = debug
if err := mvm.Run(); err != nil { if err := mvm.Run(); err != nil {
fmt.Fprintf(t, "runtime error: %v\n", err) fmt.Fprintf(t, "runtime error: %v\n", err)
continue continue
@@ -114,12 +129,14 @@ func acceptUntil(t *term.Terminal, start, end string) (string, error) {
return buf.String(), nil return buf.String(), nil
} }
func SimpleVmREPL() { func SimpleVmREPL(args []string, debug bool) {
var ( var (
state = vm.NewVMState() state = vm.NewVMState()
reader = bufio.NewReader(os.Stdin) reader = bufio.NewReader(os.Stdin)
) )
object.Args = args
PrintVersionInfo(os.Stdout) PrintVersionInfo(os.Stdout)
for { for {
fmt.Print(">>> ") fmt.Print(">>> ")
@@ -140,14 +157,20 @@ func SimpleVmREPL() {
continue continue
} }
if debug {
log.Printf("AST:\n%s\n", res)
}
c := compiler.NewWithState(state.Symbols, &state.Constants) c := compiler.NewWithState(state.Symbols, &state.Constants)
c.Debug = debug
c.SetFileInfo("<stdin>", input) c.SetFileInfo("<stdin>", input)
if err := c.Compile(res); err != nil { if err := c.Compile(res); err != nil {
fmt.Println(err) fmt.Println(err)
continue continue
} }
mvm := vm.NewWithState("<stdin>", c.Bytecode(), state)
mvm := vm.NewWithState("<stdin>", c.Bytecode(), state)
mvm.Debug = debug
if err := mvm.Run(); err != nil { if err := mvm.Run(); err != nil {
fmt.Printf("runtime error: %v\n", err) fmt.Printf("runtime error: %v\n", err)
continue continue