Skip to content

Commit ca7d65e

Browse files
committed
WIP: ternary operator
1 parent 790d4a2 commit ca7d65e

32 files changed

Lines changed: 1286 additions & 646 deletions

.cspell.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"algolia",
99
"altstack",
1010
"antlr",
11+
"assoc",
1112
"authchain",
1213
"anyhedge",
1314
"anyonecanpay",

packages/cashc/src/Errors.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
FunctionCallNode,
1010
BinaryOpNode,
1111
UnaryOpNode,
12+
TernaryNode,
1213
TimeOpNode,
1314
CastNode,
1415
AssignNode,
@@ -222,6 +223,19 @@ export class UnequalTypeError extends TypeError {
222223
}
223224
}
224225

226+
export class TernaryBranchMismatchError extends TypeError {
227+
constructor(
228+
node: TernaryNode,
229+
) {
230+
const consequent = node.consequent.type;
231+
const alternative = node.alternative.type;
232+
super(
233+
node, consequent, alternative,
234+
`Ternary branches have incompatible types '${consequent}' and '${alternative}'`,
235+
);
236+
}
237+
}
238+
225239
export class UnsupportedTypeError extends TypeError {
226240
constructor(
227241
node: BinaryOpNode | UnaryOpNode | TimeOpNode | TupleIndexOpNode | SliceNode,

packages/cashc/src/ast/AST.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,20 @@ export class UnaryOpNode extends ExpressionNode {
385385
}
386386
}
387387

388+
export class TernaryNode extends ExpressionNode {
389+
constructor(
390+
public condition: ExpressionNode,
391+
public consequent: ExpressionNode,
392+
public alternative: ExpressionNode,
393+
) {
394+
super();
395+
}
396+
397+
accept<T>(visitor: AstVisitor<T>): T {
398+
return visitor.visitTernary(this);
399+
}
400+
}
401+
388402
export class NullaryOpNode extends ExpressionNode {
389403
constructor(
390404
public operator: NullaryOperator,

packages/cashc/src/ast/AstBuilder.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
FunctionCallNode,
1919
UnaryOpNode,
2020
BinaryOpNode,
21+
TernaryNode,
2122
BoolLiteralNode,
2223
IntLiteralNode,
2324
HexLiteralNode,
@@ -74,6 +75,7 @@ import type {
7475
InstantiationContext,
7576
NullaryOpContext,
7677
UnaryIntrospectionOpContext,
78+
TernaryContext,
7779
ConsoleStatementContext,
7880
ConsoleParameterContext,
7981
StatementContext,
@@ -464,6 +466,15 @@ export default class AstBuilder
464466
return binaryOp;
465467
}
466468

469+
visitTernary(ctx: TernaryContext): TernaryNode {
470+
const condition = this.visit(ctx._condition);
471+
const consequent = this.visit(ctx._consequent);
472+
const alternative = this.visit(ctx._alternative);
473+
const ternary = new TernaryNode(condition, consequent, alternative);
474+
ternary.location = Location.fromCtx(ctx);
475+
return ternary;
476+
}
477+
467478
visitArray(ctx: ArrayContext): ArrayNode {
468479
const elements = ctx.expression_list().map((e) => this.visit(e));
469480
const array = new ArrayNode(elements);

packages/cashc/src/ast/AstTraversal.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
FunctionCallNode,
1414
UnaryOpNode,
1515
BinaryOpNode,
16+
TernaryNode,
1617
BoolLiteralNode,
1718
IntLiteralNode,
1819
HexLiteralNode,
@@ -172,6 +173,13 @@ export default class AstTraversal extends AstVisitor<Node> {
172173
return node;
173174
}
174175

176+
visitTernary(node: TernaryNode): Node {
177+
node.condition = this.visit(node.condition);
178+
node.consequent = this.visit(node.consequent);
179+
node.alternative = this.visit(node.alternative);
180+
return node;
181+
}
182+
175183
visitUnaryOp(node: UnaryOpNode): Node {
176184
node.expression = this.visit(node.expression);
177185
return node;

packages/cashc/src/ast/AstVisitor.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
FunctionCallNode,
1414
UnaryOpNode,
1515
BinaryOpNode,
16+
TernaryNode,
1617
BoolLiteralNode,
1718
IntLiteralNode,
1819
HexLiteralNode,
@@ -59,6 +60,7 @@ export default abstract class AstVisitor<T> {
5960
abstract visitSlice(node: SliceNode): T;
6061
abstract visitTupleIndexOp(node: TupleIndexOpNode): T;
6162
abstract visitBinaryOp(node: BinaryOpNode): T;
63+
abstract visitTernary(node: TernaryNode): T;
6264
abstract visitUnaryOp(node: UnaryOpNode): T;
6365
abstract visitNullaryOp(node: NullaryOpNode): T;
6466
abstract visitArray(node: ArrayNode): T;

packages/cashc/src/generation/GenerateTargetTraversal.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import {
3636
FunctionCallNode,
3737
UnaryOpNode,
3838
BinaryOpNode,
39+
TernaryNode,
3940
BoolLiteralNode,
4041
IntLiteralNode,
4142
HexLiteralNode,
@@ -806,6 +807,31 @@ export default class GenerateTargetTraversal extends AstTraversal {
806807
return node;
807808
}
808809

810+
// A ternary `condition ? consequent : alternative` compiles to OP_IF <consequent> OP_ELSE <alternative> OP_ENDIF.
811+
// The condition is consumed by OP_IF; each branch leaves exactly one value, so the net effect mirrors any other
812+
// expression: one operand consumed, one result produced. We bump scopeDepth (so variable reads inside the branches
813+
// use OP_PICK rather than OP_ROLL, keeping the stack depth identical on both paths) and reset the symbolic stack
814+
// before the alternative so both branches are tracked from the same starting point.
815+
visitTernary(node: TernaryNode): Node {
816+
node.condition = this.visit(node.condition);
817+
this.popFromStack();
818+
819+
this.scopeDepth += 1;
820+
821+
this.emit(Op.OP_IF, { location: node.consequent.location, positionHint: PositionHint.START });
822+
const stackCopy = [...this.stack];
823+
node.consequent = this.visit(node.consequent);
824+
825+
this.emit(Op.OP_ELSE, { location: node.alternative.location, positionHint: PositionHint.START });
826+
this.stack = [...stackCopy];
827+
node.alternative = this.visit(node.alternative);
828+
829+
this.emit(Op.OP_ENDIF, { location: node.location, positionHint: PositionHint.END });
830+
this.scopeDepth -= 1;
831+
832+
return node;
833+
}
834+
809835
visitNullaryOp(node: NullaryOpNode): Node {
810836
this.emit(compileNullaryOp(node.operator), { location: node.location, positionHint: PositionHint.START });
811837
this.pushToStack('(value)');

packages/cashc/src/grammar/CashScript.g4

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ expression
186186
| left=expression op='|' right=expression # BinaryOp
187187
| left=expression op='&&' right=expression # BinaryOp
188188
| left=expression op='||' right=expression # BinaryOp
189+
| <assoc=right> condition=expression '?' consequent=expression ':' alternative=expression # Ternary
189190
| '[' (expression (',' expression)* ','?)? ']' # Array
190191
| NullaryOp # NullaryOp
191192
| Identifier # Identifier

0 commit comments

Comments
 (0)