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,