Added scope package
This commit is contained in:
parent
263c0cfb8b
commit
43cdac5572
18
src/build/core/AddVariable.go
Normal file
18
src/build/core/AddVariable.go
Normal 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
11
src/build/core/Comment.go
Normal 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()
|
||||||
|
}
|
@ -14,7 +14,7 @@ func (f *Function) Compare(comparison *expression.Expression) error {
|
|||||||
|
|
||||||
if left.IsLeaf() && left.Token.Kind == token.Identifier {
|
if left.IsLeaf() && left.Token.Kind == token.Identifier {
|
||||||
name := left.Token.Text()
|
name := left.Token.Text()
|
||||||
variable := f.Variable(name)
|
variable := f.VariableByName(name)
|
||||||
|
|
||||||
if variable == nil {
|
if variable == nil {
|
||||||
return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Token.Position)
|
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)
|
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)
|
err := f.ExpressionToRegister(left, tmp)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer f.Scope().Free(tmp)
|
defer f.CurrentScope().Free(tmp)
|
||||||
return f.Execute(comparison.Token, tmp, right)
|
return f.Execute(comparison.Token, tmp, right)
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,5 @@
|
|||||||
package core
|
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.
|
// Compile turns a function into machine code.
|
||||||
func (f *Function) Compile() {
|
func (f *Function) Compile() {
|
||||||
defer close(f.finished)
|
defer close(f.finished)
|
||||||
@ -13,54 +7,3 @@ func (f *Function) Compile() {
|
|||||||
f.Err = f.CompileTokens(f.Body)
|
f.Err = f.CompileTokens(f.Body)
|
||||||
f.Return()
|
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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
18
src/build/core/CompileAST.go
Normal file
18
src/build/core/CompileAST.go
Normal 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
|
||||||
|
}
|
31
src/build/core/CompileASTNode.go
Normal file
31
src/build/core/CompileASTNode.go
Normal 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")
|
||||||
|
}
|
||||||
|
}
|
@ -17,7 +17,7 @@ func (f *Function) CompileAssign(node *ast.Assign) error {
|
|||||||
|
|
||||||
if left.IsLeaf() {
|
if left.IsLeaf() {
|
||||||
name := left.Token.Text()
|
name := left.Token.Text()
|
||||||
variable := f.Variable(name)
|
variable := f.VariableByName(name)
|
||||||
|
|
||||||
if variable == nil {
|
if variable == nil {
|
||||||
return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Token.Position)
|
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() == "@" {
|
if left.Token.Kind == token.Operator && left.Token.Text() == "@" {
|
||||||
name := left.Children[0].Token.Text()
|
name := left.Children[0].Token.Text()
|
||||||
variable := f.Variable(name)
|
variable := f.VariableByName(name)
|
||||||
|
|
||||||
if variable == nil {
|
if variable == nil {
|
||||||
return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Children[0].Token.Position)
|
return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Children[0].Token.Position)
|
||||||
|
@ -47,7 +47,7 @@ func (f *Function) CompileCall(root *expression.Expression) error {
|
|||||||
|
|
||||||
// Push
|
// Push
|
||||||
for _, register := range f.cpu.General {
|
for _, register := range f.cpu.General {
|
||||||
if f.Scope().IsUsed(register) {
|
if f.CurrentScope().IsUsed(register) {
|
||||||
f.Register(asm.PUSH, register)
|
f.Register(asm.PUSH, register)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -64,14 +64,14 @@ func (f *Function) CompileCall(root *expression.Expression) error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
f.Scope().Free(register)
|
f.CurrentScope().Free(register)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pop
|
// Pop
|
||||||
for i := len(f.cpu.General) - 1; i >= 0; i-- {
|
for i := len(f.cpu.General) - 1; i >= 0; i-- {
|
||||||
register := f.cpu.General[i]
|
register := f.cpu.General[i]
|
||||||
|
|
||||||
if f.Scope().IsUsed(register) {
|
if f.CurrentScope().IsUsed(register) {
|
||||||
f.Register(asm.POP, register)
|
f.Register(asm.POP, register)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@ package core
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"git.akyoto.dev/cli/q/src/build/asm"
|
|
||||||
"git.akyoto.dev/cli/q/src/build/expression"
|
"git.akyoto.dev/cli/q/src/build/expression"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -74,39 +73,3 @@ func (f *Function) CompileCondition(condition *expression.Expression, successLab
|
|||||||
return err
|
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -2,9 +2,7 @@ package core
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"git.akyoto.dev/cli/q/src/build/ast"
|
"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/errors"
|
||||||
"git.akyoto.dev/cli/q/src/build/expression"
|
|
||||||
"git.akyoto.dev/cli/q/src/build/token"
|
"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)
|
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
|
|
||||||
}
|
|
||||||
|
@ -18,9 +18,9 @@ func (f *Function) CompileIf(branch *ast.If) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
f.AddLabel(success)
|
f.AddLabel(success)
|
||||||
f.pushScope(branch.Body)
|
f.PushScope(branch.Body)
|
||||||
err = f.CompileAST(branch.Body)
|
err = f.CompileAST(branch.Body)
|
||||||
f.popScope()
|
f.PopScope()
|
||||||
f.AddLabel(fail)
|
f.AddLabel(fail)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -12,10 +12,10 @@ func (f *Function) CompileLoop(loop *ast.Loop) error {
|
|||||||
f.count.loop++
|
f.count.loop++
|
||||||
label := fmt.Sprintf("%s_loop_%d", f.Name, f.count.loop)
|
label := fmt.Sprintf("%s_loop_%d", f.Name, f.count.loop)
|
||||||
f.AddLabel(label)
|
f.AddLabel(label)
|
||||||
scope := f.pushScope(loop.Body)
|
scope := f.PushScope(loop.Body)
|
||||||
scope.inLoop = true
|
scope.InLoop = true
|
||||||
err := f.CompileAST(loop.Body)
|
err := f.CompileAST(loop.Body)
|
||||||
f.Jump(asm.JUMP, label)
|
f.Jump(asm.JUMP, label)
|
||||||
f.popScope()
|
f.PopScope()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
19
src/build/core/CompileTokens.go
Normal file
19
src/build/core/CompileTokens.go
Normal 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)
|
||||||
|
}
|
@ -23,8 +23,8 @@ func (f *Function) Execute(operation token.Token, register cpu.Register, value *
|
|||||||
return f.ExecuteRegisterRegister(operation, register, f.cpu.Output[0])
|
return f.ExecuteRegisterRegister(operation, register, f.cpu.Output[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
tmp := f.Scope().MustFindFree(f.cpu.General)
|
tmp := f.CurrentScope().MustFindFree(f.cpu.General)
|
||||||
defer f.Scope().Free(tmp)
|
defer f.CurrentScope().Free(tmp)
|
||||||
|
|
||||||
err := f.ExpressionToRegister(value, tmp)
|
err := f.ExpressionToRegister(value, tmp)
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, ope
|
|||||||
switch operand.Kind {
|
switch operand.Kind {
|
||||||
case token.Identifier:
|
case token.Identifier:
|
||||||
name := operand.Text()
|
name := operand.Text()
|
||||||
variable := f.Variable(name)
|
variable := f.VariableByName(name)
|
||||||
|
|
||||||
if variable == nil {
|
if variable == nil {
|
||||||
return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, operand.Position)
|
return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, operand.Position)
|
||||||
|
@ -34,14 +34,14 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp
|
|||||||
final := register
|
final := register
|
||||||
|
|
||||||
if f.UsesRegister(right, register) {
|
if f.UsesRegister(right, register) {
|
||||||
register = f.Scope().MustFindFree(f.cpu.General)
|
register = f.CurrentScope().MustFindFree(f.cpu.General)
|
||||||
|
|
||||||
if config.Comments {
|
if config.Comments {
|
||||||
f.Comment("temporary register %s", register)
|
f.Comment("temporary register %s", register)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
f.Scope().Reserve(register)
|
f.CurrentScope().Reserve(register)
|
||||||
err := f.ExpressionToRegister(left, register)
|
err := f.ExpressionToRegister(left, register)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -52,7 +52,7 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp
|
|||||||
|
|
||||||
if register != final {
|
if register != final {
|
||||||
f.RegisterRegister(asm.MOVE, final, register)
|
f.RegisterRegister(asm.MOVE, final, register)
|
||||||
f.Scope().Free(register)
|
f.CurrentScope().Free(register)
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
@ -1,52 +1,32 @@
|
|||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
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/asm"
|
||||||
"git.akyoto.dev/cli/q/src/build/cpu"
|
"git.akyoto.dev/cli/q/src/build/cpu"
|
||||||
"git.akyoto.dev/cli/q/src/build/fs"
|
"git.akyoto.dev/cli/q/src/build/fs"
|
||||||
|
"git.akyoto.dev/cli/q/src/build/scope"
|
||||||
"git.akyoto.dev/cli/q/src/build/token"
|
"git.akyoto.dev/cli/q/src/build/token"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Function represents the smallest unit of code.
|
// Function represents the smallest unit of code.
|
||||||
type Function struct {
|
type Function struct {
|
||||||
Name string
|
scope.Stack
|
||||||
File *fs.File
|
Name string
|
||||||
Body token.List
|
File *fs.File
|
||||||
State
|
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.
|
// counter stores how often a certain statement appeared so we can generate a unique label from it.
|
||||||
func NewFunction(name string, file *fs.File, body token.List) *Function {
|
type counter struct {
|
||||||
return &Function{
|
branch int
|
||||||
Name: name,
|
data int
|
||||||
File: file,
|
loop int
|
||||||
Body: body,
|
subBranch int
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"git.akyoto.dev/cli/q/src/build/asm"
|
"git.akyoto.dev/cli/q/src/build/asm"
|
||||||
"git.akyoto.dev/cli/q/src/build/config"
|
|
||||||
"git.akyoto.dev/cli/q/src/build/cpu"
|
"git.akyoto.dev/cli/q/src/build/cpu"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -15,12 +12,7 @@ func (f *Function) AddLabel(label string) {
|
|||||||
|
|
||||||
func (f *Function) Call(label string) {
|
func (f *Function) Call(label string) {
|
||||||
f.Assembler.Call(label)
|
f.Assembler.Call(label)
|
||||||
f.Scope().Use(f.cpu.Output[0])
|
f.CurrentScope().Use(f.cpu.Output[0])
|
||||||
f.postInstruction()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Function) Comment(format string, args ...any) {
|
|
||||||
f.Assembler.Comment(fmt.Sprintf(format, args...))
|
|
||||||
f.postInstruction()
|
f.postInstruction()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,35 +30,35 @@ func (f *Function) Register(mnemonic asm.Mnemonic, a cpu.Register) {
|
|||||||
f.Assembler.Register(mnemonic, a)
|
f.Assembler.Register(mnemonic, a)
|
||||||
|
|
||||||
if mnemonic == asm.POP {
|
if mnemonic == asm.POP {
|
||||||
f.Scope().Use(a)
|
f.CurrentScope().Use(a)
|
||||||
}
|
}
|
||||||
|
|
||||||
f.postInstruction()
|
f.postInstruction()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Function) RegisterNumber(mnemonic asm.Mnemonic, a cpu.Register, b int) {
|
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.SaveRegister(a)
|
||||||
}
|
}
|
||||||
|
|
||||||
f.Assembler.RegisterNumber(mnemonic, a, b)
|
f.Assembler.RegisterNumber(mnemonic, a, b)
|
||||||
|
|
||||||
if mnemonic == asm.MOVE {
|
if mnemonic == asm.MOVE {
|
||||||
f.Scope().Use(a)
|
f.CurrentScope().Use(a)
|
||||||
}
|
}
|
||||||
|
|
||||||
f.postInstruction()
|
f.postInstruction()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Function) RegisterLabel(mnemonic asm.Mnemonic, register cpu.Register, label string) {
|
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.SaveRegister(register)
|
||||||
}
|
}
|
||||||
|
|
||||||
f.Assembler.RegisterLabel(mnemonic, register, label)
|
f.Assembler.RegisterLabel(mnemonic, register, label)
|
||||||
|
|
||||||
if mnemonic == asm.MOVE {
|
if mnemonic == asm.MOVE {
|
||||||
f.Scope().Use(register)
|
f.CurrentScope().Use(register)
|
||||||
}
|
}
|
||||||
|
|
||||||
f.postInstruction()
|
f.postInstruction()
|
||||||
@ -77,14 +69,14 @@ func (f *Function) RegisterRegister(mnemonic asm.Mnemonic, a cpu.Register, b cpu
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if f.Scope().IsUsed(a) && isDestructive(mnemonic) {
|
if f.CurrentScope().IsUsed(a) && isDestructive(mnemonic) {
|
||||||
f.SaveRegister(a)
|
f.SaveRegister(a)
|
||||||
}
|
}
|
||||||
|
|
||||||
f.Assembler.RegisterRegister(mnemonic, a, b)
|
f.Assembler.RegisterRegister(mnemonic, a, b)
|
||||||
|
|
||||||
if mnemonic == asm.MOVE {
|
if mnemonic == asm.MOVE {
|
||||||
f.Scope().Use(a)
|
f.CurrentScope().Use(a)
|
||||||
}
|
}
|
||||||
|
|
||||||
f.postInstruction()
|
f.postInstruction()
|
||||||
@ -97,18 +89,10 @@ func (f *Function) Return() {
|
|||||||
|
|
||||||
func (f *Function) Syscall() {
|
func (f *Function) Syscall() {
|
||||||
f.Assembler.Syscall()
|
f.Assembler.Syscall()
|
||||||
f.Scope().Use(f.cpu.Output[0])
|
f.CurrentScope().Use(f.cpu.Output[0])
|
||||||
f.postInstruction()
|
f.postInstruction()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Function) postInstruction() {
|
|
||||||
if !config.Assembler {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
f.registerHistory = append(f.registerHistory, f.Scope().Used)
|
|
||||||
}
|
|
||||||
|
|
||||||
func isDestructive(mnemonic asm.Mnemonic) bool {
|
func isDestructive(mnemonic asm.Mnemonic) bool {
|
||||||
switch mnemonic {
|
switch mnemonic {
|
||||||
case asm.MOVE, asm.ADD, asm.SUB, asm.MUL, asm.DIV:
|
case asm.MOVE, asm.ADD, asm.SUB, asm.MUL, asm.DIV:
|
||||||
|
23
src/build/core/JumpIfFalse.go
Normal file
23
src/build/core/JumpIfFalse.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
23
src/build/core/JumpIfTrue.go
Normal file
23
src/build/core/JumpIfTrue.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
35
src/build/core/NewFunction.go
Normal file
35
src/build/core/NewFunction.go
Normal 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{}),
|
||||||
|
}
|
||||||
|
}
|
@ -5,35 +5,14 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"git.akyoto.dev/cli/q/src/build/asm"
|
"git.akyoto.dev/cli/q/src/build/asm"
|
||||||
"git.akyoto.dev/cli/q/src/build/cpu"
|
|
||||||
"git.akyoto.dev/go/color/ansi"
|
"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.
|
// PrintInstructions shows the assembly instructions.
|
||||||
func (s *State) PrintInstructions() {
|
func (f *Function) PrintInstructions() {
|
||||||
ansi.Dim.Println("╭──────────────────────────────────────────────────────────────────────────────╮")
|
ansi.Dim.Println("╭──────────────────────────────────────────────────────────────────────────────╮")
|
||||||
|
|
||||||
for i, x := range s.Assembler.Instructions {
|
for i, x := range f.Assembler.Instructions {
|
||||||
ansi.Dim.Print("│ ")
|
ansi.Dim.Print("│ ")
|
||||||
|
|
||||||
switch x.Mnemonic {
|
switch x.Mnemonic {
|
||||||
@ -54,9 +33,9 @@ func (s *State) PrintInstructions() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
registers := bytes.Buffer{}
|
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 {
|
if used&(1<<reg) != 0 {
|
||||||
registers.WriteString("⬤ ")
|
registers.WriteString("⬤ ")
|
||||||
} else {
|
} else {
|
@ -8,7 +8,7 @@ import (
|
|||||||
|
|
||||||
// SaveRegister attempts to move a variable occupying this register to another register.
|
// SaveRegister attempts to move a variable occupying this register to another register.
|
||||||
func (f *Function) SaveRegister(register cpu.Register) {
|
func (f *Function) SaveRegister(register cpu.Register) {
|
||||||
if !f.Scope().IsUsed(register) {
|
if !f.CurrentScope().IsUsed(register) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,8 +24,8 @@ func (f *Function) SaveRegister(register cpu.Register) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
newRegister := f.Scope().MustFindFree(f.cpu.General)
|
newRegister := f.CurrentScope().MustFindFree(f.cpu.General)
|
||||||
f.Scope().Reserve(newRegister)
|
f.CurrentScope().Reserve(newRegister)
|
||||||
|
|
||||||
if config.Comments {
|
if config.Comments {
|
||||||
f.Comment("save %s to %s", register, newRegister)
|
f.Comment("save %s to %s", register, newRegister)
|
||||||
|
@ -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]
|
|
||||||
}
|
|
@ -16,7 +16,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error {
|
|||||||
switch t.Kind {
|
switch t.Kind {
|
||||||
case token.Identifier:
|
case token.Identifier:
|
||||||
name := t.Text()
|
name := t.Text()
|
||||||
variable := f.Variable(name)
|
variable := f.VariableByName(name)
|
||||||
|
|
||||||
if variable == nil {
|
if variable == nil {
|
||||||
return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position)
|
return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position)
|
||||||
|
@ -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
|
|
||||||
}
|
|
8
src/build/core/VariableByName.go
Normal file
8
src/build/core/VariableByName.go
Normal 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]
|
||||||
|
}
|
17
src/build/core/VariableInRegister.go
Normal file
17
src/build/core/VariableInRegister.go
Normal 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
|
||||||
|
}
|
13
src/build/core/identifierExists.go
Normal file
13
src/build/core/identifierExists.go
Normal 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
|
||||||
|
}
|
11
src/build/core/postInstruction.go
Normal file
11
src/build/core/postInstruction.go
Normal 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)
|
||||||
|
}
|
20
src/build/core/storeVariableInRegister.go
Normal file
20
src/build/core/storeVariableInRegister.go
Normal 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
|
||||||
|
}
|
36
src/build/core/useVariable.go
Normal file
36
src/build/core/useVariable.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -10,6 +10,6 @@ import (
|
|||||||
type Scanner struct {
|
type Scanner struct {
|
||||||
functions chan *core.Function
|
functions chan *core.Function
|
||||||
errors chan error
|
errors chan error
|
||||||
group sync.WaitGroup
|
|
||||||
queued sync.Map
|
queued sync.Map
|
||||||
|
group sync.WaitGroup
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import (
|
|||||||
"git.akyoto.dev/cli/q/src/build/expression"
|
"git.akyoto.dev/cli/q/src/build/expression"
|
||||||
"git.akyoto.dev/cli/q/src/build/fs"
|
"git.akyoto.dev/cli/q/src/build/fs"
|
||||||
"git.akyoto.dev/cli/q/src/build/keyword"
|
"git.akyoto.dev/cli/q/src/build/keyword"
|
||||||
|
"git.akyoto.dev/cli/q/src/build/scope"
|
||||||
"git.akyoto.dev/cli/q/src/build/token"
|
"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)
|
return errors.New(&errors.UnusedVariable{Name: name}, file, tokens[0].Position)
|
||||||
}
|
}
|
||||||
|
|
||||||
variable := &core.Variable{
|
variable := &scope.Variable{
|
||||||
Name: name,
|
Name: name,
|
||||||
Register: register,
|
Register: register,
|
||||||
Alive: uses,
|
Alive: uses,
|
||||||
|
12
src/build/scope/Scope.go
Normal file
12
src/build/scope/Scope.go
Normal 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
51
src/build/scope/Stack.go
Normal 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
|
||||||
|
}
|
13
src/build/scope/Variable.go
Normal file
13
src/build/scope/Variable.go
Normal 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
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user