Implemented for loops

This commit is contained in:
Eduard Urbach 2025-02-19 23:46:17 +01:00
parent 7922cff7ba
commit 45a36a645a
Signed by: akyoto
GPG Key ID: C874F672B1AF20C0
16 changed files with 142 additions and 120 deletions

View File

@ -36,9 +36,9 @@
- [x] `assert` - [x] `assert`
- [x] `const` - [x] `const`
- [x] `extern`
- [x] `else` - [x] `else`
- [ ] `for` - [x] `extern`
- [x] `for`
- [x] `if` - [x] `if`
- [x] `import` - [x] `import`
- [x] `loop` - [x] `loop`

View File

@ -2,7 +2,7 @@ import io
import thread import thread
main() { main() {
loop 0..3 { for 0..3 {
thread.create(work) thread.create(work)
} }

9
src/ast/For.go Normal file
View File

@ -0,0 +1,9 @@
package ast
import "git.akyoto.dev/cli/q/src/expression"
// For is a loop with a defined iteration limit.
type For struct {
Head *expression.Expression
Body AST
}

View File

@ -1,9 +1,6 @@
package ast package ast
import "git.akyoto.dev/cli/q/src/expression" // Loop is a block of infinitely repeating instructions.
// Loop represents a block of repeatable statements.
type Loop struct { type Loop struct {
Head *expression.Expression
Body AST Body AST
} }

View File

@ -39,17 +39,21 @@ func parseKeyword(tokens token.List, source []byte, nodes AST) (Node, error) {
ifNode.Else = body ifNode.Else = body
return nil, err return nil, err
case token.Loop: case token.For:
blockStart, _, body, err := block(tokens, source) blockStart, _, body, err := block(tokens, source)
head := tokens[1:blockStart] head := tokens[1:blockStart]
loop := &Loop{ loop := &For{
Head: expression.Parse(head), Head: expression.Parse(head),
Body: body, Body: body,
} }
return loop, err return loop, err
case token.Loop:
_, _, body, err := block(tokens, source)
return &Loop{Body: body}, err
case token.Return: case token.Return:
if len(tokens) == 1 { if len(tokens) == 1 {
return &Return{}, nil return &Return{}, nil

View File

@ -28,6 +28,10 @@ func (f *Function) CompileASTNode(node ast.Node) error {
f.Fold(node.Condition) f.Fold(node.Condition)
return f.CompileIf(node) return f.CompileIf(node)
case *ast.For:
f.Fold(node.Head)
return f.CompileFor(node)
case *ast.Loop: case *ast.Loop:
return f.CompileLoop(node) return f.CompileLoop(node)

92
src/core/CompileFor.go Normal file
View File

@ -0,0 +1,92 @@
package core
import (
"fmt"
"git.akyoto.dev/cli/q/src/asm"
"git.akyoto.dev/cli/q/src/ast"
"git.akyoto.dev/cli/q/src/cpu"
"git.akyoto.dev/cli/q/src/expression"
"git.akyoto.dev/cli/q/src/token"
)
// CompileFor compiles a for loop.
func (f *Function) CompileFor(loop *ast.For) error {
for _, register := range f.CPU.Input {
f.SaveRegister(register)
}
f.count.loop++
var (
label = fmt.Sprintf("%s_loop_%d", f.UniqueName, f.count.loop)
labelEnd = fmt.Sprintf("%s_loop_%d_end", f.UniqueName, f.count.loop)
counter cpu.Register
from *expression.Expression
to *expression.Expression
)
scope := f.PushScope(loop.Body, f.File.Bytes)
scope.InLoop = true
switch loop.Head.Token.Kind {
case token.Define:
variable, err := f.Define(loop.Head.Children[0])
if err != nil {
return err
}
counter = variable.Register
from = loop.Head.Children[1].Children[0]
to = loop.Head.Children[1].Children[1]
f.AddVariable(variable)
case token.Range:
counter = f.NewRegister()
defer f.FreeRegister(counter)
from = loop.Head.Children[0]
to = loop.Head.Children[1]
default:
panic("could not recognize loop header")
}
_, err := f.ExpressionToRegister(from, counter)
if err != nil {
return err
}
if to.Token.IsNumeric() {
number, err := f.ToNumber(to.Token)
if err != nil {
return err
}
f.AddLabel(label)
f.RegisterNumber(asm.COMPARE, counter, number)
} else {
_, register, isTemporary, err := f.Evaluate(to)
if err != nil {
return err
}
if isTemporary {
defer f.FreeRegister(register)
}
f.AddLabel(label)
f.RegisterRegister(asm.COMPARE, counter, register)
}
f.Jump(asm.JGE, labelEnd)
err = f.CompileAST(loop.Body)
f.RegisterNumber(asm.ADD, counter, 1)
f.Jump(asm.JUMP, label)
f.AddLabel(labelEnd)
f.PopScope()
return err
}

View File

@ -5,92 +5,21 @@ import (
"git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/asm"
"git.akyoto.dev/cli/q/src/ast" "git.akyoto.dev/cli/q/src/ast"
"git.akyoto.dev/cli/q/src/cpu"
"git.akyoto.dev/cli/q/src/expression"
"git.akyoto.dev/cli/q/src/token"
) )
// CompileLoop compiles a loop instruction. // CompileLoop compiles an infinite loop.
func (f *Function) CompileLoop(loop *ast.Loop) error { func (f *Function) CompileLoop(loop *ast.Loop) error {
if loop.Head == nil {
return f.CompileLoopInfinite(loop)
}
for _, register := range f.CPU.Input { for _, register := range f.CPU.Input {
f.SaveRegister(register) f.SaveRegister(register)
} }
f.count.loop++ f.count.loop++
label := fmt.Sprintf("%s_loop_%d", f.UniqueName, f.count.loop)
var ( f.AddLabel(label)
label = fmt.Sprintf("%s_loop_%d", f.UniqueName, f.count.loop)
labelEnd = fmt.Sprintf("%s_loop_%d_end", f.UniqueName, f.count.loop)
counter cpu.Register
from *expression.Expression
to *expression.Expression
)
scope := f.PushScope(loop.Body, f.File.Bytes) scope := f.PushScope(loop.Body, f.File.Bytes)
scope.InLoop = true scope.InLoop = true
err := f.CompileAST(loop.Body)
switch loop.Head.Token.Kind {
case token.Define:
variable, err := f.Define(loop.Head.Children[0])
if err != nil {
return err
}
counter = variable.Register
from = loop.Head.Children[1].Children[0]
to = loop.Head.Children[1].Children[1]
f.AddVariable(variable)
case token.Range:
counter = f.NewRegister()
defer f.FreeRegister(counter)
from = loop.Head.Children[0]
to = loop.Head.Children[1]
default:
panic("could not recognize loop header")
}
_, err := f.ExpressionToRegister(from, counter)
if err != nil {
return err
}
if to.Token.IsNumeric() {
number, err := f.ToNumber(to.Token)
if err != nil {
return err
}
f.AddLabel(label)
f.RegisterNumber(asm.COMPARE, counter, number)
} else {
_, register, isTemporary, err := f.Evaluate(to)
if err != nil {
return err
}
if isTemporary {
defer f.FreeRegister(register)
}
f.AddLabel(label)
f.RegisterRegister(asm.COMPARE, counter, register)
}
f.Jump(asm.JGE, labelEnd)
err = f.CompileAST(loop.Body)
f.RegisterNumber(asm.ADD, counter, 1)
f.Jump(asm.JUMP, label) f.Jump(asm.JUMP, label)
f.AddLabel(labelEnd)
f.PopScope() f.PopScope()
return err return err
} }

View File

@ -1,25 +0,0 @@
package core
import (
"fmt"
"git.akyoto.dev/cli/q/src/asm"
"git.akyoto.dev/cli/q/src/ast"
)
// CompileLoopInfinite compiles an infinite loop.
func (f *Function) CompileLoopInfinite(loop *ast.Loop) error {
for _, register := range f.CPU.Input {
f.SaveRegister(register)
}
f.count.loop++
label := fmt.Sprintf("%s_loop_%d", f.UniqueName, f.count.loop)
f.AddLabel(label)
scope := f.PushScope(loop.Body, f.File.Bytes)
scope.InLoop = true
err := f.CompileAST(loop.Body)
f.Jump(asm.JUMP, label)
f.PopScope()
return err
}

View File

@ -69,6 +69,7 @@ const (
Const // const Const // const
Else // else Else // else
Extern // extern Extern // extern
For // for
If // if If // if
Import // import Import // import
Loop // loop Loop // loop

View File

@ -25,15 +25,16 @@ func TestFunction(t *testing.T) {
} }
func TestKeyword(t *testing.T) { func TestKeyword(t *testing.T) {
tokens := token.Tokenize([]byte("assert const if import else extern loop return struct switch")) tokens := token.Tokenize([]byte("assert const else extern if import for loop return struct switch"))
expected := []token.Kind{ expected := []token.Kind{
token.Assert, token.Assert,
token.Const, token.Const,
token.If,
token.Import,
token.Else, token.Else,
token.Extern, token.Extern,
token.If,
token.Import,
token.For,
token.Loop, token.Loop,
token.Return, token.Return,
token.Struct, token.Struct,

View File

@ -23,6 +23,8 @@ func identifier(tokens List, buffer []byte, i Position) (List, Position) {
kind = Else kind = Else
case "extern": case "extern":
kind = Extern kind = Extern
case "for":
kind = For
case "import": case "import":
kind = Import kind = Import
case "loop": case "loop":

View File

@ -3,7 +3,7 @@ import mem
main() { main() {
a := mem.alloc(4) a := mem.alloc(4)
loop i := 0..4 { for i := 0..4 {
a[i] = i * 2 a[i] = i * 2
assert a[i] == i * 2 assert a[i] == i * 2
} }

14
tests/programs/loop-for.q Normal file
View File

@ -0,0 +1,14 @@
main() {
x := 0
for 0..5 {
x += 1
}
assert x == 5
for i := 0..5 {
assert i >= 0
assert i < 5
}
}

View File

@ -1,6 +0,0 @@
main() {
loop i := 0..10 {
assert i >= 0
assert i < 10
}
}

View File

@ -59,7 +59,7 @@ var programs = []struct {
{"switch", "", "", 0}, {"switch", "", "", 0},
{"loop-infinite", "", "", 0}, {"loop-infinite", "", "", 0},
{"loop-lifetime", "", "", 0}, {"loop-lifetime", "", "", 0},
{"loop-limited", "", "", 0}, {"loop-for", "", "", 0},
{"memory-free", "", "", 0}, {"memory-free", "", "", 0},
{"out-of-memory", "", "", 0}, {"out-of-memory", "", "", 0},
{"index-static", "", "", 0}, {"index-static", "", "", 0},