Implemented for loops
This commit is contained in:
parent
7922cff7ba
commit
45a36a645a
@ -36,9 +36,9 @@
|
||||
|
||||
- [x] `assert`
|
||||
- [x] `const`
|
||||
- [x] `extern`
|
||||
- [x] `else`
|
||||
- [ ] `for`
|
||||
- [x] `extern`
|
||||
- [x] `for`
|
||||
- [x] `if`
|
||||
- [x] `import`
|
||||
- [x] `loop`
|
||||
|
@ -2,7 +2,7 @@ import io
|
||||
import thread
|
||||
|
||||
main() {
|
||||
loop 0..3 {
|
||||
for 0..3 {
|
||||
thread.create(work)
|
||||
}
|
||||
|
||||
|
9
src/ast/For.go
Normal file
9
src/ast/For.go
Normal 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
|
||||
}
|
@ -1,9 +1,6 @@
|
||||
package ast
|
||||
|
||||
import "git.akyoto.dev/cli/q/src/expression"
|
||||
|
||||
// Loop represents a block of repeatable statements.
|
||||
// Loop is a block of infinitely repeating instructions.
|
||||
type Loop struct {
|
||||
Head *expression.Expression
|
||||
Body AST
|
||||
}
|
||||
|
@ -39,17 +39,21 @@ func parseKeyword(tokens token.List, source []byte, nodes AST) (Node, error) {
|
||||
ifNode.Else = body
|
||||
return nil, err
|
||||
|
||||
case token.Loop:
|
||||
case token.For:
|
||||
blockStart, _, body, err := block(tokens, source)
|
||||
head := tokens[1:blockStart]
|
||||
|
||||
loop := &Loop{
|
||||
loop := &For{
|
||||
Head: expression.Parse(head),
|
||||
Body: body,
|
||||
}
|
||||
|
||||
return loop, err
|
||||
|
||||
case token.Loop:
|
||||
_, _, body, err := block(tokens, source)
|
||||
return &Loop{Body: body}, err
|
||||
|
||||
case token.Return:
|
||||
if len(tokens) == 1 {
|
||||
return &Return{}, nil
|
||||
|
@ -28,6 +28,10 @@ func (f *Function) CompileASTNode(node ast.Node) error {
|
||||
f.Fold(node.Condition)
|
||||
return f.CompileIf(node)
|
||||
|
||||
case *ast.For:
|
||||
f.Fold(node.Head)
|
||||
return f.CompileFor(node)
|
||||
|
||||
case *ast.Loop:
|
||||
return f.CompileLoop(node)
|
||||
|
||||
|
92
src/core/CompileFor.go
Normal file
92
src/core/CompileFor.go
Normal 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
|
||||
}
|
@ -5,92 +5,21 @@ import (
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// CompileLoop compiles a loop instruction.
|
||||
// CompileLoop compiles an infinite loop.
|
||||
func (f *Function) CompileLoop(loop *ast.Loop) error {
|
||||
if loop.Head == nil {
|
||||
return f.CompileLoopInfinite(loop)
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
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)
|
||||
err := f.CompileAST(loop.Body)
|
||||
f.Jump(asm.JUMP, label)
|
||||
f.AddLabel(labelEnd)
|
||||
f.PopScope()
|
||||
return err
|
||||
}
|
||||
|
@ -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
|
||||
}
|
@ -69,6 +69,7 @@ const (
|
||||
Const // const
|
||||
Else // else
|
||||
Extern // extern
|
||||
For // for
|
||||
If // if
|
||||
Import // import
|
||||
Loop // loop
|
||||
|
@ -25,15 +25,16 @@ func TestFunction(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{
|
||||
token.Assert,
|
||||
token.Const,
|
||||
token.If,
|
||||
token.Import,
|
||||
token.Else,
|
||||
token.Extern,
|
||||
token.If,
|
||||
token.Import,
|
||||
token.For,
|
||||
token.Loop,
|
||||
token.Return,
|
||||
token.Struct,
|
||||
|
@ -23,6 +23,8 @@ func identifier(tokens List, buffer []byte, i Position) (List, Position) {
|
||||
kind = Else
|
||||
case "extern":
|
||||
kind = Extern
|
||||
case "for":
|
||||
kind = For
|
||||
case "import":
|
||||
kind = Import
|
||||
case "loop":
|
||||
|
@ -3,7 +3,7 @@ import mem
|
||||
main() {
|
||||
a := mem.alloc(4)
|
||||
|
||||
loop i := 0..4 {
|
||||
for i := 0..4 {
|
||||
a[i] = i * 2
|
||||
assert a[i] == i * 2
|
||||
}
|
||||
|
14
tests/programs/loop-for.q
Normal file
14
tests/programs/loop-for.q
Normal 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
|
||||
}
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
main() {
|
||||
loop i := 0..10 {
|
||||
assert i >= 0
|
||||
assert i < 10
|
||||
}
|
||||
}
|
@ -59,7 +59,7 @@ var programs = []struct {
|
||||
{"switch", "", "", 0},
|
||||
{"loop-infinite", "", "", 0},
|
||||
{"loop-lifetime", "", "", 0},
|
||||
{"loop-limited", "", "", 0},
|
||||
{"loop-for", "", "", 0},
|
||||
{"memory-free", "", "", 0},
|
||||
{"out-of-memory", "", "", 0},
|
||||
{"index-static", "", "", 0},
|
||||
|
Loading…
x
Reference in New Issue
Block a user