diff --git a/src/build/Function.go b/src/build/Function.go index 7cffba6..dfce655 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -23,6 +23,7 @@ type Function struct { Assembler asm.Assembler CPU cpu.CPU Error error + count struct{ loop int } } // Compile turns a function into machine code. @@ -115,104 +116,6 @@ func (f *Function) CompileInstruction(line token.List) error { return errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text()}, f.File, expr.Token.Position) } -// CompileKeyword compiles an instruction that starts with a keyword. -func (f *Function) CompileKeyword(line token.List) error { - switch line[0].Text() { - case "return": - if len(line) > 1 { - value := expression.Parse(line[1:]) - defer value.Close() - // TODO: Set the return value - } - - f.Assembler.Return() - - 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()) - } - - return f.CompileTokens(line[blockStart:blockEnd]) - - default: - return errors.New(&errors.KeywordNotImplemented{Keyword: line[0].Text()}, f.File, line[0].Position) - } - - return nil -} - -// CompileVariableDefinition compiles a variable definition. -func (f *Function) CompileVariableDefinition(expr *expression.Expression) error { - if len(expr.Children) < 2 { - return errors.New(errors.MissingAssignValue, f.File, expr.LastChild().Token.End()) - } - - name := expr.Children[0].Token.Text() - - if f.identifierExists(name) { - return errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, expr.Children[0].Token.Position) - } - - value := expr.Children[1] - - err := value.EachLeaf(func(leaf *expression.Expression) error { - if leaf.Token.Kind == token.Identifier && !f.identifierExists(leaf.Token.Text()) { - return errors.New(&errors.UnknownIdentifier{Name: leaf.Token.Text()}, f.File, leaf.Token.Position) - } - - return nil - }) - - if err != nil { - return err - } - - reg, exists := f.CPU.FindFree() - - if !exists { - panic("no free registers") - } - - f.ExpressionToRegister(value, reg) - f.CPU.Use(reg) - - f.Variables[name] = &Variable{ - Name: name, - Register: reg, - } - - return nil -} - -// CompileFunctionCall compiles a function call. -func (f *Function) CompileFunctionCall(expr *expression.Expression) error { - funcName := expr.Children[0].Token.Text() - parameters := expr.Children[1:] - - for i, parameter := range parameters { - err := f.ExpressionToRegister(parameter, f.CPU.Syscall[i]) - - if err != nil { - return err - } - } - - if funcName == "syscall" { - f.Assembler.Syscall() - } else { - f.Assembler.Call(funcName) - } - - return nil -} - // ExpressionToRegister moves the result of an expression into the given register. func (f *Function) ExpressionToRegister(root *expression.Expression, register cpu.Register) error { if root.IsLeaf() { @@ -285,13 +188,3 @@ func (f *Function) identifierExists(name string) bool { _, exists := f.Variables[name] return exists } - -// 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() == ":=" -} - -// 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() == "λ" -} diff --git a/src/build/FunctionCall.go b/src/build/FunctionCall.go new file mode 100644 index 0000000..387fe43 --- /dev/null +++ b/src/build/FunctionCall.go @@ -0,0 +1,33 @@ +package build + +import ( + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/token" +) + +// CompileFunctionCall compiles a top-level function call. +func (f *Function) CompileFunctionCall(expr *expression.Expression) error { + funcName := expr.Children[0].Token.Text() + parameters := expr.Children[1:] + + for i, parameter := range parameters { + err := f.ExpressionToRegister(parameter, f.CPU.Syscall[i]) + + if err != nil { + return err + } + } + + if funcName == "syscall" { + f.Assembler.Syscall() + } else { + f.Assembler.Call(funcName) + } + + return nil +} + +// 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() == "λ" +} diff --git a/src/build/Keyword.go b/src/build/Keyword.go new file mode 100644 index 0000000..5913124 --- /dev/null +++ b/src/build/Keyword.go @@ -0,0 +1,46 @@ +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() { + case "return": + if len(line) > 1 { + value := expression.Parse(line[1:]) + defer value.Close() + // TODO: Set the return value + } + + f.Assembler.Return() + + 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]) + + default: + return errors.New(&errors.KeywordNotImplemented{Keyword: line[0].Text()}, f.File, line[0].Position) + } + + return nil +} diff --git a/src/build/VariableDefinition.go b/src/build/VariableDefinition.go new file mode 100644 index 0000000..9eca8e9 --- /dev/null +++ b/src/build/VariableDefinition.go @@ -0,0 +1,55 @@ +package build + +import ( + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/errors" +) + +// CompileVariableDefinition compiles a variable definition. +func (f *Function) CompileVariableDefinition(expr *expression.Expression) error { + if len(expr.Children) < 2 { + return errors.New(errors.MissingAssignValue, f.File, expr.LastChild().Token.End()) + } + + name := expr.Children[0].Token.Text() + + if f.identifierExists(name) { + return errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, expr.Children[0].Token.Position) + } + + value := expr.Children[1] + + err := value.EachLeaf(func(leaf *expression.Expression) error { + if leaf.Token.Kind == token.Identifier && !f.identifierExists(leaf.Token.Text()) { + return errors.New(&errors.UnknownIdentifier{Name: leaf.Token.Text()}, f.File, leaf.Token.Position) + } + + return nil + }) + + if err != nil { + return err + } + + reg, exists := f.CPU.FindFree() + + if !exists { + panic("no free registers") + } + + f.ExpressionToRegister(value, reg) + f.CPU.Use(reg) + + f.Variables[name] = &Variable{ + Name: name, + Register: reg, + } + + return nil +} + +// 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() == ":=" +} diff --git a/src/build/arch/x64/Jump.go b/src/build/arch/x64/Jump.go new file mode 100644 index 0000000..32b8b98 --- /dev/null +++ b/src/build/arch/x64/Jump.go @@ -0,0 +1,11 @@ +package x64 + +// Jump continues program flow at the new address. +// The address is relative to the next instruction. +func Jump8(code []byte, address int8) []byte { + return append( + code, + 0xeb, + byte(address), + ) +} diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index f159bc4..f14ca7c 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -44,14 +44,32 @@ func (a *Assembler) Finalize() ([]byte, []byte) { case CALL: code = x64.Call(code, 0x00_00_00_00) + size := 4 label := x.Data.(*Label) - nextInstructionAddress := len(code) + nextInstructionAddress := Address(len(code)) pointers = append(pointers, Pointer{ - Position: Address(len(code) - 4), + Position: Address(len(code) - size), + Size: uint8(size), Resolve: func() Address { destination := labels[label.Name] - distance := int32(destination) - int32(nextInstructionAddress) + distance := destination - nextInstructionAddress + return Address(distance) + }, + }) + + case JUMP: + code = x64.Jump8(code, 0x00) + size := 1 + label := x.Data.(*Label) + nextInstructionAddress := Address(len(code)) + + pointers = append(pointers, Pointer{ + Position: Address(len(code) - size), + Size: uint8(size), + Resolve: func() Address { + destination := labels[label.Name] + distance := destination - nextInstructionAddress return Address(distance) }, }) @@ -67,8 +85,22 @@ func (a *Assembler) Finalize() ([]byte, []byte) { // dataStart := config.BaseAddress + config.CodeOffset + Address(len(code)) for _, pointer := range pointers { - slice := code[pointer.Position : pointer.Position+4] - binary.LittleEndian.PutUint32(slice, pointer.Resolve()) + slice := code[pointer.Position : pointer.Position+Address(pointer.Size)] + address := pointer.Resolve() + + switch pointer.Size { + case 1: + slice[0] = uint8(address) + + case 2: + binary.LittleEndian.PutUint16(slice, uint16(address)) + + case 4: + binary.LittleEndian.PutUint32(slice, uint32(address)) + + case 8: + binary.LittleEndian.PutUint64(slice, uint64(address)) + } } return code, data diff --git a/src/build/asm/Instructions.go b/src/build/asm/Instructions.go index 82aebdb..3662595 100644 --- a/src/build/asm/Instructions.go +++ b/src/build/asm/Instructions.go @@ -44,6 +44,16 @@ func (a *Assembler) Call(name string) { }) } +// Jump jumps to a position that is identified by a label. +func (a *Assembler) Jump(name string) { + a.Instructions = append(a.Instructions, Instruction{ + Mnemonic: JUMP, + Data: &Label{ + Name: name, + }, + }) +} + // Return returns back to the caller. func (a *Assembler) Return() { a.Instructions = append(a.Instructions, Instruction{Mnemonic: RETURN}) diff --git a/src/build/asm/Mnemonic.go b/src/build/asm/Mnemonic.go index ad071c6..a711a0b 100644 --- a/src/build/asm/Mnemonic.go +++ b/src/build/asm/Mnemonic.go @@ -9,6 +9,7 @@ const ( SYSCALL LABEL CALL + JUMP ) // String returns a human readable version. @@ -28,6 +29,9 @@ func (m Mnemonic) String() string { case CALL: return "call" + + case JUMP: + return "jump" } return "NONE" diff --git a/src/build/asm/Pointer.go b/src/build/asm/Pointer.go index 6499ce9..9be746f 100644 --- a/src/build/asm/Pointer.go +++ b/src/build/asm/Pointer.go @@ -8,5 +8,6 @@ type Address = uint32 // Resolve: The function that will return the final address. type Pointer struct { Position Address + Size uint8 Resolve func() Address }