Skip to content

Commit 95e6311

Browse files
committed
feat(language): add logical or support
Add || parsing and typing for statement conditions and value-position conditional expressions. In value position, logical OR now preserves Pluto's conditional value semantics by returning the first admitted value and supporting plain fallback values when the left condition fails. Covers parser behavior, bitwise OR non-regression, scalar/function-argument fallback, ranged array fallback, and ranged condition gates in E2E tests.
1 parent c8ded10 commit 95e6311

8 files changed

Lines changed: 390 additions & 35 deletions

File tree

compiler/cond.go

Lines changed: 126 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55

66
"github.com/thiremani/pluto/ast"
7+
"github.com/thiremani/pluto/token"
78
"tinygo.org/x/go-llvm"
89
)
910

@@ -401,10 +402,10 @@ func (c *Compiler) valuesHaveCondExpr(values []ast.Expression) bool {
401402
}
402403

403404
// hasCondExprInTree returns true if any node in the expression tree has
404-
// CondScalar set (a scalar comparison in value position).
405+
// conditional value lowering.
405406
func (c *Compiler) hasCondExprInTree(expr ast.Expression) bool {
406407
info := c.ExprCache[key(c.FuncNameMangled, expr)]
407-
if info != nil && info.HasCondScalar() {
408+
if info != nil && info.HasCondExpr() {
408409
return true
409410
}
410411
for _, child := range ast.ExprChildren(expr) {
@@ -415,6 +416,28 @@ func (c *Compiler) hasCondExprInTree(expr ast.Expression) bool {
415416
return false
416417
}
417418

419+
func (c *Compiler) logicalOrCondExpr(expr ast.Expression) (*ast.InfixExpression, bool) {
420+
infix, ok := expr.(*ast.InfixExpression)
421+
if !ok || infix.Operator != token.SYM_LOGICAL_OR {
422+
return nil, false
423+
}
424+
425+
info := c.ExprCache[key(c.FuncNameMangled, expr)]
426+
return infix, info != nil && info.HasCondOr()
427+
}
428+
429+
func (c *Compiler) hasLogicalOrCondExprInTree(expr ast.Expression) bool {
430+
if _, ok := c.logicalOrCondExpr(expr); ok {
431+
return true
432+
}
433+
for _, child := range ast.ExprChildren(expr) {
434+
if c.hasLogicalOrCondExprInTree(child) {
435+
return true
436+
}
437+
}
438+
return false
439+
}
440+
418441
// handleComparisons processes each slot of a multi-return comparison based on
419442
// its CondMode. CondScalar slots are compared and ANDed into cond. CondArray
420443
// slots are compiled as array filters (source freed, marked borrowed).
@@ -494,13 +517,23 @@ func (c *Compiler) cleanupCondExprElse(temps []condTemp) {
494517
}
495518
}
496519

497-
// compileCondExprValue extracts cond-expr predicates for expr, branches on the
498-
// combined condition (AND with baseCond when provided), and compiles onTrue on
499-
// the true path only. False path performs standard cond-expr cleanup.
520+
// compileCondExprValue extracts cond-expr predicates for expr, branches through
521+
// value-position conditions, and compiles onTrue on admitted paths only. Plain
522+
// comparisons compose as AND; value-position logical OR tries the left operand
523+
// first and falls back to the right operand only on the left false path.
500524
func (c *Compiler) compileCondExprValue(expr ast.Expression, baseCond llvm.Value, onTrue func()) {
501525
c.pushCondLHSFrame()
502526
defer c.popCondLHSFrame()
503527

528+
c.compileCondExprValueInFrame(expr, baseCond, onTrue)
529+
}
530+
531+
func (c *Compiler) compileCondExprValueInFrame(expr ast.Expression, baseCond llvm.Value, onTrue func()) {
532+
if c.hasLogicalOrCondExprInTree(expr) {
533+
c.compileCondExprAlternative(expr, baseCond, onTrue, func() {})
534+
return
535+
}
536+
504537
cond, temps := c.extractCondExprs(expr, baseCond, nil)
505538
c.compileCondExprBranch(cond, temps, onTrue)
506539
}
@@ -512,6 +545,11 @@ func (c *Compiler) compileOperandCondExprValue(expr ast.Expression, baseCond llv
512545
c.pushCondLHSFrame()
513546
defer c.popCondLHSFrame()
514547

548+
if c.hasLogicalOrCondExprInTree(expr) {
549+
c.compileCondExprChildrenInFrame(ast.ExprChildren(expr), baseCond, onTrue, func() {})
550+
return
551+
}
552+
515553
cond := baseCond
516554
var temps []condTemp
517555
for _, child := range ast.ExprChildren(expr) {
@@ -521,6 +559,10 @@ func (c *Compiler) compileOperandCondExprValue(expr ast.Expression, baseCond llv
521559
}
522560

523561
func (c *Compiler) compileCondExprBranch(cond llvm.Value, temps []condTemp, onTrue func()) {
562+
c.compileCondExprBranchWithFailure(cond, temps, onTrue, func() {})
563+
}
564+
565+
func (c *Compiler) compileCondExprBranchWithFailure(cond llvm.Value, temps []condTemp, onTrue func(), onFalse func()) {
524566
if cond.IsNil() {
525567
onTrue()
526568
return
@@ -534,11 +576,90 @@ func (c *Compiler) compileCondExprBranch(cond llvm.Value, temps []condTemp, onTr
534576

535577
c.builder.SetInsertPointAtEnd(elseBlock)
536578
c.cleanupCondExprElse(temps)
579+
onFalse()
537580
c.builder.CreateBr(contBlock)
538581

539582
c.builder.SetInsertPointAtEnd(contBlock)
540583
}
541584

585+
func (c *Compiler) compileCondExprChildrenInFrame(children []ast.Expression, baseCond llvm.Value, onTrue func(), onFalse func()) {
586+
if len(children) == 0 {
587+
c.compileCondExprBranchWithFailure(baseCond, nil, onTrue, onFalse)
588+
return
589+
}
590+
591+
child := children[0]
592+
rest := children[1:]
593+
if c.hasCondExprInTree(child) {
594+
c.compileCondExprAlternative(child, baseCond, func() {
595+
c.compileCondExprChildrenInFrame(rest, llvm.Value{}, onTrue, onFalse)
596+
}, onFalse)
597+
return
598+
}
599+
600+
c.compileCondExprChildrenInFrame(rest, baseCond, onTrue, onFalse)
601+
}
602+
603+
func (c *Compiler) compileCondExprAlternative(expr ast.Expression, baseCond llvm.Value, onTrue func(), onFalse func()) {
604+
if logicalOr, ok := c.logicalOrCondExpr(expr); ok {
605+
c.compileLogicalOrCondExprAlternative(logicalOr, baseCond, onTrue, onFalse)
606+
return
607+
}
608+
609+
if !c.hasLogicalOrCondExprInTree(expr) {
610+
cond, temps := c.extractCondExprs(expr, baseCond, nil)
611+
c.compileCondExprBranchWithFailure(cond, temps, onTrue, onFalse)
612+
return
613+
}
614+
615+
c.compileCondExprChildrenInFrame(ast.ExprChildren(expr), baseCond, func() {
616+
if infix, ok := expr.(*ast.InfixExpression); ok {
617+
info := c.ExprCache[key(c.FuncNameMangled, infix)]
618+
if info != nil && info.HasCondScalar() && len(c.pendingLoopRanges(info.Ranges)) == 0 {
619+
left := c.compileExpression(infix.Left, nil)
620+
right := c.compileExpression(infix.Right, nil)
621+
622+
lhsSyms, cond := c.handleComparisons(infix.Operator, left, right, info, llvm.Value{})
623+
c.requireCondLHSFrame()[key(c.FuncNameMangled, expr)] = lhsSyms
624+
temps := []condTemp{{infix.Left, left}}
625+
c.freeTemporary(infix.Right, right)
626+
c.compileCondExprBranchWithFailure(cond, temps, onTrue, onFalse)
627+
return
628+
}
629+
}
630+
631+
onTrue()
632+
}, onFalse)
633+
}
634+
635+
func (c *Compiler) withCondLHS(expr ast.Expression, syms []*Symbol, body func()) {
636+
frame := c.requireCondLHSFrame()
637+
exprKey := key(c.FuncNameMangled, expr)
638+
old, hadOld := frame[exprKey]
639+
frame[exprKey] = syms
640+
body()
641+
if hadOld {
642+
frame[exprKey] = old
643+
return
644+
}
645+
delete(frame, exprKey)
646+
}
647+
648+
func (c *Compiler) compileLogicalOrCondExprAlternative(expr *ast.InfixExpression, baseCond llvm.Value, onTrue func(), onFalse func()) {
649+
leftTrue := func() {
650+
left := c.compileExpression(expr.Left, nil)
651+
c.withCondLHS(expr, left, onTrue)
652+
}
653+
rightTrue := func() {
654+
right := c.compileExpression(expr.Right, nil)
655+
c.withCondLHS(expr, right, onTrue)
656+
}
657+
658+
c.compileCondExprAlternative(expr.Left, baseCond, leftTrue, func() {
659+
c.compileCondExprAlternative(expr.Right, baseCond, rightTrue, onFalse)
660+
})
661+
}
662+
542663
func (c *Compiler) isRangeDriverCond(expr ast.Expression) bool {
543664
info := c.ExprCache[key(c.FuncNameMangled, expr)]
544665
if len(info.OutTypes) != 1 {

compiler/operators.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -650,6 +650,18 @@ var defaultOps = map[opKey]opFunc{
650650
return
651651
},
652652

653+
// Logical OR
654+
{Operator: token.SYM_LOGICAL_OR, LeftType: I1, RightType: I1}: func(c *Compiler, left, right *Symbol, compile bool) (s *Symbol) {
655+
s = &Symbol{}
656+
s.Type = left.Type
657+
if !compile {
658+
return
659+
}
660+
661+
s.Val = c.builder.CreateOr(left.Val, right.Val, "or_cond")
662+
return
663+
},
664+
653665
// Bitwise XOR
654666
{Operator: token.SYM_XOR, LeftType: I64, RightType: I64}: func(c *Compiler, left, right *Symbol, compile bool) (s *Symbol) {
655667
s = &Symbol{}

compiler/solver.go

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const (
2222
CondNone CondMode = iota // Normal expression (not a comparison in value position)
2323
CondScalar // Scalar: extract LHS value, branch on condition
2424
CondArray // Array: element-wise filter, keep LHS where condition holds
25+
CondOr // Logical OR in value position: first true operand wins
2526
)
2627

2728
type ExprInfo struct {
@@ -47,6 +48,24 @@ func (info *ExprInfo) HasCondScalar() bool {
4748
return false
4849
}
4950

51+
func (info *ExprInfo) HasCondOr() bool {
52+
for _, m := range info.CompareModes {
53+
if m == CondOr {
54+
return true
55+
}
56+
}
57+
return false
58+
}
59+
60+
func (info *ExprInfo) HasCondExpr() bool {
61+
for _, m := range info.CompareModes {
62+
if m == CondScalar || m == CondOr {
63+
return true
64+
}
65+
}
66+
return false
67+
}
68+
5069
// ExprKey is the key for ExprCache, combining function context with expression.
5170
// FuncNameMangled is the mangled function name ("" for script-level expressions).
5271
type ExprKey struct {
@@ -1454,6 +1473,89 @@ func (ts *TypeSolver) typeInfixSlot(expr *ast.InfixExpression, leftType, rightTy
14541473
return resultType, CondNone
14551474
}
14561475

1476+
func (ts *TypeSolver) logicalOrValueType(leftType, rightType Type, tok token.Token) Type {
1477+
if ptr, ok := leftType.(Ptr); ok {
1478+
leftType = ptr.Elem
1479+
}
1480+
if ptr, ok := rightType.(Ptr); ok {
1481+
rightType = ptr.Elem
1482+
}
1483+
1484+
switch {
1485+
case leftType.Kind() == UnresolvedKind:
1486+
return rightType
1487+
case rightType.Kind() == UnresolvedKind:
1488+
return leftType
1489+
case leftType.Kind() == StrKind && rightType.Kind() == StrKind:
1490+
return mergeStringFlavor(leftType, rightType)
1491+
case TypeEqual(leftType, rightType):
1492+
return leftType
1493+
default:
1494+
ts.Errors = append(ts.Errors, &token.CompileError{
1495+
Token: tok,
1496+
Msg: fmt.Sprintf("logical OR value operands must have matching output types, got %s and %s", leftType, rightType),
1497+
})
1498+
return Unresolved{}
1499+
}
1500+
}
1501+
1502+
func isI1(t Type) bool {
1503+
intType, ok := t.(Int)
1504+
return ok && intType.Width == 1
1505+
}
1506+
1507+
func (ts *TypeSolver) typeLogicalOrExpression(expr *ast.InfixExpression, left, right []Type) []Type {
1508+
leftInfo := ts.ExprCache[key(ts.FuncNameMangled, expr.Left)]
1509+
rightInfo := ts.ExprCache[key(ts.FuncNameMangled, expr.Right)]
1510+
1511+
if ts.InValueExpr {
1512+
if leftInfo == nil || !leftInfo.HasCondExpr() {
1513+
ts.Errors = append(ts.Errors, &token.CompileError{
1514+
Token: expr.Token,
1515+
Msg: "logical OR in value position requires a conditional left operand",
1516+
})
1517+
}
1518+
1519+
types := make([]Type, len(left))
1520+
compareModes := make([]CondMode, len(left))
1521+
for i := range left {
1522+
types[i] = ts.logicalOrValueType(left[i], right[i], expr.Token)
1523+
compareModes[i] = CondOr
1524+
}
1525+
1526+
ts.ExprCache[key(ts.FuncNameMangled, expr)] = &ExprInfo{
1527+
OutTypes: types,
1528+
ExprLen: len(types),
1529+
HasRanges: (leftInfo != nil && leftInfo.HasRanges) || (rightInfo != nil && rightInfo.HasRanges),
1530+
CompareModes: compareModes,
1531+
}
1532+
return types
1533+
}
1534+
1535+
types := []Type{I1}
1536+
if len(left) != 1 {
1537+
ts.Errors = append(ts.Errors, &token.CompileError{
1538+
Token: expr.Token,
1539+
Msg: fmt.Sprintf("logical OR condition operands must produce a single value, got %d", len(left)),
1540+
})
1541+
types = []Type{Unresolved{}}
1542+
} else if left[0].Kind() != UnresolvedKind && right[0].Kind() != UnresolvedKind && (!isI1(left[0]) || !isI1(right[0])) {
1543+
ts.Errors = append(ts.Errors, &token.CompileError{
1544+
Token: expr.Token,
1545+
Msg: fmt.Sprintf("logical OR requires condition operands, got %s and %s", left[0], right[0]),
1546+
})
1547+
types = []Type{Unresolved{}}
1548+
}
1549+
1550+
ts.ExprCache[key(ts.FuncNameMangled, expr)] = &ExprInfo{
1551+
OutTypes: types,
1552+
ExprLen: len(types),
1553+
HasRanges: (leftInfo != nil && leftInfo.HasRanges) || (rightInfo != nil && rightInfo.HasRanges),
1554+
CompareModes: make([]CondMode, len(types)),
1555+
}
1556+
return types
1557+
}
1558+
14571559
// TypeInfixExpression returns output types of infix expression
14581560
// If either left or right operands are pointers, it will dereference them
14591561
// This is because pointers are automatically dereferenced
@@ -1471,6 +1573,10 @@ func (ts *TypeSolver) TypeInfixExpression(expr *ast.InfixExpression) (types []Ty
14711573
return
14721574
}
14731575

1576+
if expr.Operator == token.SYM_LOGICAL_OR {
1577+
return ts.typeLogicalOrExpression(expr, left, right)
1578+
}
1579+
14741580
isValueCmp := ts.InValueExpr && expr.Token.IsComparison()
14751581
types = make([]Type, len(left))
14761582
compareModes := make([]CondMode, len(left))

0 commit comments

Comments
 (0)