diff --git a/examples/array/array.q b/examples/array/array.q new file mode 100644 index 0000000..2cdf875 --- /dev/null +++ b/examples/array/array.q @@ -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) +} \ No newline at end of file diff --git a/src/build/arch/x64/Load.go b/src/build/arch/x64/Load.go index fbef5b0..18e36d2 100644 --- a/src/build/arch/x64/Load.go +++ b/src/build/arch/x64/Load.go @@ -3,6 +3,6 @@ package x64 import "git.akyoto.dev/cli/q/src/build/cpu" // LoadRegister loads from memory into a register. -func LoadRegister(code []byte, destination cpu.Register, offset byte, numBytes byte, source cpu.Register) []byte { - return memoryAccess(code, 0x8A, 0x8B, source, offset, numBytes, destination) +func LoadRegister(code []byte, destination cpu.Register, offset byte, length byte, source cpu.Register) []byte { + return memoryAccess(code, 0x8A, 0x8B, source, offset, length, destination) } diff --git a/src/build/arch/x64/Load_test.go b/src/build/arch/x64/Load_test.go index 9cdcc62..f906a28 100644 --- a/src/build/arch/x64/Load_test.go +++ b/src/build/arch/x64/Load_test.go @@ -13,7 +13,7 @@ func TestLoadRegister(t *testing.T) { Destination cpu.Register Source cpu.Register Offset byte - NumBytes byte + Length byte Code []byte }{ // No offset @@ -150,8 +150,8 @@ func TestLoadRegister(t *testing.T) { } for _, pattern := range usagePatterns { - t.Logf("load %dB %s, [%s+%d]", pattern.NumBytes, pattern.Destination, pattern.Source, pattern.Offset) - code := x64.LoadRegister(nil, pattern.Destination, pattern.Offset, pattern.NumBytes, pattern.Source) + t.Logf("load %dB %s, [%s+%d]", pattern.Length, pattern.Destination, pattern.Source, pattern.Offset) + code := x64.LoadRegister(nil, pattern.Destination, pattern.Offset, pattern.Length, pattern.Source) assert.DeepEqual(t, code, pattern.Code) } } diff --git a/src/build/arch/x64/Store.go b/src/build/arch/x64/Store.go index 7a37870..f7c8f5b 100644 --- a/src/build/arch/x64/Store.go +++ b/src/build/arch/x64/Store.go @@ -7,10 +7,10 @@ import ( ) // 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 { - code = memoryAccess(code, 0xC6, 0xC7, register, offset, numBytes, 0b000) +func StoreNumber(code []byte, register cpu.Register, offset byte, length byte, number int) []byte { + code = memoryAccess(code, 0xC6, 0xC7, register, offset, length, 0b000) - switch numBytes { + switch length { case 8, 4: 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. -func StoreRegister(code []byte, register cpu.Register, offset byte, numBytes byte, source cpu.Register) []byte { - return memoryAccess(code, 0x88, 0x89, register, offset, numBytes, source) +func StoreRegister(code []byte, register cpu.Register, offset byte, length byte, source cpu.Register) []byte { + return memoryAccess(code, 0x88, 0x89, register, offset, length, source) } diff --git a/src/build/arch/x64/Store_test.go b/src/build/arch/x64/Store_test.go index 03f2224..81758d6 100644 --- a/src/build/arch/x64/Store_test.go +++ b/src/build/arch/x64/Store_test.go @@ -12,7 +12,7 @@ func TestStoreNumber(t *testing.T) { usagePatterns := []struct { Register cpu.Register Offset byte - NumBytes byte + Length byte Number int Code []byte }{ @@ -150,8 +150,8 @@ func TestStoreNumber(t *testing.T) { } for _, pattern := range usagePatterns { - t.Logf("store %dB [%s+%d], %d", pattern.NumBytes, pattern.Register, pattern.Offset, pattern.Number) - code := x64.StoreNumber(nil, pattern.Register, pattern.Offset, pattern.NumBytes, 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.Length, pattern.Number) assert.DeepEqual(t, code, pattern.Code) } } @@ -160,7 +160,7 @@ func TestStoreRegister(t *testing.T) { usagePatterns := []struct { RegisterTo cpu.Register Offset byte - NumBytes byte + Length byte RegisterFrom cpu.Register Code []byte }{ @@ -298,8 +298,8 @@ func TestStoreRegister(t *testing.T) { } for _, pattern := range usagePatterns { - t.Logf("store %dB [%s+%d], %s", pattern.NumBytes, pattern.RegisterTo, pattern.Offset, pattern.RegisterFrom) - code := x64.StoreRegister(nil, pattern.RegisterTo, pattern.Offset, pattern.NumBytes, 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.Length, pattern.RegisterFrom) assert.DeepEqual(t, code, pattern.Code) } } diff --git a/src/build/asm/Finalize.go b/src/build/asm/Finalize.go index 415d594..a34920a 100644 --- a/src/build/asm/Finalize.go +++ b/src/build/asm/Finalize.go @@ -165,6 +165,12 @@ func (a Assembler) Finalize() ([]byte, []byte) { case RETURN: 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: code = x64.Syscall(code) diff --git a/src/build/asm/Memory.go b/src/build/asm/Memory.go new file mode 100644 index 0000000..ef209ec --- /dev/null +++ b/src/build/asm/Memory.go @@ -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 +} diff --git a/src/build/asm/MemoryNumber.go b/src/build/asm/MemoryNumber.go new file mode 100644 index 0000000..0923bd1 --- /dev/null +++ b/src/build/asm/MemoryNumber.go @@ -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, + }, + }) +} diff --git a/src/build/asm/Mnemonic.go b/src/build/asm/Mnemonic.go index d25bc99..a1967a7 100644 --- a/src/build/asm/Mnemonic.go +++ b/src/build/asm/Mnemonic.go @@ -18,10 +18,12 @@ const ( JUMP MUL LABEL + LOAD MOVE POP PUSH RETURN + STORE SUB SYSCALL ) @@ -55,6 +57,8 @@ func (m Mnemonic) String() string { return "jump if >=" case LABEL: return "label" + case LOAD: + return "load" case MOVE: return "move" case MUL: @@ -67,6 +71,8 @@ func (m Mnemonic) String() string { return "return" case SUB: return "sub" + case STORE: + return "store" case SYSCALL: return "syscall" default: diff --git a/src/build/ast/Assign.go b/src/build/ast/Assign.go index daee95d..32c6819 100644 --- a/src/build/ast/Assign.go +++ b/src/build/ast/Assign.go @@ -1,19 +1,14 @@ package ast import ( - "fmt" - "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. type Assign struct { - Value *expression.Expression - Name token.Token - Operator token.Token + Expression *expression.Expression } func (node *Assign) String() string { - return fmt.Sprintf("(= %s %s)", node.Name.Text(), node.Value) + return node.Expression.String() } diff --git a/src/build/ast/Count.go b/src/build/ast/Count.go index d15a29e..0cc6986 100644 --- a/src/build/ast/Count.go +++ b/src/build/ast/Count.go @@ -9,11 +9,7 @@ func Count(body AST, kind token.Kind, name string) int { for _, node := range body { switch node := node.(type) { case *Assign: - if node.Name.Kind == kind && node.Name.Text() == name { - count++ - } - - count += node.Value.Count(kind, name) + count += node.Expression.Count(kind, name) case *Call: count += node.Expression.Count(kind, name) diff --git a/src/build/ast/Parse.go b/src/build/ast/Parse.go index eb55aec..eae5666 100644 --- a/src/build/ast/Parse.go +++ b/src/build/ast/Parse.go @@ -86,9 +86,7 @@ func toASTNode(tokens token.List) (Node, error) { return nil, errors.New(errors.MissingOperand, nil, expr.Token.End()) } - name := expr.Children[0].Token - value := expr.Children[1] - return &Assign{Name: name, Value: value, Operator: expr.Token}, nil + return &Assign{Expression: expr}, nil case IsFunctionCall(expr): return &Call{Expression: expr}, nil diff --git a/src/build/core/CompileAssign.go b/src/build/core/CompileAssign.go index 460ef05..fd48f87 100644 --- a/src/build/core/CompileAssign.go +++ b/src/build/core/CompileAssign.go @@ -1,19 +1,58 @@ package core 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/errors" + "git.akyoto.dev/cli/q/src/build/token" ) // CompileAssign compiles an assign statement. func (f *Function) CompileAssign(node *ast.Assign) error { - name := node.Name.Text() - variable := f.Variable(name) + operator := node.Expression.Token + left := node.Expression.Children[0] + right := node.Expression.Children[1] - if variable == nil { - return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, node.Name.Position) + if left.IsLeaf() { + name := left.Token.Text() + variable := f.Variable(name) + + if variable == nil { + return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Token.Position) + } + + defer f.useVariable(variable) + return f.Execute(operator, variable.Register, right) } - defer f.useVariable(variable) - return f.Execute(node.Operator, variable.Register, node.Value) + 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) } diff --git a/src/build/expression/Expression_test.go b/src/build/expression/Expression_test.go index 4e7ea8d..6794da8 100644 --- a/src/build/expression/Expression_test.go +++ b/src/build/expression/Expression_test.go @@ -79,6 +79,10 @@ func TestParse(t *testing.T) { {"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 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 { diff --git a/src/build/expression/Operator.go b/src/build/expression/Operator.go index 07fc5a1..b2d15e1 100644 --- a/src/build/expression/Operator.go +++ b/src/build/expression/Operator.go @@ -18,6 +18,7 @@ type Operator struct { var Operators = map[string]*Operator{ ".": {".", 13, 2}, "λ": {"λ", 12, 1}, + "@": {"@", 12, 2}, "!": {"!", 11, 1}, "*": {"*", 10, 2}, "/": {"/", 10, 2}, diff --git a/src/build/expression/Parse.go b/src/build/expression/Parse.go index ee90a0b..33dd062 100644 --- a/src/build/expression/Parse.go +++ b/src/build/expression/Parse.go @@ -6,7 +6,10 @@ import ( "git.akyoto.dev/cli/q/src/build/token" ) -var call = []byte("λ") +var ( + call = []byte("λ") + array = []byte("@") +) // Parse generates an expression tree from tokens. func Parse(tokens token.List) *Expression { @@ -18,7 +21,7 @@ func Parse(tokens token.List) *Expression { ) for i, t := range tokens { - if t.Kind == token.GroupStart { + if t.Kind == token.GroupStart || t.Kind == token.ArrayStart { groupLevel++ if groupLevel == 1 { @@ -28,23 +31,30 @@ func Parse(tokens token.List) *Expression { continue } - if t.Kind == token.GroupEnd { + if t.Kind == token.GroupEnd || t.Kind == token.ArrayEnd { groupLevel-- if groupLevel != 0 { continue } - isFunctionCall := isComplete(cursor) - - if isFunctionCall { + // Function call or array access + if isComplete(cursor) { parameters := NewList(tokens[groupPosition:i]) node := New() node.Token.Kind = token.Operator node.Token.Position = tokens[groupPosition].Position - node.Token.Bytes = call - node.Precedence = precedence("λ") + + switch t.Kind { + case token.GroupEnd: + node.Token.Bytes = call + node.Precedence = precedence("λ") + + case token.ArrayEnd: + node.Token.Bytes = array + node.Precedence = precedence("@") + } if cursor.Token.Kind == token.Operator && node.Precedence > cursor.Precedence { cursor.LastChild().Replace(node) diff --git a/tests/examples_test.go b/tests/examples_test.go index 17839db..03139f0 100644 --- a/tests/examples_test.go +++ b/tests/examples_test.go @@ -17,6 +17,7 @@ var examples = []struct { {"hello", "", "Hello", 0}, {"factorial", "", "", 120}, {"fibonacci", "", "", 55}, + {"array", "", "ABCD", 0}, } func TestExamples(t *testing.T) {