From b02b0cf4db8e177ea2e0c1fb599ff75e9e206c5d Mon Sep 17 00:00:00 2001 From: Anton Medvedev Date: Sun, 14 Dec 2025 16:07:41 +0100 Subject: [PATCH 1/3] Add `else if` support --- ast/print_test.go | 1 + expr_test.go | 4 ++++ parser/parser.go | 12 +++++++++--- parser/parser_test.go | 16 ++++++++++++++++ 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/ast/print_test.go b/ast/print_test.go index d0859606f..d7abd8a86 100644 --- a/ast/print_test.go +++ b/ast/print_test.go @@ -84,6 +84,7 @@ func TestPrint(t *testing.T) { {`(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 if false { 2 } else { 3 }`, `true ? 1 : (false ? 2 : 3)`}, } for _, tt := range tests { diff --git a/expr_test.go b/expr_test.go index a8be9ef3c..c923d5a46 100644 --- a/expr_test.go +++ b/expr_test.go @@ -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", + }, { `1; 2; 3`, 3, diff --git a/parser/parser.go b/parser/parser.go index 623ab9a00..cfd661e37 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -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, diff --git a/parser/parser_test.go b/parser/parser_test.go index 16dac6b79..ceb33496b 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -659,6 +659,16 @@ 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}}}, + }, { "1; 2; 3", &SequenceNode{ @@ -1019,6 +1029,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(#,,)`, From 5052a11dcb4b8519e1b393d5a693484537825ecb Mon Sep 17 00:00:00 2001 From: Anton Medvedev Date: Sun, 14 Dec 2025 16:14:54 +0100 Subject: [PATCH 2/3] Add test for nested `else if` handling --- parser/parser_test.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/parser/parser_test.go b/parser/parser_test.go index ceb33496b..d73354daa 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -669,6 +669,19 @@ world`}, Exp1: &IntegerNode{Value: 2}, Exp2: &IntegerNode{Value: 3}}}, }, + { + "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{ From ce727bc410b256fec6e4f303f0735df1101bd5ae Mon Sep 17 00:00:00 2001 From: Anton Medvedev Date: Sun, 14 Dec 2025 19:03:20 +0100 Subject: [PATCH 3/3] Add `Ternary` field to `ConditionalNode` and update tests and printing logic --- ast/node.go | 9 ++++--- ast/print.go | 10 +++++++ ast/print_test.go | 4 +-- parser/parser.go | 7 ++--- parser/parser_test.go | 63 ++++++++++++++++++++++++++----------------- 5 files changed, 60 insertions(+), 33 deletions(-) diff --git a/ast/node.go b/ast/node.go index f156f69be..f17fd7e52 100644 --- a/ast/node.go +++ b/ast/node.go @@ -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. diff --git a/ast/print.go b/ast/print.go index e4e45f0fa..527a5b99b 100644 --- a/ast/print.go +++ b/ast/print.go @@ -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()) diff --git a/ast/print_test.go b/ast/print_test.go index d7abd8a86..bcdad782c 100644 --- a/ast/print_test.go +++ b/ast/print_test.go @@ -83,8 +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 if false { 2 } else { 3 }`, `true ? 1 : (false ? 2 : 3)`}, + {`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 { diff --git a/parser/parser.go b/parser/parser.go index cfd661e37..90daae43b 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -368,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 diff --git a/parser/parser_test.go b/parser/parser_test.go index d73354daa..85c465750 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -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]", @@ -396,6 +400,7 @@ world`}, { "2==2 ? false : 3 not in [1, 2, 5]", &ConditionalNode{ + Ternary: true, Cond: &BinaryNode{ Operator: "==", Left: &IntegerNode{Value: 2}, @@ -710,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}, }, @@ -721,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}, @@ -737,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}, }, @@ -750,9 +758,10 @@ 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"}}, }, { @@ -760,8 +769,9 @@ world`}, &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}, @@ -785,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 })`, @@ -797,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`, @@ -806,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"}}, }, { @@ -817,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 }]`, @@ -826,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 })`,