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() == ":=" }