287 lines
6.3 KiB
Go
287 lines
6.3 KiB
Go
package build
|
|
|
|
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/cpu"
|
|
"git.akyoto.dev/cli/q/src/build/expression"
|
|
"git.akyoto.dev/cli/q/src/build/fs"
|
|
"git.akyoto.dev/cli/q/src/build/token"
|
|
"git.akyoto.dev/cli/q/src/errors"
|
|
"git.akyoto.dev/go/color/ansi"
|
|
)
|
|
|
|
// Function represents a function.
|
|
type Function struct {
|
|
Name string
|
|
File *fs.File
|
|
Body token.List
|
|
Variables map[string]*Variable
|
|
Assembler asm.Assembler
|
|
CPU cpu.CPU
|
|
Error error
|
|
count struct{ loop int }
|
|
debug []debug
|
|
}
|
|
|
|
// Compile turns a function into machine code.
|
|
func (f *Function) Compile() {
|
|
f.Assembler.Label(f.Name)
|
|
err := f.CompileTokens(f.Body)
|
|
|
|
if err != nil {
|
|
f.Error = err
|
|
return
|
|
}
|
|
|
|
f.Assembler.Return()
|
|
}
|
|
|
|
// CompileTokens compiles a token list.
|
|
func (f *Function) CompileTokens(body token.List) error {
|
|
start := 0
|
|
groupLevel := 0
|
|
blockLevel := 0
|
|
|
|
for i, t := range body {
|
|
if start == i && t.Kind == token.NewLine {
|
|
start = i + 1
|
|
continue
|
|
}
|
|
|
|
switch t.Kind {
|
|
case token.NewLine:
|
|
if groupLevel > 0 || blockLevel > 0 {
|
|
continue
|
|
}
|
|
|
|
if start != -1 {
|
|
instruction := body[start:i]
|
|
|
|
if config.Verbose {
|
|
f.debug = append(f.debug, debug{
|
|
pos: len(f.Assembler.Instructions),
|
|
instruction: instruction,
|
|
})
|
|
}
|
|
|
|
err := f.CompileInstruction(instruction)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
start = i + 1
|
|
|
|
case token.GroupStart:
|
|
groupLevel++
|
|
|
|
case token.GroupEnd:
|
|
groupLevel--
|
|
|
|
case token.BlockStart:
|
|
blockLevel++
|
|
|
|
case token.BlockEnd:
|
|
blockLevel--
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// CompileInstruction compiles a single instruction.
|
|
func (f *Function) CompileInstruction(line token.List) error {
|
|
if len(line) == 0 {
|
|
return nil
|
|
}
|
|
|
|
if line[0].Kind == token.Keyword {
|
|
return f.CompileKeyword(line)
|
|
}
|
|
|
|
expr := expression.Parse(line)
|
|
|
|
if expr == nil {
|
|
return nil
|
|
}
|
|
|
|
defer expr.Close()
|
|
|
|
if isVariableDefinition(expr) {
|
|
return f.CompileVariableDefinition(expr)
|
|
}
|
|
|
|
if isAssignment(expr) {
|
|
return f.CompileAssignment(expr)
|
|
}
|
|
|
|
if isFunctionCall(expr) {
|
|
return f.ExecuteFunctionCall(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:
|
|
return errors.New(errors.InvalidExpression, f.File, t.Position)
|
|
}
|
|
}
|
|
|
|
// PrintAsm shows the assembly instructions.
|
|
func (f *Function) PrintAsm() {
|
|
ansi.Dim.Println("╭──────────────────────────────────────╮")
|
|
|
|
for i, x := range f.Assembler.Instructions {
|
|
instruction := f.debugLine(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.
|
|
func (f *Function) String() string {
|
|
return f.Name
|
|
}
|
|
|
|
// identifierExists returns true if the identifier has been defined.
|
|
func (f *Function) identifierExists(name string) bool {
|
|
_, exists := f.Variables[name]
|
|
return exists
|
|
}
|
|
|
|
// isAssignment returns true if the expression is an assignment.
|
|
func isAssignment(expr *expression.Expression) bool {
|
|
return expr.Token.Kind == token.Operator && expr.Token.Bytes[len(expr.Token.Bytes)-1] == '='
|
|
}
|
|
|
|
// isFunctionCall returns true if the expression is a function call.
|
|
func isFunctionCall(expr *expression.Expression) bool {
|
|
return expr.Token.Kind == token.Operator && expr.Token.Text() == "λ"
|
|
}
|
|
|
|
// isVariableDefinition returns true if the expression is a variable definition.
|
|
func isVariableDefinition(expr *expression.Expression) bool {
|
|
return expr.Token.Kind == token.Operator && expr.Token.Text() == ":="
|
|
}
|