Globals to compiler
This commit is contained in:
22
.gitea/workflows/ build.yml
Normal file
22
.gitea/workflows/ build.yml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
name: Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [master]
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout Code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v4
|
||||||
|
with:
|
||||||
|
go-version: '>=1.21'
|
||||||
|
- name: Build Binary
|
||||||
|
run: make build
|
||||||
21
.gitea/workflows/ test.yml
Normal file
21
.gitea/workflows/ test.yml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
|
||||||
|
name: Test
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [master]
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout Code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v4
|
||||||
|
with:
|
||||||
|
go-version: '>=1.21'
|
||||||
|
- name: Run Tests
|
||||||
|
run: make test
|
||||||
@@ -27,6 +27,8 @@ const (
|
|||||||
OpJumpNotTruthy
|
OpJumpNotTruthy
|
||||||
OpJump
|
OpJump
|
||||||
OpNull
|
OpNull
|
||||||
|
OpGetGlobal
|
||||||
|
OpSetGlobal
|
||||||
)
|
)
|
||||||
|
|
||||||
type Definition struct {
|
type Definition struct {
|
||||||
@@ -51,6 +53,8 @@ var definitions = map[Opcode]*Definition{
|
|||||||
OpJumpNotTruthy: {"OpJumpNotTruthy", []int{2}},
|
OpJumpNotTruthy: {"OpJumpNotTruthy", []int{2}},
|
||||||
OpJump: {"OpJump", []int{2}},
|
OpJump: {"OpJump", []int{2}},
|
||||||
OpNull: {"OpNull", []int{}},
|
OpNull: {"OpNull", []int{}},
|
||||||
|
OpGetGlobal: {"OpGetGlobal", []int{2}},
|
||||||
|
OpSetGlobal: {"OpSetGlobal", []int{2}},
|
||||||
}
|
}
|
||||||
|
|
||||||
func Lookup(op byte) (*Definition, error) {
|
func Lookup(op byte) (*Definition, error) {
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ type Compiler struct {
|
|||||||
|
|
||||||
lastInstruction EmittedInstruction
|
lastInstruction EmittedInstruction
|
||||||
previousInstruction EmittedInstruction
|
previousInstruction EmittedInstruction
|
||||||
|
|
||||||
|
symbolTable *SymbolTable
|
||||||
}
|
}
|
||||||
|
|
||||||
func New() *Compiler {
|
func New() *Compiler {
|
||||||
@@ -26,6 +28,7 @@ func New() *Compiler {
|
|||||||
constants: []object.Object{},
|
constants: []object.Object{},
|
||||||
lastInstruction: EmittedInstruction{},
|
lastInstruction: EmittedInstruction{},
|
||||||
previousInstruction: EmittedInstruction{},
|
previousInstruction: EmittedInstruction{},
|
||||||
|
symbolTable: NewSymbolTable(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,6 +167,22 @@ func (c *Compiler) Compile(node ast.Node) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case *ast.LetStatement:
|
||||||
|
err := c.Compile(node.Value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
symbol := c.symbolTable.Define(node.Name.Value)
|
||||||
|
c.emit(code.OpSetGlobal, symbol.Index)
|
||||||
|
|
||||||
|
case *ast.Identifier:
|
||||||
|
symbol, ok := c.symbolTable.Resolve(node.Value)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("undefined varible %s", node.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.emit(code.OpGetGlobal, symbol.Index)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -228,6 +228,55 @@ func TestConditionals(t *testing.T) {
|
|||||||
runCompilerTests(t, tests)
|
runCompilerTests(t, tests)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGlobalLetStatements(t *testing.T) {
|
||||||
|
tests := []compilerTestCase{
|
||||||
|
{
|
||||||
|
input: `
|
||||||
|
let one = 1;
|
||||||
|
let two = 2;
|
||||||
|
`,
|
||||||
|
expectedConstants: []interface{}{1, 2},
|
||||||
|
expectedInstructions: []code.Instructions{
|
||||||
|
code.Make(code.OpConstant, 0),
|
||||||
|
code.Make(code.OpSetGlobal, 0),
|
||||||
|
code.Make(code.OpConstant, 1),
|
||||||
|
code.Make(code.OpSetGlobal, 1),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: `
|
||||||
|
let one = 1;
|
||||||
|
one;
|
||||||
|
`,
|
||||||
|
expectedConstants: []interface{}{1},
|
||||||
|
expectedInstructions: []code.Instructions{
|
||||||
|
code.Make(code.OpConstant, 0),
|
||||||
|
code.Make(code.OpSetGlobal, 0),
|
||||||
|
code.Make(code.OpGetGlobal, 0),
|
||||||
|
code.Make(code.OpPop),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: `
|
||||||
|
let one = 1;
|
||||||
|
let two = one;
|
||||||
|
two;
|
||||||
|
`,
|
||||||
|
expectedConstants: []interface{}{1},
|
||||||
|
expectedInstructions: []code.Instructions{
|
||||||
|
code.Make(code.OpConstant, 0),
|
||||||
|
code.Make(code.OpSetGlobal, 0),
|
||||||
|
code.Make(code.OpGetGlobal, 0),
|
||||||
|
code.Make(code.OpSetGlobal, 1),
|
||||||
|
code.Make(code.OpGetGlobal, 1),
|
||||||
|
code.Make(code.OpPop),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
runCompilerTests(t, tests)
|
||||||
|
}
|
||||||
|
|
||||||
func runCompilerTests(t *testing.T, tests []compilerTestCase) {
|
func runCompilerTests(t *testing.T, tests []compilerTestCase) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
|
|||||||
39
compiler/symbol_table.go
Normal file
39
compiler/symbol_table.go
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
package compiler
|
||||||
|
|
||||||
|
type SymbolScope string
|
||||||
|
|
||||||
|
const (
|
||||||
|
GlobalScope SymbolScope = "Global"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Symbol struct {
|
||||||
|
Name string
|
||||||
|
Scope SymbolScope
|
||||||
|
Index int
|
||||||
|
}
|
||||||
|
|
||||||
|
type SymbolTable struct {
|
||||||
|
store map[string]Symbol
|
||||||
|
numDefinitions int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSymbolTable() *SymbolTable {
|
||||||
|
s := make(map[string]Symbol)
|
||||||
|
return &SymbolTable{store: s}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SymbolTable) Define(name string) Symbol {
|
||||||
|
symbol := Symbol{
|
||||||
|
Name: name,
|
||||||
|
Scope: GlobalScope,
|
||||||
|
Index: s.numDefinitions,
|
||||||
|
}
|
||||||
|
s.store[name] = symbol
|
||||||
|
s.numDefinitions++
|
||||||
|
return symbol
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SymbolTable) Resolve(name string) (Symbol, bool) {
|
||||||
|
obj, ok := s.store[name]
|
||||||
|
return obj, ok
|
||||||
|
}
|
||||||
59
compiler/symbol_table_test.go
Normal file
59
compiler/symbol_table_test.go
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
package compiler
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestDefine(t *testing.T) {
|
||||||
|
expected := map[string]Symbol{
|
||||||
|
"a": {
|
||||||
|
Name: "a",
|
||||||
|
Scope: GlobalScope,
|
||||||
|
Index: 0,
|
||||||
|
},
|
||||||
|
"b": {
|
||||||
|
Name: "b",
|
||||||
|
Scope: GlobalScope,
|
||||||
|
Index: 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
global := NewSymbolTable()
|
||||||
|
|
||||||
|
a := global.Define("a")
|
||||||
|
if a != expected["a"] {
|
||||||
|
t.Errorf("expected a=%+v, got=%+v", expected["a"], a)
|
||||||
|
}
|
||||||
|
|
||||||
|
b := global.Define("b")
|
||||||
|
if a != expected["b"] {
|
||||||
|
t.Errorf("expected b=%+v, got=%+v", expected["b"], b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolveGlobal(t *testing.T) {
|
||||||
|
global := NewSymbolTable()
|
||||||
|
global.Define("a")
|
||||||
|
global.Define("b")
|
||||||
|
|
||||||
|
expected := []Symbol{
|
||||||
|
Symbol{
|
||||||
|
Name: "a",
|
||||||
|
Scope: GlobalScope,
|
||||||
|
Index: 0,
|
||||||
|
},
|
||||||
|
Symbol{
|
||||||
|
Name: "b",
|
||||||
|
Scope: GlobalScope,
|
||||||
|
Index: 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, sym := range expected {
|
||||||
|
result, ok := global.Resolve(sym.Name)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("name %s not resolvable", sym.Name)
|
||||||
|
}
|
||||||
|
if result != sym {
|
||||||
|
t.Errorf("expected %s to resolve to %+v, got=%+v", sym.Name, sym, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user