Cleaned up function type
This commit is contained in:
parent
5f2ff5e74e
commit
3e47a12ecd
@ -8,7 +8,7 @@ import (
|
|||||||
// CompileAssignment compiles an assignment.
|
// CompileAssignment compiles an assignment.
|
||||||
func (f *Function) CompileAssignment(expr *expression.Expression) error {
|
func (f *Function) CompileAssignment(expr *expression.Expression) error {
|
||||||
name := expr.Children[0].Token.Text()
|
name := expr.Children[0].Token.Text()
|
||||||
variable, exists := f.Variables[name]
|
variable, exists := f.variables[name]
|
||||||
|
|
||||||
if !exists {
|
if !exists {
|
||||||
return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Children[0].Token.Position)
|
return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Children[0].Token.Position)
|
||||||
|
@ -3,6 +3,7 @@ package build
|
|||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"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/expression"
|
"git.akyoto.dev/cli/q/src/build/expression"
|
||||||
@ -16,14 +17,14 @@ func (f *Function) Execute(operation token.Token, register cpu.Register, value *
|
|||||||
return f.ExecuteLeaf(operation, register, value.Token)
|
return f.ExecuteLeaf(operation, register, value.Token)
|
||||||
}
|
}
|
||||||
|
|
||||||
temporary, found := f.CPU.FindFree(f.CPU.General)
|
temporary, found := f.cpu.FindFree(f.cpu.General)
|
||||||
|
|
||||||
if !found {
|
if !found {
|
||||||
panic("no free registers")
|
panic("no free registers")
|
||||||
}
|
}
|
||||||
|
|
||||||
f.CPU.Use(temporary)
|
f.cpu.Use(temporary)
|
||||||
defer f.CPU.Free(temporary)
|
defer f.cpu.Free(temporary)
|
||||||
err := f.ExpressionToRegister(value, temporary)
|
err := f.ExpressionToRegister(value, temporary)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -33,38 +34,12 @@ func (f *Function) Execute(operation token.Token, register cpu.Register, value *
|
|||||||
return f.ExecuteRegisterRegister(operation, register, temporary)
|
return f.ExecuteRegisterRegister(operation, register, temporary)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecuteFunctionCall executes a function call.
|
|
||||||
func (f *Function) ExecuteFunctionCall(expr *expression.Expression) error {
|
|
||||||
funcName := expr.Children[0].Token.Text()
|
|
||||||
parameters := expr.Children[1:]
|
|
||||||
|
|
||||||
if funcName == "syscall" {
|
|
||||||
err := f.ExpressionsToRegisters(parameters, f.CPU.Syscall)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
f.Assembler.Syscall()
|
|
||||||
} else {
|
|
||||||
err := f.ExpressionsToRegisters(parameters, f.CPU.Call)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
f.Assembler.Call(funcName)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExecuteLeaf performs an operation on a register with the given leaf operand.
|
// ExecuteLeaf performs an operation on a register with the given leaf operand.
|
||||||
func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, operand token.Token) error {
|
func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, operand token.Token) error {
|
||||||
switch operand.Kind {
|
switch operand.Kind {
|
||||||
case token.Identifier:
|
case token.Identifier:
|
||||||
name := operand.Text()
|
name := operand.Text()
|
||||||
variable, exists := f.Variables[name]
|
variable, exists := f.variables[name]
|
||||||
|
|
||||||
if !exists {
|
if !exists {
|
||||||
return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, operand.Position)
|
return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, operand.Position)
|
||||||
@ -91,19 +66,19 @@ func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, ope
|
|||||||
func (f *Function) ExecuteRegisterNumber(operation token.Token, register cpu.Register, number int) error {
|
func (f *Function) ExecuteRegisterNumber(operation token.Token, register cpu.Register, number int) error {
|
||||||
switch operation.Text() {
|
switch operation.Text() {
|
||||||
case "+", "+=":
|
case "+", "+=":
|
||||||
f.Assembler.RegisterNumber(asm.ADD, register, number)
|
f.assembler.RegisterNumber(asm.ADD, register, number)
|
||||||
|
|
||||||
case "-", "-=":
|
case "-", "-=":
|
||||||
f.Assembler.RegisterNumber(asm.SUB, register, number)
|
f.assembler.RegisterNumber(asm.SUB, register, number)
|
||||||
|
|
||||||
case "*", "*=":
|
case "*", "*=":
|
||||||
f.Assembler.RegisterNumber(asm.MUL, register, number)
|
f.assembler.RegisterNumber(asm.MUL, register, number)
|
||||||
|
|
||||||
case "/", "/=":
|
case "/", "/=":
|
||||||
f.Assembler.RegisterNumber(asm.DIV, register, number)
|
f.assembler.RegisterNumber(asm.DIV, register, number)
|
||||||
|
|
||||||
case "=":
|
case "=":
|
||||||
f.Assembler.RegisterNumber(asm.MOVE, register, number)
|
f.assembler.RegisterNumber(asm.MOVE, register, number)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return errors.New(&errors.InvalidOperator{Operator: operation.Text()}, f.File, operation.Position)
|
return errors.New(&errors.InvalidOperator{Operator: operation.Text()}, f.File, operation.Position)
|
||||||
@ -116,20 +91,20 @@ func (f *Function) ExecuteRegisterNumber(operation token.Token, register cpu.Reg
|
|||||||
func (f *Function) ExecuteRegisterRegister(operation token.Token, destination cpu.Register, source cpu.Register) error {
|
func (f *Function) ExecuteRegisterRegister(operation token.Token, destination cpu.Register, source cpu.Register) error {
|
||||||
switch operation.Text() {
|
switch operation.Text() {
|
||||||
case "+", "+=":
|
case "+", "+=":
|
||||||
f.Assembler.RegisterRegister(asm.ADD, destination, source)
|
f.assembler.RegisterRegister(asm.ADD, destination, source)
|
||||||
|
|
||||||
case "-", "-=":
|
case "-", "-=":
|
||||||
f.Assembler.RegisterRegister(asm.SUB, destination, source)
|
f.assembler.RegisterRegister(asm.SUB, destination, source)
|
||||||
|
|
||||||
case "*", "*=":
|
case "*", "*=":
|
||||||
f.Assembler.RegisterRegister(asm.MUL, destination, source)
|
f.assembler.RegisterRegister(asm.MUL, destination, source)
|
||||||
|
|
||||||
case "/", "/=":
|
case "/", "/=":
|
||||||
f.Assembler.RegisterRegister(asm.DIV, destination, source)
|
f.assembler.RegisterRegister(asm.DIV, destination, source)
|
||||||
|
|
||||||
case "=":
|
case "=":
|
||||||
if destination != source {
|
if destination != source {
|
||||||
f.Assembler.RegisterRegister(asm.MOVE, destination, source)
|
f.assembler.RegisterRegister(asm.MOVE, destination, source)
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -138,3 +113,103 @@ func (f *Function) ExecuteRegisterRegister(operation token.Token, destination cp
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExpressionToRegister moves the result of an expression into the given register.
|
||||||
|
func (f *Function) ExpressionToRegister(root *expression.Expression, register cpu.Register) error {
|
||||||
|
operation := root.Token
|
||||||
|
|
||||||
|
if root.IsLeaf() {
|
||||||
|
return f.TokenToRegister(operation, register)
|
||||||
|
}
|
||||||
|
|
||||||
|
left := root.Children[0]
|
||||||
|
right := root.Children[1]
|
||||||
|
|
||||||
|
err := f.ExpressionToRegister(left, register)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return f.Execute(operation, register, right)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExpressionsToRegisters moves multiple expressions into the specified registers.
|
||||||
|
func (f *Function) ExpressionsToRegisters(expressions []*expression.Expression, registers []cpu.Register) error {
|
||||||
|
var destinations []cpu.Register
|
||||||
|
|
||||||
|
for i := len(expressions) - 1; i >= 0; i-- {
|
||||||
|
original := registers[i]
|
||||||
|
expression := expressions[i]
|
||||||
|
|
||||||
|
if expression.IsLeaf() {
|
||||||
|
variable, exists := f.variables[expression.Token.Text()]
|
||||||
|
|
||||||
|
if exists && variable.Register == original {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
register := original
|
||||||
|
save := !f.cpu.IsFree(register)
|
||||||
|
|
||||||
|
if save {
|
||||||
|
register = x64.RAX
|
||||||
|
}
|
||||||
|
|
||||||
|
err := f.ExpressionToRegister(expression, register)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if save {
|
||||||
|
destinations = append(destinations, original)
|
||||||
|
f.assembler.Register(asm.PUSH, x64.RAX)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := len(destinations) - 1; i >= 0; i-- {
|
||||||
|
f.assembler.Register(asm.POP, destinations[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TokenToRegister moves a token into a register.
|
||||||
|
// It only works with identifiers, numbers and strings.
|
||||||
|
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]
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position)
|
||||||
|
}
|
||||||
|
|
||||||
|
if register != variable.Register {
|
||||||
|
f.assembler.RegisterRegister(asm.MOVE, register, variable.Register)
|
||||||
|
}
|
||||||
|
|
||||||
|
f.useVariable(variable)
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case token.Number:
|
||||||
|
value := t.Text()
|
||||||
|
n, err := strconv.Atoi(value)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
f.assembler.RegisterNumber(asm.MOVE, register, n)
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case token.String:
|
||||||
|
return errors.New(errors.NotImplemented, f.File, t.Position)
|
||||||
|
|
||||||
|
default:
|
||||||
|
return errors.New(errors.InvalidExpression, f.File, t.Position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,18 +1,11 @@
|
|||||||
package build
|
package build
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"git.akyoto.dev/cli/q/src/build/arch/x64"
|
|
||||||
"git.akyoto.dev/cli/q/src/build/asm"
|
|
||||||
"git.akyoto.dev/cli/q/src/build/config"
|
"git.akyoto.dev/cli/q/src/build/config"
|
||||||
"git.akyoto.dev/cli/q/src/build/cpu"
|
|
||||||
"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/token"
|
"git.akyoto.dev/cli/q/src/build/token"
|
||||||
"git.akyoto.dev/cli/q/src/errors"
|
"git.akyoto.dev/cli/q/src/errors"
|
||||||
"git.akyoto.dev/go/color/ansi"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Function represents a function.
|
// Function represents a function.
|
||||||
@ -20,25 +13,14 @@ type Function struct {
|
|||||||
Name string
|
Name string
|
||||||
File *fs.File
|
File *fs.File
|
||||||
Body token.List
|
Body token.List
|
||||||
Variables map[string]*Variable
|
compiler
|
||||||
Assembler asm.Assembler
|
|
||||||
CPU cpu.CPU
|
|
||||||
Error error
|
|
||||||
count struct{ loop int }
|
|
||||||
debug []debug
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compile turns a function into machine code.
|
// Compile turns a function into machine code.
|
||||||
func (f *Function) Compile() {
|
func (f *Function) Compile() {
|
||||||
f.Assembler.Label(f.Name)
|
f.assembler.Label(f.Name)
|
||||||
err := f.CompileTokens(f.Body)
|
f.err = f.CompileTokens(f.Body)
|
||||||
|
f.assembler.Return()
|
||||||
if err != nil {
|
|
||||||
f.Error = err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
f.Assembler.Return()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CompileTokens compiles a token list.
|
// CompileTokens compiles a token list.
|
||||||
@ -59,13 +41,12 @@ func (f *Function) CompileTokens(body token.List) error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if start != -1 {
|
|
||||||
instruction := body[start:i]
|
instruction := body[start:i]
|
||||||
|
|
||||||
if config.Verbose {
|
if config.Verbose {
|
||||||
f.debug = append(f.debug, debug{
|
f.debug = append(f.debug, debug{
|
||||||
position: len(f.Assembler.Instructions),
|
position: len(f.assembler.Instructions),
|
||||||
instruction: instruction,
|
source: instruction,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,7 +55,6 @@ func (f *Function) CompileTokens(body token.List) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
start = i + 1
|
start = i + 1
|
||||||
|
|
||||||
@ -113,152 +93,21 @@ func (f *Function) CompileInstruction(line token.List) error {
|
|||||||
|
|
||||||
defer expr.Close()
|
defer expr.Close()
|
||||||
|
|
||||||
if isVariableDefinition(expr) {
|
switch true {
|
||||||
|
case isVariableDefinition(expr):
|
||||||
return f.CompileVariableDefinition(expr)
|
return f.CompileVariableDefinition(expr)
|
||||||
}
|
|
||||||
|
|
||||||
if isAssignment(expr) {
|
case isAssignment(expr):
|
||||||
return f.CompileAssignment(expr)
|
return f.CompileAssignment(expr)
|
||||||
}
|
|
||||||
|
|
||||||
if isFunctionCall(expr) {
|
case isFunctionCall(expr):
|
||||||
return f.ExecuteFunctionCall(expr)
|
return f.CompileFunctionCall(expr)
|
||||||
}
|
|
||||||
|
|
||||||
return errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text()}, f.File, expr.Token.Position)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExpressionToRegister moves the result of an expression into the given register.
|
|
||||||
func (f *Function) ExpressionToRegister(root *expression.Expression, register cpu.Register) error {
|
|
||||||
operation := root.Token
|
|
||||||
|
|
||||||
if root.IsLeaf() {
|
|
||||||
return f.TokenToRegister(operation, register)
|
|
||||||
}
|
|
||||||
|
|
||||||
left := root.Children[0]
|
|
||||||
right := root.Children[1]
|
|
||||||
|
|
||||||
err := f.ExpressionToRegister(left, register)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return f.Execute(operation, register, right)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExpressionsToRegisters moves multiple expressions into the specified registers.
|
|
||||||
func (f *Function) ExpressionsToRegisters(expressions []*expression.Expression, registers []cpu.Register) error {
|
|
||||||
var destinations []cpu.Register
|
|
||||||
|
|
||||||
for i := len(expressions) - 1; i >= 0; i-- {
|
|
||||||
original := registers[i]
|
|
||||||
expression := expressions[i]
|
|
||||||
|
|
||||||
if expression.IsLeaf() {
|
|
||||||
variable, exists := f.Variables[expression.Token.Text()]
|
|
||||||
|
|
||||||
if exists && variable.Register == original {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
register := original
|
|
||||||
save := !f.CPU.IsFree(register)
|
|
||||||
|
|
||||||
if save {
|
|
||||||
register = x64.RAX
|
|
||||||
}
|
|
||||||
|
|
||||||
err := f.ExpressionToRegister(expression, register)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if save {
|
|
||||||
destinations = append(destinations, original)
|
|
||||||
f.Assembler.Register(asm.PUSH, x64.RAX)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := len(destinations) - 1; i >= 0; i-- {
|
|
||||||
f.Assembler.Register(asm.POP, destinations[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TokenToRegister moves a token into a register.
|
|
||||||
// It only works with identifiers, numbers and strings.
|
|
||||||
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]
|
|
||||||
|
|
||||||
if !exists {
|
|
||||||
return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position)
|
|
||||||
}
|
|
||||||
|
|
||||||
if register != variable.Register {
|
|
||||||
f.Assembler.RegisterRegister(asm.MOVE, register, variable.Register)
|
|
||||||
}
|
|
||||||
|
|
||||||
f.useVariable(variable)
|
|
||||||
return nil
|
|
||||||
|
|
||||||
case token.Number:
|
|
||||||
value := t.Text()
|
|
||||||
n, err := strconv.Atoi(value)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
f.Assembler.RegisterNumber(asm.MOVE, register, n)
|
|
||||||
return nil
|
|
||||||
|
|
||||||
case token.String:
|
|
||||||
return errors.New(errors.NotImplemented, f.File, t.Position)
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return errors.New(errors.InvalidExpression, f.File, t.Position)
|
return errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text()}, f.File, expr.Token.Position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrintAsm shows the assembly instructions.
|
|
||||||
func (f *Function) PrintAsm() {
|
|
||||||
ansi.Dim.Println("╭──────────────────────────────────────╮")
|
|
||||||
|
|
||||||
for i, x := range f.Assembler.Instructions {
|
|
||||||
instruction := f.instructionAt(i)
|
|
||||||
|
|
||||||
if instruction != nil {
|
|
||||||
ansi.Dim.Println("├──────────────────────────────────────┤")
|
|
||||||
}
|
|
||||||
|
|
||||||
ansi.Dim.Print("│ ")
|
|
||||||
|
|
||||||
if x.Mnemonic == asm.LABEL {
|
|
||||||
ansi.Yellow.Printf("%-36s", x.Data.String()+":")
|
|
||||||
} else {
|
|
||||||
ansi.Green.Printf("%-8s", x.Mnemonic.String())
|
|
||||||
|
|
||||||
if x.Data != nil {
|
|
||||||
fmt.Printf("%-28s", x.Data.String())
|
|
||||||
} else {
|
|
||||||
fmt.Printf("%-28s", "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ansi.Dim.Print(" │\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
ansi.Dim.Println("╰──────────────────────────────────────╯")
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the function name.
|
// String returns the function name.
|
||||||
func (f *Function) String() string {
|
func (f *Function) String() string {
|
||||||
return f.Name
|
return f.Name
|
||||||
@ -266,7 +115,7 @@ func (f *Function) String() string {
|
|||||||
|
|
||||||
// identifierExists returns true if the identifier has been defined.
|
// identifierExists returns true if the identifier has been defined.
|
||||||
func (f *Function) identifierExists(name string) bool {
|
func (f *Function) identifierExists(name string) bool {
|
||||||
_, exists := f.Variables[name]
|
_, exists := f.variables[name]
|
||||||
return exists
|
return exists
|
||||||
}
|
}
|
||||||
|
|
||||||
|
17
src/build/FunctionCall.go
Normal file
17
src/build/FunctionCall.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package build
|
||||||
|
|
||||||
|
import "git.akyoto.dev/cli/q/src/build/expression"
|
||||||
|
|
||||||
|
// CompileFunctionCall executes a function call.
|
||||||
|
func (f *Function) CompileFunctionCall(expr *expression.Expression) error {
|
||||||
|
funcName := expr.Children[0].Token.Text()
|
||||||
|
parameters := expr.Children[1:]
|
||||||
|
|
||||||
|
if funcName == "syscall" {
|
||||||
|
defer f.assembler.Syscall()
|
||||||
|
return f.ExpressionsToRegisters(parameters, f.cpu.Syscall)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer f.assembler.Call(funcName)
|
||||||
|
return f.ExpressionsToRegisters(parameters, f.cpu.Call)
|
||||||
|
}
|
@ -1,46 +1,20 @@
|
|||||||
package build
|
package build
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"git.akyoto.dev/cli/q/src/build/expression"
|
|
||||||
"git.akyoto.dev/cli/q/src/build/token"
|
"git.akyoto.dev/cli/q/src/build/token"
|
||||||
"git.akyoto.dev/cli/q/src/errors"
|
"git.akyoto.dev/cli/q/src/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CompileKeyword compiles an instruction that starts with a keyword.
|
// CompileKeyword compiles an instruction that starts with a keyword.
|
||||||
func (f *Function) CompileKeyword(line token.List) error {
|
func (f *Function) CompileKeyword(tokens token.List) error {
|
||||||
switch line[0].Text() {
|
switch tokens[0].Text() {
|
||||||
case "return":
|
case "return":
|
||||||
if len(line) > 1 {
|
return f.CompileReturn(tokens)
|
||||||
value := expression.Parse(line[1:])
|
|
||||||
defer value.Close()
|
|
||||||
// TODO: Set the return value
|
|
||||||
}
|
|
||||||
|
|
||||||
f.Assembler.Return()
|
|
||||||
|
|
||||||
case "loop":
|
case "loop":
|
||||||
blockStart := line.IndexKind(token.BlockStart) + 1
|
return f.CompileLoop(tokens)
|
||||||
blockEnd := line.LastIndexKind(token.BlockEnd)
|
|
||||||
|
|
||||||
if blockStart == -1 {
|
|
||||||
return errors.New(errors.MissingBlockStart, f.File, line[0].End())
|
|
||||||
}
|
|
||||||
|
|
||||||
if blockEnd == -1 {
|
|
||||||
return errors.New(errors.MissingBlockEnd, f.File, line[len(line)-1].End())
|
|
||||||
}
|
|
||||||
|
|
||||||
loop := fmt.Sprintf("%s_loop_%d", f.Name, f.count.loop)
|
|
||||||
f.Assembler.Label(loop)
|
|
||||||
defer f.Assembler.Jump(loop)
|
|
||||||
f.count.loop++
|
|
||||||
return f.CompileTokens(line[blockStart:blockEnd])
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return errors.New(&errors.KeywordNotImplemented{Keyword: line[0].Text()}, f.File, line[0].Position)
|
return errors.New(&errors.KeywordNotImplemented{Keyword: tokens[0].Text()}, f.File, tokens[0].Position)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
28
src/build/Loop.go
Normal file
28
src/build/Loop.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package build
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.akyoto.dev/cli/q/src/build/token"
|
||||||
|
"git.akyoto.dev/cli/q/src/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CompileLoop compiles a loop instruction.
|
||||||
|
func (f *Function) CompileLoop(tokens token.List) error {
|
||||||
|
blockStart := tokens.IndexKind(token.BlockStart) + 1
|
||||||
|
blockEnd := tokens.LastIndexKind(token.BlockEnd)
|
||||||
|
|
||||||
|
if blockStart == -1 {
|
||||||
|
return errors.New(errors.MissingBlockStart, f.File, tokens[0].End())
|
||||||
|
}
|
||||||
|
|
||||||
|
if blockEnd == -1 {
|
||||||
|
return errors.New(errors.MissingBlockEnd, f.File, tokens[len(tokens)-1].End())
|
||||||
|
}
|
||||||
|
|
||||||
|
loop := fmt.Sprintf("%s_loop_%d", f.Name, f.count.loop)
|
||||||
|
f.assembler.Label(loop)
|
||||||
|
defer f.assembler.Jump(loop)
|
||||||
|
f.count.loop++
|
||||||
|
return f.CompileTokens(tokens[blockStart:blockEnd])
|
||||||
|
}
|
@ -31,7 +31,7 @@ func (r *Result) Finalize() ([]byte, []byte) {
|
|||||||
// This will place the main function immediately after the entry point
|
// This will place the main function immediately after the entry point
|
||||||
// and also add everything the main function calls recursively.
|
// and also add everything the main function calls recursively.
|
||||||
r.EachFunction(r.Main, map[*Function]bool{}, func(f *Function) {
|
r.EachFunction(r.Main, map[*Function]bool{}, func(f *Function) {
|
||||||
final.Merge(f.Assembler)
|
final.Merge(f.assembler)
|
||||||
})
|
})
|
||||||
|
|
||||||
code, data := final.Finalize()
|
code, data := final.Finalize()
|
||||||
@ -44,7 +44,7 @@ func (r *Result) EachFunction(caller *Function, traversed map[*Function]bool, ca
|
|||||||
call(caller)
|
call(caller)
|
||||||
traversed[caller] = true
|
traversed[caller] = true
|
||||||
|
|
||||||
for _, x := range caller.Assembler.Instructions {
|
for _, x := range caller.assembler.Instructions {
|
||||||
if x.Mnemonic != asm.CALL {
|
if x.Mnemonic != asm.CALL {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
18
src/build/Return.go
Normal file
18
src/build/Return.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package build
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.akyoto.dev/cli/q/src/build/expression"
|
||||||
|
"git.akyoto.dev/cli/q/src/build/token"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CompileReturn compiles a return instruction.
|
||||||
|
func (f *Function) CompileReturn(tokens token.List) error {
|
||||||
|
if len(tokens) > 1 {
|
||||||
|
value := expression.Parse(tokens[1:])
|
||||||
|
defer value.Close()
|
||||||
|
// TODO: Set the return value
|
||||||
|
}
|
||||||
|
|
||||||
|
f.assembler.Return()
|
||||||
|
return nil
|
||||||
|
}
|
@ -18,7 +18,7 @@ func (f *Function) CompileVariableDefinition(expr *expression.Expression) error
|
|||||||
return errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, expr.Children[0].Token.Position)
|
return errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, expr.Children[0].Token.Position)
|
||||||
}
|
}
|
||||||
|
|
||||||
reg, exists := f.CPU.FindFree(f.CPU.General)
|
reg, exists := f.cpu.FindFree(f.cpu.General)
|
||||||
|
|
||||||
if !exists {
|
if !exists {
|
||||||
panic("no free registers")
|
panic("no free registers")
|
||||||
@ -42,8 +42,8 @@ func (f *Function) CompileVariableDefinition(expr *expression.Expression) error
|
|||||||
|
|
||||||
func (f *Function) addVariable(variable *Variable) {
|
func (f *Function) addVariable(variable *Variable) {
|
||||||
variable.UsesRemaining = countIdentifier(f.Body, variable.Name)
|
variable.UsesRemaining = countIdentifier(f.Body, variable.Name)
|
||||||
f.Variables[variable.Name] = variable
|
f.variables[variable.Name] = variable
|
||||||
f.CPU.Use(variable.Register)
|
f.cpu.Use(variable.Register)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Function) useVariable(variable *Variable) {
|
func (f *Function) useVariable(variable *Variable) {
|
||||||
@ -54,7 +54,7 @@ func (f *Function) useVariable(variable *Variable) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if variable.UsesRemaining == 0 {
|
if variable.UsesRemaining == 0 {
|
||||||
f.CPU.Free(variable.Register)
|
f.cpu.Free(variable.Register)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,11 +34,11 @@ func compile(functions <-chan *Function, errs <-chan error) (Result, error) {
|
|||||||
compileFunctions(allFunctions)
|
compileFunctions(allFunctions)
|
||||||
|
|
||||||
for _, function := range allFunctions {
|
for _, function := range allFunctions {
|
||||||
if function.Error != nil {
|
if function.err != nil {
|
||||||
return result, function.Error
|
return result, function.err
|
||||||
}
|
}
|
||||||
|
|
||||||
result.InstructionCount += len(function.Assembler.Instructions)
|
result.InstructionCount += len(function.assembler.Instructions)
|
||||||
}
|
}
|
||||||
|
|
||||||
main, exists := allFunctions["main"]
|
main, exists := allFunctions["main"]
|
||||||
|
77
src/build/compiler.go
Normal file
77
src/build/compiler.go
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
package build
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.akyoto.dev/cli/q/src/build/asm"
|
||||||
|
"git.akyoto.dev/cli/q/src/build/cpu"
|
||||||
|
"git.akyoto.dev/cli/q/src/build/token"
|
||||||
|
"git.akyoto.dev/go/color/ansi"
|
||||||
|
)
|
||||||
|
|
||||||
|
// compiler is the data structure we embed in each function to preserve compilation state.
|
||||||
|
type compiler struct {
|
||||||
|
assembler asm.Assembler
|
||||||
|
count counter
|
||||||
|
cpu cpu.CPU
|
||||||
|
debug []debug
|
||||||
|
err error
|
||||||
|
variables map[string]*Variable
|
||||||
|
}
|
||||||
|
|
||||||
|
// counter stores how often a certain statement appeared so we can generate a unique label from it.
|
||||||
|
type counter struct {
|
||||||
|
loop int
|
||||||
|
}
|
||||||
|
|
||||||
|
// debug is used to look up the source code at the given position.
|
||||||
|
type debug struct {
|
||||||
|
position int
|
||||||
|
source token.List
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrintInstructions shows the assembly instructions.
|
||||||
|
func (c *compiler) PrintInstructions() {
|
||||||
|
ansi.Dim.Println("╭──────────────────────────────────────╮")
|
||||||
|
|
||||||
|
for i, x := range c.assembler.Instructions {
|
||||||
|
instruction := c.sourceAt(i)
|
||||||
|
|
||||||
|
if instruction != nil {
|
||||||
|
ansi.Dim.Println("├──────────────────────────────────────┤")
|
||||||
|
}
|
||||||
|
|
||||||
|
ansi.Dim.Print("│ ")
|
||||||
|
|
||||||
|
if x.Mnemonic == asm.LABEL {
|
||||||
|
ansi.Yellow.Printf("%-36s", x.Data.String()+":")
|
||||||
|
} else {
|
||||||
|
ansi.Green.Printf("%-8s", x.Mnemonic.String())
|
||||||
|
|
||||||
|
if x.Data != nil {
|
||||||
|
fmt.Printf("%-28s", x.Data.String())
|
||||||
|
} else {
|
||||||
|
fmt.Printf("%-28s", "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ansi.Dim.Print(" │\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
ansi.Dim.Println("╰──────────────────────────────────────╯")
|
||||||
|
}
|
||||||
|
|
||||||
|
// sourceAt retrieves the source code at the given position or `nil`.
|
||||||
|
func (c *compiler) sourceAt(position int) token.List {
|
||||||
|
for _, record := range c.debug {
|
||||||
|
if record.position == position {
|
||||||
|
return record.source
|
||||||
|
}
|
||||||
|
|
||||||
|
if record.position > position {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -1,24 +0,0 @@
|
|||||||
package build
|
|
||||||
|
|
||||||
import "git.akyoto.dev/cli/q/src/build/token"
|
|
||||||
|
|
||||||
// debug is used to look up instructions at a certain index.
|
|
||||||
type debug struct {
|
|
||||||
position int
|
|
||||||
instruction token.List
|
|
||||||
}
|
|
||||||
|
|
||||||
// instructionAt retrieves the instruction at the given index.
|
|
||||||
func (f *Function) instructionAt(index int) token.List {
|
|
||||||
for _, record := range f.debug {
|
|
||||||
if record.position == index {
|
|
||||||
return record.instruction
|
|
||||||
}
|
|
||||||
|
|
||||||
if record.position > index {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -240,15 +240,17 @@ func scanFile(path string, functions chan<- *Function) error {
|
|||||||
Name: tokens[nameStart].Text(),
|
Name: tokens[nameStart].Text(),
|
||||||
File: file,
|
File: file,
|
||||||
Body: tokens[bodyStart:i],
|
Body: tokens[bodyStart:i],
|
||||||
Variables: map[string]*Variable{},
|
compiler: compiler{
|
||||||
CPU: cpu.CPU{
|
assembler: asm.Assembler{
|
||||||
|
Instructions: make([]asm.Instruction, 0, 32),
|
||||||
|
},
|
||||||
|
cpu: cpu.CPU{
|
||||||
Call: x64.CallRegisters,
|
Call: x64.CallRegisters,
|
||||||
General: x64.GeneralRegisters,
|
General: x64.GeneralRegisters,
|
||||||
Syscall: x64.SyscallRegisters,
|
Syscall: x64.SyscallRegisters,
|
||||||
Return: x64.ReturnValueRegisters,
|
Return: x64.ReturnValueRegisters,
|
||||||
},
|
},
|
||||||
Assembler: asm.Assembler{
|
variables: map[string]*Variable{},
|
||||||
Instructions: make([]asm.Instruction, 0, 32),
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -257,7 +259,7 @@ func scanFile(path string, functions chan<- *Function) error {
|
|||||||
err := expression.EachParameter(parameters, func(tokens token.List) error {
|
err := expression.EachParameter(parameters, func(tokens token.List) error {
|
||||||
if len(tokens) == 1 {
|
if len(tokens) == 1 {
|
||||||
name := tokens[0].Text()
|
name := tokens[0].Text()
|
||||||
register := x64.CallRegisters[len(function.Variables)]
|
register := x64.CallRegisters[len(function.variables)]
|
||||||
variable := &Variable{Name: name, Register: register}
|
variable := &Variable{Name: name, Register: register}
|
||||||
function.addVariable(variable)
|
function.addVariable(variable)
|
||||||
return nil
|
return nil
|
||||||
|
@ -45,7 +45,7 @@ func Build(args []string) int {
|
|||||||
|
|
||||||
if config.Verbose {
|
if config.Verbose {
|
||||||
result.EachFunction(result.Main, map[*build.Function]bool{}, func(f *build.Function) {
|
result.EachFunction(result.Main, map[*build.Function]bool{}, func(f *build.Function) {
|
||||||
f.PrintAsm()
|
f.PrintInstructions()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user