compile functions
Some checks failed
Build / build (push) Failing after 1m57s
Test / build (push) Successful in 2m15s

This commit is contained in:
Chuck Smith
2024-02-28 16:57:01 -05:00
parent 4185926e3e
commit e56fb40f83
5 changed files with 320 additions and 34 deletions

View File

@@ -32,6 +32,9 @@ const (
OpArray OpArray
OpHash OpHash
OpIndex OpIndex
OpCall
OpReturnValue
OpReturn
) )
type Definition struct { type Definition struct {
@@ -61,6 +64,9 @@ var definitions = map[Opcode]*Definition{
OpArray: {"OpArray", []int{2}}, OpArray: {"OpArray", []int{2}},
OpHash: {"OpHash", []int{2}}, OpHash: {"OpHash", []int{2}},
OpIndex: {"OpIndex", []int{}}, OpIndex: {"OpIndex", []int{}},
OpCall: {"OpCall", []int{}},
OpReturnValue: {"OpReturnValue", []int{}},
OpReturn: {"OpReturn", []int{}},
} }
func Lookup(op byte) (*Definition, error) { func Lookup(op byte) (*Definition, error) {

View File

@@ -13,24 +13,37 @@ type EmittedInstruction struct {
Position int Position int
} }
type Compiler struct { type CompilationScope struct {
instructions code.Instructions instructions code.Instructions
constants []object.Object
lastInstruction EmittedInstruction lastInstruction EmittedInstruction
previousInstruction EmittedInstruction previousInstruction EmittedInstruction
}
type Compiler struct {
constants []object.Object
symbolTable *SymbolTable symbolTable *SymbolTable
scopes []CompilationScope
scopeIndex int
} }
func New() *Compiler { func New() *Compiler {
return &Compiler{ mainScope := CompilationScope{
instructions: code.Instructions{}, instructions: code.Instructions{},
constants: []object.Object{},
lastInstruction: EmittedInstruction{}, lastInstruction: EmittedInstruction{},
previousInstruction: EmittedInstruction{}, previousInstruction: EmittedInstruction{},
symbolTable: NewSymbolTable(),
} }
return &Compiler{
constants: []object.Object{},
symbolTable: NewSymbolTable(),
scopes: []CompilationScope{mainScope},
scopeIndex: 0,
}
}
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 {
@@ -141,14 +154,14 @@ func (c *Compiler) Compile(node ast.Node) error {
return err return err
} }
if c.lastInstructionIsPop() { if c.lastInstructionIs(code.OpPop) {
c.removeLastPop() c.removeLastPop()
} }
// Emit an `OnJump` with a bogus value // Emit an `OnJump` with a bogus value
jumpPos := c.emit(code.OpJump, 9999) jumpPos := c.emit(code.OpJump, 9999)
afterConsequencePos := len(c.instructions) afterConsequencePos := len(c.currentInstructions())
c.changeOperand(jumpNotTruthyPos, afterConsequencePos) c.changeOperand(jumpNotTruthyPos, afterConsequencePos)
if node.Alternative == nil { if node.Alternative == nil {
@@ -159,12 +172,12 @@ func (c *Compiler) Compile(node ast.Node) error {
return err return err
} }
if c.lastInstructionIsPop() { if c.lastInstructionIs(code.OpPop) {
c.removeLastPop() c.removeLastPop()
} }
} }
afterAlternativePos := len(c.instructions) afterAlternativePos := len(c.currentInstructions())
c.changeOperand(jumpPos, afterAlternativePos) c.changeOperand(jumpPos, afterAlternativePos)
case *ast.BlockStatement: case *ast.BlockStatement:
@@ -240,6 +253,42 @@ func (c *Compiler) Compile(node ast.Node) error {
c.emit(code.OpIndex) c.emit(code.OpIndex)
case *ast.FunctionLiteral:
c.enterScope()
err := c.Compile(node.Body)
if err != nil {
return err
}
if c.lastInstructionIs(code.OpPop) {
c.replaceLastPopWithReturn()
}
if !c.lastInstructionIs(code.OpReturnValue) {
c.emit(code.OpReturn)
}
instructions := c.leaveScope()
compiledFn := &object.CompiledFunction{Instructions: instructions}
c.emit(code.OpConstant, c.addConstant(compiledFn))
case *ast.ReturnStatement:
err := c.Compile(node.ReturnValue)
if err != nil {
return err
}
c.emit(code.OpReturnValue)
case *ast.CallExpression:
err := c.Compile(node.Function)
if err != nil {
return err
}
c.emit(code.OpCall)
} }
return nil return nil
@@ -260,48 +309,89 @@ func (c *Compiler) emit(op code.Opcode, operands ...int) int {
} }
func (c *Compiler) setLastInstruction(op code.Opcode, pos int) { func (c *Compiler) setLastInstruction(op code.Opcode, pos int) {
previous := c.lastInstruction previous := c.scopes[c.scopeIndex].lastInstruction
last := EmittedInstruction{Opcode: op, Position: pos} last := EmittedInstruction{Opcode: op, Position: pos}
c.previousInstruction = previous c.scopes[c.scopeIndex].previousInstruction = previous
c.lastInstruction = last c.scopes[c.scopeIndex].lastInstruction = last
} }
func (c *Compiler) Bytecode() *Bytecode { func (c *Compiler) Bytecode() *Bytecode {
return &Bytecode{ return &Bytecode{
Instructions: c.instructions, Instructions: c.currentInstructions(),
Constants: c.constants, Constants: c.constants,
} }
} }
func (c *Compiler) addInstruction(ins []byte) int { func (c *Compiler) addInstruction(ins []byte) int {
postNewInstruction := len(c.instructions) postNewInstruction := len(c.currentInstructions())
c.instructions = append(c.instructions, ins...) updatedInstructions := append(c.currentInstructions(), ins...)
c.scopes[c.scopeIndex].instructions = updatedInstructions
return postNewInstruction return postNewInstruction
} }
func (c *Compiler) lastInstructionIsPop() bool { func (c *Compiler) lastInstructionIs(op code.Opcode) bool {
return c.lastInstruction.Opcode == code.OpPop if len(c.currentInstructions()) == 0 {
return false
}
return c.scopes[c.scopeIndex].lastInstruction.Opcode == op
} }
func (c *Compiler) removeLastPop() { func (c *Compiler) removeLastPop() {
c.instructions = c.instructions[:c.lastInstruction.Position] last := c.scopes[c.scopeIndex].lastInstruction
c.lastInstruction = c.previousInstruction previous := c.scopes[c.scopeIndex].previousInstruction
old := c.currentInstructions()
new := old[:last.Position]
c.scopes[c.scopeIndex].instructions = new
c.scopes[c.scopeIndex].lastInstruction = previous
} }
func (c *Compiler) replaceInstruction(pos int, newInstruction []byte) { func (c *Compiler) replaceInstruction(pos int, newInstruction []byte) {
ins := c.currentInstructions()
for i := 0; i < len(newInstruction); i++ { for i := 0; i < len(newInstruction); i++ {
c.instructions[pos+i] = newInstruction[i] ins[pos+i] = newInstruction[i]
} }
} }
func (c *Compiler) changeOperand(opPos int, operand int) { func (c *Compiler) changeOperand(opPos int, operand int) {
op := code.Opcode(c.instructions[opPos]) op := code.Opcode(c.currentInstructions()[opPos])
newInstruction := code.Make(op, operand) newInstruction := code.Make(op, operand)
c.replaceInstruction(opPos, newInstruction) c.replaceInstruction(opPos, newInstruction)
} }
func (c *Compiler) enterScope() {
scope := CompilationScope{
instructions: code.Instructions{},
lastInstruction: EmittedInstruction{},
previousInstruction: EmittedInstruction{},
}
c.scopes = append(c.scopes, scope)
c.scopeIndex++
}
func (c *Compiler) leaveScope() code.Instructions {
instructions := c.currentInstructions()
c.scopes = c.scopes[:len(c.scopes)-1]
c.scopeIndex--
return instructions
}
func (c *Compiler) replaceLastPopWithReturn() {
lastPos := c.scopes[c.scopeIndex].lastInstruction.Position
c.replaceInstruction(lastPos, code.Make(code.OpReturnValue))
c.scopes[c.scopeIndex].lastInstruction.Opcode = code.OpReturnValue
}
type Bytecode struct { type Bytecode struct {
Instructions code.Instructions Instructions code.Instructions
Constants []object.Object Constants []object.Object

View File

@@ -421,6 +421,171 @@ func TestIndexExpressions(t *testing.T) {
code.Make(code.OpPop), code.Make(code.OpPop),
}, },
}, },
{
input: `fn() { 1; 2 }`,
expectedConstants: []interface{}{
1,
2,
[]code.Instructions{
code.Make(code.OpConstant, 0),
code.Make(code.OpPop),
code.Make(code.OpConstant, 1),
code.Make(code.OpReturnValue),
},
},
expectedInstructions: []code.Instructions{
code.Make(code.OpConstant, 2),
code.Make(code.OpPop),
},
},
}
runCompilerTests(t, tests)
}
func TestFunctions(t *testing.T) {
tests := []compilerTestCase{
{
input: "fn() { return 5 + 10 }",
expectedConstants: []interface{}{
5,
10,
[]code.Instructions{
code.Make(code.OpConstant, 0),
code.Make(code.OpConstant, 1),
code.Make(code.OpAdd),
code.Make(code.OpReturnValue),
},
},
expectedInstructions: []code.Instructions{
code.Make(code.OpConstant, 2),
code.Make(code.OpPop),
},
},
{
input: `fn() { 5 + 10 }`,
expectedConstants: []interface{}{
5,
10,
[]code.Instructions{
code.Make(code.OpConstant, 0),
code.Make(code.OpConstant, 1),
code.Make(code.OpAdd),
code.Make(code.OpReturnValue),
},
},
expectedInstructions: []code.Instructions{
code.Make(code.OpConstant, 2),
code.Make(code.OpPop),
},
},
}
runCompilerTests(t, tests)
}
func TestFunctionsWithoutReturnValue(t *testing.T) {
tests := []compilerTestCase{
{
input: `fn() { }`,
expectedConstants: []interface{}{
[]code.Instructions{
code.Make(code.OpReturn),
},
},
expectedInstructions: []code.Instructions{
code.Make(code.OpConstant, 0),
code.Make(code.OpPop),
},
},
}
runCompilerTests(t, tests)
}
func TestCompilerScopes(t *testing.T) {
compiler := New()
if compiler.scopeIndex != 0 {
t.Errorf("scopeIndex wrong. got=%d, want=%d", compiler.scopeIndex, 0)
}
compiler.emit(code.OpMul)
compiler.enterScope()
if compiler.scopeIndex != 1 {
t.Errorf("scopeIndex wrong. got=%d, want=%d", compiler.scopeIndex, 1)
}
compiler.emit(code.OpSub)
if len(compiler.scopes[compiler.scopeIndex].instructions) != 1 {
t.Errorf("instructions length is wrong. got=%d", len(compiler.scopes[compiler.scopeIndex].instructions))
}
last := compiler.scopes[compiler.scopeIndex].lastInstruction
if last.Opcode != code.OpSub {
t.Errorf("lastInstruction.OpCode wrong. got=%d, want=%d", last.Opcode, code.OpSub)
}
compiler.leaveScope()
if compiler.scopeIndex != 0 {
t.Errorf("scopeIndex wrong. got=%d, want=%d", compiler.scopeIndex, 0)
}
compiler.emit(code.OpAdd)
if len(compiler.scopes[compiler.scopeIndex].instructions) != 2 {
t.Errorf("instructions length is wrong. got=%d", len(compiler.scopes[compiler.scopeIndex].instructions))
}
last = compiler.scopes[compiler.scopeIndex].lastInstruction
if last.Opcode != code.OpAdd {
t.Errorf("lastInstruction.OpCode wrong. got=%d, want=%d", last.Opcode, code.OpSub)
}
previous := compiler.scopes[compiler.scopeIndex].previousInstruction
if previous.Opcode != code.OpMul {
t.Errorf("previousInstruction.OpCode wrong. got=%d, want=%d", last.Opcode, code.OpMul)
}
}
func TestFunctionCalls(t *testing.T) {
tests := []compilerTestCase{
{
input: `fn() { 24 }();`,
expectedConstants: []interface{}{
24,
[]code.Instructions{
code.Make(code.OpConstant, 0), // The literal "24"
code.Make(code.OpReturnValue),
},
},
expectedInstructions: []code.Instructions{
code.Make(code.OpConstant, 1), // The compiled function
code.Make(code.OpCall),
code.Make(code.OpPop),
},
},
{
input: `
let noArg = fn() { 24 };
noArg();
`,
expectedConstants: []interface{}{
24,
[]code.Instructions{
code.Make(code.OpConstant, 0), // The literal "24"
code.Make(code.OpReturnValue),
},
},
expectedInstructions: []code.Instructions{
code.Make(code.OpConstant, 1), // The compiled function
code.Make(code.OpSetGlobal, 0),
code.Make(code.OpGetGlobal, 0),
code.Make(code.OpCall),
code.Make(code.OpPop),
},
},
} }
runCompilerTests(t, tests) runCompilerTests(t, tests)
@@ -502,6 +667,17 @@ func testConstants(t *testing.T, expected []interface{}, actual []object.Object)
if err != nil { if err != nil {
return fmt.Errorf("constant %d = testStringObject failed : %s", i, err) return fmt.Errorf("constant %d = testStringObject failed : %s", i, err)
} }
case []code.Instructions:
fn, ok := actual[i].(*object.CompiledFunction)
if !ok {
return fmt.Errorf("constant %d - not a function: %T", i, actual[i])
}
err := testInstructions(constant, fn.Instructions)
if err != nil {
return fmt.Errorf("constant %d = testInstructions failed: %s", i, err)
}
} }
} }
return nil return nil

