Implemented array storage
This commit is contained in:
parent
d35c07ed1c
commit
155df7c44c
13
examples/array/array.q
Normal file
13
examples/array/array.q
Normal 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)
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
||||
|
9
src/build/asm/Memory.go
Normal file
9
src/build/asm/Memory.go
Normal 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
|
||||
}
|
27
src/build/asm/MemoryNumber.go
Normal file
27
src/build/asm/MemoryNumber.go
Normal 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,
|
||||
},
|
||||
})
|
||||
}
|
@ -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:
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -18,6 +18,7 @@ type Operator struct {
|
||||
var Operators = map[string]*Operator{
|
||||
".": {".", 13, 2},
|
||||
"λ": {"λ", 12, 1},
|
||||
"@": {"@", 12, 2},
|
||||
"!": {"!", 11, 1},
|
||||
"*": {"*", 10, 2},
|
||||
"/": {"/", 10, 2},
|
||||
|
@ -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)
|
||||
|
@ -17,6 +17,7 @@ var examples = []struct {
|
||||
{"hello", "", "Hello", 0},
|
||||
{"factorial", "", "", 120},
|
||||
{"fibonacci", "", "", 55},
|
||||
{"array", "", "ABCD", 0},
|
||||
}
|
||||
|
||||
func TestExamples(t *testing.T) {
|
||||
|
Loading…
Reference in New Issue
Block a user