Skip to content

Commit

Permalink
Merge pull request #198 from Flipez/implement-elseif
Browse files Browse the repository at this point in the history
Implement `if / elif / else`
  • Loading branch information
Flipez committed Nov 27, 2023
2 parents 0d64fb8 + 5c308df commit 2c802b9
Show file tree
Hide file tree
Showing 18 changed files with 142 additions and 33 deletions.
4 changes: 2 additions & 2 deletions ast/ast_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
19 changes: 15 additions & 4 deletions ast/if.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand All @@ -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 ")
Expand Down
28 changes: 19 additions & 9 deletions docs/docs/control_expressions/if.md
Original file line number Diff line number Diff line change
@@ -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")
<CodeBlockSimple input='a="test"
if (a.type() == "STRING")
puts("is a string")
else
puts("is not a string")
end
puts("is not a string")
end' output='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
```
<CodeBlockSimple input='a = "test"
if (a.type() == "BOOLEAN")
puts("is a boolean")
elif (a.type() == "STRING")
puts("is a string")
else
puts("i have no idea")
end' output='is a string' />
19 changes: 10 additions & 9 deletions evaluator/if.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
3 changes: 2 additions & 1 deletion parser/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
26 changes: 22 additions & 4 deletions parser/if.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
8 changes: 4 additions & 4 deletions parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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") {
Expand Down
2 changes: 2 additions & 0 deletions tests/if.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
"single if"
nil
3 changes: 3 additions & 0 deletions tests/if.rl
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
if (true)
puts("single if")
end
3 changes: 3 additions & 0 deletions tests/ifelif.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"if/elif: if block"
"if/elif: elif block"
nil
10 changes: 10 additions & 0 deletions tests/ifelif.rl
Original file line number Diff line number Diff line change
@@ -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
5 changes: 5 additions & 0 deletions tests/ifelifelifelse.expected
Original file line number Diff line number Diff line change
@@ -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
14 changes: 14 additions & 0 deletions tests/ifelifelifelse.rl
Original file line number Diff line number Diff line change
@@ -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
4 changes: 4 additions & 0 deletions tests/ifelifelse.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
"if/elif/el: if block"
"if/elif/el: elif block"
"if/elif/el: else block"
nil
12 changes: 12 additions & 0 deletions tests/ifelifelse.rl
Original file line number Diff line number Diff line change
@@ -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
3 changes: 3 additions & 0 deletions tests/ifelse.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"if/else: if block"
"if/else: else block"
nil
10 changes: 10 additions & 0 deletions tests/ifelse.rl
Original file line number Diff line number Diff line change
@@ -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
2 changes: 2 additions & 0 deletions token/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ const (
TRUE = "TRUE"
FALSE = "FALSE"
IF = "IF"
ELIF = "ELIF"
ELSE = "ELSE"
END = "END"
RETURN = "RETURN"
Expand Down Expand Up @@ -90,6 +91,7 @@ var keywords = map[string]TokenType{
"true": TRUE,
"false": FALSE,
"if": IF,
"elif": ELIF,
"end": END,
"else": ELSE,
"return": RETURN,
Expand Down

2 comments on commit 2c802b9

@vercel
Copy link

@vercel vercel bot commented on 2c802b9 Nov 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on 2c802b9 Nov 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.