diff --git a/docs/todo.md b/docs/todo.md index 6dcd482..e08b1d2 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -36,9 +36,9 @@ - [x] `assert` - [x] `const` -- [x] `extern` - [x] `else` -- [ ] `for` +- [x] `extern` +- [x] `for` - [x] `if` - [x] `import` - [x] `loop` diff --git a/examples/thread/thread.q b/examples/thread/thread.q index 91d5689..b878ce8 100644 --- a/examples/thread/thread.q +++ b/examples/thread/thread.q @@ -2,7 +2,7 @@ import io import thread main() { - loop 0..3 { + for 0..3 { thread.create(work) } diff --git a/src/ast/For.go b/src/ast/For.go new file mode 100644 index 0000000..32a080b --- /dev/null +++ b/src/ast/For.go @@ -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 +} diff --git a/src/ast/Loop.go b/src/ast/Loop.go index 4d7257c..8bf3fac 100644 --- a/src/ast/Loop.go +++ b/src/ast/Loop.go @@ -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 } diff --git a/src/ast/parseKeyword.go b/src/ast/parseKeyword.go index 9c2cd9b..a0a4d45 100644 --- a/src/ast/parseKeyword.go +++ b/src/ast/parseKeyword.go @@ -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 diff --git a/src/core/CompileASTNode.go b/src/core/CompileASTNode.go index e5daa67..a32a321 100644 --- a/src/core/CompileASTNode.go +++ b/src/core/CompileASTNode.go @@ -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) diff --git a/src/core/CompileFor.go b/src/core/CompileFor.go new file mode 100644 index 0000000..6cfde4c --- /dev/null +++ b/src/core/CompileFor.go @@ -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 +} diff --git a/src/core/CompileLoop.go b/src/core/CompileLoop.go index 1209fe1..ea9aea7 100644 --- a/src/core/CompileLoop.go +++ b/src/core/CompileLoop.go @@ -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 } diff --git a/src/core/CompileLoopInfinite.go b/src/core/CompileLoopInfinite.go deleted file mode 100644 index e8eada3..0000000 --- a/src/core/CompileLoopInfinite.go +++ /dev/null @@ -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 -} diff --git a/src/token/Kind.go b/src/token/Kind.go index 54feac0..2e0d764 100644 --- a/src/token/Kind.go +++ b/src/token/Kind.go @@ -69,6 +69,7 @@ const ( Const // const Else // else Extern // extern + For // for If // if Import // import Loop // loop diff --git a/src/token/Tokenize_test.go b/src/token/Tokenize_test.go index b38a5d7..ab256cb 100644 --- a/src/token/Tokenize_test.go +++ b/src/token/Tokenize_test.go @@ -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, diff --git a/src/token/identifier.go b/src/token/identifier.go index 6973fc6..27849ed 100644 --- a/src/token/identifier.go +++ b/src/token/identifier.go @@ -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": diff --git a/tests/programs/index-dynamic.q b/tests/programs/index-dynamic.q index 7f1cfe3..fa2e3e4 100644 --- a/tests/programs/index-dynamic.q +++ b/tests/programs/index-dynamic.q @@ -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 } diff --git a/tests/programs/loop-for.q b/tests/programs/loop-for.q new file mode 100644 index 0000000..cc85bac --- /dev/null +++ b/tests/programs/loop-for.q @@ -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 + } +} \ No newline at end of file diff --git a/tests/programs/loop-limited.q b/tests/programs/loop-limited.q deleted file mode 100644 index c26975c..0000000 --- a/tests/programs/loop-limited.q +++ /dev/null @@ -1,6 +0,0 @@ -main() { - loop i := 0..10 { - assert i >= 0 - assert i < 10 - } -} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 15e5e30..3097960 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -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},