-
Notifications
You must be signed in to change notification settings - Fork 8
/
compiler.go
120 lines (105 loc) · 2.92 KB
/
compiler.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
package bytecode
import (
"errors"
"fmt"
"evylang.dev/evy/pkg/parser"
)
// ErrUndefinedVar is returned when a variable name cannot
// be resolved in the symbol table.
var ErrUndefinedVar = errors.New("undefined variable")
// Compiler is responsible for turning a parsed evy program into
// bytecode.
type Compiler struct {
constants []value
instructions Instructions
globals *SymbolTable
}
// Bytecode represents raw evy bytecode.
type Bytecode struct {
Constants []value
Instructions Instructions
}
// NewCompiler returns a new compiler.
func NewCompiler() *Compiler {
return &Compiler{
constants: []value{},
instructions: Instructions{},
globals: NewSymbolTable(),
}
}
// Compile accepts an AST node and renders it to bytecode internally.
func (c *Compiler) Compile(node parser.Node) error {
switch node := node.(type) {
case *parser.Program:
return c.compileProgram(node)
case *parser.InferredDeclStmt:
return c.compileDecl(node.Decl)
case *parser.AssignmentStmt:
return c.compileAssignment(node)
case *parser.Var:
return c.compileVar(node)
case *parser.NumLiteral:
num := &numVal{V: node.Value}
if err := c.emit(OpConstant, c.addConstant(num)); err != nil {
return err
}
}
return nil
}
// Bytecode renders the compiler instructions into Bytecode.
func (c *Compiler) Bytecode() *Bytecode {
return &Bytecode{
Instructions: c.instructions,
Constants: c.constants,
}
}
// addConstant appends the provided value to the constants
// and returns the index of that constant.
func (c *Compiler) addConstant(obj value) int {
c.constants = append(c.constants, obj)
return len(c.constants) - 1
}
// addInstruction appends bytes to the instruction set and returns the
// position of the instruction.
func (c *Compiler) addInstruction(ins []byte) {
c.instructions = append(c.instructions, ins...)
}
// emit makes and writes an instruction to the bytecode and returns the
// position of the instruction.
func (c *Compiler) emit(op Opcode, operands ...int) error {
ins, err := Make(op, operands...)
if err != nil {
return err
}
c.addInstruction(ins)
return nil
}
func (c *Compiler) compileProgram(prog *parser.Program) error {
for _, s := range prog.Statements {
if err := c.Compile(s); err != nil {
return err
}
}
return nil
}
func (c *Compiler) compileDecl(decl *parser.Decl) error {
if err := c.Compile(decl.Value); err != nil {
return err
}
symbol := c.globals.Define(decl.Var.Name)
return c.emit(OpSetGlobal, symbol.Index)
}
func (c *Compiler) compileAssignment(stmt *parser.AssignmentStmt) error {
if err := c.Compile(stmt.Target); err != nil {
return err
}
symbol := c.globals.Define(stmt.Value.String())
return c.emit(OpSetGlobal, symbol.Index)
}
func (c *Compiler) compileVar(variable *parser.Var) error {
symbol, ok := c.globals.Resolve(variable.Name)
if !ok {
return fmt.Errorf("%w %s", ErrUndefinedVar, variable.Name)
}
return c.emit(OpGetGlobal, symbol.Index)
}