Implemented else blocks
This commit is contained in:
parent
5abe8acc70
commit
323952f4bc
@ -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`
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import sys
|
import log
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
sys.exit(factorial(5))
|
log.number(factorial(5))
|
||||||
}
|
}
|
||||||
|
|
||||||
factorial(x) {
|
factorial(x) {
|
||||||
|
@ -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
19
examples/gcd/gcd.q
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,4 +8,5 @@ import (
|
|||||||
type If struct {
|
type If struct {
|
||||||
Condition *expression.Expression
|
Condition *expression.Expression
|
||||||
Body AST
|
Body AST
|
||||||
|
Else AST
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
if branch.Else != nil {
|
||||||
|
f.PushScope(branch.Else, f.File.Bytes)
|
||||||
|
err = f.CompileAST(branch.Else)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
f.PopScope()
|
||||||
|
f.AddLabel(end)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package errors
|
package errors
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
ExpectedIfBeforeElse = &Base{"Expected an 'if' block before 'else'"}
|
||||||
InvalidNumber = &Base{"Invalid number"}
|
InvalidNumber = &Base{"Invalid number"}
|
||||||
InvalidExpression = &Base{"Invalid expression"}
|
InvalidExpression = &Base{"Invalid expression"}
|
||||||
InvalidRune = &Base{"Invalid rune"}
|
InvalidRune = &Base{"Invalid rune"}
|
||||||
|
@ -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
|
||||||
|
@ -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":
|
||||||
|
3
tests/errors/ExpectedIfBeforeElse.q
Normal file
3
tests/errors/ExpectedIfBeforeElse.q
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
main() {
|
||||||
|
else {}
|
||||||
|
}
|
4
tests/errors/ExpectedIfBeforeElse2.q
Normal file
4
tests/errors/ExpectedIfBeforeElse2.q
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
main() {
|
||||||
|
loop {}
|
||||||
|
else {}
|
||||||
|
}
|
@ -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: "+"}},
|
||||||
|
@ -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},
|
||||||
|
Loading…
Reference in New Issue
Block a user