diff --git a/evaluator/switch.go b/evaluator/switch.go index ec03280..6f4123b 100644 --- a/evaluator/switch.go +++ b/evaluator/switch.go @@ -10,6 +10,11 @@ func evaluateSwitch(node *ast.Switch, scope *object.Scope) object.Object { obj := Evaluate(node.Value, scope) for _, option := range node.Cases { + // Skip default case to handle last if needed + if option.Default { + continue + } + for _, val := range option.Value { out := Evaluate(val, scope) @@ -22,5 +27,14 @@ func evaluateSwitch(node *ast.Switch, scope *object.Scope) object.Object { } } + // Handle default case + for _, option := range node.Cases { + if option.Default { + out := evaluateBlock(option.Body, scope) + + return out + } + } + return nil } diff --git a/examples/switch.ghost b/examples/switch.ghost index 1d25b10..1b65a82 100644 --- a/examples/switch.ghost +++ b/examples/switch.ghost @@ -1,25 +1,16 @@ -// value = true - -// switch (value) { -// case false { -// print("false.") -// } - -// case true { -// print("true.") -// } -// } - -beverage = "coffee" +beverage = "tea" switch (beverage) { - case "water" { - print("Water is $0.75 per bottle.") - } - case "juice" { - print("Juice is $1.25 per bottle.") - } - case "coffee", "latte" { - print("Coffee and lattes are $2.75 per 12oz.") - } + case "water" { + print("Water is $0.75 per bottle.") + } + case "juice" { + print("Juice is $1.25 per bottle.") + } + case "coffee", "latte" { + print("Coffee and lattes are $2.75 per 12oz.") + } + default { + print("Unknown beverage.") + } } \ No newline at end of file diff --git a/parser/parser_test.go b/parser/parser_test.go index c4e423d..16f7108 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -766,6 +766,101 @@ func TestReturnStatements(t *testing.T) { } } +func TestSwitchStatements(t *testing.T) { + input := `switch (value) { + case 1 { + print('one') + } + case 2 { + print('two') + } + }` + + scanner := scanner.New(input, "test.ghost") + parser := New(scanner) + program := parser.Parse() + + failIfParserHasErrors(t, parser) + + statement, ok := program.Statements[0].(*ast.Expression) + + if !ok { + t.Fatalf("program.Statements[0] is not ast.Expression. got=%T", program.Statements[0]) + } + + switchStatement, ok := statement.Expression.(*ast.Switch) + + if !ok { + t.Fatalf("statement is not ast.Switch. got=%T", statement.Expression) + } + + if len(switchStatement.Cases) != 2 { + t.Fatalf("switchStatement.Cases has wrong length. got=%d", len(switchStatement.Cases)) + } +} + +func TestSwitchStatementsWithDefault(t *testing.T) { + input := `switch (value) { + case 1 { + print('one') + } + case 2 { + print('two') + } + default { + print('default') + } + }` + + scanner := scanner.New(input, "test.ghost") + parser := New(scanner) + program := parser.Parse() + + failIfParserHasErrors(t, parser) + + statement, ok := program.Statements[0].(*ast.Expression) + + if !ok { + t.Fatalf("program.Statements[0] is not ast.Expression. got=%T", program.Statements[0]) + } + + switchStatement, ok := statement.Expression.(*ast.Switch) + + if !ok { + t.Fatalf("statement is not ast.Switch. got=%T", statement.Expression) + } + + if len(switchStatement.Cases) != 3 { + t.Fatalf("switchStatement.Cases has wrong length. got=%d", len(switchStatement.Cases)) + } +} + +func TestSwitchStatementsWithMultipleDefaults(t *testing.T) { + input := `switch (value) { + case 1 { + print('one') + } + case 2 { + print('two') + } + case default { + print('default one') + } + default { + print('default two') + } + }` + + scanner := scanner.New(input, "test.ghost") + parser := New(scanner) + parser.Parse() + + // Expecting a parser error here for having multiple defaults + if len(parser.Errors()) != 1 { + t.Fatalf("parser should have 1 error. got=%d", len(parser.Errors())) + } +} + // ============================================================================= // Helper methods diff --git a/parser/switch.go b/parser/switch.go index f1b3be4..cccaf44 100644 --- a/parser/switch.go +++ b/parser/switch.go @@ -33,25 +33,34 @@ func (parser *Parser) switchStatement() ast.ExpressionNode { for !parser.currentTokenIs(token.RIGHTBRACE) { // check for EOF - // + if parser.currentTokenIs(token.EOF) { + return nil + } switchCase := &ast.Case{Token: parser.currentToken} - if parser.currentTokenIs(token.CASE) { + if parser.currentTokenIs(token.DEFAULT) { + switchCase.Default = true + } else if parser.currentTokenIs(token.CASE) { // read "case" parser.readToken() - // A switch case can contain multiple "values" - switchCase.Value = append(switchCase.Value, parser.parseExpression(LOWEST)) + // Allow "case default" to be valid + if parser.currentTokenIs(token.DEFAULT) { + switchCase.Default = true + } else { + // A switch case can contain multiple "values" + switchCase.Value = append(switchCase.Value, parser.parseExpression(LOWEST)) - for parser.nextTokenIs(token.COMMA) { - // read the comma - parser.readToken() + for parser.nextTokenIs(token.COMMA) { + // read the comma + parser.readToken() - // setup the expression - parser.readToken() + // setup the expression + parser.readToken() - switchCase.Value = append(switchCase.Value, parser.parseExpression(LOWEST)) + switchCase.Value = append(switchCase.Value, parser.parseExpression(LOWEST)) + } } } @@ -75,5 +84,19 @@ func (parser *Parser) switchStatement() ast.ExpressionNode { return nil } + // Check for multiple default cases + defaultCount := 0 + + for _, switchCase := range expression.Cases { + if switchCase.Default { + defaultCount++ + } + } + + if defaultCount > 1 { + parser.errors = append(parser.errors, "multiple default cases in switch statement") + return nil + } + return expression } diff --git a/scanner/scanner.go b/scanner/scanner.go index 63033f4..87c2577 100644 --- a/scanner/scanner.go +++ b/scanner/scanner.go @@ -25,6 +25,7 @@ var keywords = map[string]token.Type{ "case": token.CASE, "class": token.CLASS, "continue": token.CONTINUE, + "default": token.DEFAULT, "else": token.ELSE, "extends": token.EXTENDS, "false": token.FALSE, diff --git a/token/token.go b/token/token.go index c9f0986..7e2858e 100644 --- a/token/token.go +++ b/token/token.go @@ -65,6 +65,7 @@ const ( CASE = "case" CLASS = "class" CONTINUE = "continue" + DEFAULT = "default" ELSE = "else" EXTENDS = "extends" FALSE = "false"