diff --git a/examples/thread/thread.q b/examples/thread/thread.q index 6d0493b..91d5689 100644 --- a/examples/thread/thread.q +++ b/examples/thread/thread.q @@ -2,9 +2,10 @@ import io import thread main() { - thread.create(work) - thread.create(work) - thread.create(work) + loop 0..3 { + thread.create(work) + } + work() } diff --git a/src/ast/Loop.go b/src/ast/Loop.go index e1ca210..4d7257c 100644 --- a/src/ast/Loop.go +++ b/src/ast/Loop.go @@ -1,6 +1,9 @@ package ast +import "git.akyoto.dev/cli/q/src/expression" + // Loop represents a block of repeatable statements. type Loop struct { + Head *expression.Expression Body AST } diff --git a/src/ast/parseKeyword.go b/src/ast/parseKeyword.go index 32c2bbd..9c2cd9b 100644 --- a/src/ast/parseKeyword.go +++ b/src/ast/parseKeyword.go @@ -40,8 +40,15 @@ func parseKeyword(tokens token.List, source []byte, nodes AST) (Node, error) { return nil, err case token.Loop: - _, _, body, err := block(tokens, source) - return &Loop{Body: body}, err + blockStart, _, body, err := block(tokens, source) + head := tokens[1:blockStart] + + loop := &Loop{ + Head: expression.Parse(head), + Body: body, + } + + return loop, err case token.Return: if len(tokens) == 1 { diff --git a/src/core/ArrayElementToRegister.go b/src/core/ArrayElementToRegister.go index 46e24f7..c623035 100644 --- a/src/core/ArrayElementToRegister.go +++ b/src/core/ArrayElementToRegister.go @@ -46,6 +46,8 @@ func (f *Function) ArrayElementToRegister(node *expression.Expression, register return nil, errors.New(&errors.UnknownIdentifier{Name: indexName}, f.File, index.Token.Position) } + defer f.UseVariable(indexVariable) + if !types.Is(indexVariable.Type, types.Int) { return nil, errors.New(&errors.TypeMismatch{Encountered: indexVariable.Type.Name(), Expected: types.Int.Name()}, f.File, index.Token.Position) } diff --git a/src/core/CompileLoop.go b/src/core/CompileLoop.go index f2ef623..1209fe1 100644 --- a/src/core/CompileLoop.go +++ b/src/core/CompileLoop.go @@ -5,21 +5,92 @@ 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. 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++ - label := fmt.Sprintf("%s_loop_%d", f.UniqueName, f.count.loop) - f.AddLabel(label) + + 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 - 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.AddLabel(labelEnd) f.PopScope() return err } diff --git a/src/core/CompileLoopInfinite.go b/src/core/CompileLoopInfinite.go new file mode 100644 index 0000000..e8eada3 --- /dev/null +++ b/src/core/CompileLoopInfinite.go @@ -0,0 +1,25 @@ +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/core/Define.go b/src/core/Define.go index 94f7428..d017d85 100644 --- a/src/core/Define.go +++ b/src/core/Define.go @@ -10,7 +10,7 @@ import ( // Define defines a new variable. func (f *Function) Define(leaf *expression.Expression) (*scope.Variable, error) { name := leaf.Token.Text(f.File.Bytes) - variable, _ := f.Identifier(name) + variable := f.VariableByName(name) if variable != nil { return nil, errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, leaf.Token.Position) diff --git a/src/core/Evaluate.go b/src/core/Evaluate.go index cd3f384..68da992 100644 --- a/src/core/Evaluate.go +++ b/src/core/Evaluate.go @@ -2,6 +2,7 @@ package core import ( "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/expression" "git.akyoto.dev/cli/q/src/token" "git.akyoto.dev/cli/q/src/types" @@ -13,6 +14,10 @@ func (f *Function) Evaluate(expr *expression.Expression) (types.Type, cpu.Regist name := expr.Token.Text(f.File.Bytes) variable := f.VariableByName(name) + if variable == nil { + return nil, 0, false, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Token.Position) + } + if variable.Alive == 1 { f.UseVariable(variable) return variable.Type, variable.Register, false, nil diff --git a/src/expression/Operator.go b/src/expression/Operator.go index f69ba00..800277a 100644 --- a/src/expression/Operator.go +++ b/src/expression/Operator.go @@ -41,6 +41,7 @@ var Operators = [64]Operator{ token.LogicalAnd: {"&&", 2, 2}, token.LogicalOr: {"||", 1, 2}, + token.Range: {"..", 0, 2}, token.Separator: {",", 0, 2}, token.Assign: {"=", math.MinInt8, 2}, diff --git a/src/token/Kind.go b/src/token/Kind.go index 200d131..54feac0 100644 --- a/src/token/Kind.go +++ b/src/token/Kind.go @@ -34,6 +34,7 @@ const ( LogicalOr // || Define // := Period // . + Range // .. Call // x() Array // [x] Separator // , diff --git a/src/token/operator.go b/src/token/operator.go index 46251ad..5d74059 100644 --- a/src/token/operator.go +++ b/src/token/operator.go @@ -36,6 +36,8 @@ func operator(tokens List, buffer []byte, i Position) (List, Position) { kind = AddAssign case ".": kind = Period + case "..": + kind = Range case ":=": kind = Define case "<": diff --git a/tests/programs/array-index-dynamic.q b/tests/programs/array-index-dynamic.q deleted file mode 100644 index 4e4c03f..0000000 --- a/tests/programs/array-index-dynamic.q +++ /dev/null @@ -1,23 +0,0 @@ -import mem - -main() { - a := mem.alloc(4) - i := 0 - - a[i] = i * 2 - i += 1 - a[i] = i * 2 - i += 1 - a[i] = i * 2 - i += 1 - a[i] = i * 2 - - i = 0 - assert a[i] == i * 2 - i += 1 - assert a[i] == i * 2 - i += 1 - assert a[i] == i * 2 - i += 1 - assert a[i] == i * 2 -} \ No newline at end of file diff --git a/tests/programs/index-dynamic.q b/tests/programs/index-dynamic.q new file mode 100644 index 0000000..7f1cfe3 --- /dev/null +++ b/tests/programs/index-dynamic.q @@ -0,0 +1,10 @@ +import mem + +main() { + a := mem.alloc(4) + + loop i := 0..4 { + a[i] = i * 2 + assert a[i] == i * 2 + } +} \ No newline at end of file diff --git a/tests/programs/array-index-static.q b/tests/programs/index-static.q similarity index 100% rename from tests/programs/array-index-static.q rename to tests/programs/index-static.q diff --git a/tests/programs/loop.q b/tests/programs/loop-infinite.q similarity index 100% rename from tests/programs/loop.q rename to tests/programs/loop-infinite.q diff --git a/tests/programs/loop-limited.q b/tests/programs/loop-limited.q new file mode 100644 index 0000000..c26975c --- /dev/null +++ b/tests/programs/loop-limited.q @@ -0,0 +1,6 @@ +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 32bf77e..15e5e30 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -57,12 +57,13 @@ var programs = []struct { {"branch-save", "", "", 0}, {"jump-near", "", "", 0}, {"switch", "", "", 0}, - {"loop", "", "", 0}, + {"loop-infinite", "", "", 0}, {"loop-lifetime", "", "", 0}, + {"loop-limited", "", "", 0}, {"memory-free", "", "", 0}, {"out-of-memory", "", "", 0}, - {"array-index-static", "", "", 0}, - {"array-index-dynamic", "", "", 0}, + {"index-static", "", "", 0}, + {"index-dynamic", "", "", 0}, {"struct", "", "", 0}, {"len", "", "", 0}, }