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 ### Keywords
- [x] `assert` - [x] `assert`
- [x] `else`
- [ ] `for` - [ ] `for`
- [x] `if` - [x] `if`
- [x] `import` - [x] `import`

View File

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

View File

@ -1,7 +1,7 @@
import sys import log
main() { main() {
sys.exit(fibonacci(10)) log.number(fibonacci(10))
} }
fibonacci(x) { 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: case token.BlockEnd:
blockLevel-- 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 { type If struct {
Condition *expression.Expression Condition *expression.Expression
Body AST Body AST
Else AST
} }

View File

@ -7,19 +7,19 @@ import (
// Parse generates an AST from a list of tokens. // Parse generates an AST from a list of tokens.
func Parse(tokens []token.Token, source []byte) (AST, error) { 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 { err := EachInstruction(tokens, func(instruction token.List) error {
node, err := parseNode(instruction, source) node, err := parseNode(instruction, source, nodes)
if err == nil && node != nil { if err == nil && node != nil {
tree = append(tree, node) nodes = append(nodes, node)
} }
return err return err
}) })
return tree, err return nodes, err
} }
// IsAssignment returns true if the expression is an assignment. // IsAssignment returns true if the expression is an assignment.

View File

@ -7,7 +7,7 @@ import (
) )
// parseKeyword generates a keyword node from an instruction. // 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 { switch tokens[0].Kind {
case token.Assert: case token.Assert:
if len(tokens) == 1 { if len(tokens) == 1 {
@ -22,6 +22,23 @@ func parseKeyword(tokens token.List, source []byte) (Node, error) {
condition := expression.Parse(tokens[1:blockStart]) condition := expression.Parse(tokens[1:blockStart])
return &If{Condition: condition, Body: body}, err 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: case token.Loop:
_, _, body, err := block(tokens, source) _, _, body, err := block(tokens, source)
return &Loop{Body: body}, err return &Loop{Body: body}, err

View File

@ -7,9 +7,9 @@ import (
) )
// parseNode generates an AST node from an instruction. // 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() { if tokens[0].IsKeyword() {
return parseKeyword(tokens, source) return parseKeyword(tokens, source, nodes)
} }
expr := expression.Parse(tokens) expr := expression.Parse(tokens)

View File

@ -3,15 +3,20 @@ package core
import ( import (
"fmt" "fmt"
"git.akyoto.dev/cli/q/src/build/asm"
"git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/ast"
) )
// CompileIf compiles a branch instruction. // CompileIf compiles a branch instruction.
func (f *Function) CompileIf(branch *ast.If) error { func (f *Function) CompileIf(branch *ast.If) error {
f.count.branch++ 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) var (
err := f.CompileCondition(branch.Condition, success, fail) 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 { if err != nil {
return err return err
@ -20,7 +25,31 @@ func (f *Function) CompileIf(branch *ast.If) error {
f.AddLabel(success) f.AddLabel(success)
f.PushScope(branch.Body, f.File.Bytes) f.PushScope(branch.Body, f.File.Bytes)
err = f.CompileAST(branch.Body) 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.PopScope()
f.AddLabel(fail) 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
} }

View File

@ -1,12 +1,13 @@
package errors package errors
var ( var (
InvalidNumber = &Base{"Invalid number"} ExpectedIfBeforeElse = &Base{"Expected an 'if' block before 'else'"}
InvalidExpression = &Base{"Invalid expression"} InvalidNumber = &Base{"Invalid number"}
InvalidRune = &Base{"Invalid rune"} InvalidExpression = &Base{"Invalid expression"}
InvalidStatement = &Base{"Invalid statement"} InvalidRune = &Base{"Invalid rune"}
MissingExpression = &Base{"Missing expression"} InvalidStatement = &Base{"Invalid statement"}
MissingMainFunction = &Base{"Missing main function"} MissingExpression = &Base{"Missing expression"}
MissingOperand = &Base{"Missing operand"} MissingMainFunction = &Base{"Missing main function"}
NotImplemented = &Base{"Not implemented"} MissingOperand = &Base{"Missing operand"}
NotImplemented = &Base{"Not implemented"}
) )

View File

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

View File

@ -143,6 +143,8 @@ func Tokenize(buffer []byte) List {
kind = Assert kind = Assert
case "if": case "if":
kind = If kind = If
case "else":
kind = Else
case "import": case "import":
kind = Import kind = Import
case "loop": 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}, {"ExpectedFunctionDefinition.q", errors.ExpectedFunctionDefinition},
{"ExpectedFunctionName.q", errors.ExpectedFunctionName}, {"ExpectedFunctionName.q", errors.ExpectedFunctionName},
{"ExpectedFunctionParameters.q", errors.ExpectedFunctionParameters}, {"ExpectedFunctionParameters.q", errors.ExpectedFunctionParameters},
{"ExpectedIfBeforeElse.q", errors.ExpectedIfBeforeElse},
{"ExpectedIfBeforeElse2.q", errors.ExpectedIfBeforeElse},
{"InvalidInstructionIdentifier.q", &errors.InvalidInstruction{Instruction: "abc"}}, {"InvalidInstructionIdentifier.q", &errors.InvalidInstruction{Instruction: "abc"}},
{"InvalidInstructionNumber.q", &errors.InvalidInstruction{Instruction: "123"}}, {"InvalidInstructionNumber.q", &errors.InvalidInstruction{Instruction: "123"}},
{"InvalidInstructionExpression.q", &errors.InvalidInstruction{Instruction: "+"}}, {"InvalidInstructionExpression.q", &errors.InvalidInstruction{Instruction: "+"}},

View File

@ -16,8 +16,9 @@ var examples = []struct {
ExitCode int ExitCode int
}{ }{
{"hello", "", "Hello", 0}, {"hello", "", "Hello", 0},
{"factorial", "", "", 120}, {"factorial", "", "120", 0},
{"fibonacci", "", "", 55}, {"fibonacci", "", "55", 0},
{"gcd", "", "21", 0},
{"array", "", "Hello", 0}, {"array", "", "Hello", 0},
{"echo", "Echo", "Echo", 0}, {"echo", "Echo", "Echo", 0},
{"itoa", "", "9223372036854775807", 0}, {"itoa", "", "9223372036854775807", 0},