Implemented array storage

This commit is contained in:
Eduard Urbach 2024-07-20 17:35:26 +02:00
parent d35c07ed1c
commit 155df7c44c
Signed by: akyoto
GPG Key ID: C874F672B1AF20C0
17 changed files with 150 additions and 45 deletions

13
examples/array/array.q Normal file
View File

@ -0,0 +1,13 @@
import mem
import sys
main() {
length := 4
address := mem.alloc(length)
address[0] = 65
address[1] = 66
address[2] = 67
address[3] = 68
sys.write(1, address, length)
mem.free(address, length)
}

View File

@ -3,6 +3,6 @@ package x64
import "git.akyoto.dev/cli/q/src/build/cpu" import "git.akyoto.dev/cli/q/src/build/cpu"
// LoadRegister loads from memory into a register. // LoadRegister loads from memory into a register.
func LoadRegister(code []byte, destination cpu.Register, offset byte, numBytes byte, source cpu.Register) []byte { func LoadRegister(code []byte, destination cpu.Register, offset byte, length byte, source cpu.Register) []byte {
return memoryAccess(code, 0x8A, 0x8B, source, offset, numBytes, destination) return memoryAccess(code, 0x8A, 0x8B, source, offset, length, destination)
} }

View File

@ -13,7 +13,7 @@ func TestLoadRegister(t *testing.T) {
Destination cpu.Register Destination cpu.Register
Source cpu.Register Source cpu.Register
Offset byte Offset byte
NumBytes byte Length byte
Code []byte Code []byte
}{ }{
// No offset // No offset
@ -150,8 +150,8 @@ func TestLoadRegister(t *testing.T) {
} }
for _, pattern := range usagePatterns { for _, pattern := range usagePatterns {
t.Logf("load %dB %s, [%s+%d]", pattern.NumBytes, pattern.Destination, pattern.Source, pattern.Offset) t.Logf("load %dB %s, [%s+%d]", pattern.Length, pattern.Destination, pattern.Source, pattern.Offset)
code := x64.LoadRegister(nil, pattern.Destination, pattern.Offset, pattern.NumBytes, pattern.Source) code := x64.LoadRegister(nil, pattern.Destination, pattern.Offset, pattern.Length, pattern.Source)
assert.DeepEqual(t, code, pattern.Code) assert.DeepEqual(t, code, pattern.Code)
} }
} }

View File

