https://github.com/hookyns/tst-expression
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 < y.
https://github.com/hookyns/tst-expression
ast expression expression-transformer expression-tree javascript nodejs transformer tree typescript typescript-transformer typescript-transformer-plugin
Last synced: about 1 month ago
JSON representation
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 < y.
- Host: GitHub
- URL: https://github.com/hookyns/tst-expression
- Owner: Hookyns
- License: mit
- Created: 2019-02-10T15:28:34.000Z (over 6 years ago)
- Default Branch: main
- Last Pushed: 2022-08-24T18:45:17.000Z (over 2 years ago)
- Last Synced: 2025-03-26T00:41:26.128Z (about 2 months ago)
- Topics: ast, expression, expression-transformer, expression-tree, javascript, nodejs, transformer, tree, typescript, typescript-transformer, typescript-transformer-plugin
- Language: TypeScript
- Homepage:
- Size: 98.6 KB
- Stars: 18
- Watchers: 1
- Forks: 4
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# C# Like Expression<> for TypeScript (tst-expression)
[](https://www.npmjs.com/package/tst-expression)
[](https://www.npmjs.com/package/tst-expression-transformer)
[](https://github.com/Hookyns/tst-expression/blob/main/LICENSE)[](https://github.com/Hookyns/tst-expression)
> 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 < y.
>
> 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.## How to Start
* Install transformer package and **ttypescript** `npm i tst-expression-transformer ttypescript -D`,
* install runtime package `npm i tst-expression`,
* update *tsconfig.json* file,
```json5
{
"compilerOptions": {
// add tst-expression-transformer plugin
"plugins": [
{
"transform": "tst-expression-transformer"
}
]
}
}
```
* if You use Webpack, modify the webpack config,
```javascript
({
test: /\.(tsx?)$/,
loader: "ts-loader",
options: {
compiler: "ttypescript"
}
})
```
* write Your code,
* compile via `npx ttsc`,
* run the code.## Expression<>
```typescript
type Expression = {
/**
* Compiled executable expression.
*/
compiled: TType;/**
* Expression tree.
*/
expression: ExpressionNode;/**
* Captured context variables used in expression.
*/
context: { [capturedVariableName: string]: any };
}
```## Usage
You can use `Expression<>` as type of method or function parameters.
Transformer `tst-expression-transformer` handle all calls of those functions and methods
and result is the `Expression` object with AST, compiled expression and captured context used by the expression.Thanks to runtime package `tst-expression`, You have typing support with set of type guards,
so You can traverse the expression safely.Simple example, which returns array with name of variable and its value.
```typescript
import { assertIdentifier, assertExpression, Expression } from "tst-expression";const someVariable = 5;
const entry = getEntry(someVariable);console.log(entry); // [ 'someVariable', 5 ]
function getEntry(expression: Expression): [name: string, value: number] {
assertExpression(expression);
assertIdentifier(expression.expression);return [expression.expression.escapedText, expression.compiled];
}
```## Example
A little more complex example which takes expression of some math operations and logs the operation with its result.Result of the example is: `someVariable(5) + 5 - PI(3.141592653589793) = 6.858407346410207`
```typescript
import {
assertBinaryExpression, assertExpression, Expression, ExpressionKind, ExpressionNode,
isBinaryExpression, isNumericLiteral, isIdentifier, isPropertyAccessExpression
} from "tst-expression";const someVariable = 5;
logMathOperation(someVariable + 5 - Math.PI);function logMathOperation(operationExpression: Expression) {
assertExpression(operationExpression);
assertBinaryExpression(operationExpression.expression);const parts = splitBinaryExpressions(operationExpression.expression);
let operation = "";for (const part of parts) {
if (typeof (part) == "string") {
operation += part;
} else if (isNumericLiteral(part)) {
operation += part.text;
} else if (isIdentifier(part)) {
operation += part.escapedText + `(${operationExpression.context[part.escapedText]})`;
} else if (isPropertyAccessExpression(part)) {
if (isIdentifier(part.expression) && isIdentifier(part.name)) {
operation += part.name.escapedText + `(${operationExpression.context[part.expression.escapedText][part.name.escapedText]})`;
} else {
throw new Error("Not implemented.");
}
}
}
console.log(operation, "=", operationExpression.compiled);
}function splitBinaryExpressions(expression: any): Array {
if (isBinaryExpression(expression)) {
return [
...splitBinaryExpressions(expression.left),
" " + getOperator(expression.operatorToken) + " ",
...splitBinaryExpressions(expression.right)
];
}return [expression];
}function getOperator(operatorToken: ExpressionNode): string {
return ({
[ExpressionKind.PlusToken]: "+",
[ExpressionKind.MinusToken]: "-",
[ExpressionKind.SlashToken]: "/",
[ExpressionKind.AsteriskToken]: "*"
} as { [kind: number]: string })[operatorToken.kind];
}
```Transpiled code:
```javascript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tst_expression_1 = require("tst-expression");
const someVariable = 5;
logMathOperation({ compiled: someVariable + 5 - Math.PI, context: { someVariable, Math }, expression: {
"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" } } } });
function logMathOperation(operationExpression) {
(0, tst_expression_1.assertExpression)(operationExpression);
(0, tst_expression_1.assertBinaryExpression)(operationExpression.expression);
const parts = splitBinaryExpressions(operationExpression.expression);
let operation = "";
for (const part of parts) {
if (typeof (part) == "string") {
operation += part;
}
else if ((0, tst_expression_1.isNumericLiteral)(part)) {
operation += part.text;
}
else if ((0, tst_expression_1.isIdentifier)(part)) {
operation += part.escapedText + `(${operationExpression.context[part.escapedText]})`;
}
else if ((0, tst_expression_1.isPropertyAccessExpression)(part)) {
if ((0, tst_expression_1.isIdentifier)(part.expression) && (0, tst_expression_1.isIdentifier)(part.name)) {
operation += part.name.escapedText + `(${operationExpression.context[part.expression.escapedText][part.name.escapedText]})`;
}
else {
throw new Error("Not implemented.");
}
}
}
console.log(operation, "=", operationExpression.compiled);
}
function splitBinaryExpressions(expression) {
if ((0, tst_expression_1.isBinaryExpression)(expression)) {
return [
...splitBinaryExpressions(expression.left),
" " + getOperator(expression.operatorToken) + " ",
...splitBinaryExpressions(expression.right)
];
}
return [expression];
}
function getOperator(operatorToken) {
return {
[tst_expression_1.ExpressionKind.PlusToken]: "+",
[tst_expression_1.ExpressionKind.MinusToken]: "-",
[tst_expression_1.ExpressionKind.SlashToken]: "/",
[tst_expression_1.ExpressionKind.AsteriskToken]: "*"
}[operatorToken.kind];
}
```## Tip
For work with AST you can use https://astexplorer.net which prints tree nicely. Use the TypeScript AST.## License
This project is licensed under the [MIT license](./LICENSE).