Implemented switch statements

This commit is contained in:
Eduard Urbach 2024-08-03 22:24:40 +02:00
parent d07b455f67
commit dbf416d45b
Signed by: akyoto
GPG Key ID: C874F672B1AF20C0
10 changed files with 170 additions and 27 deletions

View File

@ -9,18 +9,18 @@ fizzbuzz(n) {
x := 1
loop {
// TODO: implement switch statement
if x % 15 == 0 {
print("FizzBuzz", 8)
} else {
if x % 5 == 0 {
switch {
x % 15 == 0 {
print("FizzBuzz", 8)
}
x % 5 == 0 {
print("Buzz", 4)
} else {
if x % 3 == 0 {
print("Fizz", 4)
} else {
log.number(x)
}
}
x % 3 == 0 {
print("Fizz", 4)
}
_ {
log.number(x)
}
}
@ -28,9 +28,9 @@ fizzbuzz(n) {
if x > n {
return
} else {
print(" ", 1)
}
print(" ", 1)
}
}

View File

@ -20,11 +20,6 @@ func Count(body AST, buffer []byte, kind token.Kind, name string) uint8 {
case *Define:
count += node.Expression.Count(buffer, kind, name)
case *Return:
if node.Value != nil {
count += node.Value.Count(buffer, kind, name)
}
case *If:
count += node.Condition.Count(buffer, kind, name)
count += Count(node.Body, buffer, kind, name)
@ -33,6 +28,20 @@ func Count(body AST, buffer []byte, kind token.Kind, name string) uint8 {
case *Loop:
count += Count(node.Body, buffer, kind, name)
case *Return:
if node.Value != nil {
count += node.Value.Count(buffer, kind, name)
}
case *Switch:
for _, c := range node.Cases {
if c.Condition != nil {
count += c.Condition.Count(buffer, kind, name)
}
count += Count(c.Body, buffer, kind, name)
}
default:
panic("unknown AST type")
}

16
src/build/ast/Switch.go Normal file
View File

@ -0,0 +1,16 @@
package ast
import (
"git.akyoto.dev/cli/q/src/build/expression"
)
// Switch represents a switch statement.
type Switch struct {
Cases []Case
}
// Case represents a case inside a switch.
type Case struct {
Condition *expression.Expression
Body AST
}

View File

@ -51,6 +51,21 @@ func parseKeyword(tokens token.List, source []byte, nodes AST) (Node, error) {
value := expression.Parse(tokens[1:])
return &Return{Value: value}, nil
case token.Switch:
blockStart := tokens.IndexKind(token.BlockStart)
blockEnd := tokens.LastIndexKind(token.BlockEnd)
if blockStart == -1 {
return nil, errors.New(errors.MissingBlockStart, nil, tokens[0].End())
}
if blockEnd == -1 {
return nil, errors.New(errors.MissingBlockEnd, nil, tokens[len(tokens)-1].End())
}
cases, err := parseSwitch(tokens[blockStart+1:blockEnd], source)
return &Switch{Cases: cases}, err
default:
return nil, errors.New(&errors.KeywordNotImplemented{Keyword: tokens[0].Text(source)}, nil, tokens[0].Position)
}

View File

@ -0,0 +1,37 @@
package ast
import (
"git.akyoto.dev/cli/q/src/build/expression"
"git.akyoto.dev/cli/q/src/build/token"
)
// parseSwitch generates the cases inside a switch statement.
func parseSwitch(tokens token.List, source []byte) ([]Case, error) {
var cases []Case
err := EachInstruction(tokens, func(caseTokens token.List) error {
blockStart, _, body, err := block(caseTokens, source)
if err != nil {
return err
}
conditionTokens := caseTokens[:blockStart]
var condition *expression.Expression
if len(conditionTokens) == 1 && conditionTokens[0].Kind == token.Identifier && conditionTokens[0].Text(source) == "_" {
condition = nil
} else {
condition = expression.Parse(conditionTokens)
}
cases = append(cases, Case{
Condition: condition,
Body: body,
})
return nil
})
return cases, err
}

View File

@ -23,10 +23,6 @@ func (f *Function) CompileASTNode(node ast.Node) error {
f.Fold(node.Expression)
return f.CompileDefinition(node)
case *ast.Return:
f.Fold(node.Value)
return f.CompileReturn(node)
case *ast.If:
f.Fold(node.Condition)
return f.CompileIf(node)
@ -34,6 +30,17 @@ func (f *Function) CompileASTNode(node ast.Node) error {
case *ast.Loop:
return f.CompileLoop(node)
case *ast.Return:
f.Fold(node.Value)
return f.CompileReturn(node)
case *ast.Switch:
for _, c := range node.Cases {
f.Fold(c.Condition)
}
return f.CompileSwitch(node)
default:
panic("unknown AST type")
}

View File

@ -0,0 +1,55 @@
package core
import (
"fmt"
"git.akyoto.dev/cli/q/src/build/asm"
"git.akyoto.dev/cli/q/src/build/ast"
)
// CompileSwitch compiles a multi-branch instruction.
func (f *Function) CompileSwitch(s *ast.Switch) error {
f.count.multiBranch++
end := fmt.Sprintf("%s_switch_%d_end", f.UniqueName, f.count.multiBranch)
for _, branch := range s.Cases {
if branch.Condition == nil {
f.PushScope(branch.Body, f.File.Bytes)
err := f.CompileAST(branch.Body)
if err != nil {
return err
}
f.PopScope()
break
}
f.count.branch++
var (
success = fmt.Sprintf("%s_case_%d_true", f.UniqueName, f.count.branch)
fail = fmt.Sprintf("%s_case_%d_false", f.UniqueName, f.count.branch)
err = f.CompileCondition(branch.Condition, success, fail)
)
if err != nil {
return err
}
f.AddLabel(success)
f.PushScope(branch.Body, f.File.Bytes)
err = f.CompileAST(branch.Body)
if err != nil {
return err
}
f.Jump(asm.JUMP, end)
f.PopScope()
f.AddLabel(fail)
}
f.AddLabel(end)
return nil
}

View File

@ -22,9 +22,10 @@ type Function struct {
// counter stores how often a certain statement appeared so we can generate a unique label from it.
type counter struct {
assert int
branch int
data int
loop int
subBranch int
assert int
branch int
multiBranch int
data int
loop int
subBranch int
}

View File

@ -69,5 +69,6 @@ const (
Import // import
Loop // loop
Return // return
Switch // switch
_keywordsEnd // </keywords>
)

View File

@ -151,6 +151,8 @@ func Tokenize(buffer []byte) List {
kind = Loop
case "return":
kind = Return
case "switch":
kind = Switch
}
tokens = append(tokens, Token{Kind: kind, Position: position, Length: Length(len(identifier))})