diff --git a/ast/ast_test.go b/ast/ast_test.go index 32fef12..352bbdd 100644 --- a/ast/ast_test.go +++ b/ast/ast_test.go @@ -33,8 +33,8 @@ func TestString(t *testing.T) { "foreach i, e in [1, 2, 3] \n puts(i)\nend", }, { - "if (true)\n return (true)\nelse\n puts(false)\nend", - "if (true)\n return (true)\nelse\n puts(false)\nend", + "if (true)\n return (true)\nelif (true)\n return (true)\nelse\n puts(false)\nend", + "if (true)\n return (true)\nelif (true)\n return (true)\nelse\n puts(false)\nend", }, { "while (true)\n puts(true)\nend", diff --git a/ast/if.go b/ast/if.go index 1415f45..5c73d26 100644 --- a/ast/if.go +++ b/ast/if.go @@ -6,10 +6,14 @@ import ( "github.com/flipez/rocket-lang/token" ) -type If struct { - Token token.Token // the if token +type ConditionConsequencePair struct { Condition Expression Consequence *Block +} + +type If struct { + Token token.Token // the if token + ConConPairs []ConditionConsequencePair Alternative *Block } @@ -18,9 +22,16 @@ func (ie *If) String() string { var out bytes.Buffer out.WriteString("if (") - out.WriteString(ie.Condition.String()) + out.WriteString(ie.ConConPairs[0].Condition.String()) out.WriteString(")\n ") - out.WriteString(ie.Consequence.String()) + out.WriteString(ie.ConConPairs[0].Consequence.String()) + + for _, pair := range ie.ConConPairs[1:] { + out.WriteString("\nelif (") + out.WriteString(pair.Condition.String()) + out.WriteString(")\n ") + out.WriteString(pair.Consequence.String()) + } if ie.Alternative != nil { out.WriteString("\nelse\n ") diff --git a/docs/docs/control_expressions/if.md b/docs/docs/control_expressions/if.md index 02ecd12..2111508 100644 --- a/docs/docs/control_expressions/if.md +++ b/docs/docs/control_expressions/if.md @@ -1,19 +1,29 @@ --- -title: "If" +title: "If / elif / else" menu: docs: parent: "controls" --- +import CodeBlockSimple from '@site/components/CodeBlockSimple' + # If With `if` and `else` keywords the flow of a program can be controlled. -```js -🚀 > if (a.type() == "STRING") - puts("is a string") + + +# Elif +`elif` allows providing of an additional consequence check after `if` and before evaluating the alternative provided by `else`. There is no limit on how many `elif` statements can be used. -// which prints -is a string -``` \ No newline at end of file + \ No newline at end of file diff --git a/evaluator/if.go b/evaluator/if.go index 9f7f25b..db54067 100644 --- a/evaluator/if.go +++ b/evaluator/if.go @@ -6,16 +6,17 @@ import ( ) func evalIf(ie *ast.If, env *object.Environment) object.Object { - condition := Eval(ie.Condition, env) - - if object.IsError(condition) { - return condition + for _, pair := range ie.ConConPairs { + condition := Eval(pair.Condition, env) + if object.IsError(condition) { + return condition + } + if object.IsTruthy(condition) { + return Eval(pair.Consequence, env) + } } - if object.IsTruthy(condition) { - return Eval(ie.Consequence, env) - } else if ie.Alternative != nil { + if ie.Alternative != nil { return Eval(ie.Alternative, env) - } else { - return object.NIL } + return object.NIL } diff --git a/parser/block.go b/parser/block.go index 7b8a317..4f535a8 100644 --- a/parser/block.go +++ b/parser/block.go @@ -11,7 +11,8 @@ func (p *Parser) parseBlock() *ast.Block { p.nextToken() - for !p.curTokenIs(token.RBRACE) && !p.curTokenIs(token.EOF) && !p.curTokenIs(token.END) && !p.curTokenIs(token.ELSE) && !p.curTokenIs(token.RESCUE) { + for !p.curTokenIs(token.RBRACE) && !p.curTokenIs(token.EOF) && !p.curTokenIs(token.END) && !p.curTokenIs(token.ELSE) && !p.curTokenIs(token.ELIF) && !p.curTokenIs(token.RESCUE) { + stmt := p.parseStatement() if stmt != nil { block.Statements = append(block.Statements, stmt) diff --git a/parser/if.go b/parser/if.go index 33a3f75..77028a5 100644 --- a/parser/if.go +++ b/parser/if.go @@ -6,23 +6,41 @@ import ( ) func (p *Parser) parseIf() ast.Expression { - expression := &ast.If{Token: p.curToken} + expression := &ast.If{Token: p.curToken, ConConPairs: make([]ast.ConditionConsequencePair, 1)} if !p.expectPeek(token.LPAREN) { return nil } p.nextToken() - expression.Condition = p.parseExpression(LOWEST) + expression.ConConPairs[0].Condition = p.parseExpression(LOWEST) if !p.expectPeek(token.RPAREN) { return nil } - expression.Consequence = p.parseBlock() + expression.ConConPairs[0].Consequence = p.parseBlock() - if p.curTokenIs(token.ELSE) { + for p.curTokenIs(token.ELIF) { + + if !p.expectPeek(token.LPAREN) { + return nil + } + p.nextToken() + + var pair ast.ConditionConsequencePair + pair.Condition = p.parseExpression(LOWEST) + + if !p.expectPeek(token.RPAREN) { + return nil + } + pair.Consequence = p.parseBlock() + expression.ConConPairs = append(expression.ConConPairs, pair) + } + + if p.curTokenIs(token.ELSE) { expression.Alternative = p.parseBlock() } + return expression } diff --git a/parser/parser_test.go b/parser/parser_test.go index e0b05d5..fb95d99 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -443,17 +443,17 @@ func TestIfExpression(t *testing.T) { t.Fatalf("stmt.Expression is not ast.IfExpression. got=%T", stmt.Expression) } - if !testInfix(t, exp.Condition, "x", "<", "y") { + if !testInfix(t, exp.ConConPairs[0].Condition, "x", "<", "y") { return } - if len(exp.Consequence.Statements) != 1 { + if len(exp.ConConPairs[0].Consequence.Statements) != 1 { t.Errorf("consequence is not 1 statements. got=%d\n", len(program.Statements)) } - consequence, ok := exp.Consequence.Statements[0].(*ast.ExpressionStatement) + consequence, ok := exp.ConConPairs[0].Consequence.Statements[0].(*ast.ExpressionStatement) if !ok { - t.Fatalf("Statements[0] is not ast.ExpressionStatement. got=%T", exp.Consequence.Statements[0]) + t.Fatalf("Statements[0] is not ast.ExpressionStatement. got=%T", exp.ConConPairs[0].Consequence.Statements[0]) } if !testIdentifier(t, consequence.Expression, "x") { diff --git a/tests/if.expected b/tests/if.expected new file mode 100644 index 0000000..3e312ad --- /dev/null +++ b/tests/if.expected @@ -0,0 +1,2 @@ +"single if" +nil diff --git a/tests/if.rl b/tests/if.rl new file mode 100644 index 0000000..9da97b9 --- /dev/null +++ b/tests/if.rl @@ -0,0 +1,3 @@ +if (true) + puts("single if") +end diff --git a/tests/ifelif.expected b/tests/ifelif.expected new file mode 100644 index 0000000..218b02f --- /dev/null +++ b/tests/ifelif.expected @@ -0,0 +1,3 @@ +"if/elif: if block" +"if/elif: elif block" +nil diff --git a/tests/ifelif.rl b/tests/ifelif.rl new file mode 100644 index 0000000..0a3395e --- /dev/null +++ b/tests/ifelif.rl @@ -0,0 +1,10 @@ +i = 0 +while (i<2) + if (i==0) + puts("if/elif: if block") + elif (i==1) + puts("if/elif: elif block") + end + + i = i+1 +end diff --git a/tests/ifelifelifelse.expected b/tests/ifelifelifelse.expected new file mode 100644 index 0000000..2f98d16 --- /dev/null +++ b/tests/ifelifelifelse.expected @@ -0,0 +1,5 @@ +"if/elif/el: if block" +"if/elif/elif/el: first elif block" +"if/elif/elif/el: second elif block" +"if/elif/else: else block" +nil diff --git a/tests/ifelifelifelse.rl b/tests/ifelifelifelse.rl new file mode 100644 index 0000000..0bccff2 --- /dev/null +++ b/tests/ifelifelifelse.rl @@ -0,0 +1,14 @@ +i = 0 +while (i<4) + if (i==0) + puts("if/elif/el: if block") + elif (i==1) + puts("if/elif/elif/el: first elif block") + elif (i==2) + puts("if/elif/elif/el: second elif block") + else + puts("if/elif/else: else block") + end + + i = i+1 +end diff --git a/tests/ifelifelse.expected b/tests/ifelifelse.expected new file mode 100644 index 0000000..2b102e8 --- /dev/null +++ b/tests/ifelifelse.expected @@ -0,0 +1,4 @@ +"if/elif/el: if block" +"if/elif/el: elif block" +"if/elif/el: else block" +nil diff --git a/tests/ifelifelse.rl b/tests/ifelifelse.rl new file mode 100644 index 0000000..21d863f --- /dev/null +++ b/tests/ifelifelse.rl @@ -0,0 +1,12 @@ +i = 0 +while (i<3) + if (i==0) + puts("if/elif/el: if block") + elif (i==1) + puts("if/elif/el: elif block") + else + puts("if/elif/el: else block") + end + + i = i+1 +end diff --git a/tests/ifelse.expected b/tests/ifelse.expected new file mode 100644 index 0000000..f131044 --- /dev/null +++ b/tests/ifelse.expected @@ -0,0 +1,3 @@ +"if/else: if block" +"if/else: else block" +nil diff --git a/tests/ifelse.rl b/tests/ifelse.rl new file mode 100644 index 0000000..0b0ba2d --- /dev/null +++ b/tests/ifelse.rl @@ -0,0 +1,10 @@ +i = 0 +while (i<2) + if (i==0) + puts("if/else: if block") + else + puts("if/else: else block") + end + + i = i+1 +end diff --git a/token/token.go b/token/token.go index 95b687b..c346a7b 100644 --- a/token/token.go +++ b/token/token.go @@ -56,6 +56,7 @@ const ( TRUE = "TRUE" FALSE = "FALSE" IF = "IF" + ELIF = "ELIF" ELSE = "ELSE" END = "END" RETURN = "RETURN" @@ -90,6 +91,7 @@ var keywords = map[string]TokenType{ "true": TRUE, "false": FALSE, "if": IF, + "elif": ELIF, "end": END, "else": ELSE, "return": RETURN,