diff --git a/examples/itoa/itoa.q b/examples/itoa/itoa.q new file mode 100644 index 0000000..6bfac19 --- /dev/null +++ b/examples/itoa/itoa.q @@ -0,0 +1,5 @@ +import io + +main() { + io.printNum(2147483647) +} \ No newline at end of file diff --git a/lib/io/io.q b/lib/io/io.q new file mode 100644 index 0000000..1f6e8fe --- /dev/null +++ b/lib/io/io.q @@ -0,0 +1,22 @@ +import mem +import sys + +printNum(x) { + length := 20 + buffer := mem.alloc(length) + end := buffer + length + tmp := end + digit := 0 + + loop { + x, digit = x / 10 + tmp -= 1 + tmp[0] = '0' + digit + + if x == 0 { + sys.write(1, tmp, end - tmp) + mem.free(buffer, length) + return + } + } +} \ No newline at end of file diff --git a/src/build/asm/Finalize.go b/src/build/asm/Finalize.go index cdd5633..e2adf3f 100644 --- a/src/build/asm/Finalize.go +++ b/src/build/asm/Finalize.go @@ -200,6 +200,8 @@ func (a Assembler) Finalize() ([]byte, []byte) { switch operands := x.Data.(type) { case *MemoryNumber: code = x64.StoreNumber(code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Number) + case *MemoryRegister: + code = x64.StoreRegister(code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Register) } case SYSCALL: diff --git a/src/build/asm/MemoryRegister.go b/src/build/asm/MemoryRegister.go new file mode 100644 index 0000000..927a97c --- /dev/null +++ b/src/build/asm/MemoryRegister.go @@ -0,0 +1,29 @@ +package asm + +import ( + "fmt" + + "git.akyoto.dev/cli/q/src/build/cpu" +) + +// MemoryRegister operates with a memory address and a number. +type MemoryRegister struct { + Address Memory + Register cpu.Register +} + +// String returns a human readable version. +func (data *MemoryRegister) String() string { + return fmt.Sprintf("%dB [%s+%d], %s", data.Address.Length, data.Address.Base, data.Address.Offset, data.Register) +} + +// MemoryRegister adds an instruction with a memory address and a number. +func (a *Assembler) MemoryRegister(mnemonic Mnemonic, address Memory, register cpu.Register) { + a.Instructions = append(a.Instructions, Instruction{ + Mnemonic: mnemonic, + Data: &MemoryRegister{ + Address: address, + Register: register, + }, + }) +} diff --git a/src/build/asm/divide.go b/src/build/asm/divide.go index e795c63..9c42a08 100644 --- a/src/build/asm/divide.go +++ b/src/build/asm/divide.go @@ -4,24 +4,18 @@ import "git.akyoto.dev/cli/q/src/build/arch/x64" // divide implements the division on x64 machines. func divide(code []byte, data any) []byte { - code = x64.PushRegister(code, x64.RDX) - switch operands := data.(type) { case *RegisterNumber: if operands.Register == x64.RAX { - code = x64.PushRegister(code, x64.RCX) code = x64.MoveRegisterNumber32(code, x64.RCX, uint32(operands.Number)) code = x64.ExtendRAXToRDX(code) code = x64.DivRegister(code, x64.RCX) - code = x64.PopRegister(code, x64.RCX) } else { - code = x64.PushRegister(code, x64.RAX) code = x64.MoveRegisterRegister64(code, x64.RAX, operands.Register) code = x64.MoveRegisterNumber32(code, operands.Register, uint32(operands.Number)) code = x64.ExtendRAXToRDX(code) code = x64.DivRegister(code, operands.Register) code = x64.MoveRegisterRegister64(code, operands.Register, x64.RAX) - code = x64.PopRegister(code, x64.RAX) } case *RegisterRegister: @@ -29,24 +23,18 @@ func divide(code []byte, data any) []byte { code = x64.ExtendRAXToRDX(code) code = x64.DivRegister(code, operands.Source) } else { - code = x64.PushRegister(code, x64.RAX) code = x64.MoveRegisterRegister64(code, x64.RAX, operands.Destination) code = x64.ExtendRAXToRDX(code) + code = x64.DivRegister(code, operands.Source) code = x64.MoveRegisterRegister64(code, operands.Destination, x64.RAX) - code = x64.PopRegister(code, x64.RAX) } } - - code = x64.PopRegister(code, x64.RDX) return code } // modulo calculates the division remainder on x64 machines. func modulo(code []byte, data any) []byte { - code = x64.PushRegister(code, x64.RDX) - code = x64.PushRegister(code, x64.RAX) - switch operands := data.(type) { case *RegisterNumber: code = x64.MoveRegisterRegister64(code, x64.RAX, operands.Register) @@ -62,7 +50,5 @@ func modulo(code []byte, data any) []byte { code = x64.MoveRegisterRegister64(code, operands.Destination, x64.RDX) } - code = x64.PopRegister(code, x64.RAX) - code = x64.PopRegister(code, x64.RDX) return code } diff --git a/src/build/core/CompileAssign.go b/src/build/core/CompileAssign.go index b811ab3..d157792 100644 --- a/src/build/core/CompileAssign.go +++ b/src/build/core/CompileAssign.go @@ -1,7 +1,6 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/token" @@ -26,36 +25,11 @@ func (f *Function) CompileAssign(node *ast.Assign) error { } if left.Token.Kind == token.Array { - name := left.Children[0].Token.Text(f.File.Bytes) - variable := f.VariableByName(name) + return f.CompileAssignArray(node) + } - if variable == nil { - return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Children[0].Token.Position) - } - - defer f.UseVariable(variable) - - index := left.Children[1] - offset, _, err := f.Number(index.Token) - - if err != nil { - return err - } - - number, size, err := f.Number(right.Token) - - if err != nil { - return err - } - - elementSize := byte(1) - - if size != elementSize { - return errors.New(&errors.NumberExceedsBounds{Number: number, ExpectedSize: elementSize, Size: size}, f.File, right.Token.Position) - } - - f.MemoryNumber(asm.STORE, asm.Memory{Base: variable.Register, Offset: byte(offset), Length: elementSize}, number) - return nil + if left.Token.Kind == token.Separator && right.Token.Kind == token.Div { + return f.CompileAssignDivision(node) } return errors.New(errors.NotImplemented, f.File, left.Token.Position) diff --git a/src/build/core/CompileAssignArray.go b/src/build/core/CompileAssignArray.go new file mode 100644 index 0000000..f519ea0 --- /dev/null +++ b/src/build/core/CompileAssignArray.go @@ -0,0 +1,37 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/ast" + "git.akyoto.dev/cli/q/src/build/errors" +) + +// CompileAssignArray compiles an assign statement for array elements. +func (f *Function) CompileAssignArray(node *ast.Assign) error { + left := node.Expression.Children[0] + right := node.Expression.Children[1] + + name := left.Children[0].Token.Text(f.File.Bytes) + variable := f.VariableByName(name) + + if variable == nil { + return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Children[0].Token.Position) + } + + defer f.UseVariable(variable) + + index := left.Children[1] + offset, _, err := f.Number(index.Token) + + if err != nil { + return err + } + + memory := asm.Memory{ + Base: variable.Register, + Offset: byte(offset), + Length: byte(1), + } + + return f.ExpressionToMemory(right, memory) +} diff --git a/src/build/core/CompileAssignDivision.go b/src/build/core/CompileAssignDivision.go new file mode 100644 index 0000000..de383c9 --- /dev/null +++ b/src/build/core/CompileAssignDivision.go @@ -0,0 +1,40 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/ast" + "git.akyoto.dev/cli/q/src/build/errors" +) + +// CompileAssignDivision compiles an assign statement that has quotient and remainder on the left side and division on the right. +func (f *Function) CompileAssignDivision(node *ast.Assign) error { + left := node.Expression.Children[0] + right := node.Expression.Children[1] + + quotient := left.Children[0] + name := quotient.Token.Text(f.File.Bytes) + quotientVariable := f.VariableByName(name) + + if quotientVariable == nil { + return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, quotient.Token.Position) + } + + remainder := left.Children[1] + name = remainder.Token.Text(f.File.Bytes) + remainderVariable := f.VariableByName(name) + + if remainderVariable == nil { + return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, remainder.Token.Position) + } + + dividend := right.Children[0] + name = dividend.Token.Text(f.File.Bytes) + dividendVariable := f.VariableByName(name) + + divisor := right.Children[1] + err := f.Execute(right.Token, dividendVariable.Register, divisor) + f.RegisterRegister(asm.MOVE, quotientVariable.Register, x64.RAX) + f.RegisterRegister(asm.MOVE, remainderVariable.Register, x64.RDX) + return err +} diff --git a/src/build/core/ExpressionToMemory.go b/src/build/core/ExpressionToMemory.go new file mode 100644 index 0000000..af7ec37 --- /dev/null +++ b/src/build/core/ExpressionToMemory.go @@ -0,0 +1,32 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/errors" + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/token" +) + +// ExpressionToMemory puts the result of an expression into the specified memory address. +func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Memory) error { + if node.IsLeaf() && (node.Token.Kind == token.Number || node.Token.Kind == token.Rune) { + number, size, err := f.Number(node.Token) + + if err != nil { + return err + } + + if size != memory.Length { + return errors.New(&errors.NumberExceedsBounds{Number: number, ExpectedSize: memory.Length, Size: size}, f.File, node.Token.Position) + } + + f.MemoryNumber(asm.STORE, memory, number) + return nil + } + + tmp := f.NewRegister() + defer f.FreeRegister(tmp) + err := f.ExpressionToRegister(node, tmp) + f.MemoryRegister(asm.STORE, memory, tmp) + return err +} diff --git a/src/build/register/MemoryRegister.go b/src/build/register/MemoryRegister.go new file mode 100644 index 0000000..6ad3bc0 --- /dev/null +++ b/src/build/register/MemoryRegister.go @@ -0,0 +1,11 @@ +package register + +import ( + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/cpu" +) + +func (f *Machine) MemoryRegister(mnemonic asm.Mnemonic, a asm.Memory, b cpu.Register) { + f.Assembler.MemoryRegister(mnemonic, a, b) + f.postInstruction() +} diff --git a/src/build/token/Kind.go b/src/build/token/Kind.go index b9490db..33df902 100644 --- a/src/build/token/Kind.go +++ b/src/build/token/Kind.go @@ -12,7 +12,6 @@ const ( Rune // Rune is a single unicode code point. String // String is an uninterpreted series of characters in the source code. Comment // Comment is a comment. - Separator // , GroupStart // ( GroupEnd // ) BlockStart // { @@ -50,6 +49,7 @@ const ( Period // . Call // x() Array // [x] + Separator // , _assignments // Assign // = AddAssign // += diff --git a/tests/examples_test.go b/tests/examples_test.go index e4b083a..94c223a 100644 --- a/tests/examples_test.go +++ b/tests/examples_test.go @@ -18,6 +18,7 @@ var examples = []struct { {"factorial", "", "", 120}, {"fibonacci", "", "", 55}, {"array", "", "Hello", 0}, + {"itoa", "", "2147483647", 0}, } func TestExamples(t *testing.T) {