From 3a647d82549b7b19e49da418edd1c12a847541fb Mon Sep 17 00:00:00 2001 From: Anton Trunov Date: Sat, 22 Jun 2024 00:02:59 +0400 Subject: [PATCH 1/3] feat(cli): constant expression evaluation --- .github/workflows/tact.yml | 4 ++++ bin/tact | 40 ++++++++++++++++++++++++++------------ src/grammar/grammar.ts | 9 +++++++++ src/node.ts | 2 ++ 4 files changed, 43 insertions(+), 12 deletions(-) diff --git a/.github/workflows/tact.yml b/.github/workflows/tact.yml index f5bf91951..4b76dedd0 100644 --- a/.github/workflows/tact.yml +++ b/.github/workflows/tact.yml @@ -113,6 +113,10 @@ jobs: run: | ! tact --config bin/test/fail.config.json + - name: CLI Test | Evaluate expression + run: | + tact -e '(1 + 2 * (pow(3,4) - 2) << 1 & 0x54 | 33 >> 1) * 2 + 2' + - name: Link Tact compiler run: | yarn link diff --git a/bin/tact b/bin/tact index 3e5f74096..f55ac6d1c 100755 --- a/bin/tact +++ b/bin/tact @@ -19,6 +19,7 @@ meowModule.then( --with-decompilation Full compilation followed by decompilation of produced binary code --func Output intermediate FunC code and exit --check Perform syntax and type checking, then exit + -e, --eval EXPRESSION Evaluate a Tact expression and exit -v, --version Print Tact compiler version and exit -h, --help Display this text and exit @@ -39,18 +40,21 @@ meowModule.then( shortFlag: "c", type: "string", isRequired: (flags, _) => { - // Require a config when the projects are specified AND version/help are not specified - if ( + // Require a config when the projects are specified + // AND version/help are not specified + // AND eval is not specified + return ( flags.projects.length !== 0 && !flags.version && - !flags.help - ) { - return true; - } - // Don't require it otherwise - return false; + !flags.help && + !flags.eval + ); }, }, + eval: { + shortFlag: "e", + type: "string", + }, projects: { shortFlag: "p", type: "string", isMultiple: true }, withDecompilation: { type: "boolean", default: false }, func: { type: "boolean", default: false }, @@ -64,10 +68,7 @@ meowModule.then( // Helper function to write less in following checks const isEmptyConfigAndInput = () => { - if (cli.flags.config === undefined && cli.input.length === 0) { - return true; - } - return false; + return cli.flags.config === undefined && cli.input.length === 0; }; // Show help regardless of other flags @@ -80,6 +81,21 @@ meowModule.then( cli.showVersion(); } + // Evaluate expression regardless of other flags + if (cli.flags.eval) { + const result = main.parseAndEvalExpression(cli.flags.eval); + switch (result.kind) { + case "ok": { + console.log(result.value); + process.exit(0); + } + case "error": { + console.log(result.message); + process.exit(30); + } + } + } + // Disallow specifying both config or Tact source file at the same time if (cli.flags.config !== undefined && cli.input.length > 0) { console.log( diff --git a/src/grammar/grammar.ts b/src/grammar/grammar.ts index b60da1874..7c03c67ec 100644 --- a/src/grammar/grammar.ts +++ b/src/grammar/grammar.ts @@ -3,6 +3,7 @@ import { ASTAugmentedAssignOperation, ASTConstantAttribute, ASTContractAttribute, + ASTExpression, ASTFunctionAttribute, ASTNode, ASTProgram, @@ -1189,6 +1190,14 @@ export function parse( }); } +export function parseExpression(sourceCode: string): ASTExpression { + const matchResult = rawGrammar.match(sourceCode, "Expression"); + if (matchResult.failed()) { + throwParseError(matchResult, ""); + } + return semantics(matchResult).astOfExpression(); +} + export function parseImports( src: string, path: string, diff --git a/src/node.ts b/src/node.ts index 56d50e716..8e5338080 100644 --- a/src/node.ts +++ b/src/node.ts @@ -122,3 +122,5 @@ export async function run(args: { } export { createNodeFileSystem } from "./vfs/createNodeFileSystem"; + +export { parseAndEvalExpression } from "./interpreter"; From 7dec1b33d4d2c5b6b0ff86a04e8610cf659241f2 Mon Sep 17 00:00:00 2001 From: Anton Trunov Date: Sat, 22 Jun 2024 00:08:13 +0400 Subject: [PATCH 2/3] update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 918111c16..e7da0a5bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- `-e` / `--eval` CLI flags to evaluate constant Tact expressions: PR [#462](https://github.com/tact-lang/tact/pull/462) + ### Changed ### Fixed From 0175fdfaff625be48ab3bd4776f57b439b238156 Mon Sep 17 00:00:00 2001 From: Anton Trunov Date: Sat, 22 Jun 2024 00:10:14 +0400 Subject: [PATCH 3/3] commit forgotten new file --- src/interpreter.ts | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 src/interpreter.ts diff --git a/src/interpreter.ts b/src/interpreter.ts new file mode 100644 index 000000000..79463ca88 --- /dev/null +++ b/src/interpreter.ts @@ -0,0 +1,27 @@ +import { evalConstantExpression } from "./constEval"; +import { CompilerContext } from "./context"; +import { TactConstEvalError, TactParseError } from "./errors"; +import { parseExpression } from "./grammar/grammar"; +import { Value } from "./types/types"; + +export type EvalResult = + | { kind: "ok"; value: Value } + | { kind: "error"; message: string }; + +export function parseAndEvalExpression(sourceCode: string): EvalResult { + try { + const ast = parseExpression(sourceCode); + const constEvalResult = evalConstantExpression( + ast, + new CompilerContext(), + ); + return { kind: "ok", value: constEvalResult }; + } catch (error) { + if ( + error instanceof TactParseError || + error instanceof TactConstEvalError + ) + return { kind: "error", message: error.message }; + throw error; + } +}