refactor how repl works
This commit is contained in:
@@ -1,137 +1,38 @@
|
||||
// Package main is the main entrypoint for the monkey interpreter
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"monkey"
|
||||
"os"
|
||||
"os/user"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/profile"
|
||||
|
||||
"monkey/internal"
|
||||
"monkey/internal/compiler"
|
||||
"monkey/internal/lexer"
|
||||
"monkey/internal/object"
|
||||
"monkey/internal/parser"
|
||||
"monkey/internal/repl"
|
||||
)
|
||||
|
||||
var (
|
||||
engine string
|
||||
interactive bool
|
||||
compile bool
|
||||
version bool
|
||||
debug bool
|
||||
|
||||
profileCPU bool
|
||||
profileMem bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.Usage = func() {
|
||||
fmt.Fprintf(flag.CommandLine.Output(), "Usage: %s [options] [<filename>]\n", path.Base(os.Args[0]))
|
||||
flag.PrintDefaults()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
flag.BoolVar(&version, "v", false, "display version information")
|
||||
flag.BoolVar(&debug, "d", false, "enable debug mode")
|
||||
flag.BoolVar(&compile, "c", false, "compile input to bytecode")
|
||||
|
||||
flag.BoolVar(&interactive, "i", false, "enable interactive mode")
|
||||
flag.StringVar(&engine, "e", "vm", "engine to use (eval or vm)")
|
||||
|
||||
flag.BoolVar(&profileCPU, "profile-cpu", false, "Enable CPU profiling.")
|
||||
flag.BoolVar(&profileMem, "profile-mem", false, "Enable Memory profiling.")
|
||||
}
|
||||
|
||||
// Indent indents a block of text with an indent string
|
||||
func Indent(text, indent string) string {
|
||||
if text[len(text)-1:] == "\n" {
|
||||
result := ""
|
||||
for _, j := range strings.Split(text[:len(text)-1], "\n") {
|
||||
result += indent + j + "\n"
|
||||
}
|
||||
return result
|
||||
}
|
||||
result := ""
|
||||
for _, j := range strings.Split(strings.TrimRight(text, "\n"), "\n") {
|
||||
result += indent + j + "\n"
|
||||
}
|
||||
return result[:len(result)-1]
|
||||
}
|
||||
|
||||
func main() {
|
||||
var (
|
||||
compile bool
|
||||
version bool
|
||||
simple 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.Parse()
|
||||
|
||||
if version {
|
||||
fmt.Printf("%s %s", path.Base(os.Args[0]), internal.FullVersion())
|
||||
os.Exit(0)
|
||||
}
|
||||
switch {
|
||||
case compile:
|
||||
monkey.CompileFiles(flag.Args())
|
||||
|
||||
user, err := user.Current()
|
||||
if err != nil {
|
||||
log.Fatalf("could not determine current user: %s", err)
|
||||
}
|
||||
case version:
|
||||
monkey.PrintVersionInfo(os.Stdout)
|
||||
|
||||
args := flag.Args()
|
||||
case flag.NArg() > 0:
|
||||
monkey.ExecFileVM(flag.Arg(0))
|
||||
|
||||
if profileCPU {
|
||||
defer profile.Start(profile.CPUProfile).Stop()
|
||||
} else if profileMem {
|
||||
defer profile.Start(profile.MemProfile).Stop()
|
||||
}
|
||||
case simple:
|
||||
monkey.SimpleVmREPL()
|
||||
|
||||
if compile {
|
||||
if len(args) < 1 {
|
||||
log.Fatal("no source file given to compile")
|
||||
}
|
||||
f, err := os.Open(args[0])
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
b, err := io.ReadAll(f)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
l := lexer.New(string(b))
|
||||
p := parser.New(l)
|
||||
|
||||
program := p.ParseProgram()
|
||||
if len(p.Errors()) != 0 {
|
||||
log.Fatal(p.Errors())
|
||||
}
|
||||
|
||||
c := compiler.New()
|
||||
err = c.Compile(program)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
code := c.Bytecode()
|
||||
fmt.Printf("Main:\n%s\n", code.Instructions)
|
||||
|
||||
fmt.Print("Constants:\n")
|
||||
for i, constant := range code.Constants {
|
||||
fmt.Printf("%04d %s\n", i, constant.Inspect())
|
||||
if fn, ok := constant.(*object.CompiledFunction); ok {
|
||||
fmt.Printf("%s\n", Indent(fn.Instructions.String(), " "))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
opts := &repl.Options{
|
||||
Debug: debug,
|
||||
Engine: engine,
|
||||
Interactive: interactive,
|
||||
}
|
||||
repl := repl.New(user.Username, args, opts)
|
||||
repl.Run()
|
||||
default:
|
||||
monkey.VmREPL()
|
||||
}
|
||||
}
|
||||
|
||||
1
go.mod
1
go.mod
@@ -5,6 +5,7 @@ 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
|
||||
)
|
||||
|
||||
require (
|
||||
|
||||
2
go.sum
2
go.sum
@@ -17,6 +17,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
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=
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
|
||||
140
monkey.go
Normal file
140
monkey.go
Normal file
@@ -0,0 +1,140 @@
|
||||
package monkey
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"monkey/internal/ast"
|
||||
"monkey/internal/compiler"
|
||||
"monkey/internal/parser"
|
||||
"monkey/internal/vm"
|
||||
)
|
||||
|
||||
const MonkeyVersion = "v0.0.1"
|
||||
|
||||
var ErrParseError = errors.New("error: parse error")
|
||||
|
||||
func mustReadFile(fname string) []byte {
|
||||
b, err := os.ReadFile(fname)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func writeFile(fname string, cont []byte) {
|
||||
if err := os.WriteFile(fname, cont, 0644); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func decode(path string) (*compiler.Bytecode, error) {
|
||||
b, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return compiler.Decode(b), nil
|
||||
}
|
||||
|
||||
func compile(path string) (bc *compiler.Bytecode, err error) {
|
||||
input := string(mustReadFile(path))
|
||||
res, errs := parser.Parse(path, input)
|
||||
if len(errs) > 0 {
|
||||
var buf strings.Builder
|
||||
|
||||
for _, e := range errs {
|
||||
buf.WriteString(e)
|
||||
buf.WriteByte('\n')
|
||||
}
|
||||
return nil, errors.New(buf.String())
|
||||
}
|
||||
|
||||
c := compiler.New()
|
||||
c.SetFileInfo(path, input)
|
||||
if err = c.Compile(res); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return c.Bytecode(), nil
|
||||
}
|
||||
|
||||
func ExecFileVM(f string) (err error) {
|
||||
var bytecode *compiler.Bytecode
|
||||
|
||||
if filepath.Ext(f) == ".monkeyc" {
|
||||
bytecode, err = decode(f)
|
||||
} else {
|
||||
bytecode, err = compile(f)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
mvm := vm.New(f, bytecode)
|
||||
if err = mvm.Run(); err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func CompileFiles(files []string) error {
|
||||
for _, f := range files {
|
||||
b := mustReadFile(f)
|
||||
|
||||
res, errs := parser.Parse(f, string(b))
|
||||
if len(errs) != 0 {
|
||||
for _, e := range errs {
|
||||
fmt.Println(e)
|
||||
}
|
||||
return ErrParseError
|
||||
}
|
||||
|
||||
c := compiler.New()
|
||||
if err := c.Compile(res); err != nil {
|
||||
fmt.Println(err)
|
||||
continue
|
||||
}
|
||||
|
||||
cnt, err := c.Bytecode().Encode()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
continue
|
||||
}
|
||||
|
||||
ext := filepath.Ext(f)
|
||||
writeFile(f[:len(f)-len(ext)]+".monkeyc", cnt)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func PrintVersionInfo(w io.Writer) {
|
||||
fmt.Fprintf(w, "Monkey %s on %s\n", MonkeyVersion, strings.Title(runtime.GOOS))
|
||||
}
|
||||
|
||||
func Parse(src string) (ast.Node, error) {
|
||||
tree, errs := parser.Parse("<input>", src)
|
||||
if len(errs) > 0 {
|
||||
var buf strings.Builder
|
||||
|
||||
buf.WriteString("parser error:\n")
|
||||
for _, e := range errs {
|
||||
buf.WriteString(e)
|
||||
buf.WriteByte('\n')
|
||||
}
|
||||
|
||||
return nil, errors.New(buf.String())
|
||||
}
|
||||
|
||||
return tree, nil
|
||||
}
|
||||
191
repl.go
Normal file
191
repl.go
Normal file
@@ -0,0 +1,191 @@
|
||||
package monkey
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/term"
|
||||
|
||||
"monkey/internal/compiler"
|
||||
"monkey/internal/object"
|
||||
"monkey/internal/parser"
|
||||
"monkey/internal/vm"
|
||||
)
|
||||
|
||||
// Package repl implements the Read-Eval-Print-Loop or interactive console
|
||||
// by lexing, parsing and evaluating the input in the interpreter
|
||||
|
||||
func VmREPL() error {
|
||||
var state = vm.NewVMState()
|
||||
|
||||
initState, err := term.MakeRaw(0)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return fmt.Errorf("error opening terminal: %w", err)
|
||||
}
|
||||
defer term.Restore(0, initState)
|
||||
|
||||
t := term.NewTerminal(os.Stdin, ">>> ")
|
||||
t.AutoCompleteCallback = autoComplete
|
||||
object.Stdout = t
|
||||
|
||||
PrintVersionInfo(t)
|
||||
for {
|
||||
input, err := t.ReadLine()
|
||||
check(t, initState, err)
|
||||
|
||||
input = strings.TrimRight(input, " ")
|
||||
if len(input) > 0 && input[len(input)-1] == '{' {
|
||||
input, err = acceptUntil(t, input, "\n\n")
|
||||
check(t, initState, err)
|
||||
}
|
||||
|
||||
res, errs := parser.Parse("<stdin>", input)
|
||||
if len(errs) != 0 {
|
||||
for _, e := range errs {
|
||||
fmt.Fprintln(t, e)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
c := compiler.NewWithState(state.Symbols, &state.Constants)
|
||||
if err := c.Compile(res); err != nil {
|
||||
fmt.Fprintln(t, err)
|
||||
continue
|
||||
}
|
||||
|
||||
mvm := vm.NewWithState("<stdin>", c.Bytecode(), state)
|
||||
if err := mvm.Run(); err != nil {
|
||||
fmt.Fprintf(t, "runtime error: %v\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if val := mvm.LastPoppedStackElem(); val.Type() != object.NULL_OBJ {
|
||||
fmt.Fprintln(t, val.Inspect())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func autoComplete(line string, pos int, key rune) (newLine string, newPos int, ok bool) {
|
||||
if key == '\t' {
|
||||
return line + " ", pos + 4, true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func check(t *term.Terminal, initState *term.State, err error) {
|
||||
if err != nil {
|
||||
// Quit without error on Ctrl^D.
|
||||
if err != io.EOF {
|
||||
fmt.Fprintln(t, err)
|
||||
}
|
||||
term.Restore(0, initState)
|
||||
fmt.Println()
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
func acceptUntil(t *term.Terminal, start, end string) (string, error) {
|
||||
var buf strings.Builder
|
||||
|
||||
buf.WriteString(start)
|
||||
buf.WriteRune('\n')
|
||||
t.SetPrompt("... ")
|
||||
defer t.SetPrompt(">>> ")
|
||||
|
||||
for {
|
||||
line, err := t.ReadLine()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
line = strings.TrimRight(line, " ")
|
||||
buf.WriteString(line)
|
||||
buf.WriteRune('\n')
|
||||
|
||||
if s := buf.String(); len(s) > len(end) && s[len(s)-len(end):] == end {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
func SimpleVmREPL() {
|
||||
var (
|
||||
state = vm.NewVMState()
|
||||
reader = bufio.NewReader(os.Stdin)
|
||||
)
|
||||
|
||||
PrintVersionInfo(os.Stdout)
|
||||
for {
|
||||
fmt.Print(">>> ")
|
||||
input, err := reader.ReadString('\n')
|
||||
simpleCheck(err)
|
||||
|
||||
input = strings.TrimRight(input, " \n")
|
||||
if len(input) > 0 && input[len(input)-1] == '{' {
|
||||
input, err = simpleAcceptUntil(reader, input, "\n\n")
|
||||
simpleCheck(err)
|
||||
}
|
||||
|
||||
res, errs := parser.Parse("<stdin>", input)
|
||||
if len(errs) != 0 {
|
||||
for _, e := range errs {
|
||||
fmt.Println(e)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
c := compiler.NewWithState(state.Symbols, &state.Constants)
|
||||
c.SetFileInfo("<stdin>", input)
|
||||
if err := c.Compile(res); err != nil {
|
||||
fmt.Println(err)
|
||||
continue
|
||||
}
|
||||
mvm := vm.NewWithState("<stdin>", c.Bytecode(), state)
|
||||
|
||||
if err := mvm.Run(); err != nil {
|
||||
fmt.Printf("runtime error: %v\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if val := mvm.LastPoppedStackElem(); val.Type() != object.NULL_OBJ {
|
||||
fmt.Println(val.Inspect())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func simpleCheck(err error) {
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
func simpleAcceptUntil(r *bufio.Reader, start, end string) (string, error) {
|
||||
var buf strings.Builder
|
||||
|
||||
buf.WriteString(start)
|
||||
buf.WriteRune('\n')
|
||||
for {
|
||||
fmt.Print("... ")
|
||||
line, err := r.ReadString('\n')
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
line = strings.TrimRight(line, " \n")
|
||||
buf.WriteString(line)
|
||||
buf.WriteRune('\n')
|
||||
|
||||
if s := buf.String(); len(s) > len(end) && s[len(s)-len(end):] == end {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return buf.String(), nil
|
||||
}
|
||||
Reference in New Issue
Block a user