diff --git a/src/build/Assignment.go b/src/build/Assignment.go index 895078b..66703da 100644 --- a/src/build/Assignment.go +++ b/src/build/Assignment.go @@ -8,7 +8,7 @@ import ( // CompileAssignment compiles an assignment. func (f *Function) CompileAssignment(expr *expression.Expression) error { name := expr.Children[0].Token.Text() - variable, exists := f.Variables[name] + variable, exists := f.variables[name] if !exists { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Children[0].Token.Position) diff --git a/src/build/Execute.go b/src/build/Execute.go index 1312766..42ffeb7 100644 --- a/src/build/Execute.go +++ b/src/build/Execute.go @@ -3,6 +3,7 @@ package build import ( "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/cpu" "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) } - temporary, found := f.CPU.FindFree(f.CPU.General) + temporary, found := f.cpu.FindFree(f.cpu.General) if !found { panic("no free registers") } - f.CPU.Use(temporary) - defer f.CPU.Free(temporary) + f.cpu.Use(temporary) + defer f.cpu.Free(temporary) err := f.ExpressionToRegister(value, temporary) if err != nil { @@ -33,38 +34,12 @@ func (f *Function) Execute(operation token.Token, register cpu.Register, value * 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. func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, operand token.Token) error { switch operand.Kind { case token.Identifier: name := operand.Text() - variable, exists := f.Variables[name] + variable, exists := f.variables[name] if !exists { 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 { switch operation.Text() { case "+", "+=": - f.Assembler.RegisterNumber(asm.ADD, register, number) + f.assembler.RegisterNumber(asm.ADD, register, number) case "-", "-=": - f.Assembler.RegisterNumber(asm.SUB, register, number) + f.assembler.RegisterNumber(asm.SUB, register, number) case "*", "*=": - f.Assembler.RegisterNumber(asm.MUL, register, number) + f.assembler.RegisterNumber(asm.MUL, register, number) case "/", "/=": - f.Assembler.RegisterNumber(asm.DIV, register, number) + f.assembler.RegisterNumber(asm.DIV, register, number) case "=": - f.Assembler.RegisterNumber(asm.MOVE, register, number) + f.assembler.RegisterNumber(asm.MOVE, register, number) default: 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 { switch operation.Text() { case "+", "+=": - f.Assembler.RegisterRegister(asm.ADD, destination, source) + f.assembler.RegisterRegister(asm.ADD, destination, source) case "-", "-=": - f.Assembler.RegisterRegister(asm.SUB, destination, source) + f.assembler.RegisterRegister(asm.SUB, destination, source) case "*", "*=": - f.Assembler.RegisterRegister(asm.MUL, destination, source) + f.assembler.RegisterRegister(asm.MUL, destination, source) case "/", "/=": - f.Assembler.RegisterRegister(asm.DIV, destination, source) + f.assembler.RegisterRegister(asm.DIV, destination, source) case "=": if destination != source { - f.Assembler.RegisterRegister(asm.MOVE, destination, source) + f.assembler.RegisterRegister(asm.MOVE, destination, source) } default: @@ -138,3 +113,103 @@ func (f *Function) ExecuteRegisterRegister(operation token.Token, destination cp 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) + } +} diff --git a/src/build/Function.go b/src/build/Function.go index 16a8c8c..2370fc8 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -1,44 +1,26 @@ 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 + Name string + File *fs.File + Body token.List + compiler } // 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() + f.assembler.Label(f.Name) + f.err = f.CompileTokens(f.Body) + f.assembler.Return() } // CompileTokens compiles a token list. @@ -59,21 +41,19 @@ func (f *Function) CompileTokens(body token.List) error { continue } - if start != -1 { - instruction := body[start:i] + instruction := body[start:i] - if config.Verbose { - f.debug = append(f.debug, debug{ - position: len(f.Assembler.Instructions), - instruction: instruction, - }) - } + if config.Verbose { + f.debug = append(f.debug, debug{ + position: len(f.assembler.Instructions), + source: instruction, + }) + } - err := f.CompileInstruction(instruction) + err := f.CompileInstruction(instruction) - if err != nil { - return err - } + if err != nil { + return err } start = i + 1 @@ -113,152 +93,21 @@ func (f *Function) CompileInstruction(line token.List) error { defer expr.Close() - if isVariableDefinition(expr) { + switch true { + case isVariableDefinition(expr): return f.CompileVariableDefinition(expr) - } - if isAssignment(expr) { + case 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) + case isFunctionCall(expr): + return f.CompileFunctionCall(expr) 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. func (f *Function) String() string { return f.Name @@ -266,7 +115,7 @@ func (f *Function) String() string { // identifierExists returns true if the identifier has been defined. func (f *Function) identifierExists(name string) bool { - _, exists := f.Variables[name] + _, exists := f.variables[name] return exists } diff --git a/src/build/FunctionCall.go b/src/build/FunctionCall.go new file mode 100644 index 0000000..06fa966 --- /dev/null +++ b/src/build/FunctionCall.go @@ -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) +} diff --git a/src/build/Keyword.go b/src/build/Keyword.go index 5913124..80e5ee9 100644 --- a/src/build/Keyword.go +++ b/src/build/Keyword.go @@ -1,46 +1,20 @@ package build import ( - "fmt" - - "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/token" "git.akyoto.dev/cli/q/src/errors" ) // CompileKeyword compiles an instruction that starts with a keyword. -func (f *Function) CompileKeyword(line token.List) error { - switch line[0].Text() { +func (f *Function) CompileKeyword(tokens token.List) error { + switch tokens[0].Text() { case "return": - if len(line) > 1 { - value := expression.Parse(line[1:]) - defer value.Close() - // TODO: Set the return value - } - - f.Assembler.Return() + return f.CompileReturn(tokens) case "loop": - blockStart := line.IndexKind(token.BlockStart) + 1 - 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]) + return f.CompileLoop(tokens) 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 } diff --git a/src/build/Loop.go b/src/build/Loop.go new file mode 100644 index 0000000..41a9b4f --- /dev/null +++ b/src/build/Loop.go @@ -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]) +} diff --git a/src/build/Result.go b/src/build/Result.go index 158f56f..d0d3699 100644 --- a/src/build/Result.go +++ b/src/build/Result.go @@ -31,7 +31,7 @@ func (r *Result) Finalize() ([]byte, []byte) { // This will place the main function immediately after the entry point // and also add everything the main function calls recursively. r.EachFunction(r.Main, map[*Function]bool{}, func(f *Function) { - final.Merge(f.Assembler) + final.Merge(f.assembler) }) code, data := final.Finalize() @@ -44,7 +44,7 @@ func (r *Result) EachFunction(caller *Function, traversed map[*Function]bool, ca call(caller) traversed[caller] = true - for _, x := range caller.Assembler.Instructions { + for _, x := range caller.assembler.Instructions { if x.Mnemonic != asm.CALL { continue } diff --git a/src/build/Return.go b/src/build/Return.go new file mode 100644 index 0000000..63ff276 --- /dev/null +++ b/src/build/Return.go @@ -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 +} diff --git a/src/build/VariableDefinition.go b/src/build/VariableDefinition.go index b952bab..560aff4 100644 --- a/src/build/VariableDefinition.go +++ b/src/build/VariableDefinition.go @@ -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) } - reg, exists := f.CPU.FindFree(f.CPU.General) + reg, exists := f.cpu.FindFree(f.cpu.General) if !exists { panic("no free registers") @@ -42,8 +42,8 @@ func (f *Function) CompileVariableDefinition(expr *expression.Expression) error func (f *Function) addVariable(variable *Variable) { variable.UsesRemaining = countIdentifier(f.Body, variable.Name) - f.Variables[variable.Name] = variable - f.CPU.Use(variable.Register) + f.variables[variable.Name] = variable + f.cpu.Use(variable.Register) } func (f *Function) useVariable(variable *Variable) { @@ -54,7 +54,7 @@ func (f *Function) useVariable(variable *Variable) { } if variable.UsesRemaining == 0 { - f.CPU.Free(variable.Register) + f.cpu.Free(variable.Register) } } diff --git a/src/build/compile.go b/src/build/compile.go index 3c1b48a..2e1f4b3 100644 --- a/src/build/compile.go +++ b/src/build/compile.go @@ -34,11 +34,11 @@ func compile(functions <-chan *Function, errs <-chan error) (Result, error) { compileFunctions(allFunctions) for _, function := range allFunctions { - if function.Error != nil { - return result, function.Error + if function.err != nil { + return result, function.err } - result.InstructionCount += len(function.Assembler.Instructions) + result.InstructionCount += len(function.assembler.Instructions) } main, exists := allFunctions["main"] diff --git a/src/build/compiler.go b/src/build/compiler.go new file mode 100644 index 0000000..3e727be --- /dev/null +++ b/src/build/compiler.go @@ -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 +} diff --git a/src/build/debug.go b/src/build/debug.go deleted file mode 100644 index 0292457..0000000 --- a/src/build/debug.go +++ /dev/null @@ -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 -} diff --git a/src/build/scan.go b/src/build/scan.go index 93149b7..b9058e8 100644 --- a/src/build/scan.go +++ b/src/build/scan.go @@ -237,18 +237,20 @@ func scanFile(path string, functions chan<- *Function) error { } function := &Function{ - Name: tokens[nameStart].Text(), - File: file, - Body: tokens[bodyStart:i], - Variables: map[string]*Variable{}, - CPU: cpu.CPU{ - Call: x64.CallRegisters, - General: x64.GeneralRegisters, - Syscall: x64.SyscallRegisters, - Return: x64.ReturnValueRegisters, - }, - Assembler: asm.Assembler{ - Instructions: make([]asm.Instruction, 0, 32), + Name: tokens[nameStart].Text(), + File: file, + Body: tokens[bodyStart:i], + compiler: compiler{ + assembler: asm.Assembler{ + Instructions: make([]asm.Instruction, 0, 32), + }, + cpu: cpu.CPU{ + Call: x64.CallRegisters, + General: x64.GeneralRegisters, + Syscall: x64.SyscallRegisters, + Return: x64.ReturnValueRegisters, + }, + variables: map[string]*Variable{}, }, } @@ -257,7 +259,7 @@ func scanFile(path string, functions chan<- *Function) error { err := expression.EachParameter(parameters, func(tokens token.List) error { if len(tokens) == 1 { name := tokens[0].Text() - register := x64.CallRegisters[len(function.Variables)] + register := x64.CallRegisters[len(function.variables)] variable := &Variable{Name: name, Register: register} function.addVariable(variable) return nil diff --git a/src/cli/Build.go b/src/cli/Build.go index c625081..a8dfee9 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -45,7 +45,7 @@ func Build(args []string) int { if config.Verbose { result.EachFunction(result.Main, map[*build.Function]bool{}, func(f *build.Function) { - f.PrintAsm() + f.PrintInstructions() }) }