Skip to content
Draft
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
1 change: 1 addition & 0 deletions .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"algolia",
"altstack",
"antlr",
"assoc",
"authchain",
"anyhedge",
"anyonecanpay",
Expand Down
14 changes: 14 additions & 0 deletions packages/cashc/src/Errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
FunctionCallNode,
BinaryOpNode,
UnaryOpNode,
TernaryNode,
TimeOpNode,
CastNode,
AssignNode,
Expand Down Expand Up @@ -222,6 +223,19 @@ export class UnequalTypeError extends TypeError {
}
}

export class TernaryBranchMismatchError extends TypeError {
constructor(
node: TernaryNode,
) {
const consequent = node.consequent.type;
const alternative = node.alternative.type;
super(
node, consequent, alternative,
`Ternary branches have incompatible types '${consequent}' and '${alternative}'`,
);
}
}

export class UnsupportedTypeError extends TypeError {
constructor(
node: BinaryOpNode | UnaryOpNode | TimeOpNode | TupleIndexOpNode | SliceNode,
Expand Down
14 changes: 14 additions & 0 deletions packages/cashc/src/ast/AST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,20 @@ export class UnaryOpNode extends ExpressionNode {
}
}

export class TernaryNode extends ExpressionNode {
constructor(
public condition: ExpressionNode,
public consequent: ExpressionNode,
public alternative: ExpressionNode,
) {
super();
}

accept<T>(visitor: AstVisitor<T>): T {
return visitor.visitTernary(this);
}
}

export class NullaryOpNode extends ExpressionNode {
constructor(
public operator: NullaryOperator,
Expand Down
11 changes: 11 additions & 0 deletions packages/cashc/src/ast/AstBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
FunctionCallNode,
UnaryOpNode,
BinaryOpNode,
TernaryNode,
BoolLiteralNode,
IntLiteralNode,
HexLiteralNode,
Expand Down Expand Up @@ -74,6 +75,7 @@ import type {
InstantiationContext,
NullaryOpContext,
UnaryIntrospectionOpContext,
TernaryContext,
ConsoleStatementContext,
ConsoleParameterContext,
StatementContext,
Expand Down Expand Up @@ -464,6 +466,15 @@ export default class AstBuilder
return binaryOp;
}

visitTernary(ctx: TernaryContext): TernaryNode {
const condition = this.visit(ctx._condition);
const consequent = this.visit(ctx._consequent);
const alternative = this.visit(ctx._alternative);
const ternary = new TernaryNode(condition, consequent, alternative);
ternary.location = Location.fromCtx(ctx);
return ternary;
}

visitArray(ctx: ArrayContext): ArrayNode {
const elements = ctx.expression_list().map((e) => this.visit(e));
const array = new ArrayNode(elements);
Expand Down
8 changes: 8 additions & 0 deletions packages/cashc/src/ast/AstTraversal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
FunctionCallNode,
UnaryOpNode,
BinaryOpNode,
TernaryNode,
BoolLiteralNode,
IntLiteralNode,
HexLiteralNode,
Expand Down Expand Up @@ -172,6 +173,13 @@ export default class AstTraversal extends AstVisitor<Node> {
return node;
}

visitTernary(node: TernaryNode): Node {
node.condition = this.visit(node.condition);
node.consequent = this.visit(node.consequent);
node.alternative = this.visit(node.alternative);
return node;
}

visitUnaryOp(node: UnaryOpNode): Node {
node.expression = this.visit(node.expression);
return node;
Expand Down
2 changes: 2 additions & 0 deletions packages/cashc/src/ast/AstVisitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
FunctionCallNode,
UnaryOpNode,
BinaryOpNode,
TernaryNode,
BoolLiteralNode,
IntLiteralNode,
HexLiteralNode,
Expand Down Expand Up @@ -59,6 +60,7 @@ export default abstract class AstVisitor<T> {
abstract visitSlice(node: SliceNode): T;
abstract visitTupleIndexOp(node: TupleIndexOpNode): T;
abstract visitBinaryOp(node: BinaryOpNode): T;
abstract visitTernary(node: TernaryNode): T;
abstract visitUnaryOp(node: UnaryOpNode): T;
abstract visitNullaryOp(node: NullaryOpNode): T;
abstract visitArray(node: ArrayNode): T;
Expand Down
26 changes: 26 additions & 0 deletions packages/cashc/src/generation/GenerateTargetTraversal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
FunctionCallNode,
UnaryOpNode,
BinaryOpNode,
TernaryNode,
BoolLiteralNode,
IntLiteralNode,
HexLiteralNode,
Expand Down Expand Up @@ -806,6 +807,31 @@ export default class GenerateTargetTraversal extends AstTraversal {
return node;
}

// A ternary `condition ? consequent : alternative` compiles to OP_IF <consequent> OP_ELSE <alternative> OP_ENDIF.
// The condition is consumed by OP_IF; each branch leaves exactly one value, so the net effect mirrors any other
// expression: one operand consumed, one result produced. We bump scopeDepth (so variable reads inside the branches
// use OP_PICK rather than OP_ROLL, keeping the stack depth identical on both paths) and reset the symbolic stack
// before the alternative so both branches are tracked from the same starting point.
visitTernary(node: TernaryNode): Node {
node.condition = this.visit(node.condition);
this.popFromStack();

this.scopeDepth += 1;

this.emit(Op.OP_IF, { location: node.consequent.location, positionHint: PositionHint.START });
const stackCopy = [...this.stack];
node.consequent = this.visit(node.consequent);

this.emit(Op.OP_ELSE, { location: node.alternative.location, positionHint: PositionHint.START });
this.stack = [...stackCopy];
node.alternative = this.visit(node.alternative);

this.emit(Op.OP_ENDIF, { location: node.location, positionHint: PositionHint.END });
this.scopeDepth -= 1;

return node;
}

visitNullaryOp(node: NullaryOpNode): Node {
this.emit(compileNullaryOp(node.operator), { location: node.location, positionHint: PositionHint.START });
this.pushToStack('(value)');
Expand Down
1 change: 1 addition & 0 deletions packages/cashc/src/grammar/CashScript.g4
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ expression
| left=expression op='|' right=expression # BinaryOp
| left=expression op='&&' right=expression # BinaryOp
| left=expression op='||' right=expression # BinaryOp
| <assoc=right> condition=expression '?' consequent=expression ':' alternative=expression # Ternary
| '[' (expression (',' expression)* ','?)? ']' # Array
| NullaryOp # NullaryOp
| Identifier # Identifier
Expand Down
Loading
Loading