View File

@@ -24,7 +24,7 @@ func TestDefine(t *testing.T) {
} }
b := global.Define("b") b := global.Define("b")
if a != expected["b"] { if b != expected["b"] {
t.Errorf("expected b=%+v, got=%+v", expected["b"], b) t.Errorf("expected b=%+v, got=%+v", expected["b"], b)
} }
} }

View File

@@ -5,22 +5,24 @@ import (
"fmt" "fmt"
"hash/fnv" "hash/fnv"
"monkey/ast" "monkey/ast"
"monkey/code"
"strings" "strings"
) )
type ObjectType string type ObjectType string
const ( const (
INTEGER_OBJ = "INTEGER" INTEGER_OBJ = "INTEGER"
BOOLEAN_OBJ = "BOOLEAN" BOOLEAN_OBJ = "BOOLEAN"
NULL_OBJ = "NULL" NULL_OBJ = "NULL"
RETURN_VALUE_OBJ = "RETURN_VALUE" RETURN_VALUE_OBJ = "RETURN_VALUE"
ERROR_OBJ = "ERROR" ERROR_OBJ = "ERROR"
FUNCTION_OBJ = "FUNCTION" FUNCTION_OBJ = "FUNCTION"
STRING_OBJ = "STRING" STRING_OBJ = "STRING"
BUILTIN_OBJ = "BUILTIN" BUILTIN_OBJ = "BUILTIN"
ARRAY_OBJ = "ARRAY" ARRAY_OBJ = "ARRAY"
HASH_OBJ = "HASH" HASH_OBJ = "HASH"
COMPILED_FUNCTION_OBJ = "COMPILED_FUNCTION_OBJ "
) )
type Object interface { type Object interface {
@@ -32,6 +34,10 @@ type Integer struct {
Value int64 Value int64
} }
type CompiledFunction struct {
Instructions code.Instructions
}
func (i *Integer) Type() ObjectType { func (i *Integer) Type() ObjectType {
return INTEGER_OBJ return INTEGER_OBJ
} }
@@ -219,3 +225,11 @@ func (h *Hash) Inspect() string {
return out.String() return out.String()
} }
func (cf *CompiledFunction) Type() ObjectType {
return COMPILED_FUNCTION_OBJ
}
func (cf *CompiledFunction) Inspect() string {
return fmt.Sprintf("CompiledFunction[%p]", cf)
}