From 323952f4bc9e1ac54b6f95c36ff43bc7388e9847 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 30 Jul 2024 16:36:33 +0200 Subject: [PATCH] Implemented else blocks --- README.md | 1 + examples/factorial/factorial.q | 4 +-- examples/fibonacci/fibonacci.q | 4 +-- examples/gcd/gcd.q | 19 ++++++++++++++ src/build/ast/EachInstruction.go | 12 +++++++++ src/build/ast/If.go | 1 + src/build/ast/Parse.go | 8 +++--- src/build/ast/parseKeyword.go | 19 +++++++++++++- src/build/ast/parseNode.go | 4 +-- src/build/core/CompileIf.go | 37 +++++++++++++++++++++++++--- src/build/errors/CompileErrors.go | 17 +++++++------ src/build/token/Kind.go | 1 + src/build/token/Tokenize.go | 2 ++ tests/errors/ExpectedIfBeforeElse.q | 3 +++ tests/errors/ExpectedIfBeforeElse2.q | 4 +++ tests/errors_test.go | 2 ++ tests/examples_test.go | 5 ++-- 17 files changed, 118 insertions(+), 25 deletions(-) create mode 100644 examples/gcd/gcd.q create mode 100644 tests/errors/ExpectedIfBeforeElse.q create mode 100644 tests/errors/ExpectedIfBeforeElse2.q diff --git a/README.md b/README.md index 8598efb..70e1ea5 100644 --- a/README.md +++ b/README.md @@ -124,6 +124,7 @@ This is what generates expressions from tokens. ### Keywords - [x] `assert` +- [x] `else` - [ ] `for` - [x] `if` - [x] `import` diff --git a/examples/factorial/factorial.q b/examples/factorial/factorial.q index 30315ec..dab665c 100644 --- a/examples/factorial/factorial.q +++ b/examples/factorial/factorial.q @@ -1,7 +1,7 @@ -import sys +import log main() { - sys.exit(factorial(5)) + log.number(factorial(5)) } factorial(x) { diff --git a/examples/fibonacci/fibonacci.q b/examples/fibonacci/fibonacci.q index 505bf26..acc3e78 100644 --- a/examples/fibonacci/fibonacci.q +++ b/examples/fibonacci/fibonacci.q @@ -1,7 +1,7 @@ -import sys +import log main() { - sys.exit(fibonacci(10)) + log.number(fibonacci(10)) } fibonacci(x) { diff --git a/examples/gcd/gcd.q b/examples/gcd/gcd.q new file mode 100644 index 0000000..9e9b5e0 --- /dev/null +++ b/examples/gcd/gcd.q @@ -0,0 +1,19 @@ +import log + +main() { + log.number(gcd(1071, 462)) +} + +gcd(a, b) { + loop { + if a == b { + return a + } + + if a > b { + a -= b + } else { + b -= a + } + } +} \ No newline at end of file diff --git a/src/build/ast/EachInstruction.go b/src/build/ast/EachInstruction.go index f11bedc..b4d9d8a 100644 --- a/src/build/ast/EachInstruction.go +++ b/src/build/ast/EachInstruction.go @@ -39,6 +39,18 @@ func EachInstruction(body token.List, call func(token.List) error) error { case token.BlockEnd: blockLevel-- + + if groupLevel > 0 || blockLevel > 0 { + continue + } + + err := call(body[start : i+1]) + + if err != nil { + return err + } + + start = i + 1 } } diff --git a/src/build/ast/If.go b/src/build/ast/If.go index 4275f34..f99307f 100644 --- a/src/build/ast/If.go +++ b/src/build/ast/If.go @@ -8,4 +8,5 @@ import ( type If struct { Condition *expression.Expression Body AST + Else AST } diff --git a/src/build/ast/Parse.go b/src/build/ast/Parse.go index 2c6fd94..582f497 100644 --- a/src/build/ast/Parse.go +++ b/src/build/ast/Parse.go @@ -7,19 +7,19 @@ import ( // Parse generates an AST from a list of tokens. func Parse(tokens []token.Token, source []byte) (AST, error) { - tree := make(AST, 0, len(tokens)/64) + nodes := make(AST, 0, len(tokens)/64) err := EachInstruction(tokens, func(instruction token.List) error { - node, err := parseNode(instruction, source) + node, err := parseNode(instruction, source, nodes) if err == nil && node != nil { - tree = append(tree, node) + nodes = append(nodes, node) } return err }) - return tree, err + return nodes, err } // IsAssignment returns true if the expression is an assignment. diff --git a/src/build/ast/parseKeyword.go b/src/build/ast/parseKeyword.go index f8aa676..a123570 100644 --- a/src/build/ast/parseKeyword.go +++ b/src/build/ast/parseKeyword.go @@ -7,7 +7,7 @@ import ( ) // parseKeyword generates a keyword node from an instruction. -func parseKeyword(tokens token.List, source []byte) (Node, error) { +func parseKeyword(tokens token.List, source []byte, nodes AST) (Node, error) { switch tokens[0].Kind { case token.Assert: if len(tokens) == 1 { @@ -22,6 +22,23 @@ func parseKeyword(tokens token.List, source []byte) (Node, error) { condition := expression.Parse(tokens[1:blockStart]) return &If{Condition: condition, Body: body}, err + case token.Else: + _, _, body, err := block(tokens, source) + + if len(nodes) == 0 { + return nil, errors.New(errors.ExpectedIfBeforeElse, nil, tokens[0].Position) + } + + last := nodes[len(nodes)-1] + ifNode, exists := last.(*If) + + if !exists { + return nil, errors.New(errors.ExpectedIfBeforeElse, nil, tokens[0].Position) + } + + ifNode.Else = body + return nil, err + case token.Loop: _, _, body, err := block(tokens, source) return &Loop{Body: body}, err diff --git a/src/build/ast/parseNode.go b/src/build/ast/parseNode.go index 9ea7174..8473c29 100644 --- a/src/build/ast/parseNode.go +++ b/src/build/ast/parseNode.go @@ -7,9 +7,9 @@ import ( ) // parseNode generates an AST node from an instruction. -func parseNode(tokens token.List, source []byte) (Node, error) { +func parseNode(tokens token.List, source []byte, nodes AST) (Node, error) { if tokens[0].IsKeyword() { - return parseKeyword(tokens, source) + return parseKeyword(tokens, source, nodes) } expr := expression.Parse(tokens) diff --git a/src/build/core/CompileIf.go b/src/build/core/CompileIf.go index 3eeb4ae..6b1a869 100644 --- a/src/build/core/CompileIf.go +++ b/src/build/core/CompileIf.go @@ -3,15 +3,20 @@ package core import ( "fmt" + "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/ast" ) // CompileIf compiles a branch instruction. func (f *Function) CompileIf(branch *ast.If) error { f.count.branch++ - success := fmt.Sprintf("%s_if_%d_true", f.Name, f.count.branch) - fail := fmt.Sprintf("%s_if_%d_false", f.Name, f.count.branch) - err := f.CompileCondition(branch.Condition, success, fail) + + var ( + end string + success = fmt.Sprintf("%s_if_%d_true", f.Name, f.count.branch) + fail = fmt.Sprintf("%s_if_%d_false", f.Name, f.count.branch) + err = f.CompileCondition(branch.Condition, success, fail) + ) if err != nil { return err @@ -20,7 +25,31 @@ func (f *Function) CompileIf(branch *ast.If) error { f.AddLabel(success) f.PushScope(branch.Body, f.File.Bytes) err = f.CompileAST(branch.Body) + + if err != nil { + return err + } + + if branch.Else != nil { + end = fmt.Sprintf("%s_if_%d_end", f.Name, f.count.branch) + f.Jump(asm.JUMP, end) + } + f.PopScope() f.AddLabel(fail) - return err + + if branch.Else != nil { + f.PushScope(branch.Else, f.File.Bytes) + err = f.CompileAST(branch.Else) + + if err != nil { + return err + } + + f.PopScope() + f.AddLabel(end) + return nil + } + + return nil } diff --git a/src/build/errors/CompileErrors.go b/src/build/errors/CompileErrors.go index 5f65804..a214de3 100644 --- a/src/build/errors/CompileErrors.go +++ b/src/build/errors/CompileErrors.go @@ -1,12 +1,13 @@ package errors var ( - InvalidNumber = &Base{"Invalid number"} - InvalidExpression = &Base{"Invalid expression"} - InvalidRune = &Base{"Invalid rune"} - InvalidStatement = &Base{"Invalid statement"} - MissingExpression = &Base{"Missing expression"} - MissingMainFunction = &Base{"Missing main function"} - MissingOperand = &Base{"Missing operand"} - NotImplemented = &Base{"Not implemented"} + ExpectedIfBeforeElse = &Base{"Expected an 'if' block before 'else'"} + InvalidNumber = &Base{"Invalid number"} + InvalidExpression = &Base{"Invalid expression"} + InvalidRune = &Base{"Invalid rune"} + InvalidStatement = &Base{"Invalid statement"} + MissingExpression = &Base{"Missing expression"} + MissingMainFunction = &Base{"Missing main function"} + MissingOperand = &Base{"Missing operand"} + NotImplemented = &Base{"Not implemented"} ) diff --git a/src/build/token/Kind.go b/src/build/token/Kind.go index 44f0132..2c12464 100644 --- a/src/build/token/Kind.go +++ b/src/build/token/Kind.go @@ -20,6 +20,7 @@ const ( ArrayEnd // ] _keywords // Assert // assert + Else // else If // if Import // import Loop // loop diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index 929eff3..c955e9a 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -143,6 +143,8 @@ func Tokenize(buffer []byte) List { kind = Assert case "if": kind = If + case "else": + kind = Else case "import": kind = Import case "loop": diff --git a/tests/errors/ExpectedIfBeforeElse.q b/tests/errors/ExpectedIfBeforeElse.q new file mode 100644 index 0000000..a4a7018 --- /dev/null +++ b/tests/errors/ExpectedIfBeforeElse.q @@ -0,0 +1,3 @@ +main() { + else {} +} \ No newline at end of file diff --git a/tests/errors/ExpectedIfBeforeElse2.q b/tests/errors/ExpectedIfBeforeElse2.q new file mode 100644 index 0000000..a2b575f --- /dev/null +++ b/tests/errors/ExpectedIfBeforeElse2.q @@ -0,0 +1,4 @@ +main() { + loop {} + else {} +} \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index 5b28ee2..88f6b3f 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -17,6 +17,8 @@ var errs = []struct { {"ExpectedFunctionDefinition.q", errors.ExpectedFunctionDefinition}, {"ExpectedFunctionName.q", errors.ExpectedFunctionName}, {"ExpectedFunctionParameters.q", errors.ExpectedFunctionParameters}, + {"ExpectedIfBeforeElse.q", errors.ExpectedIfBeforeElse}, + {"ExpectedIfBeforeElse2.q", errors.ExpectedIfBeforeElse}, {"InvalidInstructionIdentifier.q", &errors.InvalidInstruction{Instruction: "abc"}}, {"InvalidInstructionNumber.q", &errors.InvalidInstruction{Instruction: "123"}}, {"InvalidInstructionExpression.q", &errors.InvalidInstruction{Instruction: "+"}}, diff --git a/tests/examples_test.go b/tests/examples_test.go index 5c2ee12..94d5f80 100644 --- a/tests/examples_test.go +++ b/tests/examples_test.go @@ -16,8 +16,9 @@ var examples = []struct { ExitCode int }{ {"hello", "", "Hello", 0}, - {"factorial", "", "", 120}, - {"fibonacci", "", "", 55}, + {"factorial", "", "120", 0}, + {"fibonacci", "", "55", 0}, + {"gcd", "", "21", 0}, {"array", "", "Hello", 0}, {"echo", "Echo", "Echo", 0}, {"itoa", "", "9223372036854775807", 0},