Skip to content

Commit be31f09

Browse files
committed
Add switch support (#3)
1 parent 3f1d8fa commit be31f09

5 files changed

Lines changed: 323 additions & 37 deletions

File tree

lexer/lexer.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ const (
6363
RETURN
6464
IF
6565
ELSE
66+
SWITCH
67+
CASE
68+
DEFAULT
6669
FOR
6770
RANGE
6871
BREAK
@@ -182,6 +185,9 @@ var keywords = map[string]TokenType{
182185
"return": RETURN,
183186
"if": IF,
184187
"else": ELSE,
188+
"switch": SWITCH,
189+
"case": CASE,
190+
"default": DEFAULT,
185191
"for": FOR,
186192
"range": RANGE,
187193
"break": BREAK,

parser/parser.go

Lines changed: 153 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -634,7 +634,7 @@ func (p *Parser) evaluateProgram() (Program, error) {
634634
if err != nil {
635635
return Program{}, err
636636
}
637-
statementsTemp, err := p.evaluateBlockContent(lexer.EOF, nil, ctx, SCOPE_PROGRAM)
637+
statementsTemp, err := p.evaluateBlockContent([]lexer.TokenType{lexer.EOF}, nil, ctx, SCOPE_PROGRAM)
638638

639639
if err != nil {
640640
return Program{}, err
@@ -828,7 +828,7 @@ func (p *Parser) evaluateBlockBegin() error {
828828
return nil
829829
}
830830

831-
func (p *Parser) evaluateBlockContent(terminationTokenType lexer.TokenType, callback blockCallback, ctx context, scope scope) ([]Statement, error) {
831+
func (p *Parser) evaluateBlockContent(terminationTokenTypes []lexer.TokenType, callback blockCallback, ctx context, scope scope) ([]Statement, error) {
832832
var err error
833833

834834
statements := []Statement{}
@@ -853,42 +853,43 @@ func (p *Parser) evaluateBlockContent(terminationTokenType lexer.TokenType, call
853853
tokenType := token.Type()
854854
var stmt Statement
855855

856-
switch tokenType {
857-
case terminationTokenType:
858-
// Just break on termination token.
859-
loop = false
860-
case lexer.NEWLINE:
861-
// Ignore termination tokens as they are handled after the switch.
862-
default:
863-
stmt, err = p.evaluateStatement(ctx)
864-
prefix := p.prefix
865-
866-
if err != nil {
867-
break
868-
}
869-
global := ctx.global()
870-
871-
switch stmt.StatementType() {
872-
case STATEMENT_TYPE_VAR_DEFINITION:
873-
// Store new variable.
874-
err = ctx.addVariables(prefix, global, stmt.(VariableDefinition).Variables()...)
856+
if slices.Contains(terminationTokenTypes, tokenType) {
857+
loop = false // Just break on termination token.
858+
} else {
859+
switch tokenType {
860+
case lexer.NEWLINE:
861+
// Ignore termination tokens as they are handled after the switch.
862+
default:
863+
stmt, err = p.evaluateStatement(ctx)
864+
prefix := p.prefix
875865

876866
if err != nil {
877-
return nil, err
867+
break
878868
}
879-
case STATEMENT_TYPE_VAR_DEFINITION_CALL_ASSIGNMENT:
880-
// Store new variable.
881-
err = ctx.addVariables(prefix, global, stmt.(VariableDefinitionCallAssignment).Variables()...)
869+
global := ctx.global()
882870

883-
if err != nil {
884-
return nil, err
885-
}
886-
case STATEMENT_TYPE_FUNCTION_DEFINITION:
887-
// Store new function.
888-
err = ctx.addFunctions(prefix, global, stmt.(FunctionDefinition))
871+
switch stmt.StatementType() {
872+
case STATEMENT_TYPE_VAR_DEFINITION:
873+
// Store new variable.
874+
err = ctx.addVariables(prefix, global, stmt.(VariableDefinition).Variables()...)
889875

890-
if err != nil {
891-
return nil, err
876+
if err != nil {
877+
return nil, err
878+
}
879+
case STATEMENT_TYPE_VAR_DEFINITION_CALL_ASSIGNMENT:
880+
// Store new variable.
881+
err = ctx.addVariables(prefix, global, stmt.(VariableDefinitionCallAssignment).Variables()...)
882+
883+
if err != nil {
884+
return nil, err
885+
}
886+
case STATEMENT_TYPE_FUNCTION_DEFINITION:
887+
// Store new function.
888+
err = ctx.addFunctions(prefix, global, stmt.(FunctionDefinition))
889+
890+
if err != nil {
891+
return nil, err
892+
}
892893
}
893894
}
894895
}
@@ -915,7 +916,7 @@ func (p *Parser) evaluateBlockContent(terminationTokenType lexer.TokenType, call
915916
// Expect newline or termination token.
916917
if terminationToken.Type() == lexer.NEWLINE {
917918
p.eat()
918-
} else if terminationToken.Type() != terminationTokenType {
919+
} else if !slices.Contains(terminationTokenTypes, terminationToken.Type()) {
919920
err = p.expectedError("termination token", terminationToken)
920921
break
921922
}
@@ -938,7 +939,7 @@ func (p *Parser) evaluateBlock(callback blockCallback, ctx context, scope scope)
938939
if err != nil {
939940
return nil, err
940941
}
941-
statements, err := p.evaluateBlockContent(lexer.CLOSING_CURLY_BRACKET, callback, ctx, scope)
942+
statements, err := p.evaluateBlockContent([]lexer.TokenType{lexer.CLOSING_CURLY_BRACKET}, callback, ctx, scope)
942943

943944
if err != nil {
944945
return nil, err
@@ -1617,6 +1618,119 @@ func (p *Parser) evaluateIf(ctx context) (Statement, error) {
16171618
return ifStatement, nil
16181619
}
16191620

1621+
func (p *Parser) evaluateSwitch(ctx context) (Statement, error) {
1622+
switchToken := p.eat()
1623+
1624+
if switchToken.Type() != lexer.SWITCH {
1625+
return nil, p.expectedError("switch-keyword", switchToken)
1626+
}
1627+
var switchExpr Expression
1628+
var err error
1629+
1630+
exprToken := p.peek()
1631+
1632+
if exprToken.Type() == lexer.OPENING_CURLY_BRACKET {
1633+
switchExpr = BooleanLiteral{true}
1634+
} else {
1635+
switchExpr, err = p.evaluateExpression(ctx)
1636+
1637+
if err != nil {
1638+
return nil, err
1639+
}
1640+
}
1641+
switchExprValueType := switchExpr.ValueType()
1642+
1643+
if switchExprValueType.IsSlice() {
1644+
return nil, p.atError("slices are not allowed in switch statements", exprToken)
1645+
}
1646+
beginToken := p.eat()
1647+
1648+
if beginToken.Type() != lexer.OPENING_CURLY_BRACKET {
1649+
return nil, p.expectedError(`{`, beginToken)
1650+
}
1651+
nextToken := p.eat()
1652+
1653+
if nextToken.Type() != lexer.NEWLINE {
1654+
return nil, p.expectedError("newline", nextToken)
1655+
}
1656+
fakeIf := If{
1657+
ifBranch: IfBranch{
1658+
condition: BooleanLiteral{false}, // Use a fake if-branch that isn't entered if only a default branch has been set in switch.
1659+
body: []Statement{},
1660+
},
1661+
}
1662+
useMock := true
1663+
nextToken = p.peek()
1664+
defaultSet := false
1665+
1666+
// While switch has not been terminated, evaluate cases.
1667+
for nextToken.Type() != lexer.CLOSING_CURLY_BRACKET {
1668+
var compareExpr Expression
1669+
var compareExprToken lexer.Token
1670+
1671+
switch nextToken.Type() {
1672+
case lexer.CASE:
1673+
p.eat() // Eat case-token.
1674+
compareExprToken = p.peek()
1675+
exprTemp, err := p.evaluateExpression(ctx)
1676+
1677+
if err != nil {
1678+
return nil, err
1679+
}
1680+
compareExpr = exprTemp
1681+
case lexer.DEFAULT:
1682+
p.eat() // Eat default-token.
1683+
default:
1684+
return nil, p.expectedError(`"case", "default" or "}"`, nextToken)
1685+
}
1686+
colonToken := p.eat()
1687+
1688+
if colonToken.Type() != lexer.COLON {
1689+
return nil, p.expectedError(`":"`, colonToken)
1690+
}
1691+
statements, err := p.evaluateBlockContent([]lexer.TokenType{lexer.CASE, lexer.DEFAULT, lexer.CLOSING_CURLY_BRACKET}, nil, ctx, SCOPE_SWITCH)
1692+
1693+
if err != nil {
1694+
return nil, err
1695+
}
1696+
1697+
// Check if non-default case.
1698+
if compareExpr != nil {
1699+
compareExprValueType := compareExpr.ValueType()
1700+
1701+
if !switchExprValueType.Equals(compareExprValueType) {
1702+
return nil, p.atError(fmt.Sprintf("%s value cannot be compared with switch's %s value", compareExprValueType.String(), switchExprValueType.String()), compareExprToken)
1703+
}
1704+
ifBranch := IfBranch{
1705+
condition: NewComparison(switchExpr, COMPARE_OPERATOR_EQUAL, compareExpr),
1706+
body: statements,
1707+
}
1708+
1709+
// If fake-if has not been overwritten, overwrite it now.
1710+
if useMock {
1711+
fakeIf.ifBranch = ifBranch
1712+
useMock = false
1713+
} else {
1714+
fakeIf.elifBranches = append(fakeIf.elifBranches, ifBranch)
1715+
}
1716+
} else if !defaultSet {
1717+
fakeIf.elseBranch = Else{
1718+
body: statements,
1719+
}
1720+
defaultSet = true
1721+
} else {
1722+
return nil, p.atError("multiple default cases are not allowed", nextToken)
1723+
}
1724+
nextToken = p.peek()
1725+
}
1726+
p.eat() // Eat "}" token.
1727+
1728+
if nextToken.Type() != lexer.CLOSING_CURLY_BRACKET {
1729+
return nil, p.expectedError(`"}"`, nextToken)
1730+
}
1731+
return fakeIf, nil
1732+
}
1733+
16201734
func (p *Parser) evaluateFor(ctx context) (Statement, error) {
16211735
forToken := p.eat()
16221736

@@ -1642,7 +1756,7 @@ func (p *Parser) evaluateFor(ctx context) (Statement, error) {
16421756
nextToken = p.eat()
16431757

16441758
if nextToken.Type() != lexer.COMMA {
1645-
return nil, p.expectedError("\",\"", nextToken)
1759+
return nil, p.expectedError(`","`, nextToken)
16461760
}
16471761
nextToken = p.eat()
16481762
err = p.checkNewVariableNameToken(nextToken, ctx)
@@ -1654,7 +1768,7 @@ func (p *Parser) evaluateFor(ctx context) (Statement, error) {
16541768
nextToken = p.eat()
16551769

16561770
if nextToken.Type() != lexer.SHORT_INIT_OPERATOR {
1657-
return nil, p.expectedError("\":=\"", nextToken)
1771+
return nil, p.expectedError(`":="`, nextToken)
16581772
}
16591773
nextToken = p.eat()
16601774

@@ -2033,6 +2147,8 @@ func (p *Parser) evaluateStatement(ctx context) (Statement, error) {
20332147
stmt, err = p.evaluateReturn(ctx)
20342148
case lexer.IF:
20352149
stmt, err = p.evaluateIf(ctx)
2150+
case lexer.SWITCH:
2151+
stmt, err = p.evaluateSwitch(ctx)
20362152
case lexer.FOR:
20372153
stmt, err = p.evaluateFor(ctx)
20382154
case lexer.BREAK:

tests/switch.go

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
package tests
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
)
8+
9+
func testSwitchWithBoolSuccess(t *testing.T, transpilerFunc transpilerFunc) {
10+
transpilerFunc(t, `
11+
a := true
12+
13+
switch a {
14+
case true:
15+
print("ok")
16+
case false:
17+
print("nok")
18+
}
19+
`, func(output string, err error) {
20+
require.Nil(t, err)
21+
require.Equal(t, "ok", output)
22+
})
23+
}
24+
25+
func testSwitchWithBoolDefaultSuccess(t *testing.T, transpilerFunc transpilerFunc) {
26+
transpilerFunc(t, `
27+
a := true
28+
29+
switch a {
30+
case false:
31+
print("nok")
32+
default:
33+
print("ok")
34+
}
35+
`, func(output string, err error) {
36+
require.Nil(t, err)
37+
require.Equal(t, "ok", output)
38+
})
39+
}
40+
41+
func testSwitchWithImplicitBoolSuccess(t *testing.T, transpilerFunc transpilerFunc) {
42+
transpilerFunc(t, `
43+
a := true
44+
45+
switch {
46+
case true:
47+
print("ok")
48+
default:
49+
print("nok")
50+
}
51+
`, func(output string, err error) {
52+
require.Nil(t, err)
53+
require.Equal(t, "ok", output)
54+
})
55+
}
56+
57+
func testSwitchWithComparisonsSuccess(t *testing.T, transpilerFunc transpilerFunc) {
58+
transpilerFunc(t, `
59+
a := 1
60+
b := 2
61+
62+
switch {
63+
case a == 1 && b == 1:
64+
print("nok")
65+
case a == 1 && b > 1:
66+
print("ok")
67+
default:
68+
print("nok")
69+
}
70+
`, func(output string, err error) {
71+
require.Nil(t, err)
72+
require.Equal(t, "ok", output)
73+
})
74+
}
75+
76+
func testSwitchOnlyDefaultSuccess(t *testing.T, transpilerFunc transpilerFunc) {
77+
transpilerFunc(t, `
78+
switch {
79+
default:
80+
print("ok")
81+
}
82+
`, func(output string, err error) {
83+
require.Nil(t, err)
84+
require.Equal(t, "ok", output)
85+
})
86+
}
87+
88+
func testSwitchStringsSuccess(t *testing.T, transpilerFunc transpilerFunc) {
89+
transpilerFunc(t, `
90+
a := "c"
91+
92+
switch a {
93+
case "a":
94+
print("nok")
95+
case "b":
96+
print("nok")
97+
case "c":
98+
print("ok")
99+
default:
100+
print("nok")
101+
}
102+
`, func(output string, err error) {
103+
require.Nil(t, err)
104+
require.Equal(t, "ok", output)
105+
})
106+
}

0 commit comments

Comments
 (0)