Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions ast/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,12 +200,13 @@ type PointerNode struct {
Name string // Name of the pointer. Like "index" in "#index".
}

// ConditionalNode represents a ternary operator.
// ConditionalNode represents a ternary operator or if/else operator.
type ConditionalNode struct {
base
Cond Node // Condition of the ternary operator. Like "foo" in "foo ? bar : baz".
Exp1 Node // Expression 1 of the ternary operator. Like "bar" in "foo ? bar : baz".
Exp2 Node // Expression 2 of the ternary operator. Like "baz" in "foo ? bar : baz".
Ternary bool // Is it ternary or if/else operator?
Cond Node // Condition
Exp1 Node // Expression 1
Exp2 Node // Expression 2
}

// VariableDeclaratorNode represents a variable declaration.
Expand Down
10 changes: 10 additions & 0 deletions ast/print.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,16 @@ func (n *SequenceNode) String() string {
}

func (n *ConditionalNode) String() string {
if !n.Ternary {
cond := n.Cond.String()
exp1 := n.Exp1.String()
if c2, ok := n.Exp2.(*ConditionalNode); ok && !c2.Ternary {
return fmt.Sprintf("if %s { %s } else %s", cond, exp1, c2.String())
}
exp2 := n.Exp2.String()
return fmt.Sprintf("if %s { %s } else { %s }", cond, exp1, exp2)
}

var cond, exp1, exp2 string
if _, ok := n.Cond.(*ConditionalNode); ok {
cond = fmt.Sprintf("(%s)", n.Cond.String())
Expand Down
3 changes: 2 additions & 1 deletion ast/print_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ func TestPrint(t *testing.T) {
{`(2 ** 2) ** 3`, `(2 ** 2) ** 3`},
{`(3 + 5) / (5 % 3)`, `(3 + 5) / (5 % 3)`},
{`(-(1+1)) == 2`, `-(1 + 1) == 2`},
{`if true { 1 } else { 2 }`, `true ? 1 : 2`},
{`if true { 1 } else { 2 }`, `if true { 1 } else { 2 }`},
{`if true { 1 } else if false { 2 } else { 3 }`, `if true { 1 } else if false { 2 } else { 3 }`},
}

for _, tt := range tests {
Expand Down
4 changes: 4 additions & 0 deletions expr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1390,6 +1390,10 @@ func TestExpr(t *testing.T) {
`if "a" < "b" {let x = "a"; x} else {"abc"}`,
"a",
},
{
`if 1 == 2 { "no" } else if 1 == 1 { "yes" } else { "maybe" }`,
"yes",
},
Comment on lines +1393 to +1396
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

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

Consider adding a test case with multiple chained else if statements to ensure the feature works correctly for longer chains. For example: if 1 == 2 { "a" } else if 2 == 3 { "b" } else if 3 == 3 { "c" } else { "d" } with expected result "c".

Copilot uses AI. Check for mistakes.
{
`1; 2; 3`,
3,
Expand Down
19 changes: 13 additions & 6 deletions parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -334,9 +334,15 @@ func (p *Parser) parseConditionalIf() Node {
expr1 := p.parseSequenceExpression()
p.expect(Bracket, "}")
p.expect(Operator, "else")
p.expect(Bracket, "{")
expr2 := p.parseSequenceExpression()
p.expect(Bracket, "}")

var expr2 Node
if p.current.Is(Operator, "if") {
expr2 = p.parseConditionalIf()
} else {
p.expect(Bracket, "{")
expr2 = p.parseSequenceExpression()
p.expect(Bracket, "}")
}

return &ConditionalNode{
Cond: nodeCondition,
Expand All @@ -362,9 +368,10 @@ func (p *Parser) parseConditional(node Node) Node {
}

node = p.createNode(&ConditionalNode{
Cond: node,
Exp1: expr1,
Exp2: expr2,
Ternary: true,
Cond: node,
Exp1: expr1,
Exp2: expr2,
}, p.current.Location)
if node == nil {
return nil
Expand Down
92 changes: 68 additions & 24 deletions parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,15 +197,19 @@ world`},
},
{
"true ? true : false",
&ConditionalNode{Cond: &BoolNode{Value: true},
Exp1: &BoolNode{Value: true},
Exp2: &BoolNode{}},
&ConditionalNode{
Ternary: true,
Cond: &BoolNode{Value: true},
Exp1: &BoolNode{Value: true},
Exp2: &BoolNode{}},
},
{
"a?[b]:c",
&ConditionalNode{Cond: &IdentifierNode{Value: "a"},
Exp1: &ArrayNode{Nodes: []Node{&IdentifierNode{Value: "b"}}},
Exp2: &IdentifierNode{Value: "c"}},
&ConditionalNode{
Ternary: true,
Cond: &IdentifierNode{Value: "a"},
Exp1: &ArrayNode{Nodes: []Node{&IdentifierNode{Value: "b"}}},
Exp2: &IdentifierNode{Value: "c"}},
},
{
"a.b().c().d[33]",
Expand Down Expand Up @@ -396,6 +400,7 @@ world`},
{
"2==2 ? false : 3 not in [1, 2, 5]",
&ConditionalNode{
Ternary: true,
Cond: &BinaryNode{
Operator: "==",
Left: &IntegerNode{Value: 2},
Expand Down Expand Up @@ -659,6 +664,29 @@ world`},
Exp1: &BoolNode{Value: true},
Exp2: &IdentifierNode{Value: "x"}},
},
{
"if a { 1 } else if b { 2 } else { 3 }",
&ConditionalNode{
Cond: &IdentifierNode{Value: "a"},
Exp1: &IntegerNode{Value: 1},
Exp2: &ConditionalNode{
Cond: &IdentifierNode{Value: "b"},
Exp1: &IntegerNode{Value: 2},
Exp2: &IntegerNode{Value: 3}}},
},
Comment on lines +667 to +676
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

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

Consider adding a test case for multiple chained else if statements to verify that the recursive implementation correctly handles chains longer than two conditionals. For example: if a { 1 } else if b { 2 } else if c { 3 } else { 4 } would test that the parser can handle arbitrarily long chains of conditionals.

Copilot uses AI. Check for mistakes.
{
"if a { 1 } else if b { 2 } else if c { 3 } else { 4 }",
&ConditionalNode{
Cond: &IdentifierNode{Value: "a"},
Exp1: &IntegerNode{Value: 1},
Exp2: &ConditionalNode{
Cond: &IdentifierNode{Value: "b"},
Exp1: &IntegerNode{Value: 2},
Exp2: &ConditionalNode{
Cond: &IdentifierNode{Value: "c"},
Exp1: &IntegerNode{Value: 3},
Exp2: &IntegerNode{Value: 4}}}},
},
{
"1; 2; 3",
&SequenceNode{
Expand Down Expand Up @@ -687,9 +715,10 @@ world`},
&SequenceNode{
Nodes: []Node{
&ConditionalNode{
Cond: &BoolNode{Value: true},
Exp1: &IntegerNode{Value: 1},
Exp2: &IntegerNode{Value: 2}},
Ternary: true,
Cond: &BoolNode{Value: true},
Exp1: &IntegerNode{Value: 1},
Exp2: &IntegerNode{Value: 2}},
&IntegerNode{Value: 3},
&IntegerNode{Value: 4},
},
Expand All @@ -698,8 +727,9 @@ world`},
{
"true ? 1 : ( 2; 3; 4 )",
&ConditionalNode{
Cond: &BoolNode{Value: true},
Exp1: &IntegerNode{Value: 1},
Ternary: true,
Cond: &BoolNode{Value: true},
Exp1: &IntegerNode{Value: 1},
Exp2: &SequenceNode{
Nodes: []Node{
&IntegerNode{Value: 2},
Expand All @@ -714,9 +744,10 @@ world`},
&SequenceNode{
Nodes: []Node{
&ConditionalNode{
Cond: &BoolNode{Value: true},
Exp1: &BoolNode{Value: true},
Exp2: &IntegerNode{Value: 1}},
Ternary: true,
Cond: &BoolNode{Value: true},
Exp1: &BoolNode{Value: true},
Exp2: &IntegerNode{Value: 1}},
&IntegerNode{Value: 2},
&IntegerNode{Value: 3},
},
Expand All @@ -727,18 +758,20 @@ world`},
&VariableDeclaratorNode{
Name: "x",
Value: &ConditionalNode{
Cond: &BoolNode{Value: true},
Exp1: &IntegerNode{Value: 1},
Exp2: &IntegerNode{Value: 2}},
Ternary: true,
Cond: &BoolNode{Value: true},
Exp1: &IntegerNode{Value: 1},
Exp2: &IntegerNode{Value: 2}},
Expr: &IdentifierNode{Value: "x"}},
},
{
"let x = true ? 1 : ( 2; 3; 4 ); x",
&VariableDeclaratorNode{
Name: "x",
Value: &ConditionalNode{
Cond: &BoolNode{Value: true},
Exp1: &IntegerNode{Value: 1},
Ternary: true,
Cond: &BoolNode{Value: true},
Exp1: &IntegerNode{Value: 1},
Exp2: &SequenceNode{
Nodes: []Node{
&IntegerNode{Value: 2},
Expand All @@ -762,7 +795,8 @@ world`},
Nodes: []Node{
&IntegerNode{Value: 4},
&IntegerNode{Value: 5},
&IntegerNode{Value: 6}}}},
&IntegerNode{Value: 6}}},
},
},
{
`all(ls, if true { 1 } else { 2 })`,
Expand All @@ -774,7 +808,8 @@ world`},
Node: &ConditionalNode{
Cond: &BoolNode{Value: true},
Exp1: &IntegerNode{Value: 1},
Exp2: &IntegerNode{Value: 2}}}}},
Exp2: &IntegerNode{Value: 2},
}}}},
},
{
`let x = if true { 1 } else { 2 }; x`,
Expand All @@ -783,7 +818,8 @@ world`},
Value: &ConditionalNode{
Cond: &BoolNode{Value: true},
Exp1: &IntegerNode{Value: 1},
Exp2: &IntegerNode{Value: 2}},
Exp2: &IntegerNode{Value: 2},
},
Expr: &IdentifierNode{Value: "x"}},
},
{
Expand All @@ -794,7 +830,8 @@ world`},
&ConditionalNode{
Cond: &BoolNode{Value: true},
Exp1: &IntegerNode{Value: 1},
Exp2: &IntegerNode{Value: 2}}}},
Exp2: &IntegerNode{Value: 2},
}}},
},
{
`[if true { 1 } else { 2 }]`,
Expand All @@ -803,7 +840,8 @@ world`},
&ConditionalNode{
Cond: &BoolNode{Value: true},
Exp1: &IntegerNode{Value: 1},
Exp2: &IntegerNode{Value: 2}}}},
Exp2: &IntegerNode{Value: 2},
}}},
},
{
`map(ls, { 1; 2; 3 })`,
Expand Down Expand Up @@ -1019,6 +1057,12 @@ func TestParse_error(t *testing.T) {
`unexpected token Operator("if") (1:5)
| 1 + if true { 2 } else { 3 }
| ....^`,
},
{
`if a { 1 } else b`,
`unexpected token Identifier("b") (1:17)
| if a { 1 } else b
| ................^`,
},
{
`list | all(#,,)`,
Expand Down
Loading