Skip to content

Commit

Permalink
feat: try and try...catch statements (#212)
Browse files Browse the repository at this point in the history
  • Loading branch information
Gusarich authored Apr 22, 2024
1 parent a9a436d commit b9dd1b8
Show file tree
Hide file tree
Showing 16 changed files with 444 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Reserve mode constants in `@stdlib/reserve`, namely `ReserveExact`, `ReserveAllExcept`, `ReserveAtMost`, `ReserveAddOriginalBalance`, `ReserveInvertSign`, `ReserveBounceIfActionFail`: PR [#173](https://github.com/tact-lang/tact/pull/173)
- JSON Schema for `tact.config.json`: PR [#194](https://github.com/tact-lang/tact/pull/194)
- Display an error for integer overflow at compile-time: PR [#200](https://github.com/tact-lang/tact/pull/200)
- Try-Catch statements: PR [#212](https://github.com/tact-lang/tact/pull/212)
- Non-modifying `StringBuilder`'s `concat` method for chained string concatenations: PR [#217](https://github.com/tact-lang/tact/pull/217)
- `toString` extension function for `Address` type: PR [#224](https://github.com/tact-lang/tact/pull/224)
- Bitwise XOR operation (`^`): PR [#238](https://github.com/tact-lang/tact/pull/238)
Expand Down
24 changes: 24 additions & 0 deletions src/generator/writers/writeFunction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,30 @@ export function writeStatement(
});
ctx.append(`}`);
return;
} else if (f.kind === "statement_try") {
ctx.append(`try {`);
ctx.inIndent(() => {
for (const s of f.statements) {
writeStatement(s, self, returns, ctx);
}
});
ctx.append("} catch (_) { }");
return;
} else if (f.kind === "statement_try_catch") {
ctx.append(`try {`);
ctx.inIndent(() => {
for (const s of f.statements) {
writeStatement(s, self, returns, ctx);
}
});
ctx.append(`} catch (_, ${id(f.catchName)}) {`);
ctx.inIndent(() => {
for (const s of f.catchStatements) {
writeStatement(s, self, returns, ctx);
}
});
ctx.append(`}`);
return;
}

throw Error("Unknown statement kind");
Expand Down
35 changes: 34 additions & 1 deletion src/grammar/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,22 @@ export type ASTStatementRepeat = {
ref: ASTRef;
};

export type ASTStatementTry = {
kind: "statement_try";
id: number;
statements: ASTStatement[];
ref: ASTRef;
};

export type ASTStatementTryCatch = {
kind: "statement_try_catch";
id: number;
statements: ASTStatement[];
catchName: string;
catchStatements: ASTStatement[];
ref: ASTRef;
};

//
// Unions
//
Expand All @@ -487,7 +503,9 @@ export type ASTStatement =
| ASTCondition
| ASTStatementWhile
| ASTStatementUntil
| ASTStatementRepeat;
| ASTStatementRepeat
| ASTStatementTry
| ASTStatementTryCatch;
export type ASTNode =
| ASTExpression
| ASTStruct
Expand All @@ -514,6 +532,8 @@ export type ASTNode =
| ASTStatementWhile
| ASTStatementUntil
| ASTStatementRepeat
| ASTStatementTry
| ASTStatementTryCatch
| ASTReceive
| ASTLvalueRef
| ASTString
Expand Down Expand Up @@ -728,6 +748,19 @@ export function traverse(node: ASTNode, callback: (node: ASTNode) => void) {
traverse(e, callback);
}
}
if (node.kind === "statement_try") {
for (const e of node.statements) {
traverse(e, callback);
}
}
if (node.kind === "statement_try_catch") {
for (const e of node.statements) {
traverse(e, callback);
}
for (const e of node.catchStatements) {
traverse(e, callback);
}
}
if (node.kind === "op_binary") {
traverse(node.left, callback);
traverse(node.right, callback);
Expand Down
11 changes: 11 additions & 0 deletions src/grammar/clone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,17 @@ export function cloneNode<T extends ASTNode>(src: T): T {
condition: cloneNode(src.condition),
statements: src.statements.map(cloneNode),
});
} else if (src.kind === "statement_try") {
return cloneASTNode({
...src,
statements: src.statements.map(cloneNode),
});
} else if (src.kind === "statement_try_catch") {
return cloneASTNode({
...src,
statements: src.statements.map(cloneNode),
catchStatements: src.catchStatements.map(cloneNode),
});
} else if (src.kind === "def_function") {
return cloneASTNode({
...src,
Expand Down
7 changes: 7 additions & 0 deletions src/grammar/grammar.ohm
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ Tact {
| StatementWhile
| StatementRepeat
| StatementUntil
| StatementTry
StatementBlock = "{" Statement* "}"
StatementLet = let id ":" Type "=" Expression ";"
StatementReturn = return Expression ";" --withExpression
Expand All @@ -119,6 +120,8 @@ Tact {
StatementWhile = while "(" Expression ")" "{" Statement* "}"
StatementRepeat = repeat "(" Expression ")" "{" Statement* "}"
StatementUntil = do "{" Statement* "}" until "(" Expression ")" ";"
StatementTry = try "{" Statement* "}" ~catch --simple
| try "{" Statement* "}" catch "(" id ")" "{" Statement* "}" --withCatch

// L-value
LValue = id "." LValue --more
Expand Down Expand Up @@ -262,6 +265,8 @@ Tact {
| repeat
| do
| until
| try
| catch
| as
| mutates
| extends
Expand All @@ -288,6 +293,8 @@ Tact {
repeat = "repeat" ~idPart
do = "do" ~idPart
until = "until" ~idPart
try = "try" ~idPart
catch = "catch" ~idPart
as = "as" ~idPart
mutates = "mutates" ~idPart
extends = "extends" ~idPart
Expand Down
28 changes: 28 additions & 0 deletions src/grammar/grammar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -823,6 +823,34 @@ semantics.addOperation<ASTNode>("resolve_statement", {
ref: createRef(this),
});
},
StatementTry_simple(_arg0, _arg1, arg2, _arg3) {
return createNode({
kind: "statement_try",
statements: arg2.children.map((v) => v.resolve_statement()),
ref: createRef(this),
});
},
StatementTry_withCatch(
_arg0,
_arg1,
arg2,
_arg3,
_arg4,
_arg5,
arg6,
_arg7,
_arg8,
arg9,
_arg10,
) {
return createNode({
kind: "statement_try_catch",
statements: arg2.children.map((v) => v.resolve_statement()),
catchName: arg6.sourceString,
catchStatements: arg9.children.map((v) => v.resolve_statement()),
ref: createRef(this),
});
},
});

// LValue
Expand Down
103 changes: 103 additions & 0 deletions src/test/__snapshots__/feature-try-catch.spec.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`feature-ternary should implement try-catch statements correctly 1`] = `
[
{
"$seq": 1,
"events": [
{
"$type": "storage-charged",
"amount": "0.000000007",
},
{
"$type": "received",
"message": {
"body": {
"text": "increment",
"type": "text",
},
"bounce": true,
"from": "@treasure(treasure)",
"to": "kQAZccETl-kyR0WEGf-7CT-k7zXmJGLmD8_jF9zsGay7wpHn",
"type": "internal",
"value": "10",
},
},
{
"$type": "processed",
"gasUsed": 3929n,
},
],
},
{
"$seq": 2,
"events": [
{
"$type": "storage-charged",
"amount": "0.000000007",
},
{
"$type": "received",
"message": {
"body": {
"text": "incrementTryCatch",
"type": "text",
},
"bounce": true,
"from": "@treasure(treasure)",
"to": "kQAZccETl-kyR0WEGf-7CT-k7zXmJGLmD8_jF9zsGay7wpHn",
"type": "internal",
"value": "10",
},
},
{
"$type": "processed",
"gasUsed": 4507n,
},
],
},
{
"$seq": 3,
"events": [
{
"$type": "storage-charged",
"amount": "0.000000007",
},
{
"$type": "received",
"message": {
"body": {
"text": "tryCatchRegisters",
"type": "text",
},
"bounce": true,
"from": "@treasure(treasure)",
"to": "kQAZccETl-kyR0WEGf-7CT-k7zXmJGLmD8_jF9zsGay7wpHn",
"type": "internal",
"value": "10",
},
},
{
"$type": "processed",
"gasUsed": 12023n,
},
{
"$type": "sent",
"messages": [
{
"body": {
"text": "hello world 1",
"type": "text",
},
"bounce": true,
"from": "kQAZccETl-kyR0WEGf-7CT-k7zXmJGLmD8_jF9zsGay7wpHn",
"to": "@treasure(treasure)",
"type": "internal",
"value": "9.986741",
},
],
},
],
},
]
`;
47 changes: 47 additions & 0 deletions src/test/feature-try-catch.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { toNano } from "@ton/core";
import { ContractSystem } from "@tact-lang/emulator";
import { __DANGER_resetNodeId } from "../grammar/ast";
import { TryCatchTester } from "./features/output/try-catch_TryCatchTester";

describe("feature-ternary", () => {
beforeEach(() => {
__DANGER_resetNodeId();
});
it("should implement try-catch statements correctly", async () => {
// Init
const system = await ContractSystem.create();
const treasure = system.treasure("treasure");
const contract = system.open(await TryCatchTester.fromInit());
await contract.send(treasure, { value: toNano("10") }, null);
await system.run();

// Check methods
expect(await contract.getTestTryCatch1()).toEqual(7n);
expect(await contract.getTestTryCatch2()).toEqual(101n);
expect(await contract.getTestTryCatch3()).toEqual(4n);

// Check state rollbacks
const tracker = system.track(contract);

expect(await contract.getGetCounter()).toEqual(0n);
await contract.send(treasure, { value: toNano("10") }, "increment");
await system.run();
expect(await contract.getGetCounter()).toEqual(1n);
await contract.send(
treasure,
{ value: toNano("10") },
"incrementTryCatch",
);
await system.run();
expect(await contract.getGetCounter()).toEqual(1n);
await contract.send(
treasure,
{ value: toNano("10") },
"tryCatchRegisters",
);
await system.run();
expect(await contract.getGetCounter()).toEqual(2n);

expect(tracker.collect()).toMatchSnapshot();
});
});
Loading

0 comments on commit b9dd1b8

Please sign in to comment.