Skip to content

Commit 52a0b53

Browse files
committed
Implement compiler logic for .slice() feature
1 parent a143707 commit 52a0b53

File tree

8 files changed

+91
-4
lines changed

8 files changed

+91
-4
lines changed

packages/cashc/src/Errors.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
StatementNode,
2020
ContractNode,
2121
ExpressionNode,
22+
SliceNode,
2223
} from './ast/AST.js';
2324
import { Symbol, SymbolType } from './ast/SymbolTable.js';
2425
import { Location, Point } from './ast/Location.js';
@@ -73,7 +74,7 @@ export class InvalidSymbolTypeError extends CashScriptError {
7374
}
7475
}
7576

76-
export class RedefinitionError extends CashScriptError {}
77+
export class RedefinitionError extends CashScriptError { }
7778

7879
export class FunctionRedefinitionError extends RedefinitionError {
7980
constructor(
@@ -160,7 +161,7 @@ export class UnequalTypeError extends TypeError {
160161

161162
export class UnsupportedTypeError extends TypeError {
162163
constructor(
163-
node: BinaryOpNode | UnaryOpNode | TimeOpNode | TupleIndexOpNode,
164+
node: BinaryOpNode | UnaryOpNode | TimeOpNode | TupleIndexOpNode | SliceNode,
164165
actual?: Type,
165166
expected?: Type,
166167
) {
@@ -170,6 +171,8 @@ export class UnsupportedTypeError extends TypeError {
170171
} else {
171172
super(node, actual, expected, `Tried to call member 'split' on unsupported type '${actual}'`);
172173
}
174+
} else if (node instanceof SliceNode) {
175+
super(node, actual, expected, `Tried to call member 'slice' on unsupported type '${actual}'`);
173176
} else if (node instanceof BinaryOpNode) {
174177
super(node, actual, expected, `Tried to apply operator '${node.operator}' to unsupported type '${actual}'`);
175178
} else if (node instanceof UnaryOpNode && node.operator.startsWith('.')) {

packages/cashc/src/ast/AST.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ export class ParameterNode extends Node implements Named, Typed {
7979
}
8080
}
8181

82-
export abstract class StatementNode extends Node {}
82+
export abstract class StatementNode extends Node { }
8383

8484
export class VariableDefinitionNode extends StatementNode implements Named, Typed {
8585
constructor(
@@ -235,6 +235,20 @@ export class TupleIndexOpNode extends ExpressionNode {
235235
}
236236
}
237237

238+
export class SliceNode extends ExpressionNode {
239+
constructor(
240+
public element: ExpressionNode,
241+
public start: ExpressionNode,
242+
public end: ExpressionNode,
243+
) {
244+
super();
245+
}
246+
247+
accept<T>(visitor: AstVisitor<T>): T {
248+
return visitor.visitSlice(this);
249+
}
250+
}
251+
238252
export class BinaryOpNode extends ExpressionNode {
239253
constructor(
240254
public left: ExpressionNode,

packages/cashc/src/ast/AstBuilder.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
NullaryOpNode,
3434
ConsoleStatementNode,
3535
ConsoleParameterNode,
36+
SliceNode,
3637
} from './AST.js';
3738
import { UnaryOperator, BinaryOperator, NullaryOperator } from './Operator.js';
3839
import type {
@@ -66,6 +67,7 @@ import type {
6667
ConsoleParameterContext,
6768
StatementContext,
6869
RequireMessageContext,
70+
SliceContext,
6971
} from '../grammar/CashScriptParser.js';
7072
import CashScriptVisitor from '../grammar/CashScriptVisitor.js';
7173
import { Location } from './Location.js';
@@ -262,6 +264,15 @@ export default class AstBuilder
262264
return tupleIndexOp;
263265
}
264266

267+
visitSlice(ctx: SliceContext): SliceNode {
268+
const element = this.visit(ctx._element);
269+
const start = this.visit(ctx._start);
270+
const end = this.visit(ctx._end);
271+
const slice = new SliceNode(element, start, end);
272+
slice.location = Location.fromCtx(ctx);
273+
return slice;
274+
}
275+
265276
visitNullaryOp(ctx: NullaryOpContext): NullaryOpNode {
266277
const operator = ctx.getText() as NullaryOperator;
267278
const nullaryOp = new NullaryOpNode(operator);

packages/cashc/src/ast/AstTraversal.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
NullaryOpNode,
2828
ConsoleStatementNode,
2929
ConsoleParameterNode,
30+
SliceNode,
3031
} from './AST.js';
3132
import AstVisitor from './AstVisitor.js';
3233

@@ -113,6 +114,13 @@ export default class AstTraversal extends AstVisitor<Node> {
113114
return node;
114115
}
115116

117+
visitSlice(node: SliceNode): Node {
118+
node.element = this.visit(node.element);
119+
node.start = this.visit(node.start);
120+
node.end = this.visit(node.end);
121+
return node;
122+
}
123+
116124
visitBinaryOp(node: BinaryOpNode): Node {
117125
node.left = this.visit(node.left);
118126
node.right = this.visit(node.right);

packages/cashc/src/ast/AstVisitor.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
TupleAssignmentNode,
2626
NullaryOpNode,
2727
ConsoleStatementNode,
28+
SliceNode,
2829
} from './AST.js';
2930

3031
export default abstract class AstVisitor<T> {
@@ -42,6 +43,7 @@ export default abstract class AstVisitor<T> {
4243
abstract visitCast(node: CastNode): T;
4344
abstract visitFunctionCall(node: FunctionCallNode): T;
4445
abstract visitInstantiation(node: InstantiationNode): T;
46+
abstract visitSlice(node: SliceNode): T;
4547
abstract visitTupleIndexOp(node: TupleIndexOpNode): T;
4648
abstract visitBinaryOp(node: BinaryOpNode): T;
4749
abstract visitUnaryOp(node: UnaryOpNode): T;

packages/cashc/src/generation/GenerateTargetTraversal.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import {
4646
NullaryOpNode,
4747
ConsoleParameterNode,
4848
ConsoleStatementNode,
49+
SliceNode,
4950
} from '../ast/AST.js';
5051
import AstTraversal from '../ast/AstTraversal.js';
5152
import { GlobalFunction, Class } from '../ast/Globals.js';
@@ -537,6 +538,27 @@ export default class GenerateTargetTraversalWithLocation extends AstTraversal {
537538
return node;
538539
}
539540

541+
// element.slice(start, end) is equivalent to element.split(end)[0].split(start)[1]
542+
visitSlice(node: SliceNode): Node {
543+
node.element = this.visit(node.element);
544+
545+
const locationData = { location: node.location, positionHint: PositionHint.END };
546+
547+
this.visit(node.end);
548+
this.emit(Op.OP_SPLIT, locationData);
549+
this.emit(Op.OP_DROP, locationData);
550+
this.popFromStack(2);
551+
this.pushToStack('(value)');
552+
553+
this.visit(node.start);
554+
this.emit(Op.OP_SPLIT, locationData);
555+
this.emit(Op.OP_NIP, locationData);
556+
this.popFromStack(2);
557+
this.pushToStack('(value)');
558+
559+
return node;
560+
}
561+
540562
visitBinaryOp(node: BinaryOpNode): Node {
541563
node.left = this.visit(node.left);
542564
node.right = this.visit(node.right);

packages/cashc/src/print/OutputSourceCodeTraversal.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
NullaryOpNode,
2929
ConsoleStatementNode,
3030
ConsoleParameterNode,
31+
SliceNode,
3132
} from '../ast/AST.js';
3233
import AstTraversal from '../ast/AstTraversal.js';
3334

@@ -216,6 +217,16 @@ export default class OutputSourceCodeTraversal extends AstTraversal {
216217
return node;
217218
}
218219

220+
visitSlice(node: SliceNode): Node {
221+
node.element = this.visit(node.element);
222+
this.addOutput('.slice(');
223+
node.start = this.visit(node.start);
224+
this.addOutput(', ');
225+
node.end = this.visit(node.end);
226+
this.addOutput(')');
227+
return node;
228+
}
229+
219230
visitBinaryOp(node: BinaryOpNode): Node {
220231
if (node.operator.startsWith('.')) {
221232
node.left = this.visit(node.left);

packages/cashc/src/semantic/TypeCheckTraversal.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
InstantiationNode,
2828
TupleAssignmentNode,
2929
NullaryOpNode,
30+
SliceNode,
3031
} from '../ast/AST.js';
3132
import AstTraversal from '../ast/AstTraversal.js';
3233
import {
@@ -171,6 +172,21 @@ export default class TypeCheckTraversal extends AstTraversal {
171172
return node;
172173
}
173174

175+
visitSlice(node: SliceNode): Node {
176+
node.element = this.visit(node.element);
177+
node.start = this.visit(node.start);
178+
node.end = this.visit(node.end);
179+
180+
expectAnyOfTypes(node, node.element.type, [new BytesType(), PrimitiveType.STRING]);
181+
expectInt(node, node.start.type);
182+
expectInt(node, node.end.type);
183+
184+
// TODO: Proper typing
185+
node.type = node.element.type instanceof BytesType ? new BytesType() : PrimitiveType.STRING;
186+
187+
return node;
188+
}
189+
174190
visitBinaryOp(node: BinaryOpNode): Node {
175191
node.left = this.visit(node.left);
176192
node.right = this.visit(node.right);
@@ -325,7 +341,7 @@ export default class TypeCheckTraversal extends AstTraversal {
325341
}
326342
}
327343

328-
type ExpectedNode = BinaryOpNode | UnaryOpNode | TimeOpNode | TupleIndexOpNode;
344+
type ExpectedNode = BinaryOpNode | UnaryOpNode | TimeOpNode | TupleIndexOpNode | SliceNode;
329345
function expectAnyOfTypes(node: ExpectedNode, actual?: Type, expectedTypes?: Type[]): void {
330346
if (!expectedTypes || expectedTypes.length === 0) return;
331347
if (expectedTypes.find((expected) => implicitlyCastable(actual, expected))) {

0 commit comments

Comments
 (0)