{"id":20366578,"url":"https://github.com/hookyns/tst-expression","last_synced_at":"2025-04-12T05:11:20.267Z","repository":{"id":50702677,"uuid":"169992750","full_name":"Hookyns/tst-expression","owner":"Hookyns","description":"Expression trees for TypeScript similar to C#. Expression trees represent code in a tree-like data structure, where each node is an expression, for example, a method call or a binary operation such as x \u003c y.","archived":false,"fork":false,"pushed_at":"2022-08-24T18:45:17.000Z","size":101,"stargazers_count":18,"open_issues_count":2,"forks_count":4,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-26T00:41:26.128Z","etag":null,"topics":["ast","expression","expression-transformer","expression-tree","javascript","nodejs","transformer","tree","typescript","typescript-transformer","typescript-transformer-plugin"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Hookyns.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-02-10T15:28:34.000Z","updated_at":"2025-03-24T15:57:40.000Z","dependencies_parsed_at":"2022-09-01T23:21:27.530Z","dependency_job_id":null,"html_url":"https://github.com/Hookyns/tst-expression","commit_stats":null,"previous_names":["hookyns/expression-transformer"],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Hookyns%2Ftst-expression","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Hookyns%2Ftst-expression/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Hookyns%2Ftst-expression/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Hookyns%2Ftst-expression/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Hookyns","download_url":"https://codeload.github.com/Hookyns/tst-expression/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248519545,"owners_count":21117761,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ast","expression","expression-transformer","expression-tree","javascript","nodejs","transformer","tree","typescript","typescript-transformer","typescript-transformer-plugin"],"created_at":"2024-11-15T00:25:42.911Z","updated_at":"2025-04-12T05:11:20.233Z","avatar_url":"https://github.com/Hookyns.png","language":"TypeScript","readme":"# C# Like Expression\u003c\u003e for TypeScript (tst-expression)\n[![NPM version](https://img.shields.io/npm/v/tst-expression.svg?colorB=brightgreen\u0026label=tst-expression)](https://www.npmjs.com/package/tst-expression) \n[![NPM version](https://img.shields.io/npm/v/tst-expression-transformer.svg?colorB=brightgreen\u0026label=tst-expression-transformer)](https://www.npmjs.com/package/tst-expression-transformer) \n[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://github.com/Hookyns/tst-expression/blob/main/LICENSE)\n\n[![Readme Card](https://github-readme-stats.vercel.app/api/pin/?username=hookyns\u0026repo=tst-expression\u0026theme=tokyonight)](https://github.com/Hookyns/tst-expression)\n\n\u003e Expression trees for TypeScript similar to C#. \n\u003e \n\u003e Expression trees represent code in a tree-like data structure, where each node is an expression, for example, a method call or a binary operation such as x \u003c y.\n\u003e \n\u003e You can compile and run code represented by expression trees. This enables dynamic operations over compiled code, with access to the context captured by the code.\n\n## How to Start\n* Install transformer package and **ttypescript** `npm i tst-expression-transformer ttypescript -D`,\n* install runtime package `npm i tst-expression`,\n* update *tsconfig.json* file,\n```json5\n{\n    \"compilerOptions\": {\n        // add tst-expression-transformer plugin\n        \"plugins\": [\n            {\n                \"transform\": \"tst-expression-transformer\"\n            }\n        ]\n    }\n}\n```\n* if You use Webpack, modify the webpack config,\n```javascript\n({\n    test: /\\.(tsx?)$/,\n    loader: \"ts-loader\",\n    options: {\n        compiler: \"ttypescript\"\n    }\n})\n```\n* write Your code,\n* compile via `npx ttsc`,\n* run the code.\n\n## Expression\u003c\u003e\n```typescript\ntype Expression\u003cTType\u003e = {\n    /**\n     * Compiled executable expression.\n     */\n    compiled: TType;\n\n    /**\n     * Expression tree.\n     */\n    expression: ExpressionNode;\n\n    /**\n     * Captured context variables used in expression.\n     */\n    context: { [capturedVariableName: string]: any };\n}\n```\n\n## Usage\nYou can use `Expression\u003c\u003e` as type of method or function parameters. \nTransformer `tst-expression-transformer` handle all calls of those functions and methods \nand result is the `Expression` object with AST, compiled expression and captured context used by the expression.\n\nThanks to runtime package `tst-expression`, You have typing support with set of type guards, \nso You can traverse the expression safely.\n\nSimple example, which returns array with name of variable and its value.\n```typescript\nimport { assertIdentifier, assertExpression, Expression } from \"tst-expression\";\n\nconst someVariable = 5;\nconst entry = getEntry(someVariable);\n\nconsole.log(entry); // [ 'someVariable', 5 ]\n\nfunction getEntry(expression: Expression\u003cnumber\u003e): [name: string, value: number] {\n    assertExpression(expression);\n    assertIdentifier(expression.expression);\n\n    return [expression.expression.escapedText, expression.compiled];\n}\n```\n\n## Example\nA little more complex example which takes expression of some math operations and logs the operation with its result.\n\nResult of the example is: `someVariable(5) + 5 - PI(3.141592653589793) = 6.858407346410207`\n\n```typescript\nimport { \n    assertBinaryExpression, assertExpression, Expression, ExpressionKind, ExpressionNode, \n    isBinaryExpression, isNumericLiteral, isIdentifier, isPropertyAccessExpression \n} from \"tst-expression\";\n\nconst someVariable = 5;\nlogMathOperation(someVariable + 5 - Math.PI);\n\nfunction logMathOperation(operationExpression: Expression\u003cnumber\u003e) {\n    assertExpression(operationExpression);\n    assertBinaryExpression(operationExpression.expression);\n\n    const parts = splitBinaryExpressions(operationExpression.expression);\n    let operation = \"\";\n\n    for (const part of parts) {\n        if (typeof (part) == \"string\") {\n            operation += part;\n        } else if (isNumericLiteral(part)) {\n            operation += part.text;\n        } else if (isIdentifier(part)) {\n            operation += part.escapedText + `(${operationExpression.context[part.escapedText]})`;\n        } else if (isPropertyAccessExpression(part)) {\n            if (isIdentifier(part.expression) \u0026\u0026 isIdentifier(part.name)) {\n                operation += part.name.escapedText + `(${operationExpression.context[part.expression.escapedText][part.name.escapedText]})`;\n            } else {\n                throw new Error(\"Not implemented.\");\n            }\n        }\n    }\n    \n    console.log(operation, \"=\", operationExpression.compiled);\n}\n\nfunction splitBinaryExpressions(expression: any): Array\u003cExpressionNode | string\u003e {\n    if (isBinaryExpression(expression)) {\n        return [\n            ...splitBinaryExpressions(expression.left),\n            \" \" + getOperator(expression.operatorToken) + \" \",\n            ...splitBinaryExpressions(expression.right)\n        ];\n    }\n\n    return [expression];\n}\n\nfunction getOperator(operatorToken: ExpressionNode): string {\n    return ({\n        [ExpressionKind.PlusToken]: \"+\",\n        [ExpressionKind.MinusToken]: \"-\",\n        [ExpressionKind.SlashToken]: \"/\",\n        [ExpressionKind.AsteriskToken]: \"*\"\n    } as { [kind: number]: string })[operatorToken.kind];\n}\n```\n\nTranspiled code:\n```javascript\n\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nconst tst_expression_1 = require(\"tst-expression\");\nconst someVariable = 5;\nlogMathOperation({ compiled: someVariable + 5 - Math.PI, context: { someVariable, Math }, expression: {\n        \"flags\": 0, \"kind\": 219, \"left\": { \"flags\": 0, \"kind\": 219, \"left\": { \"flags\": 0, \"kind\": 79, \"escapedText\": \"someVariable\" }, \"operatorToken\": { \"flags\": 0, \"kind\": 39 }, \"right\": { \"flags\": 0, \"kind\": 8, \"text\": \"5\", \"numericLiteralFlags\": 0 } }, \"operatorToken\": { \"flags\": 0, \"kind\": 40 }, \"right\": { \"flags\": 0, \"kind\": 204, \"expression\": { \"flags\": 0, \"kind\": 79, \"escapedText\": \"Math\" }, \"name\": { \"flags\": 0, \"kind\": 79, \"escapedText\": \"PI\" } } } });\nfunction logMathOperation(operationExpression) {\n    (0, tst_expression_1.assertExpression)(operationExpression);\n    (0, tst_expression_1.assertBinaryExpression)(operationExpression.expression);\n    const parts = splitBinaryExpressions(operationExpression.expression);\n    let operation = \"\";\n    for (const part of parts) {\n        if (typeof (part) == \"string\") {\n            operation += part;\n        }\n        else if ((0, tst_expression_1.isNumericLiteral)(part)) {\n            operation += part.text;\n        }\n        else if ((0, tst_expression_1.isIdentifier)(part)) {\n            operation += part.escapedText + `(${operationExpression.context[part.escapedText]})`;\n        }\n        else if ((0, tst_expression_1.isPropertyAccessExpression)(part)) {\n            if ((0, tst_expression_1.isIdentifier)(part.expression) \u0026\u0026 (0, tst_expression_1.isIdentifier)(part.name)) {\n                operation += part.name.escapedText + `(${operationExpression.context[part.expression.escapedText][part.name.escapedText]})`;\n            }\n            else {\n                throw new Error(\"Not implemented.\");\n            }\n        }\n    }\n    console.log(operation, \"=\", operationExpression.compiled);\n}\nfunction splitBinaryExpressions(expression) {\n    if ((0, tst_expression_1.isBinaryExpression)(expression)) {\n        return [\n            ...splitBinaryExpressions(expression.left),\n            \" \" + getOperator(expression.operatorToken) + \" \",\n            ...splitBinaryExpressions(expression.right)\n        ];\n    }\n    return [expression];\n}\nfunction getOperator(operatorToken) {\n    return {\n        [tst_expression_1.ExpressionKind.PlusToken]: \"+\",\n        [tst_expression_1.ExpressionKind.MinusToken]: \"-\",\n        [tst_expression_1.ExpressionKind.SlashToken]: \"/\",\n        [tst_expression_1.ExpressionKind.AsteriskToken]: \"*\"\n    }[operatorToken.kind];\n}\n```\n\n## Tip\nFor work with AST you can use https://astexplorer.net which prints tree nicely. Use the TypeScript AST.\n\n## License\nThis project is licensed under the [MIT license](./LICENSE).","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhookyns%2Ftst-expression","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhookyns%2Ftst-expression","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhookyns%2Ftst-expression/lists"}