From 24d3e8f2be49050293cd8bfbf2c98e96e17dae5c Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 15 Jul 2024 16:51:36 +0200 Subject: [PATCH] Implemented variable scopes --- examples/factorial/factorial.q | 11 ++++++++++ src/build/core/Compare.go | 4 ++-- src/build/core/CompileAssign.go | 4 ++-- src/build/core/CompileDefinition.go | 8 ++++---- src/build/core/CompileIf.go | 9 ++++++--- src/build/core/ExecuteLeaf.go | 4 ++-- src/build/core/Function.go | 4 ++-- src/build/core/SaveRegister.go | 9 +-------- src/build/core/Scope.go | 31 +++++++++++++++++++++++++++++ src/build/core/TokenToRegister.go | 4 ++-- src/build/core/Variable.go | 16 +++++++++++++++ src/build/core/state.go | 2 +- tests/examples_test.go | 1 + 13 files changed, 81 insertions(+), 26 deletions(-) create mode 100644 examples/factorial/factorial.q create mode 100644 src/build/core/Scope.go diff --git a/examples/factorial/factorial.q b/examples/factorial/factorial.q new file mode 100644 index 0000000..f198ec1 --- /dev/null +++ b/examples/factorial/factorial.q @@ -0,0 +1,11 @@ +main() { + syscall(60, factorial(5)) +} + +factorial(x) { + if x <= 1 { + return 1 + } + + return x * factorial(x - 1) +} \ No newline at end of file diff --git a/src/build/core/Compare.go b/src/build/core/Compare.go index 2e2f29b..4aab1b6 100644 --- a/src/build/core/Compare.go +++ b/src/build/core/Compare.go @@ -14,9 +14,9 @@ func (f *Function) Compare(comparison *expression.Expression) error { if left.IsLeaf() && left.Token.Kind == token.Identifier { name := left.Token.Text() - variable, exists := f.variables[name] + variable := f.Variable(name) - if !exists { + if variable == nil { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Token.Position) } diff --git a/src/build/core/CompileAssign.go b/src/build/core/CompileAssign.go index ce46a34..460ef05 100644 --- a/src/build/core/CompileAssign.go +++ b/src/build/core/CompileAssign.go @@ -8,9 +8,9 @@ import ( // CompileAssign compiles an assign statement. func (f *Function) CompileAssign(node *ast.Assign) error { name := node.Name.Text() - variable, exists := f.variables[name] + variable := f.Variable(name) - if !exists { + if variable == nil { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, node.Name.Position) } diff --git a/src/build/core/CompileDefinition.go b/src/build/core/CompileDefinition.go index c23ac56..894dd64 100644 --- a/src/build/core/CompileDefinition.go +++ b/src/build/core/CompileDefinition.go @@ -44,7 +44,7 @@ func (f *Function) AddVariable(variable *Variable) { f.Comment("%s = %s (%s, %d uses)", variable.Name, variable.Value, variable.Register, variable.Alive) } - f.variables[variable.Name] = variable + f.Scope()[variable.Name] = variable f.cpu.Reserve(variable.Register) f.cpu.Use(variable.Register) } @@ -67,13 +67,13 @@ func (f *Function) useVariable(variable *Variable) { // identifierExists returns true if the identifier has been defined. func (f *Function) identifierExists(name string) bool { - _, exists := f.variables[name] + variable := f.Variable(name) - if exists { + if variable != nil { return true } - _, exists = f.functions[name] + _, exists := f.functions[name] return exists } diff --git a/src/build/core/CompileIf.go b/src/build/core/CompileIf.go index 7721bf6..3146cc4 100644 --- a/src/build/core/CompileIf.go +++ b/src/build/core/CompileIf.go @@ -16,8 +16,11 @@ func (f *Function) CompileIf(branch *ast.If) error { return err } - f.AddLabel(success) - defer f.AddLabel(fail) f.count.branch++ - return f.CompileAST(branch.Body) + f.AddLabel(success) + f.pushScope() + err = f.CompileAST(branch.Body) + f.popScope() + f.AddLabel(fail) + return err } diff --git a/src/build/core/ExecuteLeaf.go b/src/build/core/ExecuteLeaf.go index c08866d..3b0c5f6 100644 --- a/src/build/core/ExecuteLeaf.go +++ b/src/build/core/ExecuteLeaf.go @@ -13,9 +13,9 @@ func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, ope switch operand.Kind { case token.Identifier: name := operand.Text() - variable, exists := f.variables[name] + variable := f.Variable(name) - if !exists { + if variable == nil { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, operand.Position) } diff --git a/src/build/core/Function.go b/src/build/core/Function.go index 961f5d7..157d6ea 100644 --- a/src/build/core/Function.go +++ b/src/build/core/Function.go @@ -36,8 +36,8 @@ func NewFunction(name string, file *fs.File, body token.List) *Function { Syscall: x64.SyscallRegisters, Output: x64.ReturnValueRegisters, }, - variables: map[string]*Variable{}, - finished: make(chan struct{}), + scopes: []Scope{{}}, + finished: make(chan struct{}), }, } } diff --git a/src/build/core/SaveRegister.go b/src/build/core/SaveRegister.go index 30e3c33..2b0b914 100644 --- a/src/build/core/SaveRegister.go +++ b/src/build/core/SaveRegister.go @@ -18,14 +18,7 @@ func (f *Function) SaveRegister(register cpu.Register) { } } - var variable *Variable - - for _, v := range f.variables { - if v.Register == register { - variable = v - break - } - } + variable := f.VariableInRegister(register) if variable == nil || variable.Alive == 0 { return diff --git a/src/build/core/Scope.go b/src/build/core/Scope.go new file mode 100644 index 0000000..dcf3b3c --- /dev/null +++ b/src/build/core/Scope.go @@ -0,0 +1,31 @@ +package core + +// Scope represents a map of variables. +type Scope map[string]*Variable + +// Scope returns the current scope. +func (s *state) Scope() Scope { + return s.scopes[len(s.scopes)-1] +} + +// pushScope pushes a new scope to the top of the stack. +func (s *state) pushScope() { + lastScope := s.scopes[len(s.scopes)-1] + newScope := make(Scope, len(lastScope)) + + for k, v := range lastScope { + newScope[k] = &Variable{ + Value: v.Value, + Name: v.Name, + Register: v.Register, + Alive: v.Alive, + } + } + + s.scopes = append(s.scopes, newScope) +} + +// popScope removes the scope at the top of the stack. +func (s *state) popScope() { + s.scopes = s.scopes[:len(s.scopes)-1] +} diff --git a/src/build/core/TokenToRegister.go b/src/build/core/TokenToRegister.go index d437cec..1d3640c 100644 --- a/src/build/core/TokenToRegister.go +++ b/src/build/core/TokenToRegister.go @@ -16,9 +16,9 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { switch t.Kind { case token.Identifier: name := t.Text() - variable, exists := f.variables[name] + variable := f.Variable(name) - if !exists { + if variable == nil { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) } diff --git a/src/build/core/Variable.go b/src/build/core/Variable.go index 4557945..88e0109 100644 --- a/src/build/core/Variable.go +++ b/src/build/core/Variable.go @@ -12,3 +12,19 @@ type Variable struct { Register cpu.Register Alive int } + +// Variable returns the variable with the given name or `nil` if it doesn't exist. +func (s *state) Variable(name string) *Variable { + return s.Scope()[name] +} + +// VariableInRegister returns the variable that occupies the given register or `nil` if none occupy the register. +func (s *state) VariableInRegister(register cpu.Register) *Variable { + for _, v := range s.Scope() { + if v.Register == register { + return v + } + } + + return nil +} diff --git a/src/build/core/state.go b/src/build/core/state.go index 9269971..b155573 100644 --- a/src/build/core/state.go +++ b/src/build/core/state.go @@ -12,7 +12,7 @@ import ( // state is the data structure we embed in each function to preserve compilation state. type state struct { err error - variables map[string]*Variable + scopes []Scope functions map[string]*Function registerHistory []uint64 finished chan struct{} diff --git a/tests/examples_test.go b/tests/examples_test.go index f390eb2..d9f19f4 100644 --- a/tests/examples_test.go +++ b/tests/examples_test.go @@ -11,6 +11,7 @@ var examples = []struct { ExpectedExitCode int }{ {"hello", "Hello", 0}, + {"factorial", "", 120}, {"fibonacci", "", 55}, }