lots o fixes
This commit is contained in:
@@ -11,6 +11,7 @@ var Builtins = map[string]object.Builtin{
|
||||
"len": {Name: "len", Fn: Len},
|
||||
"input": {Name: "input", Fn: Input},
|
||||
"print": {Name: "print", Fn: Print},
|
||||
"println": {Name: "println", Fn: Println},
|
||||
"first": {Name: "first", Fn: First},
|
||||
"last": {Name: "last", Fn: Last},
|
||||
"rest": {Name: "rest", Fn: Rest},
|
||||
|
||||
@@ -16,7 +16,21 @@ func Print(args ...object.Object) object.Object {
|
||||
return newError(err.Error())
|
||||
}
|
||||
|
||||
fmt.Println(args[0].String())
|
||||
fmt.Fprint(object.Stdout, args[0].String())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Println ...
|
||||
func Println(args ...object.Object) object.Object {
|
||||
if err := typing.Check(
|
||||
"println", args,
|
||||
typing.MinimumArgs(1),
|
||||
); err != nil {
|
||||
return newError(err.Error())
|
||||
}
|
||||
|
||||
fmt.Fprintln(object.Stdout, args[0].String())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -5,23 +5,22 @@ import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"monkey/internal/code"
|
||||
"monkey/internal/object"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func Indent(text, ident string) 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 += ident + j + "\n"
|
||||
result += indent + j + "\n"
|
||||
}
|
||||
return result
|
||||
}
|
||||
result := ""
|
||||
for _, j := range strings.Split(strings.TrimRight(text, "\n"), "\n") {
|
||||
result += ident + j + "\n"
|
||||
result += indent + j + "\n"
|
||||
}
|
||||
return result[:len(result)-1]
|
||||
}
|
||||
@@ -38,7 +37,7 @@ func (b *Bytecode) String() string {
|
||||
for i, c := range b.Constants {
|
||||
s.WriteString(fmt.Sprintf("%02d %s\n", i, c))
|
||||
if cf, ok := c.(*object.CompiledFunction); ok {
|
||||
s.WriteString(fmt.Sprintf(" Instructions:\n%s\n", Indent(cf.Instructions.String(), " ")))
|
||||
s.WriteString(fmt.Sprintf(" Instructions:\n%s\n", indent(cf.Instructions.String(), " ")))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,22 +50,40 @@ type encoder struct {
|
||||
bytes.Buffer
|
||||
}
|
||||
|
||||
func (e *encoder) Write(b []byte) (err error) {
|
||||
_, err = e.Buffer.Write(b)
|
||||
return
|
||||
func (e *encoder) WriteBytes(b []byte) error {
|
||||
if err := e.WriteValue(len(b)); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := e.Buffer.Write(b); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *encoder) WriteString(s string) (err error) {
|
||||
_, err = e.Buffer.WriteString(s)
|
||||
return
|
||||
func (e *encoder) WriteString(s string) error {
|
||||
if err := e.WriteValue(len(s)); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := e.Buffer.WriteString(s); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *encoder) WriteValue(data any) error {
|
||||
return binary.Write(&e.Buffer, binary.BigEndian, data)
|
||||
switch val := data.(type) {
|
||||
case int:
|
||||
return binary.Write(&e.Buffer, binary.BigEndian, int64(val))
|
||||
case object.Type:
|
||||
return e.WriteValue(int(val))
|
||||
default:
|
||||
return binary.Write(&e.Buffer, binary.BigEndian, data)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *encoder) WriteObjects(objs ...object.Object) (err error) {
|
||||
for _, obj := range objs {
|
||||
func (e *encoder) WriteObjects(objects ...object.Object) (err error) {
|
||||
err = errors.Join(err, e.WriteValue(len(objects)))
|
||||
for _, obj := range objects {
|
||||
err = errors.Join(err, e.WriteValue(obj.Type()))
|
||||
|
||||
switch o := obj.(type) {
|
||||
@@ -77,13 +94,11 @@ func (e *encoder) WriteObjects(objs ...object.Object) (err error) {
|
||||
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))
|
||||
err = errors.Join(err, e.WriteString(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))
|
||||
err = errors.Join(err, e.WriteBytes(o.Instructions))
|
||||
}
|
||||
}
|
||||
return
|
||||
@@ -92,9 +107,7 @@ func (e *encoder) WriteObjects(objs ...object.Object) (err error) {
|
||||
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.WriteBytes(b.Instructions))
|
||||
err = errors.Join(err, e.WriteObjects(b.Constants...))
|
||||
return e.Bytes(), err
|
||||
}
|
||||
@@ -110,10 +123,8 @@ func (d *decoder) Byte() (b byte) {
|
||||
return
|
||||
}
|
||||
|
||||
func (d *decoder) Int() (i int) {
|
||||
i = int(binary.BigEndian.Uint32(d.b[d.pos:]))
|
||||
d.pos += 4
|
||||
return
|
||||
func (d *decoder) Int() int {
|
||||
return int(d.Int64())
|
||||
}
|
||||
|
||||
func (d *decoder) Uint64() (i uint64) {
|
||||
@@ -122,14 +133,10 @@ func (d *decoder) Uint64() (i uint64) {
|
||||
return
|
||||
}
|
||||
|
||||
func (d *decoder) Int64() (i int64) {
|
||||
func (d *decoder) Int64() 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
|
||||
@@ -161,7 +168,7 @@ func (d *decoder) Objects(len int) (o []object.Object) {
|
||||
Instructions: d.Bytes(d.Int()),
|
||||
})
|
||||
default:
|
||||
panic(fmt.Sprintf("decoder: unsupported decoding for type %d", t))
|
||||
panic(fmt.Sprintf("decoder: unsupported decoding for type %d (%s)", t, t))
|
||||
}
|
||||
}
|
||||
return
|
||||
|
||||
55
internal/compiler/bytecode_test.go
Normal file
55
internal/compiler/bytecode_test.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"monkey/internal/lexer"
|
||||
"monkey/internal/parser"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var hexRe = regexp.MustCompile(`0x[0-9a-f]+`)
|
||||
|
||||
func compile(input string) (*Bytecode, error) {
|
||||
l := lexer.New(input)
|
||||
p := parser.New("<text", l)
|
||||
c := New()
|
||||
if err := c.Compile(p.ParseProgram()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.Bytecode(), nil
|
||||
}
|
||||
|
||||
func TestEncodeDecode(t *testing.T) {
|
||||
input := `
|
||||
fib := fn(x) {
|
||||
if (x == 0) {
|
||||
return 0
|
||||
}
|
||||
if (x == 1) {
|
||||
return 1
|
||||
}
|
||||
return fib(x-1) + fib(x-2)
|
||||
}
|
||||
|
||||
fib(35)
|
||||
`
|
||||
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
bc, err := compile(input)
|
||||
require.NoError(err)
|
||||
|
||||
encoded, err := bc.Encode()
|
||||
require.NoError(err)
|
||||
|
||||
decoded := Decode(encoded)
|
||||
|
||||
expected := hexRe.ReplaceAllString(bc.String(), "0x1234")
|
||||
actual := hexRe.ReplaceAllString(decoded.String(), "0x1234")
|
||||
|
||||
assert.EqualValues(expected, actual)
|
||||
}
|
||||
@@ -948,7 +948,7 @@ func TestBuiltins(t *testing.T) {
|
||||
code.Make(code.OpArray, 0),
|
||||
code.Make(code.OpCall, 1),
|
||||
code.Make(code.OpPop),
|
||||
code.Make(code.OpGetBuiltin, 33),
|
||||
code.Make(code.OpGetBuiltin, 34),
|
||||
code.Make(code.OpArray, 0),
|
||||
code.Make(code.OpConstant, 0),
|
||||
code.Make(code.OpCall, 2),
|
||||
|
||||
@@ -889,7 +889,7 @@ func TestImportSearchPaths(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestExamples(t *testing.T) {
|
||||
matches, err := filepath.Glob("../../examples/*.monkey")
|
||||
matches, err := filepath.Glob("../../examples/*.m")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ func Exists(path string) bool {
|
||||
}
|
||||
|
||||
func FindModule(name string) string {
|
||||
basename := fmt.Sprintf("%s.monkey", name)
|
||||
basename := fmt.Sprintf("%s.m", name)
|
||||
for _, p := range SearchPaths {
|
||||
filename := filepath.Join(p, basename)
|
||||
if Exists(filename) {
|
||||
|
||||
@@ -5,57 +5,57 @@ import (
|
||||
"monkey/internal/object"
|
||||
)
|
||||
|
||||
type Frame struct {
|
||||
type frame struct {
|
||||
cl *object.Closure
|
||||
ip int
|
||||
basePointer int
|
||||
}
|
||||
|
||||
func NewFrame(cl *object.Closure, basePointer int) Frame {
|
||||
return Frame{
|
||||
func newFrame(cl *object.Closure, basePointer int) frame {
|
||||
return frame{
|
||||
cl: cl,
|
||||
basePointer: basePointer,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Frame) Closure() *object.Closure {
|
||||
func (f *frame) Closure() *object.Closure {
|
||||
return f.cl
|
||||
}
|
||||
|
||||
func (f *Frame) GetFree(idx uint8) object.Object {
|
||||
func (f *frame) GetFree(idx uint8) object.Object {
|
||||
return f.cl.Free[idx]
|
||||
}
|
||||
|
||||
func (f *Frame) SetFree(idx uint8, obj object.Object) {
|
||||
func (f *frame) SetFree(idx uint8, obj object.Object) {
|
||||
f.cl.Free[idx] = obj
|
||||
}
|
||||
|
||||
func (f *Frame) SetIP(ip int) {
|
||||
func (f *frame) SetIP(ip int) {
|
||||
f.ip = ip
|
||||
}
|
||||
|
||||
func (f Frame) PeekNextOp() code.Opcode {
|
||||
func (f frame) PeekNextOp() code.Opcode {
|
||||
return code.Opcode(f.cl.Fn.Instructions[f.ip])
|
||||
}
|
||||
|
||||
func (f *Frame) ReadNextOp() code.Opcode {
|
||||
func (f *frame) ReadNextOp() code.Opcode {
|
||||
op := code.Opcode(f.cl.Fn.Instructions[f.ip])
|
||||
f.ip++
|
||||
return op
|
||||
}
|
||||
|
||||
func (f *Frame) ReadUint8() uint8 {
|
||||
func (f *frame) ReadUint8() uint8 {
|
||||
n := code.ReadUint8(f.cl.Fn.Instructions[f.ip:])
|
||||
f.ip++
|
||||
return n
|
||||
}
|
||||
|
||||
func (f *Frame) ReadUint16() uint16 {
|
||||
func (f *frame) ReadUint16() uint16 {
|
||||
n := code.ReadUint16(f.cl.Fn.Instructions[f.ip:])
|
||||
f.ip += 2
|
||||
return n
|
||||
}
|
||||
|
||||
func (f Frame) Instructions() code.Instructions {
|
||||
func (f frame) Instructions() code.Instructions {
|
||||
return f.cl.Fn.Instructions
|
||||
}
|
||||
|
||||
@@ -17,9 +17,11 @@ import (
|
||||
"unicode"
|
||||
)
|
||||
|
||||
const StackSize = 2048
|
||||
const GlobalsSize = 65536
|
||||
const MaxFrames = 1024
|
||||
const (
|
||||
maxStackSize = 2048
|
||||
maxFrames = 1024
|
||||
maxGlobals = 65536
|
||||
)
|
||||
|
||||
func isTruthy(obj object.Object) bool {
|
||||
switch obj := obj.(type) {
|
||||
@@ -36,7 +38,7 @@ func isTruthy(obj object.Object) bool {
|
||||
}
|
||||
|
||||
// executeModule compiles the named module and returns a object.Module object
|
||||
func executeModule(name string, state *VMState) (object.Object, error) {
|
||||
func executeModule(name string, state *State) (object.Object, error) {
|
||||
filename := utils.FindModule(name)
|
||||
if filename == "" {
|
||||
return nil, fmt.Errorf("ImportError: no module named '%s'", name)
|
||||
@@ -73,21 +75,22 @@ func executeModule(name string, state *VMState) (object.Object, error) {
|
||||
return state.ExportedHash(), nil
|
||||
}
|
||||
|
||||
type VMState struct {
|
||||
// State is the state of the virtual machine.
|
||||
type State struct {
|
||||
Constants []object.Object
|
||||
Globals []object.Object
|
||||
Symbols *compiler.SymbolTable
|
||||
}
|
||||
|
||||
func NewVMState() *VMState {
|
||||
func NewState() *State {
|
||||
symbolTable := compiler.NewSymbolTable()
|
||||
for i, builtin := range builtins.BuiltinsIndex {
|
||||
symbolTable.DefineBuiltin(i, builtin.Name)
|
||||
}
|
||||
|
||||
return &VMState{
|
||||
return &State{
|
||||
Constants: []object.Object{},
|
||||
Globals: make([]object.Object, GlobalsSize),
|
||||
Globals: make([]object.Object, maxGlobals),
|
||||
Symbols: symbolTable,
|
||||
}
|
||||
}
|
||||
@@ -95,7 +98,7 @@ func NewVMState() *VMState {
|
||||
// exported binding in the vm state. That is every binding that starts with a
|
||||
// capital letter. This is used by the module import system to wrap up the
|
||||
// compiled and evaulated module into an object.
|
||||
func (s *VMState) ExportedHash() *object.Hash {
|
||||
func (s *State) ExportedHash() *object.Hash {
|
||||
pairs := make(map[object.HashKey]object.HashPair)
|
||||
for name, symbol := range s.Symbols.Store {
|
||||
if unicode.IsUpper(rune(name[0])) {
|
||||
@@ -113,7 +116,7 @@ type VM struct {
|
||||
Debug bool
|
||||
Trace bool
|
||||
|
||||
state *VMState
|
||||
state *State
|
||||
|
||||
dir string
|
||||
file string
|
||||
@@ -121,16 +124,16 @@ type VM struct {
|
||||
stack []object.Object
|
||||
sp int // Always points to the next value. Top of stack is stack[sp-1]
|
||||
|
||||
frames []Frame
|
||||
frames []frame
|
||||
fp int // Always points to the current frame. Current frame is frames[fp-1]
|
||||
}
|
||||
|
||||
func (vm *VM) currentFrame() *Frame {
|
||||
func (vm *VM) currentFrame() *frame {
|
||||
return &vm.frames[vm.fp-1]
|
||||
}
|
||||
|
||||
func (vm *VM) pushFrame(f Frame) {
|
||||
if vm.fp >= MaxFrames {
|
||||
func (vm *VM) pushFrame(f frame) {
|
||||
if vm.fp >= maxFrames {
|
||||
panic("frame overflow")
|
||||
}
|
||||
|
||||
@@ -138,7 +141,7 @@ func (vm *VM) pushFrame(f Frame) {
|
||||
vm.fp++
|
||||
}
|
||||
|
||||
func (vm *VM) popFrame() Frame {
|
||||
func (vm *VM) popFrame() frame {
|
||||
if vm.fp == 0 {
|
||||
panic("fame underflow")
|
||||
}
|
||||
@@ -151,18 +154,18 @@ func (vm *VM) popFrame() Frame {
|
||||
func New(fn string, bytecode *compiler.Bytecode) *VM {
|
||||
mainFn := object.CompiledFunction{Instructions: bytecode.Instructions}
|
||||
mainClosure := object.Closure{Fn: &mainFn}
|
||||
mainFrame := NewFrame(&mainClosure, 0)
|
||||
mainFrame := newFrame(&mainClosure, 0)
|
||||
|
||||
frames := make([]Frame, MaxFrames)
|
||||
frames := make([]frame, maxFrames)
|
||||
frames[0] = mainFrame
|
||||
|
||||
state := NewVMState()
|
||||
state := NewState()
|
||||
state.Constants = bytecode.Constants
|
||||
|
||||
vm := &VM{
|
||||
state: state,
|
||||
|
||||
stack: make([]object.Object, StackSize),
|
||||
stack: make([]object.Object, maxStackSize),
|
||||
sp: 0,
|
||||
|
||||
frames: frames,
|
||||
@@ -174,12 +177,12 @@ func New(fn string, bytecode *compiler.Bytecode) *VM {
|
||||
return vm
|
||||
}
|
||||
|
||||
func NewWithState(fn string, bytecode *compiler.Bytecode, state *VMState) *VM {
|
||||
func NewWithState(fn string, bytecode *compiler.Bytecode, state *State) *VM {
|
||||
mainFn := object.CompiledFunction{Instructions: bytecode.Instructions}
|
||||
mainClosure := object.Closure{Fn: &mainFn}
|
||||
mainFrame := NewFrame(&mainClosure, 0)
|
||||
mainFrame := newFrame(&mainClosure, 0)
|
||||
|
||||
frames := make([]Frame, MaxFrames)
|
||||
frames := make([]frame, maxFrames)
|
||||
frames[0] = mainFrame
|
||||
|
||||
vm := &VM{
|
||||
@@ -188,7 +191,7 @@ func NewWithState(fn string, bytecode *compiler.Bytecode, state *VMState) *VM {
|
||||
frames: frames,
|
||||
fp: 1,
|
||||
|
||||
stack: make([]object.Object, StackSize),
|
||||
stack: make([]object.Object, maxStackSize),
|
||||
sp: 0,
|
||||
}
|
||||
|
||||
@@ -198,7 +201,7 @@ func NewWithState(fn string, bytecode *compiler.Bytecode, state *VMState) *VM {
|
||||
}
|
||||
|
||||
func (vm *VM) push(o object.Object) error {
|
||||
if vm.sp >= StackSize {
|
||||
if vm.sp >= maxStackSize {
|
||||
return fmt.Errorf("stack overflow")
|
||||
}
|
||||
|
||||
@@ -712,7 +715,7 @@ func (vm *VM) callClosure(cl *object.Closure, numArgs int) error {
|
||||
}
|
||||
}
|
||||
|
||||
frame := NewFrame(cl, vm.sp-numArgs)
|
||||
frame := newFrame(cl, vm.sp-numArgs)
|
||||
vm.pushFrame(frame)
|
||||
vm.sp = frame.basePointer + cl.Fn.NumLocals
|
||||
|
||||
|
||||
@@ -1015,7 +1015,7 @@ func TestTailCalls(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIntegration(t *testing.T) {
|
||||
matches, err := filepath.Glob("../testdata/*.monkey")
|
||||
matches, err := filepath.Glob("../testdata/*.m")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -1060,7 +1060,7 @@ func TestExamples(t *testing.T) {
|
||||
t.Skip("skipping test in short mode.")
|
||||
}
|
||||
|
||||
matches, err := filepath.Glob("../examples/*.monkey")
|
||||
matches, err := filepath.Glob("../examples/*.m")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user