Implemented number output

This commit is contained in:
Eduard Urbach 2024-07-26 12:50:47 +02:00
parent f4dd9004be
commit 123738f88c
Signed by: akyoto
GPG Key ID: C874F672B1AF20C0
12 changed files with 185 additions and 46 deletions

5
examples/itoa/itoa.q Normal file
View File

@ -0,0 +1,5 @@
import io
main() {
io.printNum(2147483647)
}

22
lib/io/io.q Normal file
View File

@ -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
}
}
}

View File

@ -200,6 +200,8 @@ func (a Assembler) Finalize() ([]byte, []byte) {
switch operands := x.Data.(type) { switch operands := x.Data.(type) {
case *MemoryNumber: case *MemoryNumber:
code = x64.StoreNumber(code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Number) 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: case SYSCALL:

View File

@ -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,
},
})
}

View File

@ -4,24 +4,18 @@ import "git.akyoto.dev/cli/q/src/build/arch/x64"
// divide implements the division on x64 machines. // divide implements the division on x64 machines.
func divide(code []byte, data any) []byte { func divide(code []byte, data any) []byte {
code = x64.PushRegister(code, x64.RDX)
switch operands := data.(type) { switch operands := data.(type) {
case *RegisterNumber: case *RegisterNumber:
if operands.Register == x64.RAX { if operands.Register == x64.RAX {
code = x64.PushRegister(code, x64.RCX)
code = x64.MoveRegisterNumber32(code, x64.RCX, uint32(operands.Number)) code = x64.MoveRegisterNumber32(code, x64.RCX, uint32(operands.Number))
code = x64.ExtendRAXToRDX(code) code = x64.ExtendRAXToRDX(code)
code = x64.DivRegister(code, x64.RCX) code = x64.DivRegister(code, x64.RCX)
code = x64.PopRegister(code, x64.RCX)
} else { } else {
code = x64.PushRegister(code, x64.RAX)
code = x64.MoveRegisterRegister64(code, x64.RAX, operands.Register) code = x64.MoveRegisterRegister64(code, x64.RAX, operands.Register)
code = x64.MoveRegisterNumber32(code, operands.Register, uint32(operands.Number)) code = x64.MoveRegisterNumber32(code, operands.Register, uint32(operands.Number))
code = x64.ExtendRAXToRDX(code) code = x64.ExtendRAXToRDX(code)
code = x64.DivRegister(code, operands.Register) code = x64.DivRegister(code, operands.Register)
code = x64.MoveRegisterRegister64(code, operands.Register, x64.RAX) code = x64.MoveRegisterRegister64(code, operands.Register, x64.RAX)
code = x64.PopRegister(code, x64.RAX)
} }
case *RegisterRegister: case *RegisterRegister:
@ -29,24 +23,18 @@ func divide(code []byte, data any) []byte {
code = x64.ExtendRAXToRDX(code) code = x64.ExtendRAXToRDX(code)
code = x64.DivRegister(code, operands.Source) code = x64.DivRegister(code, operands.Source)
} else { } else {
code = x64.PushRegister(code, x64.RAX)
code = x64.MoveRegisterRegister64(code, x64.RAX, operands.Destination) code = x64.MoveRegisterRegister64(code, x64.RAX, operands.Destination)
code = x64.ExtendRAXToRDX(code) code = x64.ExtendRAXToRDX(code)
code = x64.DivRegister(code, operands.Source) code = x64.DivRegister(code, operands.Source)
code = x64.MoveRegisterRegister64(code, operands.Destination, x64.RAX) code = x64.MoveRegisterRegister64(code, operands.Destination, x64.RAX)
code = x64.PopRegister(code, x64.RAX)
} }
} }
code = x64.PopRegister(code, x64.RDX)
return code return code
} }
// modulo calculates the division remainder on x64 machines. // modulo calculates the division remainder on x64 machines.
func modulo(code []byte, data any) []byte { func modulo(code []byte, data any) []byte {
code = x64.PushRegister(code, x64.RDX)
code = x64.PushRegister(code, x64.RAX)
switch operands := data.(type) { switch operands := data.(type) {
case *RegisterNumber: case *RegisterNumber:
code = x64.MoveRegisterRegister64(code, x64.RAX, operands.Register) 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.MoveRegisterRegister64(code, operands.Destination, x64.RDX)
} }
code = x64.PopRegister(code, x64.RAX)
code = x64.PopRegister(code, x64.RDX)
return code return code
} }

View File

@ -1,7 +1,6 @@
package core package core
import ( import (
"git.akyoto.dev/cli/q/src/build/asm"
"git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/ast"
"git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/errors"
"git.akyoto.dev/cli/q/src/build/token" "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 { if left.Token.Kind == token.Array {
name := left.Children[0].Token.Text(f.File.Bytes) return f.CompileAssignArray(node)
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) if left.Token.Kind == token.Separator && right.Token.Kind == token.Div {
return f.CompileAssignDivision(node)
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
} }
return errors.New(errors.NotImplemented, f.File, left.Token.Position) return errors.New(errors.NotImplemented, f.File, left.Token.Position)

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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()
}

View File

@ -12,7 +12,6 @@ const (
Rune // Rune is a single unicode code point. Rune // Rune is a single unicode code point.
String // String is an uninterpreted series of characters in the source code. String // String is an uninterpreted series of characters in the source code.
Comment // Comment is a comment. Comment // Comment is a comment.
Separator // ,
GroupStart // ( GroupStart // (
GroupEnd // ) GroupEnd // )
BlockStart // { BlockStart // {
@ -50,6 +49,7 @@ const (
Period // . Period // .
Call // x() Call // x()
Array // [x] Array // [x]
Separator // ,
_assignments // <assignments> _assignments // <assignments>
Assign // = Assign // =
AddAssign // += AddAssign // +=

View File

@ -18,6 +18,7 @@ var examples = []struct {
{"factorial", "", "", 120}, {"factorial", "", "", 120},
{"fibonacci", "", "", 55}, {"fibonacci", "", "", 55},
{"array", "", "Hello", 0}, {"array", "", "Hello", 0},
{"itoa", "", "2147483647", 0},
} }
func TestExamples(t *testing.T) { func TestExamples(t *testing.T) {