@ -7,10 +7,10 @@ import (
) )
// StoreNumber stores a number into the memory address included in the given register. // StoreNumber stores a number into the memory address included in the given register.
func StoreNumber(code []byte, register cpu.Register, offset byte, numBytes byte, number int) []byte { func StoreNumber(code []byte, register cpu.Register, offset byte, length byte, number int) []byte {
code = memoryAccess(code, 0xC6, 0xC7, register, offset, numBytes, 0b000) code = memoryAccess(code, 0xC6, 0xC7, register, offset, length, 0b000)
switch numBytes { switch length {
case 8, 4: case 8, 4:
return binary.LittleEndian.AppendUint32(code, uint32(number)) return binary.LittleEndian.AppendUint32(code, uint32(number))
@ -22,6 +22,6 @@ func StoreNumber(code []byte, register cpu.Register, offset byte, numBytes byte,
} }
// StoreRegister stores the contents of the `source` register into the memory address included in the given register. // StoreRegister stores the contents of the `source` register into the memory address included in the given register.
func StoreRegister(code []byte, register cpu.Register, offset byte, numBytes byte, source cpu.Register) []byte { func StoreRegister(code []byte, register cpu.Register, offset byte, length byte, source cpu.Register) []byte {
return memoryAccess(code, 0x88, 0x89, register, offset, numBytes, source) return memoryAccess(code, 0x88, 0x89, register, offset, length, source)
} }

View File

@ -12,7 +12,7 @@ func TestStoreNumber(t *testing.T) {
usagePatterns := []struct { usagePatterns := []struct {
Register cpu.Register Register cpu.Register
Offset byte Offset byte
NumBytes byte Length byte
Number int Number int
Code []byte Code []byte
}{ }{
@ -150,8 +150,8 @@ func TestStoreNumber(t *testing.T) {
} }
for _, pattern := range usagePatterns { for _, pattern := range usagePatterns {
t.Logf("store %dB [%s+%d], %d", pattern.NumBytes, pattern.Register, pattern.Offset, pattern.Number) t.Logf("store %dB [%s+%d], %d", pattern.Length, pattern.Register, pattern.Offset, pattern.Number)
code := x64.StoreNumber(nil, pattern.Register, pattern.Offset, pattern.NumBytes, pattern.Number) code := x64.StoreNumber(nil, pattern.Register, pattern.Offset, pattern.Length, pattern.Number)
assert.DeepEqual(t, code, pattern.Code) assert.DeepEqual(t, code, pattern.Code)
} }
} }
@ -160,7 +160,7 @@ func TestStoreRegister(t *testing.T) {
usagePatterns := []struct { usagePatterns := []struct {
RegisterTo cpu.Register RegisterTo cpu.Register
Offset byte Offset byte
NumBytes byte Length byte
RegisterFrom cpu.Register RegisterFrom cpu.Register
Code []byte Code []byte
}{ }{
@ -298,8 +298,8 @@ func TestStoreRegister(t *testing.T) {
} }
for _, pattern := range usagePatterns { for _, pattern := range usagePatterns {
t.Logf("store %dB [%s+%d], %s", pattern.NumBytes, pattern.RegisterTo, pattern.Offset, pattern.RegisterFrom) t.Logf("store %dB [%s+%d], %s", pattern.Length, pattern.RegisterTo, pattern.Offset, pattern.RegisterFrom)
code := x64.StoreRegister(nil, pattern.RegisterTo, pattern.Offset, pattern.NumBytes, pattern.RegisterFrom) code := x64.StoreRegister(nil, pattern.RegisterTo, pattern.Offset, pattern.Length, pattern.RegisterFrom)
assert.DeepEqual(t, code, pattern.Code) assert.DeepEqual(t, code, pattern.Code)
} }
} }

View File

@ -165,6 +165,12 @@ func (a Assembler) Finalize() ([]byte, []byte) {
case RETURN: case RETURN:
code = x64.Return(code) code = x64.Return(code)
case STORE:
switch operands := x.Data.(type) {
case *MemoryNumber:
code = x64.StoreNumber(code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Number)
}
case SYSCALL: case SYSCALL:
code = x64.Syscall(code) code = x64.Syscall(code)

9
src/build/asm/Memory.go Normal file
View File

@ -0,0 +1,9 @@
package asm
import "git.akyoto.dev/cli/q/src/build/cpu"
type Memory struct {
Base cpu.Register
Offset byte
Length byte
}

View File

@ -0,0 +1,27 @@
package asm
import (
"fmt"
)
// MemoryNumber operates with a memory address and a number.
type MemoryNumber struct {
Address Memory
Number int
}
// String returns a human readable version.
func (data *MemoryNumber) String() string {
return fmt.Sprintf("%dB [%s+%d], %d", data.Address.Length, data.Address.Base, data.Address.Offset, data.Number)
}
// MemoryNumber adds an instruction with a memory address and a number.
func (a *Assembler) MemoryNumber(mnemonic Mnemonic, address Memory, number int) {
a.Instructions = append(a.Instructions, Instruction{
Mnemonic: mnemonic,
Data: &MemoryNumber{
Address: address,
Number: number,
},
})
}

View File

@ -18,10 +18,12 @@ const (
JUMP JUMP
MUL MUL
LABEL LABEL
LOAD
MOVE MOVE
POP POP
PUSH PUSH
RETURN RETURN
STORE
SUB SUB
SYSCALL SYSCALL
) )
@ -55,6 +57,8 @@ func (m Mnemonic) String() string {
return "jump if >=" return "jump if >="
case LABEL: case LABEL:
return "label" return "label"
case LOAD:
return "load"
case MOVE: case MOVE:
return "move" return "move"
case MUL: case MUL:
@ -67,6 +71,8 @@ func (m Mnemonic) String() string {
return "return" return "return"
case SUB: case SUB:
return "sub" return "sub"
case STORE:
return "store"
case SYSCALL: case SYSCALL:
return "syscall" return "syscall"
default: default:

View File

@ -1,19 +1,14 @@
package ast package ast
import ( import (
"fmt"
"git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/expression"
"git.akyoto.dev/cli/q/src/build/token"
) )
// Assign represents an assignment to an existing variable or memory location. // Assign represents an assignment to an existing variable or memory location.
type Assign struct { type Assign struct {
Value *expression.Expression Expression *expression.Expression
Name token.Token
Operator token.Token
} }
func (node *Assign) String() string { func (node *Assign) String() string {
return fmt.Sprintf("(= %s %s)", node.Name.Text(), node.Value) return node.Expression.String()
} }

View File

@ -9,11 +9,7 @@ func Count(body AST, kind token.Kind, name string) int {
for _, node := range body { for _, node := range body {
switch node := node.(type) { switch node := node.(type) {
case *Assign: case *Assign:
if node.Name.Kind == kind && node.Name.Text() == name { count += node.Expression.Count(kind, name)
count++
}
count += node.Value.Count(kind, name)
case *Call: case *Call:
count += node.Expression.Count(kind, name) count += node.Expression.Count(kind, name)

View File

@ -86,9 +86,7 @@ func toASTNode(tokens token.List) (Node, error) {
return nil, errors.New(errors.MissingOperand, nil, expr.Token.End()) return nil, errors.New(errors.MissingOperand, nil, expr.Token.End())
} }
name := expr.Children[0].Token return &Assign{Expression: expr}, nil
value := expr.Children[1]
return &Assign{Name: name, Value: value, Operator: expr.Token}, nil
case IsFunctionCall(expr): case IsFunctionCall(expr):
return &Call{Expression: expr}, nil return &Call{Expression: expr}, nil

View File

@ -1,19 +1,58 @@
package core package core
import ( import (
"strconv"
"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"
) )
// CompileAssign compiles an assign statement. // CompileAssign compiles an assign statement.
func (f *Function) CompileAssign(node *ast.Assign) error { func (f *Function) CompileAssign(node *ast.Assign) error {
name := node.Name.Text() operator := node.Expression.Token
left := node.Expression.Children[0]
right := node.Expression.Children[1]
if left.IsLeaf() {
name := left.Token.Text()
variable := f.Variable(name) variable := f.Variable(name)
if variable == nil { if variable == nil {
return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, node.Name.Position) return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Token.Position)
} }
defer f.useVariable(variable) defer f.useVariable(variable)
return f.Execute(node.Operator, variable.Register, node.Value) return f.Execute(operator, variable.Register, right)
}
if left.Token.Kind == token.Operator && left.Token.Text() == "@" {
name := left.Children[0].Token.Text()
variable := f.Variable(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 := strconv.Atoi(index.Token.Text())
if err != nil {
return err
}
num, err := strconv.Atoi(right.Token.Text())
if err != nil {
return err
}
f.Assembler.MemoryNumber(asm.STORE, asm.Memory{Base: variable.Register, Offset: byte(offset), Length: 1}, num)
return nil
}
return errors.New(errors.NotImplemented, f.File, left.Token.Position)
} }

View File

@ -79,6 +79,10 @@ func TestParse(t *testing.T) {
{"Function calls 27", "sum(a,b)*2+15*4", "(+ (* (λ sum a b) 2) (* 15 4))"}, {"Function calls 27", "sum(a,b)*2+15*4", "(+ (* (λ sum a b) 2) (* 15 4))"},
{"Package function calls", "math.sum(a,b)", "(λ (. math sum) a b)"}, {"Package function calls", "math.sum(a,b)", "(λ (. math sum) a b)"},
{"Package function calls 2", "generic.math.sum(a,b)", "(λ (. (. generic math) sum) a b)"}, {"Package function calls 2", "generic.math.sum(a,b)", "(λ (. (. generic math) sum) a b)"},
{"Array access", "a[0]", "(@ a 0)"},
{"Array access 2", "a[b+c]", "(@ a (+ b c))"},
{"Array access 3", "a.b[c]", "(@ (. a b) c)"},
{"Array access 4", "a.b[c+d]", "(@ (. a b) (+ c d))"},
} }
for _, test := range tests { for _, test := range tests {

View File

@ -18,6 +18,7 @@ type Operator struct {
var Operators = map[string]*Operator{ var Operators = map[string]*Operator{
".": {".", 13, 2}, ".": {".", 13, 2},
"λ": {"λ", 12, 1}, "λ": {"λ", 12, 1},
"@": {"@", 12, 2},
"!": {"!", 11, 1}, "!": {"!", 11, 1},
"*": {"*", 10, 2}, "*": {"*", 10, 2},
"/": {"/", 10, 2}, "/": {"/", 10, 2},

View File

@ -6,7 +6,10 @@ import (
"git.akyoto.dev/cli/q/src/build/token" "git.akyoto.dev/cli/q/src/build/token"
) )
var call = []byte("λ") var (
call = []byte("λ")
array = []byte("@")
)
// Parse generates an expression tree from tokens. // Parse generates an expression tree from tokens.
func Parse(tokens token.List) *Expression { func Parse(tokens token.List) *Expression {
@ -18,7 +21,7 @@ func Parse(tokens token.List) *Expression {
) )
for i, t := range tokens { for i, t := range tokens {
if t.Kind == token.GroupStart { if t.Kind == token.GroupStart || t.Kind == token.ArrayStart {
groupLevel++ groupLevel++
if groupLevel == 1 { if groupLevel == 1 {
@ -28,24 +31,31 @@ func Parse(tokens token.List) *Expression {
continue continue
} }
if t.Kind == token.GroupEnd { if t.Kind == token.GroupEnd || t.Kind == token.ArrayEnd {
groupLevel-- groupLevel--
if groupLevel != 0 { if groupLevel != 0 {
continue continue
} }
isFunctionCall := isComplete(cursor) // Function call or array access
if isComplete(cursor) {
if isFunctionCall {
parameters := NewList(tokens[groupPosition:i]) parameters := NewList(tokens[groupPosition:i])
node := New() node := New()
node.Token.Kind = token.Operator node.Token.Kind = token.Operator
node.Token.Position = tokens[groupPosition].Position node.Token.Position = tokens[groupPosition].Position
switch t.Kind {
case token.GroupEnd:
node.Token.Bytes = call node.Token.Bytes = call
node.Precedence = precedence("λ") node.Precedence = precedence("λ")
case token.ArrayEnd:
node.Token.Bytes = array
node.Precedence = precedence("@")
}
if cursor.Token.Kind == token.Operator && node.Precedence > cursor.Precedence { if cursor.Token.Kind == token.Operator && node.Precedence > cursor.Precedence {
cursor.LastChild().Replace(node) cursor.LastChild().Replace(node)
} else { } else {

View File

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