Files
monkey/compiler/compiler.go
2024-02-07 16:02:06 -05:00

233 lines
4.5 KiB
Go

package compiler
import (
"fmt"
"monkey/ast"
"monkey/code"
"monkey/object"
)
type EmittedInstruction struct {
Opcode code.Opcode
Position int
}
type Compiler struct {
instructions code.Instructions
constants []object.Object
lastInstruction EmittedInstruction
previousInstruction EmittedInstruction
}
func New() *Compiler {
return &Compiler{
instructions: code.Instructions{},
constants: []object.Object{},
lastInstruction: EmittedInstruction{},
previousInstruction: EmittedInstruction{},
}
}
func (c *Compiler) Compile(node ast.Node) error {
switch node := node.(type) {
case *ast.Program:
for _, s := range node.Statements {
err := c.Compile(s)
if err != nil {
return err
}
}
case *ast.ExpressionStatement:
err := c.Compile(node.Expression)
if err != nil {
return err
}
c.emit(code.OpPop)
case *ast.InfixExpression:
if node.Operator == "<" {
err := c.Compile(node.Right)
if err != nil {
return err
}
err = c.Compile(node.Left)
if err != nil {
return err
}
c.emit(code.OpGreaterThan)
return nil
}
err := c.Compile(node.Left)
if err != nil {
return err
}
err = c.Compile(node.Right)
if err != nil {
return err
}
switch node.Operator {
case "+":
c.emit(code.OpAdd)
case "-":
c.emit(code.OpSub)
case "*":
c.emit(code.OpMul)
case "/":
c.emit(code.OpDiv)
case ">":
c.emit(code.OpGreaterThan)
case "==":
c.emit(code.OpEqual)
case "!=":
c.emit(code.OpNotEqual)
default:
return fmt.Errorf("unknown operator %s", node.Operator)
}
case *ast.IntegerLiteral:
integer := &object.Integer{Value: node.Value}
c.emit(code.OpConstant, c.addConstant(integer))
case *ast.Boolean:
if node.Value {
c.emit(code.OpTrue)
} else {
c.emit(code.OpFalse)
}
case *ast.PrefixExpression:
err := c.Compile(node.Right)
if err != nil {
return err
}
switch node.Operator {
case "!":
c.emit(code.OpBang)
case "-":
c.emit(code.OpMinus)
default:
return fmt.Errorf("unknown operator %s", node.Operator)
}
case *ast.IfExpression:
err := c.Compile(node.Condition)
if err != nil {
return err
}
// Emit an `OpJumpNotTruthy` with a bogus value
jumpNotTruthyPos := c.emit(code.OpJumpNotTruthy, 9999)
err = c.Compile(node.Consequence)
if err != nil {
return err
}
if c.lastInstructionIsPop() {
c.removeLastPop()
}
// Emit an `OnJump` with a bogus value
jumpPos := c.emit(code.OpJump, 9999)
afterConsequencePos := len(c.instructions)
c.changeOperand(jumpNotTruthyPos, afterConsequencePos)
if node.Alternative == nil {
c.emit(code.OpNull)
} else {
err = c.Compile(node.Alternative)
if err != nil {
return err
}
if c.lastInstructionIsPop() {
c.removeLastPop()
}
}
afterAlternativePos := len(c.instructions)
c.changeOperand(jumpPos, afterAlternativePos)
case *ast.BlockStatement:
for _, s := range node.Statements {
err := c.Compile(s)
if err != nil {
return err
}
}
}
return nil
}
func (c *Compiler) addConstant(obj object.Object) int {
c.constants = append(c.constants, obj)
return len(c.constants) - 1
}
func (c *Compiler) emit(op code.Opcode, operands ...int) int {
ins := code.Make(op, operands...)
pos := c.addInstruction(ins)
c.setLastInstruction(op, pos)
return pos
}
func (c *Compiler) setLastInstruction(op code.Opcode, pos int) {
previous := c.lastInstruction
last := EmittedInstruction{Opcode: op, Position: pos}
c.previousInstruction = previous
c.lastInstruction = last
}
func (c *Compiler) Bytecode() *Bytecode {
return &Bytecode{
Instructions: c.instructions,
Constants: c.constants,
}
}
func (c *Compiler) addInstruction(ins []byte) int {
postNewInstruction := len(c.instructions)
c.instructions = append(c.instructions, ins...)
return postNewInstruction
}
func (c *Compiler) lastInstructionIsPop() bool {
return c.lastInstruction.Opcode == code.OpPop
}
func (c *Compiler) removeLastPop() {
c.instructions = c.instructions[:c.lastInstruction.Position]
c.lastInstruction = c.previousInstruction
}
func (c *Compiler) replaceInstruction(pos int, newInstruction []byte) {
for i := 0; i < len(newInstruction); i++ {
c.instructions[pos+i] = newInstruction[i]
}
}
func (c *Compiler) changeOperand(opPos int, operand int) {
op := code.Opcode(c.instructions[opPos])
newInstruction := code.Make(op, operand)
c.replaceInstruction(opPos, newInstruction)
}
type Bytecode struct {
Instructions code.Instructions
Constants []object.Object
}