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"
|
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)
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
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
|
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:
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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},
|
||||||
|
@ -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 {
|
||||||
|
@ -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) {
|
||||||
|
Loading…
Reference in New Issue
Block a user