Added scope package

This commit is contained in:
Eduard Urbach 2024-07-20 23:20:23 +02:00
parent 263c0cfb8b
commit 43cdac5572
Signed by: akyoto
GPG Key ID: C874F672B1AF20C0
37 changed files with 416 additions and 371 deletions

View File

@ -0,0 +1,18 @@
package core
import (
"git.akyoto.dev/cli/q/src/build/config"
"git.akyoto.dev/cli/q/src/build/scope"
)
// AddVariable adds a new variable to the current scope.
func (f *Function) AddVariable(variable *scope.Variable) {
if config.Comments {
f.Comment("%s = %s (%d uses)", variable.Name, variable.Register, variable.Alive)
}
scope := f.CurrentScope()
variable.Scope = scope
scope.Variables[variable.Name] = variable
scope.Use(variable.Register)
}

11
src/build/core/Comment.go Normal file
View File

@ -0,0 +1,11 @@
package core
import (
"fmt"
)
// Comment adds a comment to the assembler.
func (f *Function) Comment(format string, args ...any) {
f.Assembler.Comment(fmt.Sprintf(format, args...))
f.postInstruction()
}

View File

@ -14,7 +14,7 @@ func (f *Function) Compare(comparison *expression.Expression) error {
if left.IsLeaf() && left.Token.Kind == token.Identifier {
name := left.Token.Text()
variable := f.Variable(name)
variable := f.VariableByName(name)
if variable == nil {
return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Token.Position)
@ -34,13 +34,13 @@ func (f *Function) Compare(comparison *expression.Expression) error {
return f.Execute(comparison.Token, f.cpu.Output[0], right)
}
tmp := f.Scope().MustFindFree(f.cpu.General)
tmp := f.CurrentScope().MustFindFree(f.cpu.General)
err := f.ExpressionToRegister(left, tmp)
if err != nil {
return err
}
defer f.Scope().Free(tmp)
defer f.CurrentScope().Free(tmp)
return f.Execute(comparison.Token, tmp, right)
}

View File

@ -1,11 +1,5 @@
package core
import (
"git.akyoto.dev/cli/q/src/build/ast"
"git.akyoto.dev/cli/q/src/build/errors"
"git.akyoto.dev/cli/q/src/build/token"
)
// Compile turns a function into machine code.
func (f *Function) Compile() {
defer close(f.finished)
@ -13,54 +7,3 @@ func (f *Function) Compile() {
f.Err = f.CompileTokens(f.Body)
f.Return()
}
// CompileTokens compiles a token list.
func (f *Function) CompileTokens(tokens token.List) error {
body, err := ast.Parse(tokens)
if err != nil {
err.(*errors.Error).File = f.File
return err
}
return f.CompileAST(body)
}
// CompileAST compiles an abstract syntax tree.
func (f *Function) CompileAST(tree ast.AST) error {
for _, node := range tree {
err := f.CompileASTNode(node)
if err != nil {
return err
}
}
return nil
}
// CompileASTNode compiles a node in the AST.
func (f *Function) CompileASTNode(node ast.Node) error {
switch node := node.(type) {
case *ast.Assign:
return f.CompileAssign(node)
case *ast.Call:
return f.CompileCall(node.Expression)
case *ast.Define:
return f.CompileDefinition(node)
case *ast.Return:
return f.CompileReturn(node)
case *ast.If:
return f.CompileIf(node)
case *ast.Loop:
return f.CompileLoop(node)
default:
panic("unknown AST type")
}
}

View File

@ -0,0 +1,18 @@
package core
import (
"git.akyoto.dev/cli/q/src/build/ast"
)
// CompileAST compiles an abstract syntax tree.
func (f *Function) CompileAST(tree ast.AST) error {
for _, node := range tree {
err := f.CompileASTNode(node)
if err != nil {
return err
}
}
return nil
}

View File

@ -0,0 +1,31 @@
package core
import (
"git.akyoto.dev/cli/q/src/build/ast"
)
// CompileASTNode compiles a node in the AST.
func (f *Function) CompileASTNode(node ast.Node) error {
switch node := node.(type) {
case *ast.Assign:
return f.CompileAssign(node)
case *ast.Call:
return f.CompileCall(node.Expression)
case *ast.Define:
return f.CompileDefinition(node)
case *ast.Return:
return f.CompileReturn(node)
case *ast.If:
return f.CompileIf(node)
case *ast.Loop:
return f.CompileLoop(node)
default:
panic("unknown AST type")
}
}

View File

@ -17,7 +17,7 @@ func (f *Function) CompileAssign(node *ast.Assign) error {
if left.IsLeaf() {
name := left.Token.Text()
variable := f.Variable(name)
variable := f.VariableByName(name)
if variable == nil {
return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Token.Position)
@ -29,7 +29,7 @@ func (f *Function) CompileAssign(node *ast.Assign) error {
if left.Token.Kind == token.Operator && left.Token.Text() == "@" {
name := left.Children[0].Token.Text()
variable := f.Variable(name)
variable := f.VariableByName(name)
if variable == nil {
return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Children[0].Token.Position)

View File

@ -47,7 +47,7 @@ func (f *Function) CompileCall(root *expression.Expression) error {
// Push
for _, register := range f.cpu.General {
if f.Scope().IsUsed(register) {
if f.CurrentScope().IsUsed(register) {
f.Register(asm.PUSH, register)
}
}
@ -64,14 +64,14 @@ func (f *Function) CompileCall(root *expression.Expression) error {
continue
}
f.Scope().Free(register)
f.CurrentScope().Free(register)
}
// Pop
for i := len(f.cpu.General) - 1; i >= 0; i-- {
register := f.cpu.General[i]
if f.Scope().IsUsed(register) {
if f.CurrentScope().IsUsed(register) {
f.Register(asm.POP, register)
}
}

View File

@ -3,7 +3,6 @@ package core
import (
"fmt"
"git.akyoto.dev/cli/q/src/build/asm"
"git.akyoto.dev/cli/q/src/build/expression"
)
@ -74,39 +73,3 @@ func (f *Function) CompileCondition(condition *expression.Expression, successLab
return err
}
}
// JumpIfFalse jumps to the label if the previous comparison was false.
func (f *Function) JumpIfFalse(operator string, label string) {
switch operator {
case "==":
f.Jump(asm.JNE, label)
case "!=":
f.Jump(asm.JE, label)
case ">":
f.Jump(asm.JLE, label)
case "<":
f.Jump(asm.JGE, label)
case ">=":
f.Jump(asm.JL, label)
case "<=":
f.Jump(asm.JG, label)
}
}
// JumpIfTrue jumps to the label if the previous comparison was true.
func (f *Function) JumpIfTrue(operator string, label string) {
switch operator {
case "==":
f.Jump(asm.JE, label)
case "!=":
f.Jump(asm.JNE, label)
case ">":
f.Jump(asm.JG, label)
case "<":
f.Jump(asm.JL, label)
case ">=":
f.Jump(asm.JGE, label)
case "<=":
f.Jump(asm.JLE, label)
}
}

View File

@ -2,9 +2,7 @@ package core
import (
"git.akyoto.dev/cli/q/src/build/ast"
"git.akyoto.dev/cli/q/src/build/config"
"git.akyoto.dev/cli/q/src/build/errors"
"git.akyoto.dev/cli/q/src/build/expression"
"git.akyoto.dev/cli/q/src/build/token"
)
@ -24,71 +22,3 @@ func (f *Function) CompileDefinition(node *ast.Define) error {
return f.storeVariableInRegister(name, node.Value, uses)
}
func (f *Function) AddVariable(variable *Variable) {
if config.Comments {
f.Comment("%s = %s (%d uses)", variable.Name, variable.Register, variable.Alive)
}
scope := f.Scope()
variable.Scope = scope
scope.variables[variable.Name] = variable
scope.Use(variable.Register)
}
func (f *Function) useVariable(variable *Variable) {
for i, scope := range f.scopes {
if scope.inLoop && variable.Scope != scope {
continue
}
local := scope.variables[variable.Name]
if local == nil {
continue
}
local.Alive--
if local.Alive < 0 {
panic("incorrect number of variable use calls")
}
if local.Alive == 0 {
if config.Comments {
f.Comment("%s (%s) died in scope %d", local.Name, local.Register, i)
}
scope.Free(local.Register)
} else if config.Comments {
f.Comment("%s (%s) used in scope %d", local.Name, local.Register, i)
}
}
}
// identifierExists returns true if the identifier has been defined.
func (f *Function) identifierExists(name string) bool {
variable := f.Variable(name)
if variable != nil {
return true
}
_, exists := f.Functions[name]
return exists
}
func (f *Function) storeVariableInRegister(name string, value *expression.Expression, uses int) error {
reg := f.Scope().MustFindFree(f.cpu.General)
f.Scope().Reserve(reg)
err := f.ExpressionToRegister(value, reg)
f.AddVariable(&Variable{
Name: name,
Register: reg,
Alive: uses,
})
return err
}

View File

@ -18,9 +18,9 @@ func (f *Function) CompileIf(branch *ast.If) error {
}
f.AddLabel(success)
f.pushScope(branch.Body)
f.PushScope(branch.Body)
err = f.CompileAST(branch.Body)
f.popScope()
f.PopScope()
f.AddLabel(fail)
return err
}

View File

@ -12,10 +12,10 @@ func (f *Function) CompileLoop(loop *ast.Loop) error {
f.count.loop++
label := fmt.Sprintf("%s_loop_%d", f.Name, f.count.loop)
f.AddLabel(label)
scope := f.pushScope(loop.Body)
scope.inLoop = true
scope := f.PushScope(loop.Body)
scope.InLoop = true
err := f.CompileAST(loop.Body)
f.Jump(asm.JUMP, label)
f.popScope()
f.PopScope()
return err
}

View File

@ -0,0 +1,19 @@
package core
import (
"git.akyoto.dev/cli/q/src/build/ast"
"git.akyoto.dev/cli/q/src/build/errors"
"git.akyoto.dev/cli/q/src/build/token"
)
// CompileTokens compiles a token list.
func (f *Function) CompileTokens(tokens token.List) error {
body, err := ast.Parse(tokens)
if err != nil {
err.(*errors.Error).File = f.File
return err
}
return f.CompileAST(body)
}

View File

@ -23,8 +23,8 @@ func (f *Function) Execute(operation token.Token, register cpu.Register, value *
return f.ExecuteRegisterRegister(operation, register, f.cpu.Output[0])
}
tmp := f.Scope().MustFindFree(f.cpu.General)
defer f.Scope().Free(tmp)
tmp := f.CurrentScope().MustFindFree(f.cpu.General)
defer f.CurrentScope().Free(tmp)
err := f.ExpressionToRegister(value, tmp)

View File

@ -13,7 +13,7 @@ func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, ope
switch operand.Kind {
case token.Identifier:
name := operand.Text()
variable := f.Variable(name)
variable := f.VariableByName(name)
if variable == nil {
return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, operand.Position)

View File

@ -34,14 +34,14 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp
final := register
if f.UsesRegister(right, register) {
register = f.Scope().MustFindFree(f.cpu.General)
register = f.CurrentScope().MustFindFree(f.cpu.General)
if config.Comments {
f.Comment("temporary register %s", register)
}
}
f.Scope().Reserve(register)
f.CurrentScope().Reserve(register)
err := f.ExpressionToRegister(left, register)
if err != nil {
@ -52,7 +52,7 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp
if register != final {
f.RegisterRegister(asm.MOVE, final, register)
f.Scope().Free(register)
f.CurrentScope().Free(register)
}
return err

View File

@ -1,52 +1,32 @@
package core
import (
"git.akyoto.dev/cli/q/src/build/arch/x64"
"git.akyoto.dev/cli/q/src/build/asm"
"git.akyoto.dev/cli/q/src/build/cpu"
"git.akyoto.dev/cli/q/src/build/fs"
"git.akyoto.dev/cli/q/src/build/scope"
"git.akyoto.dev/cli/q/src/build/token"
)
// Function represents the smallest unit of code.
type Function struct {
Name string
File *fs.File
Body token.List
State
scope.Stack
Name string
File *fs.File
Body token.List
Assembler asm.Assembler
Functions map[string]*Function
Err error
registerHistory []uint64
finished chan struct{}
cpu cpu.CPU
count counter
}
// NewFunction creates a new function.
func NewFunction(name string, file *fs.File, body token.List) *Function {
return &Function{
Name: name,
File: file,
Body: body,
State: State{
Assembler: asm.Assembler{
Instructions: make([]asm.Instruction, 0, 32),
},
cpu: cpu.CPU{
All: x64.AllRegisters,
Input: x64.CallRegisters,
General: x64.GeneralRegisters,
Syscall: x64.SyscallRegisters,
Output: x64.ReturnValueRegisters,
},
scopes: []*Scope{
{variables: map[string]*Variable{}},
},
finished: make(chan struct{}),
},
}
}
// String returns the function name.
func (f *Function) String() string {
return f.Name
}
// Wait will block until the compilation finishes.
func (f *Function) Wait() {
<-f.finished
// counter stores how often a certain statement appeared so we can generate a unique label from it.
type counter struct {
branch int
data int
loop int
subBranch int
}

View File

@ -1,10 +1,7 @@
package core
import (
"fmt"
"git.akyoto.dev/cli/q/src/build/asm"
"git.akyoto.dev/cli/q/src/build/config"
"git.akyoto.dev/cli/q/src/build/cpu"
)
@ -15,12 +12,7 @@ func (f *Function) AddLabel(label string) {
func (f *Function) Call(label string) {
f.Assembler.Call(label)
f.Scope().Use(f.cpu.Output[0])
f.postInstruction()
}
func (f *Function) Comment(format string, args ...any) {
f.Assembler.Comment(fmt.Sprintf(format, args...))
f.CurrentScope().Use(f.cpu.Output[0])
f.postInstruction()
}
@ -38,35 +30,35 @@ func (f *Function) Register(mnemonic asm.Mnemonic, a cpu.Register) {
f.Assembler.Register(mnemonic, a)
if mnemonic == asm.POP {
f.Scope().Use(a)
f.CurrentScope().Use(a)
}
f.postInstruction()
}
func (f *Function) RegisterNumber(mnemonic asm.Mnemonic, a cpu.Register, b int) {
if f.Scope().IsUsed(a) && isDestructive(mnemonic) {
if f.CurrentScope().IsUsed(a) && isDestructive(mnemonic) {
f.SaveRegister(a)
}
f.Assembler.RegisterNumber(mnemonic, a, b)
if mnemonic == asm.MOVE {
f.Scope().Use(a)
f.CurrentScope().Use(a)
}
f.postInstruction()
}
func (f *Function) RegisterLabel(mnemonic asm.Mnemonic, register cpu.Register, label string) {
if f.Scope().IsUsed(register) && isDestructive(mnemonic) {
if f.CurrentScope().IsUsed(register) && isDestructive(mnemonic) {
f.SaveRegister(register)
}
f.Assembler.RegisterLabel(mnemonic, register, label)
if mnemonic == asm.MOVE {
f.Scope().Use(register)
f.CurrentScope().Use(register)
}
f.postInstruction()
@ -77,14 +69,14 @@ func (f *Function) RegisterRegister(mnemonic asm.Mnemonic, a cpu.Register, b cpu
return
}
if f.Scope().IsUsed(a) && isDestructive(mnemonic) {
if f.CurrentScope().IsUsed(a) && isDestructive(mnemonic) {
f.SaveRegister(a)
}
f.Assembler.RegisterRegister(mnemonic, a, b)
if mnemonic == asm.MOVE {
f.Scope().Use(a)
f.CurrentScope().Use(a)
}
f.postInstruction()
@ -97,18 +89,10 @@ func (f *Function) Return() {
func (f *Function) Syscall() {
f.Assembler.Syscall()
f.Scope().Use(f.cpu.Output[0])
f.CurrentScope().Use(f.cpu.Output[0])
f.postInstruction()
}
func (f *Function) postInstruction() {
if !config.Assembler {
return
}
f.registerHistory = append(f.registerHistory, f.Scope().Used)
}
func isDestructive(mnemonic asm.Mnemonic) bool {
switch mnemonic {
case asm.MOVE, asm.ADD, asm.SUB, asm.MUL, asm.DIV:

View File

@ -0,0 +1,23 @@
package core
import (
"git.akyoto.dev/cli/q/src/build/asm"
)
// JumpIfFalse jumps to the label if the previous comparison was false.
func (f *Function) JumpIfFalse(operator string, label string) {
switch operator {
case "==":
f.Jump(asm.JNE, label)
case "!=":
f.Jump(asm.JE, label)
case ">":
f.Jump(asm.JLE, label)
case "<":
f.Jump(asm.JGE, label)
case ">=":
f.Jump(asm.JL, label)
case "<=":
f.Jump(asm.JG, label)
}
}

View File

@ -0,0 +1,23 @@
package core
import (
"git.akyoto.dev/cli/q/src/build/asm"
)
// JumpIfTrue jumps to the label if the previous comparison was true.
func (f *Function) JumpIfTrue(operator string, label string) {
switch operator {
case "==":
f.Jump(asm.JE, label)
case "!=":
f.Jump(asm.JNE, label)
case ">":
f.Jump(asm.JG, label)
case "<":
f.Jump(asm.JL, label)
case ">=":
f.Jump(asm.JGE, label)
case "<=":
f.Jump(asm.JLE, label)
}
}

View File

@ -0,0 +1,35 @@
package core
import (
"git.akyoto.dev/cli/q/src/build/arch/x64"
"git.akyoto.dev/cli/q/src/build/asm"
"git.akyoto.dev/cli/q/src/build/cpu"
"git.akyoto.dev/cli/q/src/build/fs"
"git.akyoto.dev/cli/q/src/build/scope"
"git.akyoto.dev/cli/q/src/build/token"
)
// NewFunction creates a new function.
func NewFunction(name string, file *fs.File, body token.List) *Function {
return &Function{
Name: name,
File: file,
Body: body,
Assembler: asm.Assembler{
Instructions: make([]asm.Instruction, 0, 32),
},
Stack: scope.Stack{
Scopes: []*scope.Scope{
{Variables: map[string]*scope.Variable{}},
},
},
cpu: cpu.CPU{
All: x64.AllRegisters,
Input: x64.CallRegisters,
General: x64.GeneralRegisters,
Syscall: x64.SyscallRegisters,
Output: x64.ReturnValueRegisters,
},
finished: make(chan struct{}),
}
}

View File

@ -5,35 +5,14 @@ import (
"fmt"
"git.akyoto.dev/cli/q/src/build/asm"
"git.akyoto.dev/cli/q/src/build/cpu"
"git.akyoto.dev/go/color/ansi"
)
// State is the data structure we embed in each function to preserve compilation State.
type State struct {
Assembler asm.Assembler
Functions map[string]*Function
Err error
scopes []*Scope
registerHistory []uint64
finished chan struct{}
cpu cpu.CPU
count counter
}
// counter stores how often a certain statement appeared so we can generate a unique label from it.
type counter struct {
branch int
data int
loop int
subBranch int
}
// PrintInstructions shows the assembly instructions.
func (s *State) PrintInstructions() {
func (f *Function) PrintInstructions() {
ansi.Dim.Println("╭──────────────────────────────────────────────────────────────────────────────╮")
for i, x := range s.Assembler.Instructions {
for i, x := range f.Assembler.Instructions {
ansi.Dim.Print("│ ")
switch x.Mnemonic {
@ -54,9 +33,9 @@ func (s *State) PrintInstructions() {
}
registers := bytes.Buffer{}
used := s.registerHistory[i]
used := f.registerHistory[i]
for _, reg := range s.cpu.All {
for _, reg := range f.cpu.All {
if used&(1<<reg) != 0 {
registers.WriteString("⬤ ")
} else {

View File

@ -8,7 +8,7 @@ import (
// SaveRegister attempts to move a variable occupying this register to another register.
func (f *Function) SaveRegister(register cpu.Register) {
if !f.Scope().IsUsed(register) {
if !f.CurrentScope().IsUsed(register) {
return
}
@ -24,8 +24,8 @@ func (f *Function) SaveRegister(register cpu.Register) {
return
}
newRegister := f.Scope().MustFindFree(f.cpu.General)
f.Scope().Reserve(newRegister)
newRegister := f.CurrentScope().MustFindFree(f.cpu.General)
f.CurrentScope().Reserve(newRegister)
if config.Comments {
f.Comment("save %s to %s", register, newRegister)

View File

@ -1,65 +0,0 @@
package core
import (
"git.akyoto.dev/cli/q/src/build/ast"
"git.akyoto.dev/cli/q/src/build/config"
"git.akyoto.dev/cli/q/src/build/cpu"
"git.akyoto.dev/cli/q/src/build/token"
)
// Scope represents an independent code block.
type Scope struct {
variables map[string]*Variable
inLoop bool
cpu.State
}
// 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 (f *Function) pushScope(body ast.AST) *Scope {
scope := &Scope{}
if len(f.scopes) > 0 {
lastScope := f.scopes[len(f.scopes)-1]
scope.State = lastScope.State
scope.variables = make(map[string]*Variable, len(lastScope.variables))
scope.inLoop = lastScope.inLoop
for k, v := range lastScope.variables {
count := ast.Count(body, token.Identifier, v.Name)
if count == 0 {
continue
}
scope.variables[k] = &Variable{
Name: v.Name,
Register: v.Register,
Alive: count,
}
}
} else {
scope.variables = map[string]*Variable{}
}
f.scopes = append(f.scopes, scope)
if config.Comments {
f.Comment("scope %d start", len(f.scopes))
}
return scope
}
// popScope removes the scope at the top of the stack.
func (f *Function) popScope() {
if config.Comments {
f.Comment("scope %d end", len(f.scopes))
}
f.scopes = f.scopes[:len(f.scopes)-1]
}

View File

@ -16,7 +16,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error {
switch t.Kind {
case token.Identifier:
name := t.Text()
variable := f.Variable(name)
variable := f.VariableByName(name)
if variable == nil {
return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position)

View File

@ -1,29 +0,0 @@
package core
import (
"git.akyoto.dev/cli/q/src/build/cpu"
)
// Variable represents a named register.
type Variable struct {
Scope *Scope
Name string
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().variables[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().variables {
if v.Register == register {
return v
}
}
return nil
}

View File

@ -0,0 +1,8 @@
package core
import "git.akyoto.dev/cli/q/src/build/scope"
// VariableByName returns the variable with the given name or `nil` if it doesn't exist.
func (f *Function) VariableByName(name string) *scope.Variable {
return f.CurrentScope().Variables[name]
}

View File

@ -0,0 +1,17 @@
package core
import (
"git.akyoto.dev/cli/q/src/build/cpu"
"git.akyoto.dev/cli/q/src/build/scope"
)
// VariableInRegister returns the variable that occupies the given register or `nil` if none occupy the register.
func (f *Function) VariableInRegister(register cpu.Register) *scope.Variable {
for _, v := range f.CurrentScope().Variables {
if v.Register == register {
return v
}
}
return nil
}

View File

@ -0,0 +1,13 @@
package core
// identifierExists returns true if the identifier has been defined.
func (f *Function) identifierExists(name string) bool {
variable := f.VariableByName(name)
if variable != nil {
return true
}
_, exists := f.Functions[name]
return exists
}

View File

@ -0,0 +1,11 @@
package core
import "git.akyoto.dev/cli/q/src/build/config"
func (f *Function) postInstruction() {
if !config.Assembler {
return
}
f.registerHistory = append(f.registerHistory, f.CurrentScope().Used)
}

View File

@ -0,0 +1,20 @@
package core
import (
"git.akyoto.dev/cli/q/src/build/expression"
"git.akyoto.dev/cli/q/src/build/scope"
)
func (f *Function) storeVariableInRegister(name string, value *expression.Expression, uses int) error {
reg := f.CurrentScope().MustFindFree(f.cpu.General)
f.CurrentScope().Reserve(reg)
err := f.ExpressionToRegister(value, reg)
f.AddVariable(&scope.Variable{
Name: name,
Register: reg,
Alive: uses,
})
return err
}

View File

@ -0,0 +1,36 @@
package core
import (
"git.akyoto.dev/cli/q/src/build/config"
"git.akyoto.dev/cli/q/src/build/scope"
)
func (f *Function) useVariable(variable *scope.Variable) {
for i, scope := range f.Scopes {
if scope.InLoop && variable.Scope != scope {
continue
}
local := scope.Variables[variable.Name]
if local == nil {
continue
}
local.Alive--
if local.Alive < 0 {
panic("incorrect number of variable use calls")
}
if local.Alive == 0 {
if config.Comments {
f.Comment("%s (%s) died in scope %d", local.Name, local.Register, i)
}
scope.Free(local.Register)
} else if config.Comments {
f.Comment("%s (%s) used in scope %d", local.Name, local.Register, i)
}
}
}

View File

@ -10,6 +10,6 @@ import (
type Scanner struct {
functions chan *core.Function
errors chan error
group sync.WaitGroup
queued sync.Map
group sync.WaitGroup
}

View File

@ -12,6 +12,7 @@ import (
"git.akyoto.dev/cli/q/src/build/expression"
"git.akyoto.dev/cli/q/src/build/fs"
"git.akyoto.dev/cli/q/src/build/keyword"
"git.akyoto.dev/cli/q/src/build/scope"
"git.akyoto.dev/cli/q/src/build/token"
)
@ -214,7 +215,7 @@ func (s *Scanner) scanFile(path string, pkg string) error {
return errors.New(&errors.UnusedVariable{Name: name}, file, tokens[0].Position)
}
variable := &core.Variable{
variable := &scope.Variable{
Name: name,
Register: register,
Alive: uses,

12
src/build/scope/Scope.go Normal file
View File

@ -0,0 +1,12 @@
package scope
import (
"git.akyoto.dev/cli/q/src/build/cpu"
)
// Scope represents an independent code block.
type Scope struct {
Variables map[string]*Variable
InLoop bool
cpu.State
}

51
src/build/scope/Stack.go Normal file
View File

@ -0,0 +1,51 @@
package scope
import (
"git.akyoto.dev/cli/q/src/build/ast"
"git.akyoto.dev/cli/q/src/build/token"
)
type Stack struct {
Scopes []*Scope
}
// CurrentScope returns the current scope.
func (stack *Stack) CurrentScope() *Scope {
return stack.Scopes[len(stack.Scopes)-1]
}
// PopScope removes the scope at the top of the stack.
func (stack *Stack) PopScope() {
stack.Scopes = stack.Scopes[:len(stack.Scopes)-1]
}
// PushScope pushes a new scope to the top of the stack.
func (stack *Stack) PushScope(body ast.AST) *Scope {
s := &Scope{}
if len(stack.Scopes) > 0 {
lastScope := stack.Scopes[len(stack.Scopes)-1]
s.State = lastScope.State
s.Variables = make(map[string]*Variable, len(lastScope.Variables))
s.InLoop = lastScope.InLoop
for k, v := range lastScope.Variables {
count := ast.Count(body, token.Identifier, v.Name)
if count == 0 {
continue
}
s.Variables[k] = &Variable{
Name: v.Name,
Register: v.Register,
Alive: count,
}
}
} else {
s.Variables = map[string]*Variable{}
}
stack.Scopes = append(stack.Scopes, s)
return s
}

View File

@ -0,0 +1,13 @@
package scope
import (
"git.akyoto.dev/cli/q/src/build/cpu"
)
// Variable represents a named register.
type Variable struct {
Scope *Scope
Name string
Register cpu.Register
Alive int
}