refactor how repl works
This commit is contained in:
@@ -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}
|
||||
|
||||
@@ -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},
|
||||
|
||||
147
internal/compiler/bytecode.go
Normal file
147
internal/compiler/bytecode.go
Normal 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()),
|
||||
}
|
||||
}
|
||||
@@ -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:
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
2
internal/testdata/arrays.monkey
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
xs := [1, 2, 3]
|
||||
xs[0] + xs[1] + xs[2]
|
||||
3
internal/testdata/assign.monkey
vendored
Normal file
3
internal/testdata/assign.monkey
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
x := 1
|
||||
x = 2
|
||||
x = x + 1
|
||||
3
internal/testdata/binding.monkey
vendored
Normal file
3
internal/testdata/binding.monkey
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
x := 1
|
||||
y := 2
|
||||
z := x
|
||||
13
internal/testdata/builtins.monkey
vendored
Normal file
13
internal/testdata/builtins.monkey
vendored
Normal 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
2
internal/testdata/closures.monkey
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
f := fn(x) { fn() { x + 1 } }
|
||||
f(2)
|
||||
2
internal/testdata/expressions.monkey
vendored
Normal file
2
internal/testdata/expressions.monkey
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
1 + 2;
|
||||
(1 + 2) * 3;
|
||||
2
internal/testdata/functions.monkey
vendored
Normal file
2
internal/testdata/functions.monkey
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
f := fn(x, y) { x * y };
|
||||
f(2, 4)
|
||||
2
internal/testdata/hashes.monkey
vendored
Normal file
2
internal/testdata/hashes.monkey
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
d := {"a": 1, "b": 2}
|
||||
d["a"] + d["b"]
|
||||
5
internal/testdata/if.monkey
vendored
Normal file
5
internal/testdata/if.monkey
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
x := 1
|
||||
if (x == 1) {
|
||||
x = 2
|
||||
x
|
||||
}
|
||||
3
internal/testdata/mod.monkey
vendored
Normal file
3
internal/testdata/mod.monkey
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
a := 1
|
||||
A := 5
|
||||
Sum := fn(a, b) { return a + b }
|
||||
5
internal/testdata/selectors.monkey
vendored
Normal file
5
internal/testdata/selectors.monkey
vendored
Normal 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
2
internal/testdata/strings.monkey
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
s := "hello"
|
||||
s + " " + "world"
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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]
|
||||
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user