Implemented else blocks

This commit is contained in:
Eduard Urbach 2024-07-30 16:36:33 +02:00
parent 5abe8acc70
commit 323952f4bc
Signed by: akyoto
GPG Key ID: C874F672B1AF20C0
17 changed files with 118 additions and 25 deletions

View File

@ -124,6 +124,7 @@ This is what generates expressions from tokens.
### Keywords
- [x] `assert`
- [x] `else`
- [ ] `for`
- [x] `if`
- [x] `import`

View File

@ -1,7 +1,7 @@
import sys
import log
main() {
sys.exit(factorial(5))
log.number(factorial(5))
}
factorial(x) {

View File

@ -1,7 +1,7 @@
import sys
import log
main() {
sys.exit(fibonacci(10))
log.number(fibonacci(10))
}
fibonacci(x) {

19
examples/gcd/gcd.q Normal file
View File

@ -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
}
}
}

View File

@ -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
}
}

View File

@ -8,4 +8,5 @@ import (
type If struct {
Condition *expression.Expression
Body AST
Else AST
}

View File

@ -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.

View File

@ -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

View File

@ -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)

View File

@ -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)
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
}

View File

@ -1,6 +1,7 @@
package errors
var (
ExpectedIfBeforeElse = &Base{"Expected an 'if' block before 'else'"}
InvalidNumber = &Base{"Invalid number"}
InvalidExpression = &Base{"Invalid expression"}
InvalidRune = &Base{"Invalid rune"}

View File

@ -20,6 +20,7 @@ const (
ArrayEnd // ]
_keywords // <keywords>
Assert // assert
Else // else
If // if
Import // import
Loop // loop

View File

@ -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":

View File

@ -0,0 +1,3 @@
main() {
else {}
}

View File

@ -0,0 +1,4 @@
main() {
loop {}
else {}
}

View File

@ -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: "+"}},

View File

@ -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},