refactor how repl works
Some checks failed
Build / build (push) Failing after 5m26s
Publish Image / publish (push) Failing after 45s
Test / build (push) Failing after 5m54s

This commit is contained in:
Chuck Smith
2024-03-28 16:51:54 -04:00
parent fc6ceee02c
commit 244b71d245
32 changed files with 612 additions and 476 deletions

View File

@@ -6,7 +6,7 @@ import (
)
// Args ...
func Args(args ...object.Object) object.Object {
func args(args ...object.Object) object.Object {
if err := typing.Check(
"args", args,
typing.ExactArgs(0),
@@ -14,8 +14,8 @@ func Args(args ...object.Object) object.Object {
return newError(err.Error())
}
elements := make([]object.Object, len(object.Arguments))
for i, arg := range object.Arguments {
elements := make([]object.Object, len(object.Args))
for i, arg := range object.Args {
elements[i] = &object.String{Value: arg}
}
return &object.Array{Elements: elements}

View File

@@ -22,7 +22,7 @@ var Builtins = map[string]*object.Builtin{
"int": {Name: "int", Fn: Int},
"str": {Name: "str", Fn: Str},
"type": {Name: "type", Fn: TypeOf},
"args": {Name: "args", Fn: Args},
"args": {Name: "args", Fn: args},
"lower": {Name: "lower", Fn: Lower},
"upper": {Name: "upper", Fn: Upper},
"join": {Name: "join", Fn: Join},

View File

@@ -0,0 +1,147 @@
package compiler
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"math"
"monkey/internal/code"
"monkey/internal/object"
)
type Bytecode struct {
Instructions code.Instructions
Constants []object.Object
}
type encoder struct {
bytes.Buffer
}
func (e *encoder) Write(b []byte) (err error) {
_, err = e.Buffer.Write(b)
return
}
func (e *encoder) WriteString(s string) (err error) {
_, err = e.Buffer.WriteString(s)
return
}
func (e *encoder) WriteValue(data any) error {
return binary.Write(&e.Buffer, binary.BigEndian, data)
}
func (e *encoder) WriteObjects(objs ...object.Object) (err error) {
for _, obj := range objs {
err = errors.Join(err, e.WriteValue(len(string(obj.Type()))))
err = errors.Join(err, e.WriteString(string(obj.Type())))
switch o := obj.(type) {
case *object.Null:
break
case *object.Boolean:
err = errors.Join(err, e.WriteValue(o.Value))
case *object.Integer:
err = errors.Join(err, e.WriteValue(o.Value))
case *object.String:
err = errors.Join(err, e.WriteValue(len(o.Value)))
err = errors.Join(err, e.WriteValue(o.Value))
case *object.CompiledFunction:
err = errors.Join(err, e.WriteValue(o.NumParameters))
err = errors.Join(err, e.WriteValue(o.NumLocals))
err = errors.Join(err, e.WriteValue(len(o.Instructions)))
err = errors.Join(err, e.Write(o.Instructions))
}
}
return
}
func (b Bytecode) Encode() (data []byte, err error) {
var e encoder
err = errors.Join(err, e.WriteValue(len(b.Instructions)))
err = errors.Join(err, e.Write(b.Instructions))
err = errors.Join(err, e.WriteValue(len(b.Constants)))
err = errors.Join(err, e.WriteObjects(b.Constants...))
return e.Bytes(), err
}
type decoder struct {
pos int
b []byte
}
func (d *decoder) Byte() (b byte) {
b = d.b[d.pos]
d.pos++
return
}
func (d *decoder) Int() (i int) {
i = int(binary.BigEndian.Uint32(d.b[d.pos:]))
d.pos += 4
return
}
func (d *decoder) Uint64() (i uint64) {
i = binary.BigEndian.Uint64(d.b[d.pos:])
d.pos += 8
return
}
func (d *decoder) Int64() (i int64) {
return int64(d.Uint64())
}
func (d *decoder) Float64() (f float64) {
return math.Float64frombits(d.Uint64())
}
func (d *decoder) Bytes(len int) (b []byte) {
b = d.b[d.pos : d.pos+len]
d.pos += len
return
}
func (d *decoder) String(len int) (s string) {
s = string(d.b[d.pos : d.pos+len])
d.pos += len
return
}
func (d *decoder) Objects(len int) (o []object.Object) {
for i := 0; i < len; i++ {
switch t := d.String(d.Int()); t {
case object.NULL_OBJ:
o = append(o, &object.Null{})
case object.BOOLEAN_OBJ:
o = append(o, &object.Boolean{Value: d.Byte() == 1})
case object.INTEGER_OBJ:
o = append(o, &object.Integer{Value: d.Int64()})
case object.STRING_OBJ:
o = append(o, &object.String{Value: d.String(d.Int())})
case object.COMPILED_FUNCTION_OBJ:
// The order of the fields has to reflect the data layout in the encoded bytecode.
o = append(o, &object.CompiledFunction{
NumParameters: d.Int(),
NumLocals: d.Int(),
Instructions: d.Bytes(d.Int()),
})
default:
panic(fmt.Sprintf("decoder: unsupported decoding for type %s", t))
}
}
return
}
func Decode(b []byte) *Bytecode {
var d = decoder{b: b}
// The order of the fields has to reflect the data layout in the encoded bytecode.
return &Bytecode{
Instructions: d.Bytes(d.Int()),
Constants: d.Objects(d.Int()),
}
}

View File

@@ -25,8 +25,11 @@ type CompilationScope struct {
type Compiler struct {
Debug bool
fn string
input string
l int
constants []object.Object
constants *[]object.Object
symbolTable *SymbolTable
@@ -48,7 +51,7 @@ func New() *Compiler {
}
return &Compiler{
constants: []object.Object{},
constants: &[]object.Object{},
symbolTable: symbolTable,
scopes: []CompilationScope{mainScope},
scopeIndex: 0,
@@ -59,7 +62,7 @@ func (c *Compiler) currentInstructions() code.Instructions {
return c.scopes[c.scopeIndex].instructions
}
func NewWithState(s *SymbolTable, constants []object.Object) *Compiler {
func NewWithState(s *SymbolTable, constants *[]object.Object) *Compiler {
compiler := New()
compiler.symbolTable = s
compiler.constants = constants
@@ -442,7 +445,7 @@ func (c *Compiler) Compile(node ast.Node) error {
}
freeSymbols := c.symbolTable.FreeSymbols
numLocals := c.symbolTable.numDefinitions
numLocals := c.symbolTable.NumDefinitions
instructions := c.leaveScope()
for _, s := range freeSymbols {
@@ -529,8 +532,8 @@ func (c *Compiler) Compile(node ast.Node) error {
}
func (c *Compiler) addConstant(obj object.Object) int {
c.constants = append(c.constants, obj)
return len(c.constants) - 1
*c.constants = append(*c.constants, obj)
return len(*c.constants) - 1
}
func (c *Compiler) emit(op code.Opcode, operands ...int) int {
@@ -542,6 +545,11 @@ func (c *Compiler) emit(op code.Opcode, operands ...int) int {
return pos
}
func (c *Compiler) SetFileInfo(fn, input string) {
c.fn = fn
c.input = input
}
func (c *Compiler) setLastInstruction(op code.Opcode, pos int) {
previous := c.scopes[c.scopeIndex].lastInstruction
last := EmittedInstruction{Opcode: op, Position: pos}
@@ -553,7 +561,7 @@ func (c *Compiler) setLastInstruction(op code.Opcode, pos int) {
func (c *Compiler) Bytecode() *Bytecode {
return &Bytecode{
Instructions: c.currentInstructions(),
Constants: c.constants,
Constants: *c.constants,
}
}
@@ -630,11 +638,6 @@ func (c *Compiler) replaceLastPopWithReturn() {
c.scopes[c.scopeIndex].lastInstruction.Opcode = code.OpReturn
}
type Bytecode struct {
Instructions code.Instructions
Constants []object.Object
}
func (c *Compiler) loadSymbol(s Symbol) {
switch s.Scope {
case GlobalScope:

View File

@@ -1090,7 +1090,7 @@ func runCompilerTests2(t *testing.T, tests []compilerTestCase2) {
func parse(input string) *ast.Program {
l := lexer.New(input)
p := parser.New(l)
p := parser.New("<text", l)
return p.ParseProgram()
}

View File

@@ -20,7 +20,7 @@ type SymbolTable struct {
Outer *SymbolTable
Store map[string]Symbol
numDefinitions int
NumDefinitions int
FreeSymbols []Symbol
}
@@ -38,7 +38,7 @@ func NewSymbolTable() *SymbolTable {
}
func (s *SymbolTable) Define(name string) Symbol {
symbol := Symbol{Name: name, Index: s.numDefinitions}
symbol := Symbol{Name: name, Index: s.NumDefinitions}
if s.Outer == nil {
symbol.Scope = GlobalScope
} else {
@@ -46,7 +46,7 @@ func (s *SymbolTable) Define(name string) Symbol {
}
s.Store[name] = symbol
s.numDefinitions++
s.NumDefinitions++
return symbol
}

View File

@@ -518,7 +518,7 @@ func EvalModule(name string) object.Object {
}
l := lexer.New(string(b))
p := parser.New(l)
p := parser.New(fmt.Sprintf("<module %s>", name), l)
module := p.ParseProgram()
if len(p.Errors()) != 0 {

View File

@@ -796,7 +796,7 @@ func TestWhileExpressions(t *testing.T) {
func testEval(input string) object.Object {
l := lexer.New(input)
p := parser.New(l)
p := parser.New("<test>", l)
program := p.ParseProgram()
env := object.NewEnvironment()
@@ -862,9 +862,9 @@ func TestImportExpressions(t *testing.T) {
input string
expected interface{}
}{
{`mod := import("../../testdata/mod"); mod.A`, 5},
{`mod := import("../../testdata/mod"); mod.Sum(2, 3)`, 5},
{`mod := import("../../testdata/mod"); mod.a`, nil},
{`mod := import("../testdata/mod"); mod.A`, 5},
{`mod := import("../testdata/mod"); mod.Sum(2, 3)`, 5},
{`mod := import("../testdata/mod"); mod.a`, nil},
}
for _, tt := range tests {

View File

@@ -1,10 +1,14 @@
package object
import "io"
import (
"io"
"os"
)
var (
Arguments []string
StandardInput io.Reader
StandardOutput io.Writer
ExitFunction func(int)
Args []string = os.Args
Stdin io.Reader = os.Stdin
Stdout io.Writer = os.Stdout
Stderr io.Writer = os.Stderr
ExitFunction func(int) = os.Exit
)

View File

@@ -61,6 +61,7 @@ type (
)
type Parser struct {
fn string
l *lexer.Lexer
errors []string
@@ -71,8 +72,9 @@ type Parser struct {
infixParseFns map[token.TokenType]infixParseFn
}
func New(l *lexer.Lexer) *Parser {
func New(fn string, l *lexer.Lexer) *Parser {
p := &Parser{
fn: fn,
l: l,
errors: []string{},
}
@@ -619,3 +621,11 @@ func (p *Parser) parseImportExpression() ast.Expression {
return expression
}
// 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.
func Parse(fn, input string) (prog ast.Node, errors []string) {
p := New(fn, lexer.New(input))
return p.ParseProgram(), p.Errors()
}

View File

@@ -20,7 +20,7 @@ func TestBindExpressions(t *testing.T) {
for _, tt := range tests {
l := lexer.New(tt.input)
p := New(l)
p := New("<test>", l)
program := p.ParseProgram()
checkParserErrors(t, p)
@@ -40,7 +40,7 @@ func TestReturnStatements(t *testing.T) {
for _, tt := range tests {
l := lexer.New(tt.input)
p := New(l)
p := New("<test>", l)
program := p.ParseProgram()
checkParserErrors(t, p)
@@ -68,7 +68,7 @@ func TestIdentifierExpression(t *testing.T) {
input := "foobar;"
l := lexer.New(input)
p := New(l)
p := New("<test>", l)
program := p.ParseProgram()
checkParserErrors(t, p)
@@ -99,7 +99,7 @@ func TestIntegerLiteralExpression(t *testing.T) {
input := "5;"
l := lexer.New(input)
p := New(l)
p := New("<test>", l)
program := p.ParseProgram()
checkParserErrors(t, p)
@@ -143,7 +143,7 @@ func TestParsingPrefixExpressions(t *testing.T) {
for _, tt := range prefixTests {
l := lexer.New(tt.input)
p := New(l)
p := New("<test>", l)
program := p.ParseProgram()
checkParserErrors(t, p)
@@ -202,7 +202,7 @@ func TestParsingInfixExpressions(t *testing.T) {
for _, tt := range infixTests {
l := lexer.New(tt.input)
p := New(l)
p := New("<test>", l)
program := p.ParseProgram()
checkParserErrors(t, p)
@@ -353,7 +353,7 @@ func TestOperatorPrecedenceParsing(t *testing.T) {
for _, tt := range tests {
l := lexer.New(tt.input)
p := New(l)
p := New("<test>", l)
program := p.ParseProgram()
checkParserErrors(t, p)
@@ -375,7 +375,7 @@ func TestBooleanExpression(t *testing.T) {
for _, tt := range tests {
l := lexer.New(tt.input)
p := New(l)
p := New("<test>", l)
program := p.ParseProgram()
checkParserErrors(t, p)
@@ -405,7 +405,7 @@ func TestIfExpression(t *testing.T) {
input := `if (x < y) { x }`
l := lexer.New(input)
p := New(l)
p := New("<test>", l)
program := p.ParseProgram()
checkParserErrors(t, p)
@@ -452,7 +452,7 @@ func TestIfExpression(t *testing.T) {
func TestNullExpression(t *testing.T) {
l := lexer.New("null")
p := New(l)
p := New("<test>", l)
program := p.ParseProgram()
checkParserErrors(t, p)
@@ -477,7 +477,7 @@ func TestIfElseExpression(t *testing.T) {
input := `if (x < y) { x } else { y }`
l := lexer.New(input)
p := New(l)
p := New("<test>", l)
program := p.ParseProgram()
checkParserErrors(t, p)
@@ -536,7 +536,7 @@ func TestIfElseIfExpression(t *testing.T) {
input := `if (x < y) { x } else if (x == y) { y }`
l := lexer.New(input)
p := New(l)
p := New("<test>", l)
program := p.ParseProgram()
checkParserErrors(t, p)
@@ -615,7 +615,7 @@ func TestFunctionLiteralParsing(t *testing.T) {
input := `fn(x, y) { x + y; }`
l := lexer.New(input)
p := New(l)
p := New("<test>", l)
program := p.ParseProgram()
checkParserErrors(t, p)
@@ -670,7 +670,7 @@ func TestFunctionParameterParsing(t *testing.T) {
for _, tt := range tests {
l := lexer.New(tt.input)
p := New(l)
p := New("<test>", l)
program := p.ParseProgram()
checkParserErrors(t, p)
@@ -692,7 +692,7 @@ func TestCallExpressionParsing(t *testing.T) {
input := "add(1, 2 * 3, 4 + 5);"
l := lexer.New(input)
p := New(l)
p := New("<test>", l)
program := p.ParseProgram()
checkParserErrors(t, p)
@@ -751,7 +751,7 @@ func TestCallExpressionParameterParsing(t *testing.T) {
for _, tt := range tests {
l := lexer.New(tt.input)
p := New(l)
p := New("<test>", l)
program := p.ParseProgram()
checkParserErrors(t, p)
@@ -784,7 +784,7 @@ func TestStringLiteralExpression(t *testing.T) {
input := `"hello world";`
l := lexer.New(input)
p := New(l)
p := New("<test>", l)
program := p.ParseProgram()
checkParserErrors(t, p)
@@ -803,7 +803,7 @@ func TestParsingArrayLiterals(t *testing.T) {
input := "[1, 2 * 2, 3 + 3]"
l := lexer.New(input)
p := New(l)
p := New("<test>", l)
program := p.ParseProgram()
checkParserErrors(t, p)
@@ -826,7 +826,7 @@ func TestParsingSelectorExpressions(t *testing.T) {
input := "myHash.foo"
l := lexer.New(input)
p := New(l)
p := New("<test>", l)
program := p.ParseProgram()
checkParserErrors(t, p)
stmt, ok := program.Statements[0].(*ast.ExpressionStatement)
@@ -860,7 +860,7 @@ func TestParsingIndexExpressions(t *testing.T) {
input := "myArray[1 + 1]"
l := lexer.New(input)
p := New(l)
p := New("<test>", l)
program := p.ParseProgram()
checkParserErrors(t, p)
@@ -883,7 +883,7 @@ func TestParsingHashLiteralsStringKeys(t *testing.T) {
input := `{"one": 1, "two": 2, "three": 3}`
l := lexer.New(input)
p := New(l)
p := New("<test>", l)
program := p.ParseProgram()
checkParserErrors(t, p)
@@ -919,7 +919,7 @@ func TestParsingEmptyHashLiteral(t *testing.T) {
input := "{}"
l := lexer.New(input)
p := New(l)
p := New("<test>", l)
program := p.ParseProgram()
checkParserErrors(t, p)
@@ -938,7 +938,7 @@ func TestParsingHashLiteralsWithExpressions(t *testing.T) {
input := `{"one": 0 + 1, "two": 10 - 8, "three": 15 / 5}`
l := lexer.New(input)
p := New(l)
p := New("<test>", l)
program := p.ParseProgram()
checkParserErrors(t, p)
@@ -987,7 +987,7 @@ func TestFunctionDefinitionParsing(t *testing.T) {
input := `add := fn(x, y) { x + y; }`
l := lexer.New(input)
p := New(l)
p := New("<test>", l)
program := p.ParseProgram()
checkParserErrors(t, p)
@@ -998,7 +998,7 @@ func TestWhileExpression(t *testing.T) {
input := `while (x < y) { x }`
l := lexer.New(input)
p := New(l)
p := New("<test>", l)
program := p.ParseProgram()
checkParserErrors(t, p)
@@ -1055,7 +1055,7 @@ func TestAssignmentExpressions(t *testing.T) {
for _, tt := range tests {
l := lexer.New(tt.input)
p := New(l)
p := New("<test>", l)
program := p.ParseProgram()
checkParserErrors(t, p)
@@ -1197,7 +1197,7 @@ func TestComments(t *testing.T) {
for _, tt := range tests {
l := lexer.New(tt.input)
p := New(l)
p := New("<test>", l)
program := p.ParseProgram()
checkParserErrors(t, p)
@@ -1240,7 +1240,7 @@ func TestParsingImportExpressions(t *testing.T) {
for _, tt := range tests {
l := lexer.New(tt.input)
p := New(l)
p := New("<test>", l)
program := p.ParseProgram()
checkParserErrors(t, p)

View File

@@ -1,257 +0,0 @@
package repl
// Package repl implements the Read-Eval-Print-Loop or interactive console
// by lexing, parsing and evaluating the input in the interpreter
import (
"bufio"
"fmt"
"io"
"log"
"monkey/internal/compiler"
"monkey/internal/evaluator"
"monkey/internal/lexer"
"monkey/internal/object"
"monkey/internal/parser"
"monkey/internal/vm"
"os"
)
// PROMPT is the REPL prompt displayed for each input
const PROMPT = ">> "
// MonkeyFace is the REPL's face of shock and horror when you encounter a
// parser error :D
const MonkeyFace = ` __,__
.--. .-" "-. .--.
/ .. \/ .-. .-. \/ .. \
| | '| / Y \ |' | |
| \ \ \ 0 | 0 / / / |
\ '- ,\.-"""""""-./, -' /
''-' /_ ^ ^ _\ '-''
| \._ _./ |
\ \ '~' / /
'._ '-=-' _.'
'-----'
`
type Options struct {
Debug bool
Engine string
Interactive bool
}
type REPL struct {
user string
args []string
opts *Options
}
func New(user string, args []string, opts *Options) *REPL {
object.StandardInput = os.Stdin
object.StandardOutput = os.Stdout
object.ExitFunction = os.Exit
return &REPL{user, args, opts}
}
// Eval parses and evalulates the program given by f and returns the resulting
// environment, any errors are printed to stderr
func (r *REPL) Eval(f io.Reader) (env *object.Environment) {
env = object.NewEnvironment()
b, err := io.ReadAll(f)
if err != nil {
fmt.Fprintf(os.Stderr, "error reading source file: %s", err)
return
}
l := lexer.New(string(b))
p := parser.New(l)
program := p.ParseProgram()
if len(p.Errors()) != 0 {
printParserErrors(os.Stderr, p.Errors())
return
}
evaluator.Eval(program, env)
return
}
// Exec parses, compiles and executes the program given by f and returns
// the resulting virtual machine, any errors are printed to stderr
func (r *REPL) Exec(f io.Reader) (state *vm.VMState) {
b, err := io.ReadAll(f)
if err != nil {
fmt.Fprintf(os.Stderr, "error reading source file: %s", err)
return
}
state = vm.NewVMState()
l := lexer.New(string(b))
p := parser.New(l)
program := p.ParseProgram()
if len(p.Errors()) != 0 {
printParserErrors(os.Stderr, p.Errors())
return
}
c := compiler.NewWithState(state.Symbols, state.Constants)
c.Debug = r.opts.Debug
err = c.Compile(program)
if err != nil {
fmt.Fprintf(os.Stderr, "Woops! Compilation failed:\n %s\n", err)
return
}
code := c.Bytecode()
state.Constants = code.Constants
machine := vm.NewWithState(code, state)
machine.Debug = r.opts.Debug
err = machine.Run()
if err != nil {
fmt.Fprintf(os.Stderr, "Woops! Executing bytecode failed:\n %s\n", err)
return
}
return
}
// StartEvalLoop starts the REPL in a continious eval loop
func (r *REPL) StartEvalLoop(in io.Reader, out io.Writer, env *object.Environment) {
scanner := bufio.NewScanner(in)
if env == nil {
env = object.NewEnvironment()
}
for {
fmt.Printf(PROMPT)
scanned := scanner.Scan()
if !scanned {
return
}
line := scanner.Text()
l := lexer.New(line)
p := parser.New(l)
program := p.ParseProgram()
if len(p.Errors()) != 0 {
printParserErrors(out, p.Errors())
continue
}
obj := evaluator.Eval(program, env)
if _, ok := obj.(*object.Null); !ok {
io.WriteString(out, obj.Inspect())
io.WriteString(out, "\n")
}
}
}
// StartExecLoop starts the REPL in a continious exec loop
func (r *REPL) StartExecLoop(in io.Reader, out io.Writer, state *vm.VMState) {
scanner := bufio.NewScanner(in)
if state == nil {
state = vm.NewVMState()
}
for {
fmt.Printf(PROMPT)
scanned := scanner.Scan()
if !scanned {
return
}
line := scanner.Text()
l := lexer.New(line)
p := parser.New(l)
program := p.ParseProgram()
if len(p.Errors()) != 0 {
printParserErrors(out, p.Errors())
continue
}
c := compiler.NewWithState(state.Symbols, state.Constants)
c.Debug = r.opts.Debug
err := c.Compile(program)
if err != nil {
fmt.Fprintf(os.Stderr, "Woops! Compilation failed:\n %s\n", err)
return
}
code := c.Bytecode()
state.Constants = code.Constants
machine := vm.NewWithState(code, state)
machine.Debug = r.opts.Debug
err = machine.Run()
if err != nil {
fmt.Fprintf(os.Stderr, "Woops! Executing bytecode failed:\n %s\n", err)
return
}
obj := machine.LastPoppedStackElem()
if _, ok := obj.(*object.Null); !ok {
io.WriteString(out, obj.Inspect())
io.WriteString(out, "\n")
}
}
}
func (r *REPL) Run() {
object.Arguments = make([]string, len(r.args))
copy(object.Arguments, r.args)
if len(r.args) == 0 {
fmt.Printf("Hello %s! This is the Monkey programming language!\n", r.user)
fmt.Printf("Feel free to type in commands\n")
if r.opts.Engine == "eval" {
r.StartEvalLoop(os.Stdin, os.Stdout, nil)
} else {
r.StartExecLoop(os.Stdin, os.Stdout, nil)
}
return
}
if len(r.args) > 0 {
f, err := os.Open(r.args[0])
if err != nil {
log.Fatalf("could not open source file %s: %s", r.args[0], err)
}
// Remove program argument (zero)
r.args = r.args[1:]
object.Arguments = object.Arguments[1:]
if r.opts.Engine == "eval" {
env := r.Eval(f)
if r.opts.Interactive {
r.StartEvalLoop(os.Stdin, os.Stdout, env)
}
} else {
state := r.Exec(f)
if r.opts.Interactive {
r.StartExecLoop(os.Stdin, os.Stdout, state)
}
}
}
}
func printParserErrors(out io.Writer, errors []string) {
io.WriteString(out, MonkeyFace)
io.WriteString(out, "Woops! We ran into some monkey business here!\n")
io.WriteString(out, " parser errors:\n")
for _, msg := range errors {
io.WriteString(out, "\t"+msg+"\n")
}
}

2
internal/testdata/arrays.monkey vendored Normal file
View File

@@ -0,0 +1,2 @@
xs := [1, 2, 3]
xs[0] + xs[1] + xs[2]

3
internal/testdata/assign.monkey vendored Normal file
View File

@@ -0,0 +1,3 @@
x := 1
x = 2
x = x + 1

3
internal/testdata/binding.monkey vendored Normal file
View File

@@ -0,0 +1,3 @@
x := 1
y := 2
z := x

13
internal/testdata/builtins.monkey vendored Normal file
View File

@@ -0,0 +1,13 @@
xs := [1, 2, 3]
len(xs)
first(xs)
rest(xs)
last(xs)
push(xs, 5)
pop(xs)
len("foo")
x := input()
print(x)

2
internal/testdata/closures.monkey vendored Normal file
View File

@@ -0,0 +1,2 @@
f := fn(x) { fn() { x + 1 } }
f(2)

2
internal/testdata/expressions.monkey vendored Normal file
View File

@@ -0,0 +1,2 @@
1 + 2;
(1 + 2) * 3;

2
internal/testdata/functions.monkey vendored Normal file
View File

@@ -0,0 +1,2 @@
f := fn(x, y) { x * y };
f(2, 4)

2
internal/testdata/hashes.monkey vendored Normal file
View File

@@ -0,0 +1,2 @@
d := {"a": 1, "b": 2}
d["a"] + d["b"]

5
internal/testdata/if.monkey vendored Normal file
View File

@@ -0,0 +1,5 @@
x := 1
if (x == 1) {
x = 2
x
}

3
internal/testdata/mod.monkey vendored Normal file
View File

@@ -0,0 +1,3 @@
a := 1
A := 5
Sum := fn(a, b) { return a + b }

5
internal/testdata/selectors.monkey vendored Normal file
View File

@@ -0,0 +1,5 @@
d := {"foo": 1, "bar": 2}
assert(d.foo == 1, "d.foo != 1")
assert(d.bar == 2, "d.bar != 2")
assert(d.bogus == null, "d.bogus != null")

2
internal/testdata/strings.monkey vendored Normal file
View File

@@ -0,0 +1,2 @@
s := "hello"
s + " " + "world"

View File

@@ -1,18 +0,0 @@
package internal
import (
"fmt"
)
var (
// Version release version
Version = "0.0.1"
// GitCommit will be overwritten automatically by the build system
GitCommit = "HEAD"
)
// FullVersion returns the full version and commit hash
func FullVersion() string {
return fmt.Sprintf("%s@%s", Version, GitCommit)
}

View File

@@ -2,7 +2,6 @@ package vm
import (
"fmt"
"io/ioutil"
"log"
"monkey/internal/builtins"
"monkey/internal/code"
@@ -11,6 +10,8 @@ import (
"monkey/internal/object"
"monkey/internal/parser"
"monkey/internal/utils"
"os"
"path/filepath"
"strings"
"unicode"
)
@@ -30,20 +31,20 @@ func ExecModule(name string, state *VMState) (object.Object, error) {
return nil, fmt.Errorf("ImportError: no module named '%s'", name)
}
b, err := ioutil.ReadFile(filename)
b, err := os.ReadFile(filename)
if err != nil {
return nil, fmt.Errorf("IOError: error reading module '%s': %s", name, err)
}
l := lexer.New(string(b))
p := parser.New(l)
p := parser.New(fmt.Sprintf("<module %s>", name), l)
module := p.ParseProgram()
if len(p.Errors()) != 0 {
return nil, fmt.Errorf("ParseError: %s", p.Errors())
}
c := compiler.NewWithState(state.Symbols, state.Constants)
c := compiler.NewWithState(state.Symbols, &state.Constants)
err = c.Compile(module)
if err != nil {
return nil, fmt.Errorf("CompileError: %s", err)
@@ -52,7 +53,7 @@ func ExecModule(name string, state *VMState) (object.Object, error) {
code := c.Bytecode()
state.Constants = code.Constants
machine := NewWithState(code, state)
machine := NewWithState(fmt.Sprintf("<module %s>", name), code, state)
err = machine.Run()
if err != nil {
return nil, fmt.Errorf("RuntimeError: error loading module '%s'", err)
@@ -102,6 +103,9 @@ type VM struct {
state *VMState
dir string
file string
stack []object.Object
sp int // Always points to the next value. Top of stack is stack[sp-1]
@@ -111,7 +115,7 @@ type VM struct {
}
// New constructs a new monkey-lang bytecode virtual machine
func New(bytecode *compiler.Bytecode) *VM {
func New(fn string, bytecode *compiler.Bytecode) *VM {
mainFn := &object.CompiledFunction{Instructions: bytecode.Instructions}
mainClosure := &object.Closure{Fn: mainFn}
mainFrame := NewFrame(mainClosure, 0)
@@ -122,7 +126,7 @@ func New(bytecode *compiler.Bytecode) *VM {
state := NewVMState()
state.Constants = bytecode.Constants
return &VM{
vm := &VM{
state: state,
stack: make([]object.Object, StackSize),
@@ -132,9 +136,13 @@ func New(bytecode *compiler.Bytecode) *VM {
frame: mainFrame,
fp: 1,
}
vm.dir, vm.file = filepath.Split(fn)
return vm
}
func NewWithState(bytecode *compiler.Bytecode, state *VMState) *VM {
func NewWithState(fn string, bytecode *compiler.Bytecode, state *VMState) *VM {
mainFn := &object.CompiledFunction{Instructions: bytecode.Instructions}
mainClosure := &object.Closure{Fn: mainFn}
mainFrame := NewFrame(mainClosure, 0)
@@ -142,7 +150,7 @@ func NewWithState(bytecode *compiler.Bytecode, state *VMState) *VM {
frames := make([]*Frame, MaxFrames)
frames[0] = mainFrame
return &VM{
vm := &VM{
state: state,
frames: frames,
@@ -152,6 +160,10 @@ func NewWithState(bytecode *compiler.Bytecode, state *VMState) *VM {
stack: make([]object.Object, StackSize),
sp: 0,
}
vm.dir, vm.file = filepath.Split(fn)
return vm
}
func (vm *VM) pushFrame(f *Frame) {
@@ -429,7 +441,7 @@ func (vm *VM) Run() error {
case code.OpGetBuiltin:
builtinIndex := code.ReadUint8(ins[ip+1:])
vm.frame.ip += 1
vm.frame.ip++
builtin := builtins.BuiltinsIndex[builtinIndex]

View File

@@ -49,7 +49,7 @@ func runVmTests(t *testing.T, tests []vmTestCase) {
// fmt.Printf("\n")
//}
vm := New(comp.Bytecode())
vm := New("<test>", comp.Bytecode())
err = vm.Run()
if err != nil {
t.Fatalf("vm error: %s", err)
@@ -148,7 +148,7 @@ func testExpectedObject(t *testing.T, expected interface{}, actual object.Object
func parse(input string) *ast.Program {
l := lexer.New(input)
p := parser.New(l)
p := parser.New("<test>", l)
return p.ParseProgram()
}
@@ -694,7 +694,7 @@ func TestCallingFunctionsWithWrongArguments(t *testing.T) {
t.Fatalf("compiler error: %s", err)
}
vm := New(comp.Bytecode())
vm := New("<test>", comp.Bytecode())
err = vm.Run()
if err == nil {
t.Fatalf("expected VM error but resulted in none.")
@@ -1040,7 +1040,7 @@ func TestIntegration(t *testing.T) {
t.Fatalf("compiler error: %s", err)
}
vm := New(c.Bytecode())
vm := New("<test>", c.Bytecode())
err = vm.Run()
if err != nil {
@@ -1089,7 +1089,7 @@ func TestExamples(t *testing.T) {
t.Fatalf("compiler error: %s", err)
}
vm := New(c.Bytecode())
vm := New("<test>", c.Bytecode())
err = vm.Run()
if err != nil {
@@ -1166,7 +1166,7 @@ func BenchmarkFibonacci(b *testing.B) {
b.Fatalf("compiler error: %s", err)
}
vm := New(c.Bytecode())
vm := New("<test>", c.Bytecode())
err = vm.Run()
if err != nil {
@@ -1185,15 +1185,15 @@ func BenchmarkFibonacci(b *testing.B) {
func TestImportExpressions(t *testing.T) {
tests := []vmTestCase{
{
input: `mod := import("../../testdata/mod"); mod.A`,
input: `mod := import("../testdata/mod"); mod.A`,
expected: 5,
},
{
input: `mod := import("../../testdata/mod"); mod.Sum(2, 3)`,
input: `mod := import("../testdata/mod"); mod.Sum(2, 3)`,
expected: 5,
},
{
input: `mod := import("../../testdata/mod"); mod.a`,
input: `mod := import("../testdata/mod"); mod.a`,
expected: nil,
},
}
@@ -1202,11 +1202,11 @@ func TestImportExpressions(t *testing.T) {
}
func TestImportSearchPaths(t *testing.T) {
utils.AddPath("../../testdata")
utils.AddPath("../testdata")
tests := []vmTestCase{
{
input: `mod := import("../../testdata/mod"); mod.A`,
input: `mod := import("../testdata/mod"); mod.A`,
expected: 5,
},
}