Skip to content

Commit

Permalink
feat: string escaping and escape backticks in generated TS error mess…
Browse files Browse the repository at this point in the history
…ages (#192)

full support for `\n`, `\t`, etc. and Unicode escaping sequences
  • Loading branch information
Gusarich authored Apr 2, 2024
1 parent c1f013f commit 3b5f211
Show file tree
Hide file tree
Showing 12 changed files with 115 additions and 13 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Update the `dump` function to handle addresses: PR [#175](https://github.com/tact-lang/tact/pull/175)
- The implicit empty `init` function is now present by default in the contract if not declared: PR [#167](https://github.com/tact-lang/tact/pull/167)
- `@stdlib/stoppable` now imports `@stdlib/ownable` so the programmer does not have to do it separately: PR [#193](https://github.com/tact-lang/tact/pull/193)
- Support escape sequences for strings (`\\`, `\"`, `\n`, `\r`, `\t`, `\v`, `\b`, `\f`, `\u{ABC}`, `\uABCD`, `\xAB`): PR [#192](https://github.com/tact-lang/tact/pull/192)

### Fixed
- Incorrect "already exists" errors when using names such as `toString` or `valueOf`: PR [#208](https://github.com/tact-lang/tact/pull/208)
- Escape backticks in error messages for generated TypeScript code: PR [#192](https://github.com/tact-lang/tact/pull/192)

## [1.2.0] - 2024-02-29

Expand Down
6 changes: 5 additions & 1 deletion src/bindings/writeTypescript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,11 @@ export function writeTypescript(abi: ContractABI, init?: {
w.inIndent(() => {
if (abi.errors) {
for (const k in abi.errors) {
w.append(`${k}: { message: \`${abi.errors[parseInt(k, 10)].message}\` },`);
w.append(
`${k}: { message: \`${abi.errors[
parseInt(k, 10)
].message.replaceAll('`', '\\`')}\` },`
);
}
}
});
Expand Down
39 changes: 38 additions & 1 deletion src/generator/writers/writeExpression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,44 @@ export function writeExpression(f: ASTExpression, ctx: WriterContext): string {
//

if (f.kind === 'string') {
const id = writeString(f.value, ctx);
const s = f.value.replace(/\\\\|\\"|\\n|\\r|\\t|\\v|\\b|\\f|\\u{([0-9A-Fa-f]+)}|\\u([0-9A-Fa-f]{4})|\\x([0-9A-Fa-f]{2})/g, (match, unicodeCodePoint, unicodeEscape, hexEscape) => {
switch (match) {
case '\\\\':
return '\\';
case '\\"':
return '"';
case '\\n':
return '\n';
case '\\r':
return '\r';
case '\\t':
return '\t';
case '\\v':
return '\v';
case '\\b':
return '\b';
case '\\f':
return '\f';
default:
// Handle Unicode code point escape
if (unicodeCodePoint) {
const codePoint = parseInt(unicodeCodePoint, 16);
return String.fromCodePoint(codePoint);
}
// Handle Unicode escape
if (unicodeEscape) {
const codeUnit = parseInt(unicodeEscape, 16);
return String.fromCharCode(codeUnit);
}
// Handle hex escape
if (hexEscape) {
const hexValue = parseInt(hexEscape, 16);
return String.fromCharCode(hexValue);
}
return match;
}
});
const id = writeString(s, ctx);
ctx.used(id);
return `${id}()`;
}
Expand Down
15 changes: 13 additions & 2 deletions src/grammar/grammar.ohm
Original file line number Diff line number Diff line change
Expand Up @@ -215,8 +215,19 @@ Tact {
boolLiteral = ("true" | "false") ~idPart

// String literal
stringLiteralCharacter = ~("\"" | "\\" | lineTerminator) any
stringLiteral = "\"" stringLiteralCharacter* "\""
stringLiteral = "\"" (nonQuoteOrBackslashChar | escapeSequence)* "\""
nonQuoteOrBackslashChar = ~("\"" | "\\") any
escapeSequence = "\\\\" -- backslash
| "\\\"" -- doubleQuote
| "\\n" -- newline
| "\\r" -- carriageReturn
| "\\t" -- tab
| "\\v" -- verticalTab
| "\\b" -- backspace
| "\\f" -- formFeed
| "\\u{" hexDigit hexDigit? hexDigit? hexDigit? hexDigit? hexDigit? "}" -- unicodeCodePoint
| "\\u" hexDigit hexDigit hexDigit hexDigit -- unicodeEscape
| "\\x" hexDigit hexDigit -- hexEscape

// Keywords
// NOTE Order is important
Expand Down
14 changes: 13 additions & 1 deletion src/grammar/grammar.ohm-bundle.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,20 @@ export interface TactActionDict<T> extends ActionDict<T> {
funcLetter?: (this: NonterminalNode, arg0: NonterminalNode | TerminalNode) => T;
funcId?: (this: NonterminalNode, arg0: NonterminalNode, arg1: IterationNode) => T;
boolLiteral?: (this: NonterminalNode, arg0: TerminalNode) => T;
stringLiteralCharacter?: (this: NonterminalNode, arg0: NonterminalNode) => T;
stringLiteral?: (this: NonterminalNode, arg0: TerminalNode, arg1: IterationNode, arg2: TerminalNode) => T;
nonQuoteOrBackslashChar?: (this: NonterminalNode, arg0: NonterminalNode) => T;
escapeSequence_backslash?: (this: NonterminalNode, arg0: TerminalNode) => T;
escapeSequence_doubleQuote?: (this: NonterminalNode, arg0: TerminalNode) => T;
escapeSequence_newline?: (this: NonterminalNode, arg0: TerminalNode) => T;
escapeSequence_carriageReturn?: (this: NonterminalNode, arg0: TerminalNode) => T;
escapeSequence_tab?: (this: NonterminalNode, arg0: TerminalNode) => T;
escapeSequence_verticalTab?: (this: NonterminalNode, arg0: TerminalNode) => T;
escapeSequence_backspace?: (this: NonterminalNode, arg0: TerminalNode) => T;
escapeSequence_formFeed?: (this: NonterminalNode, arg0: TerminalNode) => T;
escapeSequence_unicodeCodePoint?: (this: NonterminalNode, arg0: TerminalNode, arg1: NonterminalNode, arg2: IterationNode, arg3: IterationNode, arg4: IterationNode, arg5: IterationNode, arg6: IterationNode, arg7: TerminalNode) => T;
escapeSequence_unicodeEscape?: (this: NonterminalNode, arg0: TerminalNode, arg1: NonterminalNode, arg2: NonterminalNode, arg3: NonterminalNode, arg4: NonterminalNode) => T;
escapeSequence_hexEscape?: (this: NonterminalNode, arg0: TerminalNode, arg1: NonterminalNode, arg2: NonterminalNode) => T;
escapeSequence?: (this: NonterminalNode, arg0: NonterminalNode) => T;
keyword?: (this: NonterminalNode, arg0: NonterminalNode) => T;
contract?: (this: NonterminalNode, arg0: TerminalNode) => T;
let?: (this: NonterminalNode, arg0: TerminalNode) => T;
Expand Down
2 changes: 1 addition & 1 deletion src/grammar/grammar.ohm-bundle.js

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions src/test/__snapshots__/bugs.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ exports[`bugs should deploy contract correctly 1`] = `
"$type": "received",
"message": {
"body": {
"cell": "x{178D45190000000000000000502540BE400801D98D6D0FE85B55D6D58229C8ED55470B1C744D6BEEAD475C27236E9908A993110016E3A425A4E75B646191AC9A34FE5D050BD101A5C490F87D01C66D885D09BC1082_}",
"cell": "x{178D45190000000000000000502540BE400800A8651ACEAB81220DBBF8AA566E0CBE7FC77A30EADF32B457F1CF4DE9575E5A210016E3A425A4E75B646191AC9A34FE5D050BD101A5C490F87D01C66D885D09BC1082_}",
"type": "cell",
},
"bounce": false,
"from": "kQDsxraH9C2q62rBFOR2qqOFjjomtfdWo64TkbdMhFTJiLsN",
"to": "kQBGSDIgoUMAjGBNBXjsdBRlfPtqK5rKgQOD5N7yKFfIXeuh",
"from": "kQBUMo1nVcCRBt38VSs3Bl8_470YdW-ZWiv456b0q68tEE5x",
"to": "kQCzGu8bropdv4KCT6jpaDeRXU1coEBo8pjeCjMAt-xYDAXm",
"type": "internal",
"value": "9.95885",
},
Expand All @@ -38,7 +38,7 @@ exports[`bugs should deploy contract correctly 1`] = `
},
},
"bounce": false,
"from": "kQBGSDIgoUMAjGBNBXjsdBRlfPtqK5rKgQOD5N7yKFfIXeuh",
"from": "kQCzGu8bropdv4KCT6jpaDeRXU1coEBo8pjeCjMAt-xYDAXm",
"to": "@treasure(treasure)",
"type": "internal",
"value": "9.914852826",
Expand Down
2 changes: 2 additions & 0 deletions src/test/bugs.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ describe('bugs', () => {
await contract.send(treasure, { value: toNano('10') }, { $$type: 'Mint', receiver: treasure.address, amount: toNano('10') });
await system.run();

expect(contract.abi.errors!['31733'].message).toStrictEqual('condition can`t be...')

expect(tracker.collect()).toMatchSnapshot();
});
});
3 changes: 2 additions & 1 deletion src/test/bugs/bugs.tact
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
import "./issue42.tact";
import "./issue43.tact";
import "./issue43.tact";
import "./issue53.tact";
6 changes: 6 additions & 0 deletions src/test/bugs/issue53.tact
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
contract Issue53 {
init() {}
receive() {
require(1 == 2, "condition can`t be...");
}
}
13 changes: 13 additions & 0 deletions src/test/feature-strings.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,18 @@ describe('feature-strings', () => {
const d = r.beginParse().loadBuffer(r.bits.length / 8);
expect(d.toString('hex')).toEqual(s.toString('hex'));
}

expect(await contract.getStringWithEscapedChars1()).toBe(
"test \n \n \\ \\\n \"string\""
);
expect(await contract.getStringWithEscapedChars2()).toEqual(
"test \n test \t test \r test \b test \f test \" test ' test \\ \\\\ \"_\" \"\" test"
);
expect(await contract.getStringWithEscapedChars3()).toEqual(
"test \\n test \\t test \\r test \\\\b\b test \\f test \\\" test \\' test \v \v \\\\ \\\\\\\\ \\\"_\\\" \\\"\\\" test"
);
expect(await contract.getStringWithEscapedChars4()).toEqual(
"\u{2028}\u{2029} \u0044 \x41\x42\x43"
);
});
});
18 changes: 16 additions & 2 deletions src/test/features/strings.tact
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
contract StringsTester {

init() {

}
Expand Down Expand Up @@ -66,7 +65,6 @@ contract StringsTester {
return b.toString();
}


get fun stringWithLargeNumber(): String {
let b: StringBuilder = beginString();
b.append("Hello, your balance: ");
Expand All @@ -85,4 +83,20 @@ contract StringsTester {
get fun processBase64(src: String): Slice {
return src.fromBase64();
}

get fun stringWithEscapedChars1(): String {
return "test \n \n \\ \\\n \"string\"";
}

get fun stringWithEscapedChars2(): String {
return "test \n test \t test \r test \b test \f test \" test ' test \\ \\\\ \"_\" \"\" test";
}

get fun stringWithEscapedChars3(): String {
return "test \\n test \\t test \\r test \\\\b\b test \\f test \\\" test \\' test \v \v \\\\ \\\\\\\\ \\\"_\\\" \\\"\\\" test";
}

get fun stringWithEscapedChars4(): String {
return "\u{2028}\u{2029} \u0044 \x41\x42\x43";
}
}

0 comments on commit 3b5f211

Please sign in to comment.