├── .github └── workflows │ └── node.js.yml ├── .gitignore ├── .npmignore ├── .npmrc ├── .vscode ├── launch.json └── settings.json ├── README.md ├── badges ├── coverage-branches.svg ├── coverage-functions.svg ├── coverage-jest coverage.svg ├── coverage-lines.svg └── coverage-statements.svg ├── benchTest.js ├── dist ├── transpiler.cjs ├── transpiler.d.ts └── transpiler.js ├── examples ├── cs │ └── interop-non-transpilable-code │ │ ├── README.md │ │ ├── input │ │ ├── index.ts │ │ └── nonTranspilableHelper.ts │ │ ├── output │ │ ├── helpers.cs │ │ └── index.cs │ │ └── transpiler.ts ├── php │ └── interop-non-transpilable-code │ │ ├── README.md │ │ ├── input │ │ ├── index.ts │ │ └── nonTranspilableHelper.ts │ │ ├── output │ │ ├── helper.php │ │ └── index.php │ │ └── transpiler.ts └── python │ └── interop-non-transpilable-code │ ├── README.md │ ├── input │ ├── index.ts │ └── nonTranspilableHelper.ts │ ├── output │ ├── helper.py │ └── index.py │ └── transpiler.ts ├── go.mod ├── helpers ├── c# │ └── helpers.cs └── go │ ├── go.mod │ ├── helpers.go │ ├── main │ └── main.go ├── jest.config.json ├── manual.ts ├── package-lock.json ├── package.json ├── publish.sh ├── src ├── .eslintrc ├── baseTranspiler.ts ├── csharpTranspiler.ts ├── dirname.cjs ├── goTranspiler.ts ├── logger.ts ├── phpTranspiler.ts ├── pythonTranspiler.ts ├── transpiler.ts ├── types.ts └── utils.ts ├── test.ts ├── tests ├── csharpTranspiler.test.ts ├── fileExportsTranspiler.test.ts ├── fileImportsTranspiler.test.ts ├── files │ ├── input │ │ └── test1.ts │ └── output │ │ ├── php │ │ └── test1.php │ │ └── python │ │ └── test1.py ├── goTranspiler.test.ts ├── integration │ ├── .gitignore │ ├── cs │ │ ├── Program.cs │ │ ├── TranspilerHelpers.cs │ │ ├── cs.csproj │ │ ├── cs.sln │ │ └── transpilable.cs │ ├── go │ │ ├── helpers.go │ │ ├── main.go │ │ └── transpilable.go │ ├── php │ │ ├── init.php │ │ └── transpilable.php │ ├── py │ │ ├── __pycache__ │ │ │ └── transpilable.cpython-311.pyc │ │ ├── init.py │ │ └── transpilable.py │ ├── source │ │ ├── init.ts │ │ └── transpilable.ts │ └── test.ts ├── phpTranspiler.test.ts └── pythonTranspiler.test.ts ├── tmp.ts ├── tsconfig.json └── worker.ts /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | paths-ignore: 7 | - '**/README.md' 8 | - '**.cs' 9 | - 'examples/' 10 | - '.npmignore' 11 | - '.gitignore' 12 | pull_request: 13 | branches: [ master ] 14 | 15 | jobs: 16 | build: 17 | 18 | runs-on: ubuntu-latest 19 | 20 | strategy: 21 | matrix: 22 | node-version: [18.x] 23 | 24 | steps: 25 | - uses: actions/checkout@v3 26 | - name: Use Node.js ${{ matrix.node-version }} 27 | uses: actions/setup-node@v3 28 | with: 29 | node-version: ${{ matrix.node-version }} 30 | - name: Install 31 | run: npm ci 32 | - name: Build 33 | run: npm run build --if-present 34 | - name: Test 35 | run: go build ./tests/integration/go # debug just to download the go version, do this properly todo! 36 | - name: Test 37 | run: npm run test-ci 38 | - name: Integration test 39 | run: npm run integration 40 | - name: Generating coverage badges 41 | if: github.ref == 'refs/heads/master' 42 | uses: jpb06/jest-badges-action@latest 43 | with: 44 | branches: master 45 | - name: Pushing generated files 46 | if: github.ref == 'refs/heads/master' 47 | uses: EndBug/add-and-commit@v9 # You can change this to use a specific version. 48 | with: 49 | add: 'dist/ -f' 50 | message: 'Update generated files' 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | coverage/ 3 | dist/ 4 | out/ -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | badges/ 3 | examples/ 4 | node_modules/ 5 | tests/ 6 | manual.ts 7 | out/ -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | //registry.npmjs.org/:_authToken=${NPM_TOKEN} -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // { 2 | // "version": "0.2.0", 3 | // "configurations": [ 4 | // { 5 | // "name": "Launch Program", 6 | // "runtimeExecutable": "ts-node", 7 | // // "cwd": "${workspaceFolder}/packages/api", 8 | // "request": "launch", 9 | // "skipFiles": ["/**"], 10 | // "type": "node", 11 | // // "args": ["build/index.js"], 12 | // // "runtimeArgs": [ "-r", "ts-node/register" ], 13 | // "outputCapture": "std" 14 | // } 15 | // ] 16 | // } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # AST-Transpiler 3 | [![Build](https://github.com/ccxt/ast-transpiler/actions/workflows/node.js.yml/badge.svg)](https://github.com/ccxt/ast-transpiler/actions/workflows/node.js.yml) 4 | ![Jest coverage](./badges/coverage-jest%20coverage.svg) 5 | ![Functions](./badges/coverage-functions.svg) 6 | ![Lines](./badges/coverage-lines.svg) 7 | ![Statements](./badges/coverage-statements.svg) 8 | 9 | **⚠️ WORK IN PROGRESS** 10 | 11 | `ast-transpiler` is a library that allows transpiling typescript code to different languages using typescript's abstract syntax tree (AST) and type checker in an easy way abstracting most of the complexity behind the process. 12 | 13 | ### [Install](#-installation) · [Usage](#-usage) · [Languages](#-languages) · [Options](#-options) · [Overrides](#-overrides) · [Examples](https://github.com/carlosmiei/ast-transpiler/tree/master/examples) 14 | 15 | As expected, it's not possible to transpile Typescript to Python or PHP in a 1:1 parity because they are different languages a lot of features are not interchangeable. Nonetheless, this library supports as many features as possible, doing some adaptions (more to come). 16 | 17 | Although we transpile TS code directly to the other languages, this library does not touch import or exports statements because each language has its own module/namespace model. Instead, we return a unified list of imports and exports separately, allowing the user to adapt it to the target language easily and append it to the generated code (check `IFileImport` and `IFileExport`). 18 | 19 | In order to facilitate the transpilation process, we should try to add as many types as possible otherwise, we might get invalid results. 20 | 21 | **❌ Bad Example** 22 | ```Javascript 23 | function importantFunction(argument) { // type of argumment is unknown 24 | const length = argument.length; 25 | } 26 | ``` 27 | 28 | ⬆️ In this case, we have no means to infer the argument's type, so for instance in PHP we don't know if `.length` should be transpiled to `str_len` or `count`. 29 | 30 | 31 | **✅ Good Example** 32 | 33 | ```Javascript 34 | function importantFunction(argument: string[]) { 35 | const length = argument.length; 36 | } 37 | ``` 38 | ⬆️ argument's type is known so all good, no ambiguities here. 39 | 40 | 41 | **❌ Bad Example (C# only)** 42 | ```Javascript 43 | class x { 44 | myMethod () { 45 | let i = 1; 46 | i = "string"; 47 | } 48 | } 49 | ``` 50 | Unlike other languages, C# does not allow changing the type of a variable/rebinding the same name to a different type (we don't use `dynamic` values because of the performance impact). So, if you intend to transpile to C# avoid this pattern. 51 | 52 | #### What about javascript? 53 | Obviously, all Javascript code is valid Typescript, so in theory, it should transpile Javascript seamlessly as well. This is in part true, but for the lacking of types, we might get some invalid results when the types are not clear (check bad example). 54 | 55 | #### ESM or CJS? 56 | This library works better with ESM because has dedicated `import/export` tokens in the AST whereas CJS `require/module.exports` are just regular properties and call expressions. **Nonetheless, both are supported**. 57 | 58 | ## 🎯 Languages 59 | Currently the following languages are supported: 60 | - Python 61 | - PHP 62 | - C# (WIP) 63 | 64 | ## 🔌 Installation 65 | 66 | Use the package manager [npm](https://www.npmjs.com/) to install foobar. 67 | 68 | ```bash 69 | npm install ast-transpiler 70 | ``` 71 | 72 | ## 📌 Usage 73 | 74 | `ast-transpiler` is a hybrid package, supporting ESM and CJS out of the box. Choose the one that fits you better. 75 | 76 | ### Transpiling Typescript to Python from string 77 | ```Javascript 78 | import Transpiler from 'ast-transpiler' 79 | 80 | const transpiler = new Transpiler({ 81 | python: { 82 | uncamelcaseIdentifiers: true, 83 | } 84 | }); 85 | 86 | const ts = "const myVar = 1;" 87 | const transpiled = transpiler.transpilePython(ts); 88 | 89 | console.log(transpiled.content) // prints my_var = 1 90 | ``` 91 | 92 | ### Transpiling Typescript to PHP from file 93 | (preferred way if needs to resolve imports) 94 | 95 | ```Javascript 96 | const Transpiler = require(`ast-transpiler`); 97 | 98 | const transpiler = new Transpiler(); 99 | const transpiled = transpiler.transpilePhpByPath("./my/path/file.ts"); 100 | 101 | console.log(transpiled.content) // prints transpiled php 102 | console.log(transpiled.imports) // prints unified imports statements if any 103 | console.log(transpiled.exports) // prints unified export statements if any 104 | ``` 105 | 106 | ## ⚡ C# Notes 107 | ### Helpers 108 | C# is very different from languages like Typescript, Python or PHP since it's statically typed and much more restricted than the others mentioned. Things like falsy values, empty default objects, dynamic properties, different type comparison, untyped arguments/return type, etc do not exist so I had to create a set of wrappers that will emulate these features in C#. So in order to make your code run you need to make all the **helper** methods available [here](https://github.com/carlosmiei/ast-transpiler/blob/c%23/helpers/c%23/helpers.cs) accessible from your code (wip). 109 | 110 | 111 | ### Mandatory return type/parameter type 112 | As you probably know c# requires you to define the type for every parameter and method declaration whereas Typescript/Javascript does not, so this package will try to infer the type if not available or default to `object`, so it's preferable to declare them to avoid errors. 113 | 114 | ### Number ambiguity 115 | Unfortunately Typescript/Javascript has only one type, `Number` which represents both integers and floating-point numbers. This is problematic because C# offers a variety of types (uint, int, Int64, double, float) to represent numeric values so it's very hard if not impossible to correctly transpile `Number`. For now we're converting it to `object` . 116 | 117 | ### Json Objects 118 | Unlike Ts, Py, PHP, we don't have a direct way to represent a JSON object, so I try to represent it using either a `Dictionary` or `List` 119 | 120 | Warning: Under active development so can change at any time! 121 | 122 | ## ✅ Supported Features 123 | - Identation 124 | - Does not rely on the original indentation but on the hierarchy of the statements, can be controlled by setting `DEFAULT_IDENTATION` (default value is four spaces) 125 | - Variable declarations 126 | - Class/function/methods declarations 127 | - For/While loops 128 | - Basic string manipulation 129 | - `concat`, `length`, `includes`, `indexOf` 130 | - Basic arrays manipulation 131 | - `includes`, `length`, `push`, `pop`, `reverse`, `shift` 132 | - Basic object manipulation 133 | - `Object.keys`, `Object.values` 134 | - Binary expressions 135 | - `+,-,*,/,mod` 136 | - Condition expressions 137 | - `&&, ||` 138 | - Basic math functions 139 | - `Math.min, Math.max, Math.floor, Math.ceil, parseFloat, parseInt` 140 | - Basic JSON methods 141 | - `JSON.stringify, JSON.parse` 142 | - Throw statements 143 | - Conditional Expressions 144 | - Break expressions 145 | - Basic instanceof statements 146 | - Comments 147 | - ⚠️ Some comments are not available in the AST, so those are lost 148 | - Snake casing of variables/calls/functions/methods 149 | - Import/Export statements parsing (ESM/CJS) 150 | - ⚠️ Avoid complex CJS exports 151 | - Basic async support (async methods/functions, await, promise.all) 152 | - ⚠️ PHP: By default it uses the `ReactPHP` approach 153 | - Scope Resolution Operator conversion (PHP only) 154 | - etc 155 | 156 | We will try to add more features/conversions in the future but this process is also customizable, check the Overrides section. 157 | 158 | ## 🔧 Options 159 | 160 | As mentioned above, this library allows for some customization through the offered options and available overrides. 161 | 162 | Currently there are two generic boolean transpiling options, `uncamelcaseIdentifiers` and `asyncTranspiling`. As the name suggests the former defines if all identifiers (variables, methods, functions, expression calls) should uncamelcased and the latter if we want our transpiled code to be async. 163 | 164 | You can also turn warn the warnings by setting the `verbose` option. 165 | 166 | They can be set upon instantiating our transpiler, or using setter methods 167 | 168 | ```Javascript 169 | const transpiler = new Transpiler({ 170 | verbose: true 171 | python: { 172 | uncamelcaseIdentifiers: false, // default value 173 | asyncTranspiling: true // default value 174 | }, 175 | php: { 176 | uncamelcaseIdentifiers: false, // default value 177 | asyncTranspiling: true // default value 178 | } 179 | }); 180 | 181 | // Alternatively 182 | transpiler.setPhpUncamelCaseIdentifiers(true); 183 | transpiler.setPythonAsyncTranspiling(false); 184 | ``` 185 | 186 | ## 🔨 Overrides 187 | 188 | There is no perfect recipe for transpiling one language in another completely different so we have to made some choices that you might not find the most correct or might want to change it slightly. For that reason this library exposes some objects and methods that you might load up with your own options. 189 | ### Parser 190 | 191 | This object contains all tokens used to convert one language into another (if token, return token, while token, etc). Let's say that you prefer the `array()` notation instead of the default `[]` syntax. You can easily do that by overriding the `ARRAY_OPENING_TOKEN` and `ARRAY_CLOSING_TOKEN`. You can check all available tokens [here](https://github.com/carlosmiei/ast-transpiler/blob/master/dist/transpiler.d.ts#L19) 192 | 193 | Example: 194 | 195 | ```Javascript 196 | const customParserConfig = { 197 | 'ARRAY_OPENING_TOKEN': 'array(', 198 | 'ARRAY_CLOSING_TOKEN': ')' 199 | } 200 | 201 | const config = { 202 | "php": { 203 | "parser": customParserConfig 204 | } 205 | } 206 | const transpiler = new Transpiler(config) 207 | ``` 208 | 209 | ### FullPropertyAccessReplacements 210 | 211 | By default this library will literally convert property access expressions, for instance `myVar.x` will be converted to `myVar.x` in python but there are certain properties we want map to a different value in order to preserve functionality. In python we don't want to use `console.log` to print a message, so we need to convert this property to `print`. `FullPropertyAccessReplacements` contains all of those property conversions. So if we want to convert `JSON.parse` to `json.loads` we just need to add it here (this particular conversion is done by default so you don't need to add it manually). 212 | 213 | ```Javascript 214 | 215 | const customFullPropertyAccessReplacements = { 216 | 'JSON.parse': 'json.loads', 217 | } 218 | 219 | const config = { 220 | "python": { 221 | "FullPropertyAccessReplacements": customParserConfig 222 | } 223 | } 224 | ``` 225 | 226 | #### LeftPropertyAccessReplacements 227 | - Same logic as for `FullPropertyAccessReplacements` but we should use this object when we want to replace the `left` side only. This is useful for mapping `this` to the correspondent value in the other language, but you might want to customize it as well. 228 | 229 | ```Javascript 230 | const LeftPropertyAccessReplacements = { 231 | 'this': 'self', 232 | } 233 | 234 | const config = { 235 | "python": { 236 | "LeftPropertyAccessReplacements": LeftPropertyAccessReplacements 237 | } 238 | } 239 | // this.x will be converted to self.x 240 | ``` 241 | #### RightPropertyAccessReplacements 242 | - Same story as for `FullPropertyAccessReplacements` but only replaces the `right` side. 243 | 244 | ```Javascript 245 | const customRightPropertyAccessReplacements = { 246 | 'toUpperCase': 'upper', 247 | } 248 | 249 | const config = { 250 | "python": { 251 | "RightPropertyAccessReplacements": customRightPropertyAccessReplacements 252 | } 253 | } 254 | // x.toUpperCase() will be converted to x.upper() 255 | ``` 256 | 257 | #### CallExpressionReplacements 258 | Similar to `FullPropertyAccessReplacements` but applies to expression calls only. 259 | 260 | 261 | ```Javascript 262 | const CallExpressionReplacements = { 263 | 'parseInt': 'float', 264 | } 265 | 266 | const config = { 267 | "python": { 268 | "CallExpressionReplacements": CallExpressionReplacements 269 | } 270 | } 271 | // parseInt("1") will be converted to float("1") 272 | ``` 273 | 274 | #### StringLiteralReplacements 275 | Similar to `FullPropertyAccessReplacements` but applies to string literals 276 | 277 | 278 | ```Javascript 279 | const StringLiteralReplacements = { 280 | 'sha256': 'hashlib.sha256', 281 | } 282 | 283 | const config = { 284 | "python": { 285 | "StringLiteralReplacements": StringLiteralReplacements 286 | } 287 | } 288 | // "sha256" will be converted to hashlib.sha256 289 | ``` 290 | 291 | #### ReservedKeywordsReplacements 292 | Languages like C# have a lot of reserved words (string, object, params, base, internal, event, etc) so you can use this object to add your replacements. 293 | 294 | #### ScopeResolutionProps (PHP only) 295 | In PHP, there is the *Scope Resolution Operation* that allows access to *static/constant/overridden* properties, so in these cases, we must use a different property access token. Since this concept does not exist in typescript, we have to rely on a list of properties provided by the user where the `::` operator should be applied. 296 | 297 | 298 | ```Javascript 299 | const ScopeResolutionProps = [ 300 | 'Precise' 301 | ] 302 | 303 | const config = { 304 | "php": { 305 | "ScopeResolutionProps": ScopeResolutionProps 306 | } 307 | } 308 | 309 | // Precise.string() will be converted to Precise::string() 310 | ``` 311 | 312 | #### Methods 313 | 314 | Due to the nature of this process, there are a lot of things that can't be transpiled by direct replacements so we need to add custom logic depending on the target language. For that reason, there are a lot of small atomic methods that can be overridden to add custom modifications. 315 | 316 | ##### Example 1: Removing all comments from PHP code 317 | 318 | ```Javascript 319 | 320 | const transpiler = new Transpiler(); 321 | 322 | function myPrintFunctionComment (comment) { 323 | return ""; 324 | } 325 | 326 | transpiler.phpTranspiler.transformLeadingComment = myPrintFunctionComment; 327 | transpiler.phpTranspiler.transformTrailingComment = myPrintFunctionComment; 328 | ``` 329 | 330 | ##### Example 2: Custom call expression modification in Python 331 | 332 | ```Javascript 333 | const transpiler = new Transpiler(); 334 | 335 | function printOutOfOrderCallExpressionIfAny(node, identation) { 336 | const expressionText = node.expression.getText(); 337 | const args = node.arguments; 338 | if (expressionText === "Array.isArray") { 339 | return "isinstance(" + this.printNode(args[0], 0) + ", list)"; // already done out of the box so no need to add it 340 | } 341 | 342 | return super.printOutOfOrderCallExpressionIfAny(node, identation); // avoid interfering with the builtn modifications 343 | } 344 | 345 | transpiler.pythonTranspiler.printOutOfOrderCallExpressionIfAny = printOutOfOrderCallExpressionIfAny; 346 | ``` 347 | 348 | ## Contributing 349 | 350 | Pull requests are welcome. For major changes, please open an issue first 351 | to discuss what you would like to change. 352 | 353 | Please make sure to update tests as appropriate. 354 | 355 | ## License 356 | 357 | [MIT](https://choosealicense.com/licenses/mit/) 358 | -------------------------------------------------------------------------------- /badges/coverage-branches.svg: -------------------------------------------------------------------------------- 1 | branches: 48.99%branches48.99% -------------------------------------------------------------------------------- /badges/coverage-functions.svg: -------------------------------------------------------------------------------- 1 | functions: 59.01%functions59.01% -------------------------------------------------------------------------------- /badges/coverage-jest coverage.svg: -------------------------------------------------------------------------------- 1 | jest coverage: 59.54%jest coverage59.54% -------------------------------------------------------------------------------- /badges/coverage-lines.svg: -------------------------------------------------------------------------------- 1 | lines: 65.3%lines65.3% -------------------------------------------------------------------------------- /badges/coverage-statements.svg: -------------------------------------------------------------------------------- 1 | statements: 64.84%statements64.84% -------------------------------------------------------------------------------- /examples/cs/interop-non-transpilable-code/README.md: -------------------------------------------------------------------------------- 1 | # POC: Interop with non transpilable code 2 | - This example demonstrates how we can structure a project to combine code transpiled by this library with code that cannot be transpiled. 3 | 4 | - How to run directly from the ts file? 5 | `node --loader ts-node/esm transpiler.ts` 6 | -------------------------------------------------------------------------------- /examples/cs/interop-non-transpilable-code/input/index.ts: -------------------------------------------------------------------------------- 1 | import { nonTranspilableFeature } from "./nonTranspilableHelper.js"; 2 | 3 | // dummy class just for demonstrating purposes 4 | class MyClass { 5 | 6 | mainFeature(message) { 7 | const ar = [ 1, 2, 3 ]; 8 | ar.reverse (); 9 | console.log (ar); 10 | console.log("Hello! I'm inside main class:" + message) 11 | nonTranspilableFeature(); // invoke non-transpilable code here normally 12 | } 13 | 14 | convertToInt(number: string) { 15 | const conversion = parseInt(number); 16 | return conversion; 17 | } 18 | } 19 | 20 | export { 21 | MyClass 22 | } -------------------------------------------------------------------------------- /examples/cs/interop-non-transpilable-code/input/nonTranspilableHelper.ts: -------------------------------------------------------------------------------- 1 | 2 | // assuming this is the "non transpilable" code, isolated in a separated 3 | // file and imported by the main file 4 | function nonTranspilableFeature() { 5 | return 1; 6 | } 7 | 8 | export { 9 | nonTranspilableFeature 10 | } -------------------------------------------------------------------------------- /examples/cs/interop-non-transpilable-code/output/helpers.cs: -------------------------------------------------------------------------------- 1 | 2 | class Helpers 3 | { 4 | public virtual void nonTranspilableFeature(object message) 5 | { 6 | return 1 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/cs/interop-non-transpilable-code/output/index.cs: -------------------------------------------------------------------------------- 1 | using Helpers; 2 | 3 | class MyClass 4 | { 5 | public virtual void mainFeature(object message) 6 | { 7 | object ar = new List() {1, 2, 3}; 8 | ar = (ar as IList).Reverse(); 9 | Console.WriteLine(ar); 10 | Console.WriteLine(add("Hello! I\'m inside main class:", message)); 11 | nonTranspilableFeature(); // invoke non-transpilable code here normally 12 | } 13 | 14 | public virtual object convertToInt(object number) 15 | { 16 | object conversion = parseInt(number); 17 | return conversion; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/cs/interop-non-transpilable-code/transpiler.ts: -------------------------------------------------------------------------------- 1 | 2 | // Small POC showcasing how this library can be applied to real-world libraries 3 | // where not every code is transpilable 4 | 5 | import { Transpiler } from '../../../src/transpiler.js'; 6 | import { writeFileSync } from 'fs'; 7 | 8 | const transpiler = new Transpiler({ 9 | csharp: { 10 | parser: { 11 | "ELEMENT_ACCESS_WRAPPER_OPEN": "getValue(", 12 | "ELEMENT_ACCESS_WRAPPER_CLOSE": ")" 13 | } 14 | } 15 | }); 16 | 17 | const FILE_INPUT = "./input/index.ts"; 18 | const FILE_OUTPUT = "./output/index.cs"; 19 | 20 | const transpiledCode = transpiler.transpileCSharpByPath(FILE_INPUT); 21 | 22 | // handle imports (here we're making use of namespaces to access the method so there is not much to do) 23 | const imports = transpiledCode.imports; 24 | console.log(imports); 25 | 26 | let requireStr = ""; 27 | imports.forEach(imp => { 28 | // custom logic to resolve ts->php imports 29 | if (imp.path === "./nonTranspilableHelper.js" && imp.name === 'nonTranspilableFeature') { 30 | requireStr = "using Helpers;" 31 | } 32 | }) 33 | 34 | 35 | const finalCode = requireStr + '\n\n' + transpiledCode.content; 36 | 37 | writeFileSync(FILE_OUTPUT, finalCode); -------------------------------------------------------------------------------- /examples/php/interop-non-transpilable-code/README.md: -------------------------------------------------------------------------------- 1 | # POC: Interop with non transpilable code 2 | - This example demonstrates how we can structure a project to combine code transpiled by this library with code that cannot be transpiled. 3 | 4 | - How to run directly from the ts file? 5 | `node --loader ts-node/esm transpiler.ts` 6 | -------------------------------------------------------------------------------- /examples/php/interop-non-transpilable-code/input/index.ts: -------------------------------------------------------------------------------- 1 | import { nonTranspilableFeature } from "./nonTranspilableHelper.js"; 2 | 3 | // dummy class just for demonstrating purposes 4 | class MyClass { 5 | 6 | mainFeature(message) { 7 | console.log("Hello! I'm inside main class:" + message) 8 | nonTranspilableFeature(); // invoke non-transpilable code here normally 9 | } 10 | 11 | convertToInt(number: string) { 12 | const conversion = parseInt(number); 13 | return conversion; 14 | } 15 | } 16 | 17 | export { 18 | MyClass 19 | } -------------------------------------------------------------------------------- /examples/php/interop-non-transpilable-code/input/nonTranspilableHelper.ts: -------------------------------------------------------------------------------- 1 | 2 | // assuming this is the "non transpilable" code, isolated in a separated 3 | // file and imported by the main file 4 | function nonTranspilableFeature() { 5 | return 1; 6 | } 7 | 8 | export { 9 | nonTranspilableFeature 10 | } -------------------------------------------------------------------------------- /examples/php/interop-non-transpilable-code/output/helper.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/php/interop-non-transpilable-code/output/index.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/php/interop-non-transpilable-code/transpiler.ts: -------------------------------------------------------------------------------- 1 | 2 | // Small POC showcasing how this library can be applied to real-world libraries 3 | // where not every code is transpilable 4 | 5 | import { Transpiler } from '../../../src/transpiler.js'; 6 | import { writeFileSync } from 'fs'; 7 | 8 | const transpiler = new Transpiler({ 9 | php: { 10 | uncamelcaseIdentifiers: true, 11 | } 12 | }); 13 | 14 | const FILE_INPUT = "./input/index.ts"; 15 | const FILE_OUTPUT = "./output/index.php"; 16 | 17 | const transpiledCode = transpiler.transpilePhpByPath(FILE_INPUT); 18 | 19 | // handle imports (here we're making use of namespaces to access the method so there is not much to do) 20 | const imports = transpiledCode.imports; 21 | console.log(imports); 22 | 23 | let requireStr = ""; 24 | imports.forEach(imp => { 25 | // custom logic to resolve ts->php imports 26 | if (imp.path === "./nonTranspilableHelper.js" && imp.name === 'nonTranspilableFeature') { 27 | requireStr = "require('helper.php');" 28 | } 29 | }) 30 | 31 | 32 | let finalCode = requireStr + '\n\n' + transpiledCode.content; 33 | finalCode = ``; 34 | 35 | writeFileSync(FILE_OUTPUT, finalCode); -------------------------------------------------------------------------------- /examples/python/interop-non-transpilable-code/README.md: -------------------------------------------------------------------------------- 1 | # POC: Interop with non transpilable code 2 | - This example demonstrates how we can structure a project to combine code transpiled by this library with code that cannot be transpiled. 3 | 4 | - How to run directly from the ts file? 5 | `node --loader ts-node/esm transpiler.ts` 6 | -------------------------------------------------------------------------------- /examples/python/interop-non-transpilable-code/input/index.ts: -------------------------------------------------------------------------------- 1 | import { nonTranspilableFeature } from "./nonTranspilableHelper.js"; 2 | 3 | // dummy class just for demonstrating purposes 4 | class MyClass { 5 | 6 | mainFeature(message) { 7 | console.log("Hello! I'm inside main class:" + message) 8 | nonTranspilableFeature(); // invoke non-transpilable code here normally 9 | } 10 | 11 | convertToInt(number: string) { 12 | const conversion = parseInt(number); 13 | return conversion; 14 | } 15 | } 16 | 17 | export { 18 | MyClass 19 | } -------------------------------------------------------------------------------- /examples/python/interop-non-transpilable-code/input/nonTranspilableHelper.ts: -------------------------------------------------------------------------------- 1 | 2 | // assuming this is the "non transpilable" code, isolated in a separated 3 | // file and imported by the main file 4 | function nonTranspilableFeature() { 5 | return 1; 6 | } 7 | 8 | export { 9 | nonTranspilableFeature 10 | } -------------------------------------------------------------------------------- /examples/python/interop-non-transpilable-code/output/helper.py: -------------------------------------------------------------------------------- 1 | # correspondent, manually transpiled "non transpilable code" 2 | 3 | def non_transpilable_feature(): 4 | return 1 -------------------------------------------------------------------------------- /examples/python/interop-non-transpilable-code/output/index.py: -------------------------------------------------------------------------------- 1 | from helper import non_transpilable_feature 2 | 3 | 4 | class MyClass: 5 | def main_feature(self, message): 6 | print('Hello! I\'m inside main class:' + message) 7 | non_transpilable_feature() # invoke non-transpilable code here normally 8 | 9 | def convert_to_int(self, number): 10 | conversion = int(number) 11 | return conversion 12 | -------------------------------------------------------------------------------- /examples/python/interop-non-transpilable-code/transpiler.ts: -------------------------------------------------------------------------------- 1 | 2 | // Small POC showcasing how this library can be applied to real-world libraries 3 | // where not every code is transpilable 4 | 5 | import { Transpiler } from '../../../src/transpiler.js'; 6 | import { writeFileSync } from 'fs'; 7 | 8 | const transpiler = new Transpiler({ 9 | python: { 10 | uncamelcaseIdentifiers: true, 11 | } 12 | }); 13 | 14 | const FILE_INPUT = "./input/index.ts"; 15 | const FILE_OUTPUT = "./output/index.py"; 16 | 17 | const transpiledCode = transpiler.transpilePythonByPath(FILE_INPUT); 18 | 19 | // handle imports 20 | const imports = transpiledCode.imports; 21 | let importsStr = ""; 22 | imports.forEach(imp => { 23 | let impName = ""; 24 | let impPackage = ""; 25 | // custom logic to resolve ts->py imports 26 | if (imp.name === 'nonTranspilableFeature') { 27 | impName = "non_transpilable_feature" 28 | } 29 | if (imp.path === "./nonTranspilableHelper.js") { 30 | impPackage = "helper" 31 | } 32 | importsStr+= `from ${impPackage} import ${impName}\n`; 33 | }) 34 | 35 | const finalCode = importsStr + '\n\n' + transpiledCode.content; 36 | 37 | writeFileSync(FILE_OUTPUT, finalCode); -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module main 2 | 3 | go 1.22.5 4 | -------------------------------------------------------------------------------- /helpers/c#/helpers.cs: -------------------------------------------------------------------------------- 1 | namespace ccxt; 2 | 3 | using System.Globalization; 4 | using System.Reflection; 5 | 6 | using dict = Dictionary; 7 | 8 | 9 | public partial class Exchange 10 | { 11 | 12 | // tmp most of these methods are going to be re-implemented in the future to be more generic and efficient 13 | 14 | public static object normalizeIntIfNeeded(object a) 15 | { 16 | if (a == null) 17 | return null; 18 | 19 | if (a is int) 20 | { 21 | return System.Convert.ToInt64(a); 22 | } 23 | return a; 24 | } 25 | public static object postFixIncrement(ref object a) 26 | { 27 | if (a is Int64) 28 | { 29 | a = (Int64)a + 1; 30 | } 31 | else if (a is int) 32 | { 33 | a = (int)a + 1; 34 | } 35 | else if (a is double) 36 | { 37 | a = (double)a + 1; 38 | } 39 | else if (a is string) 40 | { 41 | a = (string)a + 1; 42 | } 43 | else 44 | { 45 | return null; 46 | } 47 | return a; 48 | } 49 | 50 | public static object postFixDecrement(ref object a) 51 | { 52 | 53 | if (a is Int64) 54 | { 55 | a = (Int64)a - 1; 56 | } 57 | else if (a is int) 58 | { 59 | a = (int)a - 1; 60 | } 61 | else if (a is double) 62 | { 63 | a = (double)a - 1; 64 | } 65 | else 66 | { 67 | return null; 68 | } 69 | return a; 70 | 71 | } 72 | 73 | public static object prefixUnaryNeg(ref object a) 74 | { 75 | if (a.GetType() == typeof(Int64)) 76 | { 77 | a = -(Int64)a; 78 | } 79 | else if (a.GetType() == typeof(int)) 80 | { 81 | a = -(int)a; 82 | } 83 | else if (a.GetType() == typeof(double)) 84 | { 85 | a = -(double)a; 86 | } 87 | else if (a.GetType() == typeof(string)) 88 | { 89 | return null; 90 | } 91 | else 92 | { 93 | return null; 94 | } 95 | return a; 96 | } 97 | 98 | public static object prefixUnaryPlus(ref object a) 99 | { 100 | if (a.GetType() == typeof(Int64)) 101 | { 102 | a = +(Int64)a; 103 | } 104 | else if (a.GetType() == typeof(int)) 105 | { 106 | a = +(int)a; 107 | } 108 | else if (a.GetType() == typeof(double)) 109 | { 110 | a = +(double)a; 111 | } 112 | else if (a.GetType() == typeof(string)) 113 | { 114 | return null; 115 | } 116 | else 117 | { 118 | return null; 119 | } 120 | return a; 121 | } 122 | 123 | public static object plusEqual(object a, object value) 124 | { 125 | 126 | a = normalizeIntIfNeeded(a); 127 | value = normalizeIntIfNeeded(value); 128 | 129 | if (value == null) 130 | return null; 131 | if (a.GetType() == typeof(Int64)) 132 | { 133 | a = (Int64)a + (Int64)value; 134 | } 135 | else if (a.GetType() == typeof(int)) 136 | { 137 | a = (int)a + (int)value; 138 | } 139 | else if (a.GetType() == typeof(double)) 140 | { 141 | a = (double)a + (double)value; 142 | } 143 | else if (a.GetType() == typeof(string)) 144 | { 145 | a = (string)a + (string)value; 146 | } 147 | else 148 | { 149 | return null; 150 | } 151 | return a; 152 | } 153 | 154 | public object parseJson(object json) 155 | { 156 | // var jsonString = json.ToString(); 157 | // if (jsonString.StartsWith("[".ToString())) 158 | // { 159 | // return JsonConvert.DeserializeObject>(jsonString); 160 | // } 161 | // return JsonConvert.DeserializeObject((string)json); 162 | return JsonHelper.Deserialize((string)json); 163 | } 164 | 165 | public static bool isTrue(object value) 166 | { 167 | if (value == null) 168 | { 169 | return false; 170 | } 171 | 172 | value = normalizeIntIfNeeded(value); 173 | 174 | // return value != null && value != false && value != 0 && value != "" && value != "0" && value != "false" && value != "False" && value != "FALSE"; 175 | if (value is (bool)) 176 | { 177 | return (bool)value; 178 | } 179 | else if (value is (Int64)) 180 | { 181 | return (Int64)value != 0; 182 | } 183 | else if (value is (double)) 184 | { 185 | return (double)value != 0; 186 | } 187 | else if (value is (string)) 188 | { 189 | return (string)value != ""; 190 | } 191 | else if (value is (IList)) 192 | { 193 | return ((IList)value).Count > 0; 194 | } 195 | else if (value is (IList)) 196 | { 197 | return ((IList)value).Count > 0; 198 | } 199 | else if (value is (IList)) 200 | { 201 | return ((IList)value).Count > 0; 202 | } 203 | else if (value is (IList)) 204 | { 205 | return ((IList)value).Count > 0; 206 | } 207 | else if (value is (IList)) 208 | { 209 | return ((IList)value).Count > 0; 210 | } 211 | else if (value is (IDictionary)) 212 | { 213 | return true; 214 | } 215 | else 216 | { 217 | return false; 218 | } 219 | } 220 | 221 | public static bool isNumber(object number) 222 | { 223 | return Double.TryParse(number.ToString(), out _); 224 | } 225 | 226 | public static bool isEqual(object a, object b) 227 | { 228 | 229 | try 230 | { 231 | 232 | if (a == null && b == null) 233 | { 234 | return true; 235 | } 236 | else if (a == null || b == null) 237 | { 238 | return false; 239 | } 240 | 241 | if (a.GetType() != b.GetType() && (!isNumber(a) || !isNumber(b))) 242 | { 243 | return false; 244 | } 245 | 246 | // if (a.GetType() != b.GetType()) 247 | // { 248 | // return false; 249 | // } 250 | if (IsInteger(a) && IsInteger(b)) 251 | { 252 | return Convert.ToInt64(a) == Convert.ToInt64(b); 253 | } 254 | if (a.GetType() == typeof(Int64) && b.GetType() == typeof(Int64)) 255 | { 256 | return Convert.ToInt64(a) == Convert.ToInt64(b); 257 | } 258 | if (a.GetType() == typeof(decimal) || b.GetType() == typeof(decimal)) 259 | { 260 | return Convert.ToDecimal(a) == Convert.ToDecimal(b); 261 | } 262 | else if (a.GetType() == typeof(int)) 263 | { 264 | return (int)a == (int)b; 265 | } 266 | else if (a.GetType() == typeof(double) || b.GetType() == typeof(double)) 267 | { 268 | return Convert.ToDouble(a) == Convert.ToDouble(b); 269 | } 270 | else if (a.GetType() == typeof(decimal) || b.GetType() == typeof(decimal)) 271 | { 272 | return Convert.ToDecimal(a) == Convert.ToDecimal(b); 273 | } 274 | else if (a.GetType() == typeof(Single) || b.GetType() == typeof(Single)) 275 | { 276 | return Convert.ToSingle(a) == Convert.ToSingle(b); 277 | } 278 | // else if (a.GetType() == typeof(double)) 279 | // { 280 | // return (double)a == (double)b; 281 | // } 282 | else if (a.GetType() == typeof(string)) 283 | { 284 | return ((string)a) == ((string)b); 285 | } 286 | else if (a.GetType() == typeof(bool)) 287 | { 288 | return ((bool)a) == ((bool)b); 289 | } 290 | else 291 | { 292 | return false; 293 | } 294 | } 295 | catch (Exception e) 296 | { 297 | return false; 298 | } 299 | 300 | 301 | } 302 | 303 | public static bool isGreaterThan(object a, object b) 304 | { 305 | if (a != null && b == null) 306 | { 307 | return true; 308 | } 309 | else if (a == null || b == null) 310 | { 311 | return false; 312 | } 313 | 314 | a = normalizeIntIfNeeded(a); 315 | b = normalizeIntIfNeeded(b); 316 | 317 | if (a.GetType() == typeof(Int64) && b.GetType() == typeof(Int64)) 318 | { 319 | return Convert.ToInt64(a) > Convert.ToInt64(b); 320 | } 321 | else if (a.GetType() == typeof(int) && b.GetType() == typeof(int)) 322 | { 323 | return (int)a > (int)b; 324 | } 325 | else if (a.GetType() == typeof(double) || b.GetType() == typeof(double)) 326 | { 327 | return Convert.ToDouble(a) > Convert.ToDouble(b); 328 | } 329 | else if (a.GetType() == typeof(string)) 330 | { 331 | return ((string)a).CompareTo((string)b) > 0; 332 | } 333 | else 334 | { 335 | return false; 336 | } 337 | } 338 | 339 | public static bool isLessThan(object a, object b) 340 | { 341 | 342 | return !isGreaterThan(a, b) && !isEqual(a, b); 343 | } 344 | 345 | public static bool isGreaterThanOrEqual(object a, object b) 346 | { 347 | return isGreaterThan(a, b) || isEqual(a, b); 348 | } 349 | 350 | public static bool isLessThanOrEqual(object a, object b) 351 | { 352 | return isLessThan(a, b) || isEqual(a, b); 353 | } 354 | 355 | public static object mod(object a, object b) 356 | { 357 | if (a == null || b == null) 358 | { 359 | return null; 360 | } 361 | 362 | a = normalizeIntIfNeeded(a); 363 | b = normalizeIntIfNeeded(b); 364 | 365 | if (a.GetType() == typeof(string) || a.GetType() == typeof(Int64) || a.GetType() == typeof(int) || a.GetType() == typeof(double)) 366 | return (Convert.ToDouble(a)) % (Convert.ToDouble(b)); 367 | 368 | return null; 369 | 370 | // return add(a, b); 371 | } 372 | 373 | public static object add(object a, object b) 374 | { 375 | a = normalizeIntIfNeeded(a); 376 | b = normalizeIntIfNeeded(b); 377 | 378 | if (a is (Int64)) 379 | { 380 | return (Int64)a + (Int64)b; 381 | } 382 | else if (a is (double)) 383 | { 384 | return (double)a + Convert.ToDouble(b); 385 | } 386 | else if (a is (string)) 387 | { 388 | return (string)a + (string)b; 389 | } 390 | else 391 | { 392 | return null; 393 | } 394 | } 395 | 396 | public static string add(string a, string b) 397 | { 398 | return a + b; 399 | } 400 | 401 | public static string add(string a, object b) 402 | { 403 | return add(a, b.ToString()); 404 | } 405 | 406 | // public static string add(object a, string b) 407 | // { 408 | // if (a == null || b == null) 409 | // { 410 | // return null; 411 | // } 412 | // if (a.GetType() != b.GetType()) 413 | // return null; 414 | 415 | // if (a.GetType() == typeof(string) || a.GetType() == typeof(Int64) || a.GetType() == typeof(int)) 416 | // return a + b; 417 | 418 | // return null; 419 | 420 | // // return add(a, b); 421 | // } 422 | 423 | // public static int add(int a, int b) 424 | // { 425 | // return a + b; 426 | // } 427 | 428 | // public float add(float a, float b) 429 | // { 430 | // return a + b; 431 | // } 432 | 433 | public static object subtract(object a, object b) 434 | { 435 | a = normalizeIntIfNeeded(a); 436 | b = normalizeIntIfNeeded(b); 437 | 438 | // subtract logic 439 | if (a.GetType() == typeof(Int64)) 440 | { 441 | return (Int64)a - (Int64)b; 442 | } 443 | else if (a.GetType() == typeof(int)) 444 | { 445 | return (int)a - (int)b; 446 | } 447 | else if (a.GetType() == typeof(double)) 448 | { 449 | return (double)a - (double)b; 450 | } 451 | else 452 | { 453 | return null; 454 | } 455 | } 456 | 457 | public static int subtract(int a, int b) 458 | { 459 | return a - b; 460 | } 461 | 462 | public float subtract(float a, float b) 463 | { 464 | return a - b; 465 | } 466 | 467 | public static object divide(object a, object b) 468 | { 469 | a = normalizeIntIfNeeded(a); 470 | b = normalizeIntIfNeeded(b); 471 | 472 | if (a == null || b == null) 473 | { 474 | return null; 475 | } 476 | 477 | if (a.GetType() == typeof(Int64) && b.GetType() == typeof(Int64)) 478 | { 479 | return (Int64)a / (Int64)b; 480 | } 481 | else if (a.GetType() == typeof(double) && b.GetType() == typeof(double)) 482 | { 483 | return (double)a / (double)b; 484 | } 485 | else 486 | { 487 | return Convert.ToDouble(a) / Convert.ToDouble(b); 488 | } 489 | } 490 | 491 | public static object multiply(object a, object b) 492 | { 493 | a = normalizeIntIfNeeded(a); 494 | b = normalizeIntIfNeeded(b); 495 | if (a == null || b == null) 496 | { 497 | return null; 498 | } 499 | 500 | if (a is Int64 && b is Int64) 501 | { 502 | return (Int64)a * (Int64)b; 503 | } 504 | var first = Convert.ToDouble(a); 505 | var second = Convert.ToDouble(b); 506 | 507 | var res = first * second; 508 | 509 | if (IsInteger(res)) 510 | { 511 | return Convert.ToInt64(res); 512 | } 513 | else 514 | { 515 | return res; 516 | } 517 | } 518 | 519 | public static int getArrayLength(object value) 520 | { 521 | if (value == null) 522 | { 523 | return 0; 524 | } 525 | 526 | if (value is (IList)) 527 | { 528 | return ((IList)value).Count; 529 | } 530 | else if (value is (IList)) 531 | { 532 | return ((IList)value).Count; 533 | } 534 | else if (value is (List)) 535 | { 536 | return ((List)value).Count; 537 | } 538 | else if (value is (string)) 539 | { 540 | return ((string)value).Length; // fallback that should not be used 541 | } 542 | else 543 | { 544 | return 0; 545 | } 546 | } 547 | 548 | public static bool IsInteger(object value) 549 | { 550 | if (value == null) 551 | { 552 | return false; 553 | } 554 | 555 | Type type = value.GetType(); 556 | 557 | // Check for integral types 558 | if (type == typeof(int) || type == typeof(long) || type == typeof(short) || type == typeof(byte) || type == typeof(sbyte) || type == typeof(uint) || type == typeof(ulong) || type == typeof(ushort)) 559 | { 560 | return true; 561 | } 562 | 563 | // Check for floating-point types and verify if they can be converted to an integer without losing precision 564 | if (type == typeof(float) || type == typeof(double) || type == typeof(decimal)) 565 | { 566 | decimal decimalValue = Convert.ToDecimal(value); 567 | return decimalValue == Math.Floor(decimalValue); 568 | } 569 | 570 | // Add any additional type checks if necessary 571 | 572 | return false; 573 | } 574 | 575 | public static object mathMin(object a, object b) 576 | { 577 | if (a == null || b == null) 578 | { 579 | return null; 580 | } 581 | var first = Convert.ToDouble(a); 582 | var second = Convert.ToDouble(b); 583 | 584 | if (first < second) 585 | { 586 | return a; 587 | } 588 | else 589 | { 590 | return b; 591 | } 592 | 593 | // a = normalizeIntIfNeeded(a); 594 | // b = normalizeIntIfNeeded(b); 595 | // if (a.GetType() == typeof(Int64)) 596 | // { 597 | // return Math.Min((Int64)a, (Int64)b); 598 | // } 599 | // else if (a.GetType() == typeof(double)) 600 | // { 601 | // return Math.Min((double)a, (double)b); 602 | // } 603 | // else if (a.GetType() == typeof(float)) 604 | // { 605 | // return Math.Min((float)a, (float)b); 606 | // } 607 | // else if (a.GetType() == typeof(int)) 608 | // { 609 | // return Math.Min((int)a, (int)b); 610 | // } 611 | // else 612 | // { 613 | // return null; 614 | // } 615 | } 616 | 617 | public static object mathMax(object a, object b) 618 | { 619 | if (a == null || b == null) 620 | { 621 | return null; 622 | } 623 | var first = Convert.ToDouble(a); 624 | var second = Convert.ToDouble(b); 625 | 626 | if (first > second) 627 | { 628 | return a; 629 | } 630 | else 631 | { 632 | return b; 633 | } 634 | } 635 | 636 | public static int getIndexOf(object str, object target) 637 | { 638 | if (str is IList) 639 | { 640 | return ((IList)str).IndexOf(target); 641 | } 642 | else if (str is IList) 643 | { 644 | return ((IList)str).IndexOf((string)target); 645 | } 646 | else if (str is (string)) 647 | { 648 | return ((string)str).IndexOf((string)target); 649 | } 650 | else 651 | { 652 | return -1; 653 | } 654 | } 655 | 656 | public static object parseInt(object a) 657 | { 658 | object parsedValue = null; 659 | try 660 | { 661 | parsedValue = (Convert.ToInt64(a)); 662 | } 663 | catch (Exception e) 664 | { 665 | } 666 | return parsedValue; 667 | } 668 | 669 | public static object parseFloat(object a) 670 | { 671 | object parsedValue = null; 672 | try 673 | { 674 | // parsedValue = float.Parse((string)a, CultureInfo.InvariantCulture.NumberFormat); 675 | parsedValue = (Convert.ToDouble(a, CultureInfo.InvariantCulture.NumberFormat)); 676 | } 677 | catch (Exception e) 678 | { 679 | } 680 | return parsedValue; 681 | } 682 | 683 | // generic getValue to replace elementAccesses 684 | public object getValue(object a, object b) => GetValue(a, b); 685 | public static object GetValue(object value2, object key) 686 | { 687 | if (value2 == null || key == null) 688 | { 689 | return null; 690 | } 691 | 692 | if (value2.GetType() == typeof(string)) 693 | { 694 | var str = (string)value2; 695 | return (str[Convert.ToInt32(key)]).ToString(); 696 | } 697 | 698 | // check if array 699 | object value = value2; 700 | if (value2.GetType().IsArray == true) 701 | { 702 | value = new List((object[])value2); 703 | } 704 | 705 | 706 | if (value is IDictionary) 707 | { 708 | var dictValue = (IDictionary)value; 709 | if (dictValue.ContainsKey((string)key)) 710 | { 711 | return dictValue[(string)key]; 712 | } 713 | else 714 | { 715 | return null; 716 | } 717 | } 718 | else if (value is IList) 719 | { 720 | // check here if index is out of bounds 721 | int parsed = Convert.ToInt32(key); 722 | var listLength = getArrayLength(value); 723 | if (parsed >= listLength) 724 | { 725 | return null; 726 | } 727 | return ((IList)value)[parsed]; 728 | } 729 | else if (value is List) 730 | { 731 | // check here if index is out of bounds 732 | int parsed = Convert.ToInt32(key); 733 | var listLength = getArrayLength(value); 734 | if (parsed >= listLength) 735 | { 736 | return null; 737 | } 738 | return ((List)value)[parsed]; 739 | } 740 | else if (value.GetType() == typeof(List)) 741 | { 742 | int parsed = Convert.ToInt32(key); 743 | var listLength = getArrayLength(value); 744 | if (parsed >= listLength) 745 | { 746 | return null; 747 | } 748 | return ((List)value)[parsed]; 749 | } 750 | else if (value is List) 751 | { 752 | int parsed = Convert.ToInt32(key); 753 | return ((List)value)[parsed]; 754 | } 755 | // check this last, avoid reflection 756 | else if (key.GetType() == typeof(string) && (value.GetType()).GetProperty((string)key) != null) 757 | { 758 | var prop = (value.GetType()).GetProperty((string)key); 759 | if (prop != null) 760 | { 761 | return prop.GetValue(value2, null); 762 | } 763 | else 764 | { 765 | return null; 766 | } 767 | } 768 | else 769 | { 770 | return null; 771 | } 772 | } 773 | 774 | public async Task> promiseAll(object promisesObj) => await PromiseAll(promisesObj); 775 | 776 | public static async Task> PromiseAll(object promisesObj) 777 | { 778 | var promises = (IList)promisesObj; 779 | var tasks = new List>(); 780 | foreach (var promise in promises) 781 | { 782 | if (promise is Task) 783 | { 784 | tasks.Add((Task)promise); 785 | } 786 | } 787 | var results = await Task.WhenAll(tasks); 788 | return results.ToList(); 789 | } 790 | 791 | public static string toStringOrNull(object value) 792 | { 793 | if (value == null) 794 | { 795 | return null; 796 | } 797 | else 798 | { 799 | return (string)value; 800 | } 801 | } 802 | 803 | public void throwDynamicException(object exception, object message) 804 | { 805 | var Exception = NewException((Type)exception, (string)message); 806 | } 807 | 808 | // This function is the salient bit here 809 | public object newException(object exception, object message) 810 | { 811 | return Activator.CreateInstance(exception as Type, message as String) as Exception; 812 | } 813 | 814 | public static Exception NewException(Type exception, String message) 815 | { 816 | return Activator.CreateInstance(exception, message) as Exception; 817 | } 818 | 819 | public static object toFixed(object number, object decimals) 820 | { 821 | return Math.Round((double)number, (int)decimals); 822 | } 823 | 824 | public static object callDynamically(object obj, object methodName, object[] args = null) 825 | { 826 | args ??= new object[] { }; 827 | if (args.Length == 0) 828 | { 829 | args = new object[] { null }; 830 | } 831 | return obj.GetType().GetMethod((string)methodName, BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Invoke(obj, args); 832 | } 833 | 834 | public static async Task callDynamicallyAsync(object obj, object methodName, object[] args = null) 835 | { 836 | args ??= new object[] { }; 837 | var res = obj.GetType().GetMethod((string)methodName, BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Invoke(obj, args); 838 | return await ((Task)res); 839 | } 840 | 841 | public bool inOp(object obj, object key) => InOp(obj, key); 842 | 843 | public static bool InOp(object obj, object key) 844 | { 845 | if (obj == null || key == null) 846 | { 847 | return false; 848 | } 849 | if (obj is (IList)) 850 | { 851 | return ((IList)obj).Contains(key); 852 | } 853 | else if (obj is (IList)) 854 | { 855 | return ((IList)obj).Contains((string)key); 856 | } 857 | else if (obj is (List)) 858 | { 859 | return ((List)obj).Contains((Int64)key); 860 | } 861 | else if (obj is (IDictionary)) 862 | { 863 | if (key is (string)) 864 | return ((IDictionary)obj).ContainsKey((string)key); 865 | else 866 | return false; 867 | } 868 | else 869 | { 870 | return false; 871 | } 872 | } 873 | 874 | public string slice(object str2, object idx1, object idx2) => Slice(str2, idx1, idx2); 875 | 876 | public static string Slice(object str2, object idx1, object idx2) 877 | { 878 | if (str2 == null) 879 | { 880 | return null; 881 | } 882 | var str = (string)str2; 883 | var start = idx1 != null ? Convert.ToInt32(idx1) : -1; 884 | if (idx2 == null) 885 | { 886 | if (start < 0) 887 | { 888 | var innerStart = str.Length + start; 889 | innerStart = innerStart < 0 ? 0 : innerStart; 890 | return str[(innerStart)..]; 891 | } 892 | else 893 | { 894 | return str[start..]; 895 | } 896 | } 897 | else 898 | { 899 | var end = Convert.ToInt32(idx2); 900 | if (start < 0) 901 | { 902 | start = str.Length + start; 903 | } 904 | if (end < 0) 905 | { 906 | end = str.Length + end; 907 | } 908 | if (end > str.Length) 909 | { 910 | end = str.Length; 911 | } 912 | return str[start..end]; 913 | } 914 | } 915 | 916 | public static object concat(object a, object b) 917 | { 918 | if (a == null && b == null) 919 | { 920 | return null; 921 | } 922 | else if (a == null) 923 | { 924 | return b; 925 | } 926 | else if (b == null) 927 | { 928 | return a; 929 | } 930 | 931 | if (a is IList && b is IList) 932 | { 933 | List result = new List((IList)a); 934 | result.AddRange((IList)b); 935 | return result; 936 | } 937 | else if (a is IList && b is IList) 938 | { 939 | List result = new List((IList)a); 940 | result.AddRange((IList)b); 941 | return result; 942 | } 943 | else if (a is IList> && b is IList>) 944 | { 945 | List> result = new List>((IList>)a); 946 | result.AddRange((IList>)b); 947 | return result; 948 | } 949 | else 950 | { 951 | throw new InvalidOperationException("Unsupported types for concatenation."); 952 | } 953 | } 954 | 955 | } -------------------------------------------------------------------------------- /helpers/go/go.mod: -------------------------------------------------------------------------------- 1 | module main 2 | 3 | go 1.19 4 | -------------------------------------------------------------------------------- /helpers/go/main: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccxt/ast-transpiler/f1ed8267b45f8ed23c74a275f0bc73bf905f3b29/helpers/go/main -------------------------------------------------------------------------------- /helpers/go/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | type A struct { 6 | } 7 | 8 | func (this *A) main(a interface{}, b interface{}) { 9 | // arr := []interface{}{} 10 | // arr = appendToArray(arr, 1).([]interface{}) 11 | // fmt.Println(arr) 12 | arr := []interface{}{1} 13 | addElementToObject(arr, 0, 2) 14 | fmt.Println(arr) 15 | } 16 | 17 | func main() { 18 | a := A{} 19 | a.main(1, 2) 20 | } 21 | -------------------------------------------------------------------------------- /jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "transform": {"^.+\\.ts?$": "ts-jest"}, 3 | "testEnvironment": "node", 4 | "testRegex": "/tests/.*\\.(test|spec)?\\.(ts|tsx)$", 5 | "moduleFileExtensions": ["ts", "js","json", "node", "cjs"], 6 | "transformIgnorePatterns": [], 7 | "moduleDirectories": ["src", "node_modules"], 8 | "moduleNameMapper": { 9 | "^(\\.{1,2}/.*)\\.js$": "$1" 10 | }, 11 | "rootDir": "./", 12 | "coverageReporters": ["json-summary"] 13 | } -------------------------------------------------------------------------------- /manual.ts: -------------------------------------------------------------------------------- 1 | import { Transpiler } from './src/transpiler.js'; 2 | import * as fs from 'fs'; 3 | import { IInput } from './src/types.js'; 4 | 5 | const { readFileSync, writeFileSync } = fs; 6 | 7 | const transpiler = new Transpiler({ 8 | python: { 9 | uncamelcaseIdentifiers: true, 10 | }, 11 | php: { 12 | uncamelcaseIdentifiers: true, 13 | }, 14 | csharp: { 15 | parser: { 16 | "ELEMENT_ACCESS_WRAPPER_OPEN": "getValue(", 17 | "ELEMENT_ACCESS_WRAPPER_CLOSE": ")", 18 | 'VAR_TOKEN': 'var', 19 | } 20 | } 21 | }); 22 | 23 | function customPropAssignment(node, identation) { 24 | return ""; 25 | } 26 | 27 | transpiler.csharpTranspiler.printCustomRightSidePropertyAssignment = customPropAssignment; 28 | 29 | transpiler.setPHPPropResolution(['super', 'Precise']); 30 | 31 | transpiler.setPythonStringLiteralReplacements({ 32 | 'sha256': 'hashlib.sha256', 33 | }); 34 | 35 | const file = "tmp.ts"; 36 | 37 | const i = 0; 38 | // // while (i < 150) { 39 | // const pythonRes = transpiler.transpilePythonByPath(file); 40 | // const php = transpiler.transpilePhpByPath(file); 41 | // // const csharp = transpiler.transpileCSharpByPath(file); 42 | // // i++; 43 | // // } 44 | 45 | // const phpRes = ``; 46 | 47 | // transpiler.setPhpAsyncTranspiling(false); 48 | // const phpSyncRes = ``; 49 | 50 | // transpiler.setPythonAsyncTranspiling(false); 51 | // const pythonSync = transpiler.transpilePythonByPath(file).content; 52 | 53 | const config = [ 54 | // { 55 | // language: "php", 56 | // async: true 57 | // }, 58 | // { 59 | // language: "php", 60 | // async: false 61 | // }, 62 | // { 63 | // language: "python", 64 | // async: false 65 | // }, 66 | 67 | { 68 | language: "csharp", 69 | async: true 70 | }, 71 | 72 | { 73 | language: "python", 74 | async: true 75 | }, 76 | { 77 | language: "php", 78 | async: true 79 | }, 80 | { 81 | language: "go", 82 | async: true 83 | }, 84 | ] 85 | 86 | const result = transpiler.transpileDifferentLanguagesByPath(config as any, file); 87 | 88 | const phpRes = ``; 89 | // const phpSyncRes = ``; 90 | // const pythonSync = result[2].content; 91 | const pythonAsync = result[1].content; 92 | 93 | const csharp = result[0].content; 94 | const PHP_OUTPUT = "./out/output.php"; 95 | const PHP_SYNC_OUTPUT = "./out/output-sync.php"; 96 | const PYTHON_OUTPUT = "./out/output.py"; 97 | const PYTHON_SYNC_OUTPUT = "./out/output-sync.py"; 98 | const CSHARP_OUTPUT = "./out/output.cs"; 99 | 100 | const GO_OUTPUT = "./out/output.go"; 101 | const go = result[3].content; 102 | 103 | writeFileSync(PHP_OUTPUT, phpRes); 104 | // // writeFileSync(PYTHON_OUTPUT, pythonRes.content ?? ""); 105 | writeFileSync(PYTHON_OUTPUT, pythonAsync ?? ""); 106 | // writeFileSync(PYTHON_SYNC_OUTPUT, pythonSync ?? ""); 107 | // writeFileSync(PHP_SYNC_OUTPUT, phpSyncRes); 108 | 109 | writeFileSync(CSHARP_OUTPUT, csharp); 110 | writeFileSync(GO_OUTPUT, go); 111 | 112 | console.log("TRANSPILED!!"); 113 | 114 | 115 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ast-transpiler", 3 | "version": "0.0.66", 4 | "type": "module", 5 | "module": "./dist/transpiler.js", 6 | "types": "./dist/transpiler.d.ts", 7 | "exports": { 8 | ".": { 9 | "require": "./dist/transpiler.cjs", 10 | "import": "./dist/transpiler.js", 11 | "types": "./dist/transpiler.d.ts" 12 | } 13 | }, 14 | "engines": { 15 | "node": ">=16.0.0" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/ccxt/ast-transpiler" 20 | }, 21 | "readme": "README.md", 22 | "scripts": { 23 | "build": "npm run lint && tsup src/transpiler.ts --format cjs,esm --dts --clean --splitting --shims", 24 | "build2": "npm run lint && tsc", 25 | "start": "node --loader ts-node/esm manual.ts", 26 | "start-py": "node --loader ts-node/esm src/pythonTranspiler.ts", 27 | "test-ci": "jest --ci --coverage", 28 | "test": "jest --verbose", 29 | "testv": "jest --verbose --coverage", 30 | "integration": "node --loader ts-node/esm tests/integration/test.ts", 31 | "lint": "eslint src/ --ext .ts", 32 | "publishPackage": "sh publish.sh && git push && git push --tags && npm publish" 33 | }, 34 | "devDependencies": { 35 | "@types/jest": "^29.2.0", 36 | "@types/node": "^18.11.4", 37 | "@typescript-eslint/eslint-plugin": "^5.42.0", 38 | "@typescript-eslint/parser": "^5.42.0", 39 | "eslint": "^8.26.0", 40 | "jest": "^29.2.2", 41 | "test-jest": "^1.0.1", 42 | "ts-jest": "^29.0.3", 43 | "ts-node": "^10.9.1", 44 | "tsup": "^6.4.0" 45 | }, 46 | "dependencies": { 47 | "colorette": "^2.0.19", 48 | "piscina": "^3.2.0", 49 | "typescript": "^4.8.4" 50 | }, 51 | "author": { 52 | "name": "Carlos Gonçalves", 53 | "url": "https://github.com/carlosmiei" 54 | }, 55 | "license": "MIT", 56 | "bugs": { 57 | "url": "https://github.com/ccxt/ccxt/issues" 58 | }, 59 | "homepage": "https://ccxt.com", 60 | "keywords": [ 61 | "transpiling", 62 | "transpiler", 63 | "ast", 64 | "javascript", 65 | "typescript", 66 | "python", 67 | "php", 68 | "compiler", 69 | "esm", 70 | "cjs" 71 | ] 72 | } 73 | -------------------------------------------------------------------------------- /publish.sh: -------------------------------------------------------------------------------- 1 | 2 | #!/bin/bash 3 | set -e 4 | 5 | npm run build 6 | 7 | # release defaults to patch (last number in semver) 8 | RELEASE="patch" && [ -n "$1" ] && RELEASE=$1 9 | 10 | # cut the release 11 | VERSION=$(npm --no-git-tag-version version $RELEASE | sed 's/v//') 12 | 13 | git add package.json 14 | git commit -m "release: cut the $VERSION release" 15 | 16 | # tag the release 17 | git tag $VERSION 18 | git tag -l 19 | 20 | echo -e "\033[1;92m You are ready to publish!" 21 | echo -e "\033[1;95m git push" 22 | echo -e "\033[1;95m git push --tags" 23 | echo -e "\033[1;95m npm publish" 24 | -------------------------------------------------------------------------------- /src/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "plugins": [ 5 | "@typescript-eslint" 6 | ], 7 | "extends": [ 8 | "eslint:recommended", 9 | "plugin:@typescript-eslint/eslint-recommended", 10 | "plugin:@typescript-eslint/recommended" 11 | ], 12 | "rules": { 13 | "indent": ["error", 4], 14 | "no-trailing-spaces": "error", 15 | "no-console": 1, 16 | "semi": "error" 17 | } 18 | } -------------------------------------------------------------------------------- /src/dirname.cjs: -------------------------------------------------------------------------------- 1 | // https://github.com/kulshekhar/ts-jest/issues/1174 2 | module.exports = __dirname; -------------------------------------------------------------------------------- /src/logger.ts: -------------------------------------------------------------------------------- 1 | import { green, yellow, red } from "colorette"; 2 | 3 | class Logger { 4 | static verbose = true; 5 | 6 | // static createInstanceIfNeeded(): void { 7 | // if (!this._instance) { 8 | // this._instance = new Logger(); 9 | // } 10 | // } 11 | 12 | static setVerboseMode(verbose: boolean) { 13 | this.verbose = verbose; 14 | } 15 | 16 | static log(message: string) { 17 | if (this.verbose) { 18 | console.log(message); 19 | } 20 | } 21 | 22 | static success(message: string) { 23 | this.log(green(`[SUCCESS]: ${message}`)); 24 | } 25 | 26 | static warning(message: string) { 27 | this.log(yellow(`[WARNING]: ${message}`)); 28 | } 29 | 30 | static error(message: string) { 31 | this.log(red(`[ERROR]: ${message}`)); 32 | } 33 | } 34 | 35 | export { 36 | Logger 37 | }; 38 | 39 | -------------------------------------------------------------------------------- /src/phpTranspiler.ts: -------------------------------------------------------------------------------- 1 | import { BaseTranspiler } from "./baseTranspiler.js"; 2 | import ts, { TypeChecker } from 'typescript'; 3 | import { unCamelCase, regexAll } from "./utils.js"; 4 | import { Logger } from "./logger.js"; 5 | 6 | const SyntaxKind = ts.SyntaxKind; 7 | 8 | const parserConfig = { 9 | 'ELSEIF_TOKEN': 'elseif', 10 | 'THIS_TOKEN': '$this', 11 | 'PROPERTY_ACCESS_TOKEN': '->', 12 | 'UNDEFINED_TOKEN': 'null', 13 | 'NOT_TOKEN': '!', 14 | 'LINE_TERMINATOR': ';', 15 | 'ARRAY_OPENING_TOKEN':"[", 16 | 'ARRAY_CLOSING_TOKEN':"]", 17 | 'OBJECT_OPENING':"array(", 18 | 'OBJECT_CLOSING':")", 19 | 'FUNCTION_TOKEN': 'function', 20 | 'ASYNC_TOKEN': '', 21 | 'PROPERTY_ASSIGNMENT_TOKEN': ' =>', 22 | 'NEW_TOKEN': 'new', 23 | 'THROW_TOKEN': 'throw', 24 | 'SUPER_TOKEN': 'parent', 25 | 'CONSTRUCTOR_TOKEN': 'function __construct', 26 | 'SUPER_CALL_TOKEN': 'parent::__construct', 27 | 'CATCH_DECLARATION': 'Exception', 28 | 'CATCH_TOKEN': 'catch', 29 | 'BLOCK_OPENING_TOKEN' :'{', 30 | 'BLOCK_CLOSING_TOKEN' :'}', 31 | 'CONDITION_OPENING' :'(', 32 | 'CONDITION_CLOSE' :')', 33 | 'PLUS_PLUS_TOKEN': '++', 34 | 'MINUS_MINUS_TOKEN': '--', 35 | 'SPACE_DEFAULT_PARAM': ' ', 36 | 'EXCLAMATION_EQUALS_EQUALS_TOKEN': '!==', 37 | 'EQUALS_EQUALS_EQUALS_TOKEN': '===', 38 | 'STRING_QUOTE_TOKEN': '\'', 39 | 'EXTENDS_TOKEN': 'extends', 40 | }; 41 | 42 | export class PhpTranspiler extends BaseTranspiler { 43 | awaitWrapper; 44 | propRequiresScopeResolutionOperator: string[]; 45 | AWAIT_WRAPPER_OPEN; 46 | AWAIT_WRAPPER_CLOSE; 47 | ASYNC_FUNCTION_WRAPPER_OPEN = ""; 48 | constructor(config = {}) { 49 | 50 | config['parser'] = Object.assign ({}, parserConfig, config['parser'] ?? {}); 51 | 52 | super(config); 53 | this.id = "php"; 54 | this.asyncTranspiling = config['async'] ?? true; 55 | this.uncamelcaseIdentifiers = config['uncamelcaseIdentifiers'] ?? false; 56 | this.removeVariableDeclarationForFunctionExpression = config['removeFunctionAssignToVariable'] ?? false; 57 | this.includeFunctionNameInFunctionExpressionDeclaration = config['includeFunctionNameInFunctionExpressionDeclaration'] ?? false; 58 | 59 | this.propRequiresScopeResolutionOperator = ['super'] + (config['ScopeResolutionProps'] ?? []); 60 | 61 | this.initConfig(); 62 | 63 | // user overrides 64 | this.applyUserOverrides(config); 65 | 66 | // const propertyAccessRemoval = config['PropertyAccessRequiresParenthesisRemoval'] ?? []; 67 | // this.PropertyAccessRequiresParenthesisRemoval.push(...propertyAccessRemoval); 68 | 69 | this.AWAIT_WRAPPER_OPEN = config['AWAIT_WRAPPER_OPEN'] ?? "Async\\await("; 70 | this.AWAIT_WRAPPER_CLOSE = config['AWAIT_WRAPPER_CLOSE'] ?? ")"; 71 | } 72 | 73 | printAwaitExpression(node, identation) { 74 | const expression = this.printNode(node.expression, identation); 75 | 76 | if (!this.asyncTranspiling) { 77 | return expression; 78 | } 79 | 80 | return this.AWAIT_WRAPPER_OPEN + expression + this.AWAIT_WRAPPER_CLOSE; 81 | } 82 | 83 | transformIdentifier(node, identifier) { 84 | if (this.uncamelcaseIdentifiers) { 85 | identifier = this.unCamelCaseIfNeeded(identifier); 86 | } 87 | 88 | // Get the symbol for the identifier 89 | const symbol = global.checker.getSymbolAtLocation(node); 90 | 91 | // Check if the symbol references a function declaration or expression 92 | if (symbol && symbol.valueDeclaration) { 93 | const valueDecl = symbol.valueDeclaration; 94 | 95 | // Check if it's a function (FunctionDeclaration, FunctionExpression, ArrowFunction) 96 | if (ts.isFunctionDeclaration(valueDecl) || ts.isFunctionExpression(valueDecl) || ts.isArrowFunction(valueDecl)) { 97 | // Check if the identifier is passed as an argument in a function call 98 | if (node.parent && ts.isCallExpression(node.parent) && node.parent.arguments.includes(node)) { 99 | return `'${identifier}'`; // Transpile function reference as string 100 | } 101 | } 102 | } 103 | 104 | // below is commented, due to : https://github.com/ccxt/ast-transpiler/pull/15 105 | // 106 | // If the identifier is a function parameter (callback), it should remain a variable with `$` prefix 107 | // if (node.parent && (ts.isParameter(node.parent) || (ts.isCallExpression(node.parent) && ts.isIdentifier(node)))) { 108 | // return "$" + identifier; 109 | // } 110 | 111 | // Default case: prepend $ for variables (non-functions), unless it's a class or constant 112 | if (!this.startsWithUpperCase(identifier)) { 113 | return "$" + identifier; // Prepend $ for variable names 114 | } 115 | 116 | return identifier; 117 | } 118 | 119 | 120 | getCustomOperatorIfAny(left, right, operator) { 121 | const STRING_CONCAT = '.'; 122 | const PLUS_EQUALS_TOKEN = '.='; 123 | if (operator.kind == SyntaxKind.PlusToken || operator.kind == SyntaxKind.PlusEqualsToken) { 124 | 125 | const TOKEN = operator.kind == SyntaxKind.PlusToken ? STRING_CONCAT : PLUS_EQUALS_TOKEN; 126 | 127 | if (left.kind == SyntaxKind.StringLiteral || right.kind == SyntaxKind.StringLiteral) { 128 | return TOKEN; 129 | } 130 | 131 | const leftType = global.checker.getTypeAtLocation(left); 132 | const rightType = global.checker.getTypeAtLocation(right); 133 | 134 | if (leftType.flags === ts.TypeFlags.String || rightType.flags === ts.TypeFlags.String) { 135 | return TOKEN; 136 | } 137 | if (leftType.flags === ts.TypeFlags.StringLiteral || rightType.flags === ts.TypeFlags.StringLiteral) { 138 | return TOKEN; 139 | } 140 | } 141 | return undefined; 142 | } 143 | 144 | printLengthProperty(node, identation, name = undefined) { 145 | const leftSide = this.printNode(node.expression, 0); 146 | const type = (global.checker as TypeChecker).getTypeAtLocation(node.expression); // eslint-disable-line 147 | this.warnIfAnyType(node, type.flags, leftSide, "length"); 148 | return this.isStringType(type.flags) ? `strlen(${leftSide})` : `count(${leftSide})`; 149 | } 150 | 151 | printPopCall(node, identation, name = undefined) { 152 | return `array_pop(${name})`; 153 | } 154 | 155 | printReverseCall(node, identation, name = undefined) { 156 | return `${name} = array_reverse(${name})`; 157 | } 158 | 159 | printShiftCall(node, identation, name = undefined) { 160 | return `array_shift(${name})`; 161 | } 162 | 163 | printToLowerCaseCall(node, identation, name = undefined) { 164 | return `strtolower(${name})`; 165 | } 166 | 167 | printToUpperCaseCall(node, identation, name = undefined) { 168 | return `strtoupper(${name})`; 169 | } 170 | 171 | printToStringCall(node, identation, name = undefined) { 172 | return `((string) ${name})`; 173 | } 174 | 175 | printArrayIsArrayCall(node, identation, parsedArg = undefined) { 176 | return `gettype(${parsedArg}) === 'array' && array_is_list(${parsedArg})`; 177 | } 178 | 179 | printObjectKeysCall(node, identation, parsedArg = undefined) { 180 | return `is_array(${parsedArg}) ? array_keys(${parsedArg}) : array()`; 181 | } 182 | 183 | printObjectValuesCall(node, identation, parsedArg = undefined) { 184 | return `is_array(${parsedArg}) ? array_values(${parsedArg}) : array()`; 185 | } 186 | 187 | printJsonParseCall(node, identation, parsedArg?) { 188 | return `json_decode(${parsedArg}, $as_associative_array = true)`; 189 | } 190 | 191 | printJsonStringifyCall(node: any, identation: any, parsedArg?: any) { 192 | return `json_encode(${parsedArg})`; 193 | } 194 | 195 | printArrayPushCall(node, identation, name = undefined, parsedArg = undefined) { 196 | return `${name}[] = ${parsedArg}`; 197 | } 198 | 199 | printPromiseAllCall(node, identation, parsedArg = undefined) { 200 | return `Promise\\all(${parsedArg})`; 201 | } 202 | 203 | printMathCeilCall(node, identation, parsedArg = undefined) { 204 | return `((int) ceil(${parsedArg}))`; 205 | } 206 | 207 | printNumberIsIntegerCall(node, identation, parsedArg?) { 208 | return `is_int(${parsedArg})`; 209 | } 210 | 211 | printMathRoundCall(node, identation, parsedArg = undefined) { 212 | return `((int) round(${parsedArg}))`; 213 | } 214 | 215 | printMathFloorCall(node: any, identation: any, parsedArg?: any) { 216 | return `((int) floor(${parsedArg}))`; 217 | } 218 | 219 | printReplaceCall(node, identation, name = undefined, parsedArg = undefined, parsedArg2 = undefined) { 220 | return `str_replace(${parsedArg}, ${parsedArg2}, ${name})`; 221 | } 222 | 223 | printReplaceAllCall(node, identation, name = undefined, parsedArg = undefined, parsedArg2 = undefined) { 224 | return `str_replace(${parsedArg}, ${parsedArg2}, ${name})`; 225 | } 226 | 227 | printIncludesCall(node, identation, name = undefined, parsedArg = undefined) { 228 | // "ol".includes("o") -> str_contains("ol", "o") or [12,3,4].includes(3) -> in_array(3, [12,3,4]) 229 | const leftSide = node.expression?.expression; 230 | const leftSideText = this.printNode(leftSide, 0); 231 | const type = global.checker.getTypeAtLocation(leftSide); // eslint-disable-line 232 | this.warnIfAnyType(node, type.flags, leftSideText, "includes"); 233 | this.warnIfAnyType(node, type.flags, leftSideText, "includes"); 234 | if (this.isStringType(type.flags)) { 235 | return `str_contains(${name}, ${parsedArg})`; 236 | } else { 237 | return `in_array(${parsedArg}, ${name})`; 238 | } 239 | } 240 | 241 | printIndexOfCall(node, identation, name = undefined, parsedArg = undefined) { 242 | const leftSide = node.expression?.expression; 243 | const leftSideText = this.printNode(leftSide, 0); 244 | const type = global.checker.getTypeAtLocation(leftSide); // eslint-disable-line 245 | this.warnIfAnyType(node, type.flags, leftSideText, "indexOf"); 246 | if (this.isStringType(type.flags)) { 247 | return `mb_strpos(${name}, ${parsedArg})`; 248 | } else { 249 | return `array_search(${parsedArg}, ${name})`; 250 | } 251 | } 252 | 253 | printSearchCall(node, identation, name = undefined, parsedArg = undefined) { 254 | return `mb_strpos(${name}, ${parsedArg})`; 255 | } 256 | 257 | printStartsWithCall(node, identation, name = undefined, parsedArg = undefined) { 258 | return `str_starts_with(${name}, ${parsedArg})`; 259 | } 260 | 261 | printEndsWithCall(node, identation, name = undefined, parsedArg = undefined) { 262 | return `str_ends_with(${name}, ${parsedArg})`; 263 | } 264 | 265 | printTrimCall(node, identation, name = undefined) { 266 | return `trim(${name})`; 267 | } 268 | 269 | printJoinCall(node, identation, name = undefined, parsedArg = undefined) { 270 | return `implode(${parsedArg}, ${name})`; 271 | } 272 | 273 | printSplitCall(node, identation, name = undefined, parsedArg = undefined) { 274 | return `explode(${parsedArg}, ${name})`; 275 | } 276 | 277 | printConcatCall(node: any, identation: any, name?: any, parsedArg?: any) { 278 | return `array_merge(${name}, ${parsedArg})`; 279 | } 280 | 281 | printPadEndCall(node, identation, name, parsedArg, parsedArg2) { 282 | return `str_pad(${name}, ${parsedArg}, ${parsedArg2}, STR_PAD_RIGHT)`; 283 | } 284 | 285 | printPadStartCall(node, identation, name, parsedArg, parsedArg2) { 286 | return `str_pad(${name}, ${parsedArg}, ${parsedArg2}, STR_PAD_LEFT)`; 287 | } 288 | 289 | printDateNowCall(node, identation) { 290 | return "round(microtime(true) * 1000)"; 291 | } 292 | 293 | printInstanceOfExpression(node, identation) { 294 | // const left = this.printNode(node.left, 0); 295 | // const right = this.printNode(node.right, 0); 296 | const left = node.left.escapedText; 297 | const right = node.right.escapedText; 298 | return this.getIden(identation) + "$"+left+" instanceof "+right+""; 299 | } 300 | 301 | printDeleteExpression(node, identation) { 302 | const expression = this.printNode (node.expression, 0); 303 | return `unset(${expression})`; 304 | } 305 | 306 | getExceptionalAccessTokenIfAny(node) { 307 | const leftSide = node.expression.escapedText ?? node.expression.getFullText().trim(); 308 | 309 | if (!leftSide) { 310 | return undefined; 311 | } 312 | 313 | if (this.propRequiresScopeResolutionOperator.includes(leftSide)) { 314 | return "::"; 315 | } 316 | return undefined; 317 | } 318 | 319 | handleTypeOfInsideBinaryExpression(node, identation) { 320 | const left = node.left; 321 | const right = node.right.text; 322 | const op = node.operatorToken.kind; 323 | const expression = left.expression; 324 | 325 | const isDifferentOperator = op === SyntaxKind.ExclamationEqualsEqualsToken || op === SyntaxKind.ExclamationEqualsToken; 326 | const notOperator = isDifferentOperator ? this.NOT_TOKEN : ""; 327 | 328 | const opComp = isDifferentOperator ? this.EXCLAMATION_EQUALS_EQUALS_TOKEN : this.EQUALS_EQUALS_EQUALS_TOKEN; 329 | 330 | switch (right) { 331 | case "string": 332 | return this.getIden(identation) + notOperator + "is_string(" + this.printNode(expression, 0) + ")"; 333 | case "number": 334 | return this.getIden(identation) + notOperator + "(is_int(" + this.printNode(expression, 0) + ") || is_float(" + this.printNode(expression, 0) + "))"; 335 | case "boolean": 336 | return this.getIden(identation) + notOperator + "is_bool(" + this.printNode(expression, 0) + ")"; 337 | case "object": 338 | return this.getIden(identation) + notOperator + "is_array(" + this.printNode(expression, 0) + ")"; 339 | case "undefined": 340 | return this.getIden(identation) + this.printNode(expression, 0) + " " + opComp + " null"; 341 | } 342 | 343 | return undefined; 344 | 345 | } 346 | 347 | printCustomBinaryExpressionIfAny(node, identation) { 348 | const left = node.left; 349 | const right = node.right.text; 350 | 351 | const op = node.operatorToken.kind; 352 | 353 | if (left.kind === SyntaxKind.TypeOfExpression) { 354 | // handle typeof operator 355 | // Example: typeof a === "string" 356 | const typeOfExpression = this.handleTypeOfInsideBinaryExpression(node, identation); 357 | if (typeOfExpression) { 358 | return typeOfExpression; 359 | } 360 | } 361 | 362 | if (op === ts.SyntaxKind.InKeyword) { 363 | const rightSide = this.printNode(node.right, 0); 364 | const leftSide = this.printNode(node.left, 0); 365 | return `${this.getIden(identation)}is_array(${rightSide}) && array_key_exists(${leftSide}, ${rightSide})`; 366 | } 367 | 368 | const prop = node?.left?.expression?.name?.text; 369 | 370 | if (prop) { 371 | const args = left.arguments; 372 | const parsedArg = (args && args.length > 0) ? this.printNode(args[0], 0): undefined; 373 | const leftSideOfIndexOf = left.expression.expression; // myString in myString.indexOf 374 | const leftSide = this.printNode(leftSideOfIndexOf, 0); 375 | const rightType = global.checker.getTypeAtLocation(leftSideOfIndexOf); // type of myString in myString.indexOf ("b") >= 0; 376 | switch(prop) { 377 | case 'indexOf': 378 | if (op === SyntaxKind.GreaterThanEqualsToken && right === '0') { 379 | this.warnIfAnyType(node, rightType.flags,leftSide, "indexOf"); 380 | if (this.isStringType(rightType.flags)) { 381 | return this.getIden(identation) + "mb_strpos(" + leftSide + ", " + parsedArg + ") !== false"; 382 | } else { 383 | return this.getIden(identation) + "in_array(" + parsedArg + ", " + leftSide + ")"; 384 | } 385 | } 386 | } 387 | } 388 | return undefined; 389 | } 390 | 391 | printFunctionDeclaration(node, identation) { 392 | let functionDef = this.printFunctionDefinition(node, identation); 393 | const funcBody = this.printFunctionBody(node, identation); 394 | functionDef += funcBody; 395 | 396 | return this.printNodeCommentsIfAny(node, identation, functionDef); 397 | } 398 | 399 | printFunctionBody(node, identation) { 400 | 401 | if (this.asyncTranspiling && this.isAsyncFunction(node)) { 402 | const blockOpen = this.getBlockOpen(identation); 403 | const blockClose = this.getBlockClose(identation); 404 | 405 | const parsedArgs = node.parameters.map(param => this.printParameter(param, false)).join(", "); 406 | const params = parsedArgs ? " use (" + parsedArgs + ")" : ""; 407 | 408 | const bodyStms = [...node.body.statements]; 409 | const firstBodyStm = this.printNode(bodyStms[0], identation+2); 410 | bodyStms.shift(); 411 | const funcBody = bodyStms.map((s) => this.printNode(s, identation+2)).join("\n"); 412 | 413 | // reformat first comment 414 | const bodyParts = firstBodyStm.split("\n"); 415 | const commentPart = bodyParts.filter(line => this.isComment(line)); 416 | const isComment = commentPart.length > 0; 417 | let header = this.getIden(identation+1) + "return Async\\async(function ()" + params + " {\n"; 418 | if (isComment) { 419 | const commentPartString = commentPart.map((c) => this.getIden(identation+1) + c.trim()).join("\n"); 420 | const firstStmNoComment = bodyParts.filter(line => !this.isComment(line)).join("\n"); 421 | header = commentPartString + "\n" + header + firstStmNoComment + "\n"; 422 | } else { 423 | header += firstBodyStm + "\n"; 424 | } 425 | 426 | const result = header 427 | + funcBody + "\n" 428 | + this.getIden(identation+1) + "}) ();"; 429 | 430 | return blockOpen + result + blockClose; 431 | } 432 | return super.printFunctionBody(node, identation); 433 | } 434 | 435 | printPropertyAccessModifiers(node) { 436 | const modifiers = super.printPropertyAccessModifiers(node); 437 | return modifiers ? modifiers : "public "; // default to public 438 | } 439 | 440 | transformLeadingComment(comment) { 441 | const commentRegex = [ 442 | [ /\{([\]\[\|a-zA-Z0-9_-]+?)\}/g, '~$1~' ], // eslint-disable-line -- resolve the "arrays vs url params" conflict (both are in {}-brackets) 443 | [ /\[([^\]\[]*)\]\{(@link .*)\}/g, '~$2 $1~' ], // eslint-disable-line -- docstring item with link 444 | [ /\s+\* @method/g, '' ], // docstring @method 445 | [ /(\s+)\* @description (.*)/g, '$1\* $2' ], // eslint-disable-line 446 | [ /\s+\* @name .*/g, '' ], // docstring @name 447 | [ /(\s+)\* @returns/g, '$1\* @return' ], // eslint-disable-line 448 | [ /\~([\]\[\|@\.\s+\:\/#\-a-zA-Z0-9_-]+?)\~/g, '{$1}' ], // eslint-disable-line -- resolve the "arrays vs url params" conflict (both are in {}-brackets) 449 | [ /(\s+ \* @(param|return) {[^}]*)object([^}]*}.*)/g, '$1array$3' ], // docstring type conversion 450 | ]; 451 | 452 | const transformed = regexAll(comment, commentRegex); 453 | 454 | return transformed; 455 | } 456 | 457 | initConfig() { 458 | this.LeftPropertyAccessReplacements = { 459 | 'this': '$this', 460 | }; 461 | 462 | this.RightPropertyAccessReplacements = { 463 | 464 | }; 465 | 466 | this.FullPropertyAccessReplacements = { 467 | 'Number.MAX_SAFE_INTEGER': 'PHP_INT_MAX', 468 | 'JSON.stringify': 'json_encode', 469 | 'console.log': 'var_dump', 470 | 'process.exit': 'exit', 471 | 'Math.log': 'log', 472 | 'Math.abs': 'abs', 473 | 'Math.floor': '(int) floor', 474 | 'Math.ceil': '(int) ceil', 475 | 'Math.round': '(int) round', 476 | 'Math.pow': 'pow', 477 | 'Math.min': 'min', 478 | 'Math.max': 'max', 479 | // 'Promise.all': 'Promise\\all', 480 | }; 481 | 482 | this.CallExpressionReplacements = { 483 | 'parseFloat': 'floatval', 484 | 'parseInt': 'intval', 485 | }; 486 | 487 | this.PropertyAccessRequiresParenthesisRemoval = [ 488 | // 'length', 489 | // 'toString', 490 | // 'toUpperCase', 491 | // 'toLowerCase', 492 | // 'pop', 493 | // 'reverse', 494 | // 'shift', 495 | ]; 496 | } 497 | 498 | } 499 | -------------------------------------------------------------------------------- /src/pythonTranspiler.ts: -------------------------------------------------------------------------------- 1 | import { BaseTranspiler } from "./baseTranspiler.js"; 2 | import { regexAll } from "./utils.js"; 3 | import ts from 'typescript'; 4 | 5 | const SyntaxKind = ts.SyntaxKind; 6 | 7 | const parserConfig = { 8 | 'STATIC_TOKEN': '', // to do static decorator 9 | 'PUBLIC_KEYWORD': '', 10 | 'UNDEFINED_TOKEN': 'None', 11 | 'IF_TOKEN': 'if', 12 | 'ELSE_TOKEN': 'else', 13 | 'ELSEIF_TOKEN': 'elif', 14 | 'THIS_TOKEN': 'self', 15 | 'AMPERSTAND_APERSAND_TOKEN': 'and', 16 | 'BAR_BAR_TOKEN': 'or', 17 | 'SPACE_DEFAULT_PARAM': '', 18 | 'BLOCK_OPENING_TOKEN': ':', 19 | 'BLOCK_CLOSING_TOKEN': '', 20 | 'SPACE_BEFORE_BLOCK_OPENING': '', 21 | 'CONDITION_OPENING': '', 22 | 'CONDITION_CLOSE': '', 23 | 'TRUE_KEYWORD': 'True', 24 | 'FALSE_KEYWORD': 'False', 25 | 'THROW_TOKEN': 'raise', 26 | 'NOT_TOKEN': 'not ', 27 | 'PLUS_PLUS_TOKEN': ' += 1', 28 | 'MINUS_MINUS_TOKEN': ' -= 1', 29 | 'CONSTRUCTOR_TOKEN': 'def __init__', 30 | 'SUPER_CALL_TOKEN': 'super().__init__', 31 | 'PROPERTY_ASSIGNMENT_TOKEN': ':', 32 | 'FUNCTION_TOKEN': 'def', 33 | 'SUPER_TOKEN': 'super()', 34 | 'NEW_TOKEN': '', 35 | 'STRING_QUOTE_TOKEN': '\'', 36 | 'LINE_TERMINATOR': '', 37 | 'METHOD_TOKEN': 'def', 38 | 'CATCH_TOKEN': 'except', 39 | 'CATCH_DECLARATION': 'Exception as', 40 | 'METHOD_DEFAULT_ACCESS': '', 41 | 'SPREAD_TOKEN': '*', 42 | 'NULL_TOKEN': 'None', 43 | }; 44 | export class PythonTranspiler extends BaseTranspiler { 45 | constructor(config = {}) { 46 | 47 | config['parser'] = Object.assign ({}, parserConfig, config['parser'] ?? {}); 48 | 49 | super(config); 50 | this.id = "python"; 51 | 52 | this.initConfig(); 53 | this.asyncTranspiling = config['async'] ?? true; 54 | this.uncamelcaseIdentifiers = config['uncamelcaseIdentifiers'] ?? true; 55 | this.removeVariableDeclarationForFunctionExpression = config['removeVariableDeclarationForFunctionExpression'] ?? true; 56 | this.includeFunctionNameInFunctionExpressionDeclaration = config['includeFunctionNameInFunctionExpressionDeclaration'] ?? true; 57 | 58 | // user overrides 59 | this.applyUserOverrides(config); 60 | } 61 | 62 | initConfig() { 63 | this.LeftPropertyAccessReplacements = { 64 | 'this': 'self' 65 | }; 66 | this.RightPropertyAccessReplacements = { 67 | 'push': 'append', 68 | 'toUpperCase': 'upper', 69 | 'toLowerCase': 'lower', 70 | // 'parseFloat': 'float', 71 | // 'parseInt': 'int', 72 | 'indexOf': 'find', 73 | 'padEnd': 'ljust', 74 | 'padStart': 'rjust' 75 | }; 76 | this.FullPropertyAccessReplacements = { 77 | 'console.log': 'print', 78 | 'JSON.stringify': 'json.dumps', 79 | 'JSON.parse': 'json.loads', 80 | 'Math.log': 'math.log', 81 | 'Math.abs': 'abs', 82 | 'Math.min': 'min', 83 | 'Math.max': 'max', 84 | 'Math.ceil': 'math.ceil', 85 | 'Math.round': 'math.round', 86 | 'Math.floor': 'math.floor', 87 | 'Math.pow': 'math.pow', 88 | 'process.exit': 'sys.exit', 89 | 'Number.MAX_SAFE_INTEGER': 'float(\'inf\')', 90 | }; 91 | this.CallExpressionReplacements = { 92 | 'parseInt': 'int', 93 | 'parseFloat': 'float', 94 | }; 95 | 96 | this.PropertyAccessRequiresParenthesisRemoval = [ 97 | // 'length', 98 | // 'toString', 99 | ]; 100 | } 101 | 102 | printArrayIsArrayCall(node, identation, parsedArg = undefined) { 103 | return `isinstance(${parsedArg}, list)`; 104 | } 105 | 106 | printObjectKeysCall(node, identation, parsedArg = undefined) { 107 | return `list(${parsedArg}.keys())`; 108 | } 109 | 110 | printObjectValuesCall(node, identation, parsedArg = undefined) { 111 | return `list(${parsedArg}.values())`; 112 | } 113 | 114 | printPromiseAllCall(node, identation, parsedArg) { 115 | return `asyncio.gather(*${parsedArg})`; 116 | } 117 | 118 | printMathFloorCall(node, identation, parsedArg = undefined) { 119 | return `int(math.floor(${parsedArg}))`; 120 | } 121 | 122 | printMathCeilCall(node, identation, parsedArg = undefined) { 123 | return `int(math.ceil(${parsedArg}))`; 124 | } 125 | 126 | printNumberIsIntegerCall(node, identation , parsedArg = undefined) { 127 | return `isinstance(${parsedArg}, int)`; 128 | } 129 | 130 | printMathRoundCall(node, identation, parsedArg = undefined) { 131 | return `int(round(${parsedArg}))`; 132 | } 133 | 134 | printIncludesCall(node, identation, name?, parsedArg?) { 135 | return `${parsedArg} in ${name}`; 136 | } 137 | 138 | printJoinCall(node: any, identation: any, name?: any, parsedArg?: any) { 139 | return `${parsedArg}.join(${name})`; 140 | } 141 | 142 | printSplitCall(node: any, identation: any, name?: any, parsedArg?: any) { 143 | return `${name}.split(${parsedArg})`; 144 | } 145 | 146 | printConcatCall(node: any, identation: any, name?: any, parsedArg?: any) { 147 | return `${name} + ${parsedArg}`; 148 | } 149 | 150 | printPopCall(node: any, identation: any, name?: any) { 151 | return `${name}.pop()`; 152 | } 153 | 154 | printShiftCall(node: any, identation: any, name?: any) { 155 | return `${name}.pop(0)`; 156 | } 157 | 158 | printReverseCall(node, identation, name = undefined) { 159 | return `${name}.reverse()`; 160 | } 161 | 162 | printArrayPushCall(node, identation, name, parsedArg) { 163 | return `${name}.append(${parsedArg})`; 164 | } 165 | 166 | printToStringCall(node, identation, name = undefined) { 167 | return `str(${name})`; 168 | } 169 | 170 | printIndexOfCall(node, identation, name = undefined, parsedArg = undefined) { 171 | return `${name}.find(${parsedArg})`; 172 | } 173 | 174 | printSearchCall(node, identation, name = undefined, parsedArg = undefined) { 175 | return `${name}.find(${parsedArg})`; 176 | } 177 | 178 | printStartsWithCall(node, identation, name = undefined, parsedArg = undefined) { 179 | return `${name}.startswith(${parsedArg})`; 180 | } 181 | 182 | printEndsWithCall(node, identation, name = undefined, parsedArg = undefined) { 183 | return `${name}.endswith(${parsedArg})`; 184 | } 185 | 186 | printPadEndCall(node, identation, name, parsedArg, parsedArg2) { 187 | return `${name}.ljust(${parsedArg}, ${parsedArg2})`; 188 | } 189 | 190 | printPadStartCall(node, identation, name, parsedArg, parsedArg2) { 191 | return `${name}.rjust(${parsedArg}, ${parsedArg2})`; 192 | } 193 | 194 | printTrimCall(node, identation, name = undefined) { 195 | return `${name}.strip()`; 196 | } 197 | 198 | printToUpperCaseCall(node, identation, name = undefined) { 199 | return `${name}.upper()`; 200 | } 201 | 202 | printToLowerCaseCall(node, identation, name = undefined) { 203 | return `${name}.lower()`; 204 | } 205 | 206 | printJsonParseCall(node: any, identation: any, parsedArg?: any) { 207 | return `json.loads(${parsedArg})`; 208 | } 209 | 210 | printJsonStringifyCall(node: any, identation: any, parsedArg?: any) { 211 | return `json.dumps(${parsedArg})`; 212 | } 213 | 214 | printReplaceCall(node: any, identation: any, name?: any, parsedArg?: any, parsedArg2?: any) { 215 | return `${name}.replace(${parsedArg}, ${parsedArg2})`; 216 | } 217 | 218 | printReplaceAllCall(node: any, identation: any, name?: any, parsedArg?: any, parsedArg2?: any) { 219 | return `${name}.replace(${parsedArg}, ${parsedArg2})`; 220 | } 221 | 222 | printElementAccessExpressionExceptionIfAny(node) { 223 | if (node.expression.kind === SyntaxKind.ThisKeyword) { 224 | return "getattr(self, " + this.printNode(node.argumentExpression, 0) + ")"; 225 | } 226 | } 227 | 228 | printAssertCall(node, identation, parsedArgs) { 229 | return `assert ${parsedArgs}`; 230 | } 231 | 232 | printDateNowCall(node, identation) { 233 | return "int(time.time() * 1000)"; 234 | } 235 | 236 | 237 | printForStatement(node, identation) { 238 | const varName = node.initializer.declarations[0].name.escapedText; 239 | const initValue = this.printNode(node.initializer.declarations[0].initializer, 0); 240 | const roofValue = this.printNode(node.condition.right,0); 241 | 242 | const forStm = this.getIden(identation) + this.FOR_TOKEN + " " + varName + " in range(" + initValue + ", " + roofValue + "):\n" + node.statement.statements.map(st => this.printNode(st, identation+1)).join("\n"); 243 | return this.printNodeCommentsIfAny(node, identation, forStm); 244 | } 245 | 246 | printPropertyAccessModifiers(node) { 247 | return ""; // no access modifier in python 248 | } 249 | 250 | transformLeadingComment(comment) { 251 | const commentRegex = [ 252 | [ /(^|\s)\/\//g, '$1#' ], // regular comments 253 | [ /\/\*\*/, '\"\"\"' ], // eslint-disable-line 254 | [ / \*\//, '\"\"\"' ], // eslint-disable-line 255 | [ /\[([^\[\]]*)\]\{@link (.*)\}/g, '`$1 <$2>`' ], // eslint-disable-line 256 | [ /\s+\* @method/g, '' ], // docstring @method 257 | [ /(\s+) \* @description (.*)/g, '$1$2' ], // docstring description 258 | [ /\s+\* @name .*/g, '' ], // docstring @name 259 | [ /(\s+) \* @see( .*)/g, '$1see$2' ], // docstring @see 260 | [ /(\s+ \* @(param|returns) {[^}]*)string([^}]*}.*)/g, '$1str$3' ], // docstring type conversion 261 | [ /(\s+ \* @(param|returns) {[^}]*)object([^}]*}.*)/g, '$1dict$3' ], // doctstrubg type conversion 262 | [ /(\s+) \* @returns ([^\{])/g, '$1:returns: $2' ], // eslint-disable-line 263 | [ /(\s+) \* @returns \{(.+)\}/g, '$1:returns $2:' ], // docstring return 264 | [ /(\s+ \* @param \{[\]\[\|a-zA-Z]+\} )([a-zA-Z0-9_-]+)\.([a-zA-Z0-9_-]+) (.*)/g, '$1$2[\'$3\'] $4' ], // eslint-disable-line 265 | [ /(\s+) \* @([a-z]+) \{([\]\[a-zA-Z\|]+)\} ([a-zA-Z0-9_\-\.\[\]\']+)/g, '$1:$2 $3 $4:' ], // eslint-disable-line 266 | ]; 267 | 268 | const transformed = regexAll(comment, commentRegex); 269 | return transformed; 270 | } 271 | 272 | transformTrailingComment(comment) { 273 | const commentRegex = [ 274 | [ /(^|\s)\/\//g, '$1#' ], // regular comments 275 | ]; 276 | 277 | const transformed = regexAll(comment, commentRegex); 278 | return " " + transformed; 279 | } 280 | 281 | transformPropertyAcessExpressionIfNeeded(node: any) { 282 | const expression = node.expression; 283 | const leftSide = this.printNode(expression, 0); 284 | const rightSide = node.name.escapedText; 285 | 286 | let rawExpression = undefined; 287 | 288 | if (rightSide === "length") { 289 | rawExpression = "len(" + leftSide + ")"; 290 | } else if (rightSide === "toString") { 291 | rawExpression = "str(" + leftSide + ")"; 292 | } 293 | return rawExpression; 294 | } 295 | 296 | printClassDefinition(node: any, identation: any): string { 297 | const className = node.name.escapedText; 298 | const heritageClauses = node.heritageClauses; 299 | 300 | let classInit = ""; 301 | if (heritageClauses !== undefined) { 302 | const classExtends = heritageClauses[0].types[0].expression.escapedText; 303 | classInit = this.getIden(identation) + "class " + className + "(" + classExtends + "):\n"; 304 | } else { 305 | classInit = this.getIden(identation) + "class " + className + ":\n"; 306 | } 307 | return classInit; 308 | } 309 | 310 | printMethodParameters(node) { 311 | let parsedArgs = super.printMethodParameters(node); 312 | parsedArgs = parsedArgs ? "self, " + parsedArgs : "self"; 313 | return parsedArgs; 314 | } 315 | 316 | printInstanceOfExpression(node, identation) { 317 | const left = this.printNode(node.left, 0); 318 | const right = this.printNode(node.right, 0); 319 | return this.getIden(identation) + `isinstance(${left}, ${right})`; 320 | } 321 | 322 | handleTypeOfInsideBinaryExpression(node, identation) { 323 | const expression = node.left.expression; 324 | const right = node.right.text; 325 | 326 | const op = node.operatorToken.kind; 327 | const isDifferentOperator = op === SyntaxKind.ExclamationEqualsEqualsToken || op === SyntaxKind.ExclamationEqualsToken; 328 | const notOperator = isDifferentOperator ? this.NOT_TOKEN : ""; 329 | 330 | switch (right) { 331 | case "string": 332 | return this.getIden(identation) + notOperator + "isinstance(" + this.printNode(expression, 0) + ", str)"; 333 | case "number": 334 | return this.getIden(identation) + notOperator + "isinstance(" + this.printNode(expression, 0) + ", numbers.Real)"; 335 | case "boolean": 336 | return this.getIden(identation) + notOperator + "isinstance(" + this.printNode(expression, 0) + ", bool)"; 337 | case "object": 338 | return this.getIden(identation) + notOperator + "isinstance(" + this.printNode(expression, 0) + ", dict)"; 339 | case "undefined": 340 | return this.getIden(identation) + this.printNode(expression, 0) + " is " + notOperator + "None"; 341 | } 342 | 343 | return undefined; 344 | 345 | } 346 | 347 | printCustomBinaryExpressionIfAny(node, identation) { 348 | const left = node.left; 349 | const right = node.right.text; 350 | 351 | const op = node.operatorToken.kind; 352 | 353 | // Fix E712 comparison: if cond == True -> if cond: 354 | if ((op === ts.SyntaxKind.EqualsEqualsToken || op === ts.SyntaxKind.EqualsEqualsEqualsToken) && node.right.kind === ts.SyntaxKind.TrueKeyword) { 355 | return this.getIden(identation) + this.printNode(node.left, 0); 356 | } 357 | 358 | if (left.kind === SyntaxKind.TypeOfExpression) { 359 | const typeOfExpression = this.handleTypeOfInsideBinaryExpression(node, identation); 360 | if (typeOfExpression) { 361 | return typeOfExpression; 362 | } 363 | } 364 | 365 | const prop = node?.left?.expression?.name?.text; 366 | 367 | if (prop) { 368 | const args = left.arguments; 369 | const parsedArg = (args && args.length > 0) ? this.printNode(args[0], 0): undefined; 370 | const leftSideOfIndexOf = left.expression.expression; // myString in myString.indexOf 371 | const leftSide = this.printNode(leftSideOfIndexOf, 0); 372 | // const rightType = global.checker.getTypeAtLocation(leftSideOfIndexOf); // type of myString in myString.indexOf ("b") >= 0; 373 | 374 | switch(prop) { 375 | case 'indexOf': 376 | if (op === SyntaxKind.GreaterThanEqualsToken && right === '0') { 377 | return this.getIden(identation) + `${parsedArg} in ${leftSide}`; 378 | } 379 | } 380 | } 381 | return undefined; 382 | } 383 | 384 | printConditionalExpression(node, identation) { 385 | const condition = this.printNode(node.condition, 0); 386 | const whenTrue = this.printNode(node.whenTrue, 0); 387 | const whenFalse = this.printNode(node.whenFalse, 0); 388 | 389 | return this.getIden(identation) + whenTrue + " if " + condition + " else " + whenFalse; 390 | } 391 | 392 | printDeleteExpression(node, identation) { 393 | const expression = this.printNode (node.expression); 394 | return `del ${expression}`; 395 | } 396 | 397 | getCustomOperatorIfAny(left, right, operator) { 398 | const rightText = right.getText(); 399 | const isUndefined = rightText === "undefined"; 400 | if (isUndefined) { 401 | switch (operator.kind) { 402 | case ts.SyntaxKind.EqualsEqualsToken: 403 | return "is"; 404 | case ts.SyntaxKind.ExclamationEqualsToken: 405 | return "is not"; 406 | case ts.SyntaxKind.ExclamationEqualsEqualsToken: 407 | return "is not"; 408 | case ts.SyntaxKind.EqualsEqualsEqualsToken: 409 | return "is"; 410 | } 411 | } 412 | } 413 | } 414 | -------------------------------------------------------------------------------- /src/transpiler.ts: -------------------------------------------------------------------------------- 1 | import ts from 'typescript'; 2 | import currentPath from "./dirname.cjs"; 3 | import { PythonTranspiler } from './pythonTranspiler.js'; 4 | import { PhpTranspiler } from './phpTranspiler.js'; 5 | import { CSharpTranspiler } from './csharpTranspiler.js'; 6 | import * as path from "path"; 7 | import { Logger } from './logger.js'; 8 | import { Languages, TranspilationMode, IFileExport, IFileImport, ITranspiledFile, IInput } from './types.js'; 9 | import { GoTranspiler } from './goTranspiler.js'; 10 | 11 | const __dirname_mock = currentPath; 12 | 13 | function getProgramAndTypeCheckerFromMemory (rootDir: string, text: string, options: any = {}): [any,any,any] { 14 | options = options || ts.getDefaultCompilerOptions(); 15 | const inMemoryFilePath = path.resolve(path.join(rootDir, "__dummy-file.ts")); 16 | const textAst = ts.createSourceFile(inMemoryFilePath, text, options.target || ts.ScriptTarget.Latest); 17 | const host = ts.createCompilerHost(options, true); 18 | function overrideIfInMemoryFile(methodName: keyof ts.CompilerHost, inMemoryValue: any) { 19 | const originalMethod = host[methodName] as Function; // eslint-disable-line 20 | host[methodName] = (...args: unknown[]) => { 21 | // resolve the path because typescript will normalize it 22 | // to forward slashes on windows 23 | const filePath = path.resolve(args[0] as string); 24 | if (filePath === inMemoryFilePath) 25 | return inMemoryValue; 26 | return originalMethod.apply(host, args); 27 | }; 28 | } 29 | 30 | overrideIfInMemoryFile("getSourceFile", textAst); 31 | overrideIfInMemoryFile("readFile", text); 32 | overrideIfInMemoryFile("fileExists", true); 33 | 34 | const program = ts.createProgram({ 35 | options, 36 | rootNames: [inMemoryFilePath], 37 | host 38 | }); 39 | 40 | const typeChecker = program.getTypeChecker(); 41 | const sourceFile = program.getSourceFile(inMemoryFilePath); 42 | 43 | return [ program, typeChecker, sourceFile]; 44 | } 45 | 46 | export default class Transpiler { 47 | config; 48 | pythonTranspiler: PythonTranspiler; 49 | phpTranspiler: PhpTranspiler; 50 | csharpTranspiler: CSharpTranspiler; 51 | goTranspiler: GoTranspiler; 52 | constructor(config = {}) { 53 | this.config = config; 54 | const phpConfig = config["php"] || {}; 55 | const pythonConfig = config["python"] || {}; 56 | const csharpConfig = config["csharp"] || {}; 57 | const goConfig = config["go"] || {}; 58 | 59 | if ("verbose" in config) { 60 | Logger.setVerboseMode(config['verbose']); 61 | } 62 | 63 | this.pythonTranspiler = new PythonTranspiler(pythonConfig); 64 | this.phpTranspiler = new PhpTranspiler(phpConfig); 65 | this.csharpTranspiler = new CSharpTranspiler(csharpConfig); 66 | this.goTranspiler = new GoTranspiler(goConfig); 67 | } 68 | 69 | setVerboseMode(verbose: boolean) { 70 | Logger.setVerboseMode(verbose); 71 | } 72 | 73 | createProgramInMemoryAndSetGlobals(content) { 74 | const [ memProgram, memType, memSource] = getProgramAndTypeCheckerFromMemory(__dirname_mock, content); 75 | global.src = memSource; 76 | global.checker = memType as ts.TypeChecker; 77 | global.program = memProgram; 78 | } 79 | 80 | createProgramByPathAndSetGlobals(path) { 81 | const program = ts.createProgram([path], {}); 82 | const sourceFile = program.getSourceFile(path); 83 | const typeChecker = program.getTypeChecker(); 84 | 85 | global.src = sourceFile; 86 | global.checker = typeChecker; 87 | global.program = program; 88 | } 89 | 90 | checkFileDiagnostics() { 91 | const diagnostics = ts.getPreEmitDiagnostics(global.program, global.src); 92 | if (diagnostics.length > 0) { 93 | let errorMessage = "Errors found in the typescript code. Transpilation might produce invalid results:\n"; 94 | diagnostics.forEach( msg => { 95 | errorMessage+= " - " + msg.messageText + "\n"; 96 | }); 97 | Logger.warning(errorMessage); 98 | } 99 | } 100 | 101 | transpile(lang: Languages, mode: TranspilationMode, file: string, sync = false, setGlobals = true, handleImports = true): ITranspiledFile { 102 | // improve this logic later 103 | if (setGlobals) { 104 | if (mode === TranspilationMode.ByPath) { 105 | this.createProgramByPathAndSetGlobals(file); 106 | } else { 107 | this.createProgramInMemoryAndSetGlobals(file); 108 | } 109 | 110 | // check for warnings and errors 111 | this.checkFileDiagnostics(); 112 | } 113 | 114 | let transpiledContent = undefined; 115 | switch(lang) { 116 | case Languages.Python: 117 | this.pythonTranspiler.asyncTranspiling = !sync; 118 | transpiledContent = this.pythonTranspiler.printNode(global.src, -1); 119 | this.pythonTranspiler.asyncTranspiling = true; // reset to default 120 | break; 121 | case Languages.Php: 122 | this.phpTranspiler.asyncTranspiling = !sync; 123 | transpiledContent = this.phpTranspiler.printNode(global.src, -1); 124 | this.phpTranspiler.asyncTranspiling = true; // reset to default 125 | break; 126 | case Languages.CSharp: 127 | transpiledContent = this.csharpTranspiler.printNode(global.src, -1); 128 | break; 129 | case Languages.Go: 130 | transpiledContent = this.goTranspiler.printNode(global.src, -1); 131 | break; 132 | } 133 | let imports = []; 134 | let exports = []; 135 | 136 | if (handleImports) { 137 | imports = this.pythonTranspiler.getFileImports(global.src); 138 | exports = this.pythonTranspiler.getFileExports(global.src); 139 | } 140 | 141 | const methodsTypes = this.pythonTranspiler.getMethodTypes(global.src); 142 | Logger.success("transpilation finished successfully"); 143 | 144 | return { 145 | content: transpiledContent, 146 | imports, 147 | exports, 148 | methodsTypes 149 | }; 150 | } 151 | 152 | transpileDifferentLanguagesGeneric(mode: TranspilationMode, input: IInput[], content: string): ITranspiledFile[] { 153 | if (mode === TranspilationMode.ByPath) { 154 | this.createProgramByPathAndSetGlobals(content); 155 | } else { 156 | this.createProgramInMemoryAndSetGlobals(content); 157 | } 158 | 159 | // check for warnings and errors 160 | this.checkFileDiagnostics(); 161 | 162 | const files = []; 163 | input.forEach( (inp) => { 164 | const async = inp.async; 165 | 166 | files.push({ 167 | content: this.transpile(inp.language, mode, content, !async, false, false).content 168 | }); 169 | }); 170 | 171 | const methodsTypes = this.pythonTranspiler.getMethodTypes(global.src); 172 | 173 | const imports = this.pythonTranspiler.getFileImports(global.src); 174 | const exports = this.pythonTranspiler.getFileExports(global.src); 175 | 176 | const output = files.map( (file) => { 177 | return { 178 | content: file.content, 179 | imports, 180 | exports, 181 | methodsTypes 182 | }; 183 | }); 184 | 185 | return output; 186 | } 187 | 188 | transpileDifferentLanguages(input: any[], content: string): ITranspiledFile[] { 189 | const config = input.map( (inp) => { 190 | return { 191 | language: this.convertStringToLanguageEnum(inp.language), 192 | async: inp.async 193 | }; 194 | } ); 195 | return this.transpileDifferentLanguagesGeneric(TranspilationMode.ByContent, config, content); 196 | } 197 | 198 | transpileDifferentLanguagesByPath(input: any[], content: string): ITranspiledFile[] { 199 | const config = input.map( (inp) => { 200 | return { 201 | language: this.convertStringToLanguageEnum(inp.language), 202 | async: inp.async 203 | }; 204 | } ); 205 | return this.transpileDifferentLanguagesGeneric(TranspilationMode.ByPath, config, content); 206 | } 207 | 208 | transpilePython(content): ITranspiledFile { 209 | return this.transpile(Languages.Python, TranspilationMode.ByContent, content, !this.pythonTranspiler.asyncTranspiling); 210 | } 211 | 212 | transpilePythonByPath(path): ITranspiledFile { 213 | return this.transpile(Languages.Python, TranspilationMode.ByPath, path, !this.pythonTranspiler.asyncTranspiling); 214 | } 215 | 216 | transpilePhp(content): ITranspiledFile { 217 | return this.transpile(Languages.Php, TranspilationMode.ByContent, content, !this.phpTranspiler.asyncTranspiling); 218 | } 219 | 220 | transpilePhpByPath(path): ITranspiledFile { 221 | return this.transpile(Languages.Php, TranspilationMode.ByPath, path, !this.phpTranspiler.asyncTranspiling); 222 | } 223 | 224 | transpileCSharp(content): ITranspiledFile { 225 | return this.transpile(Languages.CSharp, TranspilationMode.ByContent, content); 226 | } 227 | 228 | transpileCSharpByPath(path): ITranspiledFile { 229 | return this.transpile(Languages.CSharp, TranspilationMode.ByPath, path); 230 | } 231 | 232 | transpileGoByPath(path): ITranspiledFile { 233 | return this.transpile(Languages.Go, TranspilationMode.ByPath, path); 234 | } 235 | 236 | transpileGo(content): ITranspiledFile { 237 | return this.transpile(Languages.Go, TranspilationMode.ByContent, content); 238 | } 239 | 240 | 241 | getFileImports(content: string): IFileImport[] { 242 | this.createProgramInMemoryAndSetGlobals(content); 243 | return this.phpTranspiler.getFileImports(global.src); 244 | } 245 | 246 | getFileExports(content: string): IFileExport[] { 247 | this.createProgramInMemoryAndSetGlobals(content); 248 | return this.phpTranspiler.getFileExports(global.src); 249 | } 250 | 251 | setPHPPropResolution(props: string[]) { 252 | this.phpTranspiler.propRequiresScopeResolutionOperator = props; 253 | } 254 | 255 | setPhpUncamelCaseIdentifiers(uncamelCase: boolean) { 256 | this.phpTranspiler.uncamelcaseIdentifiers = uncamelCase; 257 | } 258 | 259 | setPythonUncamelCaseIdentifiers(uncamelCase: boolean) { 260 | this.pythonTranspiler.uncamelcaseIdentifiers = uncamelCase; 261 | } 262 | 263 | setPhpAsyncTranspiling(async: boolean) { 264 | this.phpTranspiler.asyncTranspiling = async; 265 | } 266 | 267 | setPythonAsyncTranspiling(async: boolean) { 268 | this.pythonTranspiler.asyncTranspiling = async; 269 | } 270 | 271 | setPythonStringLiteralReplacements(replacements): void { 272 | this.pythonTranspiler.StringLiteralReplacements = replacements; 273 | } 274 | 275 | convertStringToLanguageEnum(lang: string): Languages { 276 | switch(lang) { 277 | case "python": 278 | return Languages.Python; 279 | case "php": 280 | return Languages.Php; 281 | case "csharp": 282 | return Languages.CSharp; 283 | case "go": 284 | return Languages.Go; 285 | } 286 | } 287 | } 288 | 289 | export { 290 | Transpiler 291 | }; 292 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | 2 | interface IInput { 3 | language: Languages; 4 | async: boolean; 5 | } 6 | 7 | 8 | interface IParameterType { 9 | name: string; 10 | type: string; 11 | isOptional: boolean; 12 | initializer?: string; 13 | } 14 | 15 | interface IMethodType { 16 | async: boolean; 17 | name: string; 18 | returnType: string; 19 | parameters: IParameterType[]; 20 | } 21 | 22 | interface IFileImport { 23 | name: string; 24 | path: string; 25 | isDefault: boolean; 26 | } 27 | 28 | interface IFileExport { 29 | name: string; 30 | isDefault: boolean; 31 | } 32 | 33 | interface ITranspiledFile { 34 | content: string; 35 | imports: IFileImport[]; 36 | exports: IFileExport[]; 37 | methodsTypes?: IMethodType[]; 38 | } 39 | 40 | enum Languages { 41 | Python, 42 | Php, 43 | CSharp, 44 | Go 45 | } 46 | 47 | enum TranspilationMode { 48 | ByPath, 49 | ByContent 50 | } 51 | 52 | // const TranspilingError = (message) => ({ 53 | // error: new Error(message), 54 | // code: 'TRANSPILING ERROR' 55 | // }); 56 | 57 | class TranspilationError extends Error { 58 | constructor (id, message, nodeText, start, end) { 59 | const parsedMessage = `Lang: ${id} Error: ${message} at ${start}:${end} node: "${nodeText}"`; 60 | super (parsedMessage); 61 | this.name = 'TranspilationError'; 62 | 63 | } 64 | } 65 | 66 | // class FunctionReturnTypeError extends TranspilationError { 67 | // constructor (message) { 68 | // super (message); 69 | // this.name = 'FuctionReturnTypeError'; 70 | // } 71 | // } 72 | 73 | // class FunctionArgumentTypeError extends TranspilationError { 74 | // constructor (message) { 75 | // super (message); 76 | // this.name = 'FunctionArgumentTypeError'; 77 | // } 78 | // } 79 | 80 | export { 81 | Languages, 82 | TranspilationMode, 83 | IFileImport, 84 | ITranspiledFile, 85 | IFileExport, 86 | TranspilationError, 87 | // FunctionReturnTypeError, 88 | // FunctionArgumentTypeError, 89 | IInput, 90 | IMethodType, 91 | IParameterType 92 | }; -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | function regexAll (text: string, array: any[]): string { 4 | for (const i in array) { 5 | let regex = array[i][0]; 6 | const flags = (typeof regex === 'string') ? 'g' : undefined; 7 | regex = new RegExp (regex, flags); 8 | text = text.replace (regex, array[i][1]); 9 | } 10 | return text; 11 | } 12 | 13 | function unCamelCase (s: string): string | undefined { 14 | return s.match (/[A-Z]/) ? s.replace (/[a-z0-9][A-Z]/g, x => x[0] + '_' + x[1]).replace(/[A-Z0-9][A-Z0-9][a-z][^$]/g, x => x[0] + '_' + x[1] + x[2] + x[3]).replace(/[a-z][0-9]$/g, x=> x[0] + '_' + x[1]).toLowerCase () : undefined; 15 | } 16 | 17 | // function extend (...args) { 18 | // Object.assign ({}, ...args); 19 | // } 20 | 21 | export { 22 | regexAll, 23 | unCamelCase, 24 | }; 25 | -------------------------------------------------------------------------------- /test.ts: -------------------------------------------------------------------------------- 1 | import { Piscina } from 'piscina'; 2 | import fs from 'fs'; 3 | 4 | 5 | const piscina = new Piscina({ 6 | // The URL must be a file:// URL 7 | filename: new URL('./worker.js', import.meta.url).href 8 | }); 9 | 10 | 11 | const data = fs.readFileSync('./benchTest.js'); 12 | 13 | const config = { 14 | verbose:false, 15 | python: { 16 | uncamelcaseIdentifiers: true, 17 | }, 18 | php: { 19 | uncamelcaseIdentifiers: true, 20 | }, 21 | csharp: { 22 | parser: { 23 | "ELEMENT_ACCESS_WRAPPER_OPEN": "getValue(", 24 | "ELEMENT_ACCESS_WRAPPER_CLOSE": ")" 25 | } 26 | } 27 | } 28 | 29 | const transpileConfig = [ 30 | { 31 | language: "php", 32 | async: true 33 | }, 34 | { 35 | language: "php", 36 | async: false 37 | }, 38 | { 39 | language: "python", 40 | async: false 41 | }, 42 | { 43 | language: "python", 44 | async: true 45 | }, 46 | ] 47 | 48 | 49 | const filesConfig = [] as any[]; 50 | for (let i = 0; i < 50; i++) { 51 | filesConfig.push({ 52 | name: `file${i}.js`, 53 | content: data.toString(), 54 | config: transpileConfig 55 | }); 56 | } 57 | 58 | // piscina.run({transpilerConfig:config, filesConfig:filesConfig}) 59 | 60 | const chunkSize = 10; 61 | const promises = [] as any[]; 62 | console.log("Will construct promises"); 63 | for (let i = 0; i < filesConfig.length; i += chunkSize) { 64 | const chunk = filesConfig.slice(i, i + chunkSize); 65 | promises.push(piscina.run({transpilerConfig:config, filesConfig:chunk})); 66 | // do whatever 67 | } 68 | console.log("Will await promises"); 69 | const result = await Promise.all(promises); 70 | console.log(result); // Prints 10 -------------------------------------------------------------------------------- /tests/fileExportsTranspiler.test.ts: -------------------------------------------------------------------------------- 1 | import { IFileExport } from "../src/types"; 2 | import { Transpiler } from "../src/transpiler"; 3 | 4 | let transpiler: Transpiler; 5 | 6 | beforeAll(() => { 7 | transpiler = new Transpiler({}); 8 | }) 9 | 10 | describe('file exports tests [esm]', () => { 11 | test('basic default export [esm]', () => { 12 | const ts = "export default x;" 13 | const expected: IFileExport[] = [ 14 | { 15 | "name": "x", 16 | "isDefault": true, 17 | } 18 | ]; 19 | const output = transpiler.getFileExports(ts); 20 | expect(output).toMatchObject(expected); 21 | }); 22 | test('basic named export [esm]', () => { 23 | const ts = "export {x}" 24 | const expected: IFileExport[] = [ 25 | { 26 | "name": "x", 27 | "isDefault": false, 28 | } 29 | ]; 30 | const output = transpiler.getFileExports(ts); 31 | expect(output).toMatchObject(expected); 32 | }); 33 | test('multiple named exports [esm]', () => { 34 | const ts = "export {o,a};" 35 | const expected: IFileExport[] = [ 36 | { 37 | "name": "o", 38 | "isDefault": false, 39 | }, 40 | { 41 | "name": "a", 42 | "isDefault": false, 43 | } 44 | ]; 45 | const output = transpiler.getFileExports(ts); 46 | expect(output).toMatchObject(expected); 47 | }); 48 | test('default class export [esm]', () => { 49 | const ts = "export default class test() {};" 50 | const expected: IFileExport[] = [ 51 | { 52 | "name": "test", 53 | "isDefault": true, 54 | } 55 | ]; 56 | const output = transpiler.getFileExports(ts); 57 | expect(output).toMatchObject(expected); 58 | }); 59 | test('default function export [esm]', () => { 60 | const ts = "export default function test() {};" 61 | const expected: IFileExport[] = [ 62 | { 63 | "name": "test", 64 | "isDefault": true, 65 | } 66 | ]; 67 | const output = transpiler.getFileExports(ts); 68 | expect(output).toMatchObject(expected); 69 | }); 70 | test('default export [cjs', () => { 71 | const ts = "module.exports = test" 72 | const expected: IFileExport[] = [ 73 | { 74 | "name": "test", 75 | "isDefault": true, 76 | } 77 | ]; 78 | const output = transpiler.getFileExports(ts); 79 | expect(output).toMatchObject(expected); 80 | }); 81 | test('multiple exports [cjs]', () => { 82 | const ts = 83 | "module.exports = {\n" + 84 | " a,\n" + 85 | " b,\n" + 86 | " c,\n" + 87 | "}"; 88 | const expected: IFileExport[] = [ 89 | { 90 | "name": "a", 91 | "isDefault": false, 92 | }, 93 | { 94 | "name": "b", 95 | "isDefault": false, 96 | }, 97 | { 98 | "name": "c", 99 | "isDefault": false, 100 | } 101 | ]; 102 | const output = transpiler.getFileExports(ts); 103 | expect(output).toMatchObject(expected); 104 | }); 105 | }); -------------------------------------------------------------------------------- /tests/fileImportsTranspiler.test.ts: -------------------------------------------------------------------------------- 1 | import { IFileImport } from "../src/types"; 2 | import { Transpiler } from "../src/transpiler"; 3 | 4 | let transpiler: Transpiler; 5 | 6 | beforeAll(() => { 7 | transpiler = new Transpiler({}); 8 | }) 9 | 10 | describe('file imports tests', () => { 11 | test('basic default import [esm]', () => { 12 | const ts = "import o from 'otherfile.js'" 13 | const expected: IFileImport[] = [ 14 | { 15 | "name": "o", 16 | "path": "otherfile.js", 17 | "isDefault": true, 18 | } 19 | ]; 20 | const output = transpiler.getFileImports(ts); 21 | expect(output).toMatchObject(expected); 22 | }); 23 | test('basic named import [esm]', () => { 24 | const ts = "import {o} from 'otherfile.js'" 25 | const expected: IFileImport[] = [ 26 | { 27 | "name": "o", 28 | "path": "otherfile.js", 29 | "isDefault": false, 30 | } 31 | ]; 32 | const output = transpiler.getFileImports(ts); 33 | expect(output).toMatchObject(expected); 34 | }); 35 | test('multiple named imports [esm]', () => { 36 | const ts = "import {o,a} from 'otherfile.js'" 37 | const expected: IFileImport[] = [ 38 | { 39 | "name": "o", 40 | "path": "otherfile.js", 41 | "isDefault": false, 42 | }, 43 | { 44 | "name": "a", 45 | "path": "otherfile.js", 46 | "isDefault": false, 47 | } 48 | ]; 49 | const output = transpiler.getFileImports(ts); 50 | expect(output).toMatchObject(expected); 51 | }); 52 | test('default and named imports and namespace import [esm]', () => { 53 | const ts = "import {o,a} from 'otherfile.js'\n" + 54 | "import * as functions from 'functions.js'\n" + 55 | "import b from 'otherfile.js'" 56 | 57 | const expected: IFileImport[] = [ 58 | { 59 | "name": "o", 60 | "path": "otherfile.js", 61 | "isDefault": false, 62 | }, 63 | { 64 | "name": "a", 65 | "path": "otherfile.js", 66 | "isDefault": false, 67 | }, 68 | { 69 | "name": "functions", 70 | "path": "functions.js", 71 | "isDefault": false, 72 | }, 73 | { 74 | "name": "b", 75 | "path": "otherfile.js", 76 | "isDefault": true, 77 | } 78 | ]; 79 | const output = transpiler.getFileImports(ts); 80 | expect(output).toMatchObject(expected); 81 | }); 82 | test('basic default import [cjs]', () => { 83 | const ts = "const o = require('otherfile.js')" 84 | const expected: IFileImport[] = [ 85 | { 86 | "name": "o", 87 | "path": "otherfile.js", 88 | "isDefault": true, 89 | } 90 | ]; 91 | const output = transpiler.getFileImports(ts); 92 | expect(output).toMatchObject(expected); 93 | }); 94 | test('basic named import [cjs]', () => { 95 | const ts = "const {o} = require('otherfile.js')" 96 | const expected: IFileImport[] = [ 97 | { 98 | "name": "o", 99 | "path": "otherfile.js", 100 | "isDefault": false, 101 | } 102 | ]; 103 | const output = transpiler.getFileImports(ts); 104 | expect(output).toMatchObject(expected); 105 | }); 106 | test('multiple named imports [cjs]', () => { 107 | const ts = "const {o,a} = require('otherfile.js')" 108 | const expected: IFileImport[] = [ 109 | { 110 | "name": "o", 111 | "path": "otherfile.js", 112 | "isDefault": false, 113 | }, 114 | { 115 | "name": "a", 116 | "path": "otherfile.js", 117 | "isDefault": false, 118 | } 119 | ]; 120 | const output = transpiler.getFileImports(ts); 121 | expect(output).toMatchObject(expected); 122 | }); 123 | test('default and named imports [cjs]', () => { 124 | const ts = "const {o,a} = require('otherfile.js')\n" + 125 | "const b = require('otherfile.js')" 126 | const expected: IFileImport[] = [ 127 | { 128 | "name": "o", 129 | "path": "otherfile.js", 130 | "isDefault": false, 131 | }, 132 | { 133 | "name": "a", 134 | "path": "otherfile.js", 135 | "isDefault": false, 136 | }, 137 | { 138 | "name": "b", 139 | "path": "otherfile.js", 140 | "isDefault": true, 141 | } 142 | ]; 143 | const output = transpiler.getFileImports(ts); 144 | expect(output).toMatchObject(expected); 145 | }); 146 | }); -------------------------------------------------------------------------------- /tests/files/input/test1.ts: -------------------------------------------------------------------------------- 1 | class myClass { 2 | 3 | createString () { 4 | return "hello"; 5 | } 6 | createList() { 7 | return [1, 2, 3]; 8 | } 9 | } 10 | 11 | const myInstance = new myClass(); 12 | 13 | let myString = myInstance.createString(); 14 | 15 | 16 | myString += "append"; 17 | 18 | 19 | let myList = myInstance.createList(); 20 | 21 | myList.push(4) 22 | 23 | const myStringLen = myString.length; 24 | const myListLen = myList.length; -------------------------------------------------------------------------------- /tests/files/output/php/test1.php: -------------------------------------------------------------------------------- 1 | class myClass { 2 | public function create_string() { 3 | return 'hello'; 4 | } 5 | 6 | public function create_list() { 7 | return [1, 2, 3]; 8 | } 9 | } 10 | $my_instance = new myClass(); 11 | $my_string = $my_instance->create_string(); 12 | $my_string .= 'append'; 13 | $my_list = $my_instance->create_list(); 14 | $my_list[] = 4; 15 | $my_string_len = strlen($my_string); 16 | $my_list_len = count($my_list); -------------------------------------------------------------------------------- /tests/files/output/python/test1.py: -------------------------------------------------------------------------------- 1 | class myClass: 2 | def create_string(self): 3 | return 'hello' 4 | 5 | def create_list(self): 6 | return [1, 2, 3] 7 | my_instance = myClass() 8 | my_string = my_instance.create_string() 9 | my_string += 'append' 10 | my_list = my_instance.create_list() 11 | my_list.append(4) 12 | my_string_len = len(my_string) 13 | my_list_len = len(my_list) -------------------------------------------------------------------------------- /tests/goTranspiler.test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from 'console'; 2 | import { Transpiler } from '../src/transpiler'; 3 | import { readFileSync } from 'fs'; 4 | 5 | jest.mock('module',()=>({ 6 | __esModule: true, // this makes it work 7 | default: jest.fn() 8 | })); 9 | 10 | let transpiler: Transpiler; 11 | 12 | beforeAll(() => { 13 | const config = { 14 | 'verbose': false, 15 | 'go': { 16 | 'parser': { 17 | 'NUM_LINES_END_FILE': 0, 18 | } 19 | } 20 | } 21 | transpiler = new Transpiler(config); 22 | }) 23 | 24 | describe('go transpiling tests', () => { 25 | test('basic variable declaration', () => { 26 | const ts = "const x = 1;" 27 | const go = "var x interface{} = 1" 28 | const output = transpiler.transpileGo(ts).content; 29 | expect(output).toBe(go); 30 | }); 31 | test('basic while loop', () => { 32 | const ts = 33 | "while (true) {\n" + 34 | " const x = 1;\n" + 35 | " break;\n" + 36 | "}" 37 | const go = 38 | "for true {\n" + 39 | " var x interface{} = 1\n" + 40 | " break\n" + 41 | "}"; 42 | const output = transpiler.transpileGo(ts).content; 43 | expect(output).toBe(go); 44 | }); 45 | test('basic class declaration', () => { 46 | const ts = 47 | "class Test {\n" + 48 | " main() {\n" + 49 | " return 1\n" + 50 | " }\n" + 51 | "}"; 52 | const go = 53 | "type Test struct {\n"+ 54 | "\n"+ 55 | "}\n"+ 56 | "\n"+ 57 | "func NewTest() Test {\n"+ 58 | " p := Test{}\n"+ 59 | " setDefaults(&p)\n"+ 60 | " return p\n"+ 61 | "}\n"+ 62 | "\n"+ 63 | "func (this *Test) Main() interface{} {\n"+ 64 | " return 1\n"+ 65 | "}"; 66 | const output = transpiler.transpileGo(ts).content; 67 | expect(output).toBe(go); 68 | }); 69 | test('falsy values', () => { 70 | const ts = 71 | "const a = \"hi\";\n" + 72 | "const b = false;\n" + 73 | "const c = a && b;\n" + 74 | "const d = !a && !b;\n" + 75 | "const e = (a || !b);\n" + 76 | "if (a) {\n" + 77 | " const f = 1;\n" + 78 | "}"; 79 | const go = 80 | "var a interface{} = \"hi\"\n" + 81 | "var b interface{} = false\n" + 82 | "var c interface{} = IsTrue(a) && IsTrue(b)\n" + 83 | "var d interface{} = !IsTrue(a) && !IsTrue(b)\n" + 84 | "var e interface{} = (IsTrue(a) || !IsTrue(b))\n" + 85 | "if IsTrue(a) {\n" + 86 | " var f interface{} = 1\n" + 87 | "}" 88 | const output = transpiler.transpileGo(ts).content; 89 | expect(output).toBe(go); 90 | }); 91 | // test('basic try catch', () => { 92 | // assert true 93 | // const ts = 94 | // "class A {\n" + 95 | // " main() {\n" + 96 | // " try {\n" + 97 | // " if (1 == 1+1) {\n" + 98 | // " return 1\n" + 99 | // " }\n" + 100 | // " } catch (e) {\n" + 101 | // " return 2\n" + 102 | // " }\n" + 103 | // " }\n" + 104 | // "}"; 105 | // const go = 106 | // "type A struct {\n"+ 107 | // "\n"+ 108 | // "}\n"+ 109 | // "\n"+ 110 | // "func NewA() A {\n"+ 111 | // " p := A{}\n"+ 112 | // " setDefaults(&p)\n"+ 113 | // " return p\n"+ 114 | // "}\n"+ 115 | // "\n"+ 116 | // "func (this *A) Main() interface{} {\n"+ 117 | // " \n"+ 118 | // " { ret__ := func(this *A) (ret_ interface{}) {\n"+ 119 | // " defer func() {\n"+ 120 | // " if e := recover().(interface{}); e != nil {\n"+ 121 | // " if e == \"break\" {\n"+ 122 | // " return\n"+ 123 | // " }\n"+ 124 | // " ret_ = func(this *A) interface{} {\n"+ 125 | // " // catch block:\n"+ 126 | // " return 2\n"+ 127 | // " return nil\n"+ 128 | // " }(this)\n"+ 129 | // " }\n"+ 130 | // " }()\n"+ 131 | // " // try block:\n"+ 132 | // " if IsTrue(IsEqual(1, Add(1, 1))) {\n"+ 133 | // " return 1\n"+ 134 | // " }\n"+ 135 | // " return nil\n"+ 136 | // " }(this)\n"+ 137 | // " if ret__ != nil {\n"+ 138 | // " return ret__\n"+ 139 | // " }\n"+ 140 | // " }\n"+ 141 | // "}"; 142 | // const output = transpiler.transpileGo(ts).content; 143 | // expect(output).toBe(go); 144 | // }); 145 | test('should convert concat', () => { 146 | const ts = "y.concat(z)"; 147 | const result = "Concat(y, z)"; 148 | const output = transpiler.transpileGo(ts).content; 149 | expect(output).toBe(result); 150 | }); 151 | test('should transpile spread operator when passed to function', () => { 152 | const ts = 153 | "const x = [1,2,3]\n" + 154 | "foo(...x)"; 155 | const go = 156 | "var x interface{} = []interface{}{1, 2, 3}\n" + 157 | "Foo(x...)"; 158 | const output = transpiler.transpileGo(ts).content; 159 | expect(output).toBe(go); 160 | }); 161 | }); 162 | -------------------------------------------------------------------------------- /tests/integration/.gitignore: -------------------------------------------------------------------------------- 1 | obj/ 2 | __pycache__ 3 | bin/ 4 | -------------------------------------------------------------------------------- /tests/integration/cs/Program.cs: -------------------------------------------------------------------------------- 1 | namespace tests; 2 | 3 | public static class Program 4 | { 5 | 6 | public static void Main(string[] args) 7 | { 8 | var test = new Test(); 9 | test.test(); 10 | } 11 | } -------------------------------------------------------------------------------- /tests/integration/cs/TranspilerHelpers.cs: -------------------------------------------------------------------------------- 1 | // tmp solution, duplicated file: 2 | // original file: src/TranspilerHelpers.cs 3 | 4 | namespace tests; 5 | 6 | using System.Globalization; 7 | using System.Reflection; 8 | 9 | using dict = Dictionary; 10 | 11 | 12 | public partial class Test 13 | { 14 | 15 | // tmp most of these methods are going to be re-implemented in the future to be more generic and efficient 16 | 17 | public static object normalizeIntIfNeeded(object a) 18 | { 19 | if (a == null) 20 | return null; 21 | 22 | if (a is int) 23 | { 24 | return System.Convert.ToInt64(a); 25 | } 26 | return a; 27 | } 28 | public static object postFixIncrement(ref object a) 29 | { 30 | if (a is Int64) 31 | { 32 | a = (Int64)a + 1; 33 | } 34 | else if (a is int) 35 | { 36 | a = (int)a + 1; 37 | } 38 | else if (a is double) 39 | { 40 | a = (double)a + 1; 41 | } 42 | else if (a is string) 43 | { 44 | a = (string)a + 1; 45 | } 46 | else 47 | { 48 | return null; 49 | } 50 | return a; 51 | } 52 | 53 | public static object postFixDecrement(ref object a) 54 | { 55 | 56 | if (a is Int64) 57 | { 58 | a = (Int64)a - 1; 59 | } 60 | else if (a is int) 61 | { 62 | a = (int)a - 1; 63 | } 64 | else if (a is double) 65 | { 66 | a = (double)a - 1; 67 | } 68 | else 69 | { 70 | return null; 71 | } 72 | return a; 73 | 74 | } 75 | 76 | public static object prefixUnaryNeg(ref object a) 77 | { 78 | if (a.GetType() == typeof(Int64)) 79 | { 80 | a = -(Int64)a; 81 | } 82 | else if (a.GetType() == typeof(int)) 83 | { 84 | a = -(int)a; 85 | } 86 | else if (a.GetType() == typeof(double)) 87 | { 88 | a = -(double)a; 89 | } 90 | else if (a.GetType() == typeof(string)) 91 | { 92 | return null; 93 | } 94 | else 95 | { 96 | return null; 97 | } 98 | return a; 99 | } 100 | 101 | public static object prefixUnaryPlus(ref object a) 102 | { 103 | if (a.GetType() == typeof(Int64)) 104 | { 105 | a = +(Int64)a; 106 | } 107 | else if (a.GetType() == typeof(int)) 108 | { 109 | a = +(int)a; 110 | } 111 | else if (a.GetType() == typeof(double)) 112 | { 113 | a = +(double)a; 114 | } 115 | else if (a.GetType() == typeof(string)) 116 | { 117 | return null; 118 | } 119 | else 120 | { 121 | return null; 122 | } 123 | return a; 124 | } 125 | 126 | public static object plusEqual(object a, object value) 127 | { 128 | 129 | a = normalizeIntIfNeeded(a); 130 | value = normalizeIntIfNeeded(value); 131 | 132 | if (value == null) 133 | return null; 134 | if (a.GetType() == typeof(Int64)) 135 | { 136 | a = (Int64)a + (Int64)value; 137 | } 138 | else if (a.GetType() == typeof(int)) 139 | { 140 | a = (int)a + (int)value; 141 | } 142 | else if (a.GetType() == typeof(double)) 143 | { 144 | a = (double)a + (double)value; 145 | } 146 | else if (a.GetType() == typeof(string)) 147 | { 148 | a = (string)a + (string)value; 149 | } 150 | else 151 | { 152 | return null; 153 | } 154 | return a; 155 | } 156 | 157 | // public object parseJson(object json) 158 | // { 159 | // // var jsonString = json.ToString(); 160 | // // if (jsonString.StartsWith("[".ToString())) 161 | // // { 162 | // // return JsonConvert.DeserializeObject>(jsonString); 163 | // // } 164 | // // return JsonConvert.DeserializeObject((string)json); 165 | // return JsonHelper.Deserialize((string)json); 166 | // } 167 | 168 | public static bool isTrue(object value) 169 | { 170 | if (value == null) 171 | { 172 | return false; 173 | } 174 | 175 | value = normalizeIntIfNeeded(value); 176 | 177 | // return value != null && value != false && value != 0 && value != "" && value != "0" && value != "false" && value != "False" && value != "FALSE"; 178 | if (value is (bool)) 179 | { 180 | return (bool)value; 181 | } 182 | else if (value is (Int64)) 183 | { 184 | return (Int64)value != 0; 185 | } 186 | else if (value is (double)) 187 | { 188 | return (double)value != 0; 189 | } 190 | else if (value is (string)) 191 | { 192 | return (string)value != ""; 193 | } 194 | else if (value is (IList)) 195 | { 196 | return ((IList)value).Count > 0; 197 | } 198 | else if (value is (IList)) 199 | { 200 | return ((IList)value).Count > 0; 201 | } 202 | else if (value is (IList)) 203 | { 204 | return ((IList)value).Count > 0; 205 | } 206 | else if (value is (IList)) 207 | { 208 | return ((IList)value).Count > 0; 209 | } 210 | else if (value is (IList)) 211 | { 212 | return ((IList)value).Count > 0; 213 | } 214 | else if (value is (IDictionary)) 215 | { 216 | return true; 217 | } 218 | else 219 | { 220 | return false; 221 | } 222 | } 223 | 224 | public static bool isNumber(object number) 225 | { 226 | return Double.TryParse(number.ToString(), out _); 227 | } 228 | 229 | public static bool isEqual(object a, object b) 230 | { 231 | 232 | try 233 | { 234 | 235 | if (a == null && b == null) 236 | { 237 | return true; 238 | } 239 | else if (a == null || b == null) 240 | { 241 | return false; 242 | } 243 | 244 | if (a.GetType() != b.GetType() && (!isNumber(a) || !isNumber(b))) 245 | { 246 | return false; 247 | } 248 | 249 | // if (a.GetType() != b.GetType()) 250 | // { 251 | // return false; 252 | // } 253 | if (IsInteger(a) && IsInteger(b)) 254 | { 255 | return Convert.ToInt64(a) == Convert.ToInt64(b); 256 | } 257 | if (a.GetType() == typeof(Int64) && b.GetType() == typeof(Int64)) 258 | { 259 | return Convert.ToInt64(a) == Convert.ToInt64(b); 260 | } 261 | if (a.GetType() == typeof(decimal) || b.GetType() == typeof(decimal)) 262 | { 263 | return Convert.ToDecimal(a) == Convert.ToDecimal(b); 264 | } 265 | else if (a.GetType() == typeof(int)) 266 | { 267 | return (int)a == (int)b; 268 | } 269 | else if (a.GetType() == typeof(double) || b.GetType() == typeof(double)) 270 | { 271 | return Convert.ToDouble(a) == Convert.ToDouble(b); 272 | } 273 | else if (a.GetType() == typeof(decimal) || b.GetType() == typeof(decimal)) 274 | { 275 | return Convert.ToDecimal(a) == Convert.ToDecimal(b); 276 | } 277 | else if (a.GetType() == typeof(Single) || b.GetType() == typeof(Single)) 278 | { 279 | return Convert.ToSingle(a) == Convert.ToSingle(b); 280 | } 281 | // else if (a.GetType() == typeof(double)) 282 | // { 283 | // return (double)a == (double)b; 284 | // } 285 | else if (a.GetType() == typeof(string)) 286 | { 287 | return ((string)a) == ((string)b); 288 | } 289 | else if (a.GetType() == typeof(bool)) 290 | { 291 | return ((bool)a) == ((bool)b); 292 | } 293 | else 294 | { 295 | return false; 296 | } 297 | } 298 | catch (Exception e) 299 | { 300 | return false; 301 | } 302 | 303 | 304 | } 305 | 306 | public static bool isGreaterThan(object a, object b) 307 | { 308 | if (a != null && b == null) 309 | { 310 | return true; 311 | } 312 | else if (a == null || b == null) 313 | { 314 | return false; 315 | } 316 | 317 | a = normalizeIntIfNeeded(a); 318 | b = normalizeIntIfNeeded(b); 319 | 320 | if (a.GetType() == typeof(Int64) && b.GetType() == typeof(Int64)) 321 | { 322 | return Convert.ToInt64(a) > Convert.ToInt64(b); 323 | } 324 | else if (a.GetType() == typeof(int) && b.GetType() == typeof(int)) 325 | { 326 | return (int)a > (int)b; 327 | } 328 | else if (a.GetType() == typeof(double) || b.GetType() == typeof(double)) 329 | { 330 | return Convert.ToDouble(a) > Convert.ToDouble(b); 331 | } 332 | else if (a.GetType() == typeof(string)) 333 | { 334 | return ((string)a).CompareTo((string)b) > 0; 335 | } 336 | else 337 | { 338 | return false; 339 | } 340 | } 341 | 342 | public static bool isLessThan(object a, object b) 343 | { 344 | 345 | return !isGreaterThan(a, b) && !isEqual(a, b); 346 | } 347 | 348 | public static bool isGreaterThanOrEqual(object a, object b) 349 | { 350 | return isGreaterThan(a, b) || isEqual(a, b); 351 | } 352 | 353 | public static bool isLessThanOrEqual(object a, object b) 354 | { 355 | return isLessThan(a, b) || isEqual(a, b); 356 | } 357 | 358 | public static object mod(object a, object b) 359 | { 360 | if (a == null || b == null) 361 | { 362 | return null; 363 | } 364 | 365 | a = normalizeIntIfNeeded(a); 366 | b = normalizeIntIfNeeded(b); 367 | 368 | if (a.GetType() == typeof(string) || a.GetType() == typeof(Int64) || a.GetType() == typeof(int) || a.GetType() == typeof(double)) 369 | return (Convert.ToDouble(a)) % (Convert.ToDouble(b)); 370 | 371 | return null; 372 | 373 | // return add(a, b); 374 | } 375 | 376 | public static object add(object a, object b) 377 | { 378 | a = normalizeIntIfNeeded(a); 379 | b = normalizeIntIfNeeded(b); 380 | 381 | if (a is (Int64)) 382 | { 383 | return (Int64)a + (Int64)b; 384 | } 385 | else if (a is (double)) 386 | { 387 | return (double)a + Convert.ToDouble(b); 388 | } 389 | else if (a is (string)) 390 | { 391 | return (string)a + (string)b; 392 | } 393 | else 394 | { 395 | return null; 396 | } 397 | } 398 | 399 | public static string add(string a, string b) 400 | { 401 | return a + b; 402 | } 403 | 404 | public static string add(string a, object b) 405 | { 406 | return add(a, b.ToString()); 407 | } 408 | 409 | // public static string add(object a, string b) 410 | // { 411 | // if (a == null || b == null) 412 | // { 413 | // return null; 414 | // } 415 | // if (a.GetType() != b.GetType()) 416 | // return null; 417 | 418 | // if (a.GetType() == typeof(string) || a.GetType() == typeof(Int64) || a.GetType() == typeof(int)) 419 | // return a + b; 420 | 421 | // return null; 422 | 423 | // // return add(a, b); 424 | // } 425 | 426 | // public static int add(int a, int b) 427 | // { 428 | // return a + b; 429 | // } 430 | 431 | // public float add(float a, float b) 432 | // { 433 | // return a + b; 434 | // } 435 | 436 | public static object subtract(object a, object b) 437 | { 438 | a = normalizeIntIfNeeded(a); 439 | b = normalizeIntIfNeeded(b); 440 | 441 | // subtract logic 442 | if (a.GetType() == typeof(Int64)) 443 | { 444 | return (Int64)a - (Int64)b; 445 | } 446 | else if (a.GetType() == typeof(int)) 447 | { 448 | return (int)a - (int)b; 449 | } 450 | else if (a.GetType() == typeof(double)) 451 | { 452 | return (double)a - (double)b; 453 | } 454 | else 455 | { 456 | return null; 457 | } 458 | } 459 | 460 | public static int subtract(int a, int b) 461 | { 462 | return a - b; 463 | } 464 | 465 | public float subtract(float a, float b) 466 | { 467 | return a - b; 468 | } 469 | 470 | public static object divide(object a, object b) 471 | { 472 | a = normalizeIntIfNeeded(a); 473 | b = normalizeIntIfNeeded(b); 474 | 475 | if (a == null || b == null) 476 | { 477 | return null; 478 | } 479 | 480 | if (a.GetType() == typeof(Int64) && b.GetType() == typeof(Int64)) 481 | { 482 | return (Int64)a / (Int64)b; 483 | } 484 | else if (a.GetType() == typeof(double) && b.GetType() == typeof(double)) 485 | { 486 | return (double)a / (double)b; 487 | } 488 | else 489 | { 490 | return Convert.ToDouble(a) / Convert.ToDouble(b); 491 | } 492 | } 493 | 494 | public static object multiply(object a, object b) 495 | { 496 | a = normalizeIntIfNeeded(a); 497 | b = normalizeIntIfNeeded(b); 498 | if (a == null || b == null) 499 | { 500 | return null; 501 | } 502 | 503 | if (a is Int64 && b is Int64) 504 | { 505 | return (Int64)a * (Int64)b; 506 | } 507 | var first = Convert.ToDouble(a); 508 | var second = Convert.ToDouble(b); 509 | 510 | var res = first * second; 511 | 512 | if (IsInteger(res)) 513 | { 514 | return Convert.ToInt64(res); 515 | } 516 | else 517 | { 518 | return res; 519 | } 520 | } 521 | 522 | public static int getArrayLength(object value) 523 | { 524 | if (value == null) 525 | { 526 | return 0; 527 | } 528 | 529 | if (value is (IList)) 530 | { 531 | return ((IList)value).Count; 532 | } 533 | else if (value is (IList)) 534 | { 535 | return ((IList)value).Count; 536 | } 537 | else if (value is (List)) 538 | { 539 | return ((List)value).Count; 540 | } 541 | else if (value is (string)) 542 | { 543 | return ((string)value).Length; // fallback that should not be used 544 | } 545 | else 546 | { 547 | return 0; 548 | } 549 | } 550 | 551 | public static bool IsInteger(object value) 552 | { 553 | if (value == null) 554 | { 555 | return false; 556 | } 557 | 558 | Type type = value.GetType(); 559 | 560 | // Check for integral types 561 | if (type == typeof(int) || type == typeof(long) || type == typeof(short) || type == typeof(byte) || type == typeof(sbyte) || type == typeof(uint) || type == typeof(ulong) || type == typeof(ushort)) 562 | { 563 | return true; 564 | } 565 | 566 | // Check for floating-point types and verify if they can be converted to an integer without losing precision 567 | if (type == typeof(float) || type == typeof(double) || type == typeof(decimal)) 568 | { 569 | decimal decimalValue = Convert.ToDecimal(value); 570 | return decimalValue == Math.Floor(decimalValue); 571 | } 572 | 573 | // Add any additional type checks if necessary 574 | 575 | return false; 576 | } 577 | 578 | public static object mathMin(object a, object b) 579 | { 580 | if (a == null || b == null) 581 | { 582 | return null; 583 | } 584 | var first = Convert.ToDouble(a); 585 | var second = Convert.ToDouble(b); 586 | 587 | if (first < second) 588 | { 589 | return a; 590 | } 591 | else 592 | { 593 | return b; 594 | } 595 | 596 | // a = normalizeIntIfNeeded(a); 597 | // b = normalizeIntIfNeeded(b); 598 | // if (a.GetType() == typeof(Int64)) 599 | // { 600 | // return Math.Min((Int64)a, (Int64)b); 601 | // } 602 | // else if (a.GetType() == typeof(double)) 603 | // { 604 | // return Math.Min((double)a, (double)b); 605 | // } 606 | // else if (a.GetType() == typeof(float)) 607 | // { 608 | // return Math.Min((float)a, (float)b); 609 | // } 610 | // else if (a.GetType() == typeof(int)) 611 | // { 612 | // return Math.Min((int)a, (int)b); 613 | // } 614 | // else 615 | // { 616 | // return null; 617 | // } 618 | } 619 | 620 | public static object mathMax(object a, object b) 621 | { 622 | if (a == null || b == null) 623 | { 624 | return null; 625 | } 626 | var first = Convert.ToDouble(a); 627 | var second = Convert.ToDouble(b); 628 | 629 | if (first > second) 630 | { 631 | return a; 632 | } 633 | else 634 | { 635 | return b; 636 | } 637 | } 638 | 639 | public static int getIndexOf(object str, object target) 640 | { 641 | if (str is IList) 642 | { 643 | return ((IList)str).IndexOf(target); 644 | } 645 | else if (str is IList) 646 | { 647 | return ((IList)str).IndexOf((string)target); 648 | } 649 | else if (str is (string)) 650 | { 651 | return ((string)str).IndexOf((string)target); 652 | } 653 | else 654 | { 655 | return -1; 656 | } 657 | } 658 | 659 | public static object parseInt(object a) 660 | { 661 | object parsedValue = null; 662 | try 663 | { 664 | parsedValue = (Convert.ToInt64(a)); 665 | } 666 | catch (Exception e) 667 | { 668 | } 669 | return parsedValue; 670 | } 671 | 672 | public static object parseFloat(object a) 673 | { 674 | object parsedValue = null; 675 | try 676 | { 677 | // parsedValue = float.Parse((string)a, CultureInfo.InvariantCulture.NumberFormat); 678 | parsedValue = (Convert.ToDouble(a, CultureInfo.InvariantCulture.NumberFormat)); 679 | } 680 | catch (Exception e) 681 | { 682 | } 683 | return parsedValue; 684 | } 685 | 686 | // generic getValue to replace elementAccesses 687 | public object getValue(object a, object b) => GetValue(a, b); 688 | public static object GetValue(object value2, object key) 689 | { 690 | if (value2 == null || key == null) 691 | { 692 | return null; 693 | } 694 | 695 | if (value2.GetType() == typeof(string)) 696 | { 697 | var str = (string)value2; 698 | return (str[Convert.ToInt32(key)]).ToString(); 699 | } 700 | 701 | // check if array 702 | object value = value2; 703 | if (value2.GetType().IsArray == true) 704 | { 705 | value = new List((object[])value2); 706 | } 707 | 708 | 709 | if (value is IDictionary) 710 | { 711 | var dictValue = (IDictionary)value; 712 | if (dictValue.ContainsKey((string)key)) 713 | { 714 | return dictValue[(string)key]; 715 | } 716 | else 717 | { 718 | return null; 719 | } 720 | } 721 | else if (value is IList) 722 | { 723 | // check here if index is out of bounds 724 | int parsed = Convert.ToInt32(key); 725 | var listLength = getArrayLength(value); 726 | if (parsed >= listLength) 727 | { 728 | return null; 729 | } 730 | return ((IList)value)[parsed]; 731 | } 732 | else if (value is List) 733 | { 734 | // check here if index is out of bounds 735 | int parsed = Convert.ToInt32(key); 736 | var listLength = getArrayLength(value); 737 | if (parsed >= listLength) 738 | { 739 | return null; 740 | } 741 | return ((List)value)[parsed]; 742 | } 743 | else if (value.GetType() == typeof(List)) 744 | { 745 | int parsed = Convert.ToInt32(key); 746 | var listLength = getArrayLength(value); 747 | if (parsed >= listLength) 748 | { 749 | return null; 750 | } 751 | return ((List)value)[parsed]; 752 | } 753 | else if (value is List) 754 | { 755 | int parsed = Convert.ToInt32(key); 756 | return ((List)value)[parsed]; 757 | } 758 | // check this last, avoid reflection 759 | else if (key.GetType() == typeof(string) && (value.GetType()).GetProperty((string)key) != null) 760 | { 761 | var prop = (value.GetType()).GetProperty((string)key); 762 | if (prop != null) 763 | { 764 | return prop.GetValue(value2, null); 765 | } 766 | else 767 | { 768 | return null; 769 | } 770 | } 771 | else 772 | { 773 | return null; 774 | } 775 | } 776 | 777 | public async Task> promiseAll(object promisesObj) => await PromiseAll(promisesObj); 778 | 779 | public static async Task> PromiseAll(object promisesObj) 780 | { 781 | var promises = (IList)promisesObj; 782 | var tasks = new List>(); 783 | foreach (var promise in promises) 784 | { 785 | if (promise is Task) 786 | { 787 | tasks.Add((Task)promise); 788 | } 789 | } 790 | var results = await Task.WhenAll(tasks); 791 | return results.ToList(); 792 | } 793 | 794 | public static string toStringOrNull(object value) 795 | { 796 | if (value == null) 797 | { 798 | return null; 799 | } 800 | else 801 | { 802 | return (string)value; 803 | } 804 | } 805 | 806 | public void throwDynamicException(object exception, object message) 807 | { 808 | var Exception = NewException((Type)exception, (string)message); 809 | } 810 | 811 | // This function is the salient bit here 812 | public object newException(object exception, object message) 813 | { 814 | return Activator.CreateInstance(exception as Type, message as String) as Exception; 815 | } 816 | 817 | public static Exception NewException(Type exception, String message) 818 | { 819 | return Activator.CreateInstance(exception, message) as Exception; 820 | } 821 | 822 | public static object toFixed(object number, object decimals) 823 | { 824 | return Math.Round((double)number, (int)decimals); 825 | } 826 | 827 | public static object callDynamically(object obj, object methodName, object[] args = null) 828 | { 829 | args ??= new object[] { }; 830 | if (args.Length == 0) 831 | { 832 | args = new object[] { null }; 833 | } 834 | return obj.GetType().GetMethod((string)methodName, BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Invoke(obj, args); 835 | } 836 | 837 | public static async Task callDynamicallyAsync(object obj, object methodName, object[] args = null) 838 | { 839 | args ??= new object[] { }; 840 | var res = obj.GetType().GetMethod((string)methodName, BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Invoke(obj, args); 841 | return await ((Task)res); 842 | } 843 | 844 | public bool inOp(object obj, object key) => InOp(obj, key); 845 | 846 | public static bool InOp(object obj, object key) 847 | { 848 | if (obj == null || key == null) 849 | { 850 | return false; 851 | } 852 | if (obj is (IList)) 853 | { 854 | return ((IList)obj).Contains(key); 855 | } 856 | else if (obj is (IList)) 857 | { 858 | return ((IList)obj).Contains((string)key); 859 | } 860 | else if (obj is (List)) 861 | { 862 | return ((List)obj).Contains((Int64)key); 863 | } 864 | else if (obj is (IDictionary)) 865 | { 866 | if (key is (string)) 867 | return ((IDictionary)obj).ContainsKey((string)key); 868 | else 869 | return false; 870 | } 871 | else 872 | { 873 | return false; 874 | } 875 | } 876 | 877 | public string slice(object str2, object idx1, object idx2) => Slice(str2, idx1, idx2); 878 | 879 | public static string Slice(object str2, object idx1, object idx2) 880 | { 881 | if (str2 == null) 882 | { 883 | return null; 884 | } 885 | var str = (string)str2; 886 | var start = idx1 != null ? Convert.ToInt32(idx1) : -1; 887 | if (idx2 == null) 888 | { 889 | if (start < 0) 890 | { 891 | var innerStart = str.Length + start; 892 | innerStart = innerStart < 0 ? 0 : innerStart; 893 | return str[(innerStart)..]; 894 | } 895 | else 896 | { 897 | return str[start..]; 898 | } 899 | } 900 | else 901 | { 902 | var end = Convert.ToInt32(idx2); 903 | if (start < 0) 904 | { 905 | start = str.Length + start; 906 | } 907 | if (end < 0) 908 | { 909 | end = str.Length + end; 910 | } 911 | if (end > str.Length) 912 | { 913 | end = str.Length; 914 | } 915 | return str[start..end]; 916 | } 917 | } 918 | 919 | public static object concat(object a, object b) 920 | { 921 | if (a == null && b == null) 922 | { 923 | return null; 924 | } 925 | else if (a == null) 926 | { 927 | return b; 928 | } 929 | else if (b == null) 930 | { 931 | return a; 932 | } 933 | 934 | if (a is IList && b is IList) 935 | { 936 | List result = new List((IList)a); 937 | result.AddRange((IList)b); 938 | return result; 939 | } 940 | else if (a is IList && b is IList) 941 | { 942 | List result = new List((IList)a); 943 | result.AddRange((IList)b); 944 | return result; 945 | } 946 | else if (a is IList> && b is IList>) 947 | { 948 | List> result = new List>((IList>)a); 949 | result.AddRange((IList>)b); 950 | return result; 951 | } 952 | else 953 | { 954 | throw new InvalidOperationException("Unsupported types for concatenation."); 955 | } 956 | } 957 | } -------------------------------------------------------------------------------- /tests/integration/cs/cs.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /tests/integration/cs/cs.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31903.59 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "cs", "cs.csproj", "{93B8D727-2000-4D25-B1F8-6EF5DD591EA3}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(SolutionProperties) = preSolution 14 | HideSolutionNode = FALSE 15 | EndGlobalSection 16 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 17 | {93B8D727-2000-4D25-B1F8-6EF5DD591EA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 18 | {93B8D727-2000-4D25-B1F8-6EF5DD591EA3}.Debug|Any CPU.Build.0 = Debug|Any CPU 19 | {93B8D727-2000-4D25-B1F8-6EF5DD591EA3}.Release|Any CPU.ActiveCfg = Release|Any CPU 20 | {93B8D727-2000-4D25-B1F8-6EF5DD591EA3}.Release|Any CPU.Build.0 = Release|Any CPU 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /tests/integration/cs/transpilable.cs: -------------------------------------------------------------------------------- 1 | namespace tests; 2 | class Second 3 | { 4 | public string myClassProperty = "classProp"; 5 | 6 | public virtual object stringifyNumber(object arg) 7 | { 8 | return ((object)arg).ToString(); 9 | } 10 | } 11 | partial class Test 12 | { 13 | public virtual void test() 14 | { 15 | object a = 1; 16 | object b = 2; 17 | object c = add(a, b); 18 | Console.WriteLine(c); // should print 3 19 | object s1 = "a"; 20 | object s2 = "b"; 21 | object s3 = add(s1, s2); 22 | object stringVar = null; 23 | stringVar = "hello"; 24 | Console.WriteLine(stringVar); // should print "hello" 25 | Console.WriteLine(s3); // should print "ab" 26 | object x = false; 27 | if (isTrue(x)) 28 | { 29 | Console.WriteLine("x is true"); 30 | } else 31 | { 32 | Console.WriteLine("x is false"); // should print "x is false" 33 | } 34 | var instance = new Second(); 35 | Console.WriteLine(instance.stringifyNumber(4)); // should print 4 36 | Console.WriteLine(instance.myClassProperty); // should print "classProp" 37 | object arr = new List() {1, 2, 3, 4}; 38 | Console.WriteLine(getArrayLength(arr)); // should print 4 39 | object first = getValue(arr, 0); 40 | Console.WriteLine(first); // should print 1 41 | object dict = new Dictionary() { 42 | { "a", "b" }, 43 | }; 44 | Console.WriteLine(getValue(dict, "a")); // should print "b" 45 | object i = 0; 46 | for (object w = 0; isLessThan(w, 10); postFixIncrement(ref w)) 47 | { 48 | i = add(i, 1); 49 | } 50 | Console.WriteLine(((object)i).ToString()); // should print 10 51 | object list2 = new List() {1, 2, 3, 4, 5}; 52 | list2 = (list2 as IList).Reverse().ToList(); 53 | Console.WriteLine(getValue(list2, 0)); // should print 5 54 | //should delete key from dict 55 | object dict2 = new Dictionary() { 56 | { "a", 1 }, 57 | { "b", 2 }, 58 | }; 59 | ((IDictionary)dict2).Remove((string)"a"); 60 | object dictKeys = new List(((IDictionary)dict2).Keys); 61 | Console.WriteLine(getArrayLength(dictKeys)); // should print 1 62 | Console.WriteLine(getValue(dictKeys, 0)); // should print "b" 63 | object firstConcat = new List() {"a", "b"}; 64 | object secondConcat = new List() {"c", "d"}; 65 | object both = concat(firstConcat, secondConcat); 66 | Console.WriteLine(getArrayLength(both)); // should print 4 67 | Console.WriteLine(getValue(both, 2)); // should print "c" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /tests/integration/go/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func main() { 4 | 5 | instance := new(Test) 6 | instance.Test() 7 | } 8 | -------------------------------------------------------------------------------- /tests/integration/go/transpilable.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | 4 | import ( 5 | "fmt" 6 | ) 7 | 8 | type Second struct { 9 | MyClassProperty string `default:"classProp"` 10 | } 11 | 12 | func NewSecond() Second { 13 | p := Second{} 14 | setDefaults(&p) 15 | return p 16 | } 17 | 18 | func (this *Second) StringifyNumber(arg interface{}) interface{} { 19 | return ToString(arg) 20 | } 21 | type Test struct { 22 | 23 | } 24 | 25 | func NewTest() Test { 26 | p := Test{} 27 | setDefaults(&p) 28 | return p 29 | } 30 | 31 | func (this *Test) Test() { 32 | var a interface{} = 1 33 | var b interface{} = 2 34 | var c interface{} = Add(a, b) 35 | fmt.Println(c) // should print 3 36 | var s1 interface{} = "a" 37 | var s2 interface{} = "b" 38 | var s3 interface{} = Add(s1, s2) 39 | var stringVar interface{} = nil 40 | stringVar = "hello" 41 | fmt.Println(stringVar) // should print "hello" 42 | fmt.Println(s3) // should print "ab" 43 | var x interface{} = false 44 | if IsTrue(x) { 45 | fmt.Println("x is true") 46 | } else { 47 | fmt.Println("x is false") // should print "x is false" 48 | } 49 | instance := NewSecond() 50 | fmt.Println(instance.StringifyNumber(4)) // should print 4 51 | fmt.Println(instance.MyClassProperty) // should print "classProp" 52 | var arr interface{} = []interface{}{1, 2, 3, 4} 53 | fmt.Println(GetArrayLength(arr)) // should print 4 54 | var first interface{} = GetValue(arr, 0) 55 | fmt.Println(first) // should print 1 56 | var dict interface{} = map[string]interface{} { 57 | "a": "b", 58 | } 59 | fmt.Println(GetValue(dict, "a")) // should print "b" 60 | var i interface{} = 0 61 | for w := 0; IsLessThan(w, 10); w++ { 62 | i = Add(i, 1) 63 | } 64 | fmt.Println(ToString(i)) // should print 10 65 | var list2 interface{} = []interface{}{1, 2, 3, 4, 5} 66 | Reverse(list2) 67 | fmt.Println(GetValue(list2, 0)) // should print 5 68 | //should delete key from dict 69 | var dict2 interface{} = map[string]interface{} { 70 | "a": 1, 71 | "b": 2, 72 | } 73 | Remove(dict2, "a") 74 | var dictKeys interface{} = ObjectKeys(dict2) 75 | fmt.Println(GetArrayLength(dictKeys)) // should print 1 76 | fmt.Println(GetValue(dictKeys, 0)) // should print "b" 77 | var firstConcat interface{} = []interface{}{"a", "b"} 78 | var secondConcat interface{} = []interface{}{"c", "d"} 79 | var both interface{} = Concat(firstConcat, secondConcat) 80 | fmt.Println(GetArrayLength(both)) // should print 4 81 | fmt.Println(GetValue(both, 2)) // should print "c" 82 | } 83 | -------------------------------------------------------------------------------- /tests/integration/php/init.php: -------------------------------------------------------------------------------- 1 | test(); 7 | -------------------------------------------------------------------------------- /tests/integration/php/transpilable.php: -------------------------------------------------------------------------------- 1 | stringifyNumber(4)); // should print 4 32 | custom_echo($instance->myClassProperty); // should print "classProp" 33 | $arr = [1, 2, 3, 4]; 34 | custom_echo(count($arr)); // should print 4 35 | $first = $arr[0]; 36 | custom_echo($first); // should print 1 37 | $dict = array( 38 | 'a' => 'b', 39 | ); 40 | custom_echo($dict['a']); // should print "b" 41 | $i = 0; 42 | for ($w = 0; $w < 10; $w++) { 43 | $i = $i + 1; 44 | } 45 | custom_echo(((string) $i)); // should print 10 46 | $list2 = [1, 2, 3, 4, 5]; 47 | $list2 = array_reverse($list2); 48 | custom_echo($list2[0]); // should print 5 49 | //should delete key from dict 50 | $dict2 = array( 51 | 'a' => 1, 52 | 'b' => 2, 53 | ); 54 | unset($dict2['a']); 55 | $dictKeys = is_array($dict2) ? array_keys($dict2) : array(); 56 | custom_echo(count($dictKeys)); // should print 1 57 | custom_echo($dictKeys[0]); // should print "b" 58 | $firstConcat = ['a', 'b']; 59 | $secondConcat = ['c', 'd']; 60 | $both = array_merge($firstConcat, $secondConcat); 61 | custom_echo(count($both)); // should print 4 62 | custom_echo($both[2]); // should print "c" 63 | } 64 | } 65 | 66 | ?> -------------------------------------------------------------------------------- /tests/integration/py/__pycache__/transpilable.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccxt/ast-transpiler/f1ed8267b45f8ed23c74a275f0bc73bf905f3b29/tests/integration/py/__pycache__/transpilable.cpython-311.pyc -------------------------------------------------------------------------------- /tests/integration/py/init.py: -------------------------------------------------------------------------------- 1 | from transpilable import Test 2 | 3 | def main(): 4 | test = Test() 5 | test.test() 6 | main() -------------------------------------------------------------------------------- /tests/integration/py/transpilable.py: -------------------------------------------------------------------------------- 1 | class Second: 2 | my_class_property = 'classProp' 3 | 4 | def stringify_number(self, arg): 5 | return str(arg) 6 | class Test: 7 | def test(self): 8 | a = 1 9 | b = 2 10 | c = a + b 11 | print(c) # should print 3 12 | s1 = 'a' 13 | s2 = 'b' 14 | s3 = s1 + s2 15 | string_var = None 16 | string_var = 'hello' 17 | print(string_var) # should print "hello" 18 | print(s3) # should print "ab" 19 | x = False 20 | if x: 21 | print('x is true') 22 | else: 23 | print('x is false') # should print "x is false" 24 | instance = Second() 25 | print(instance.stringify_number(4)) # should print 4 26 | print(instance.my_class_property) # should print "classProp" 27 | arr = [1, 2, 3, 4] 28 | print(len(arr)) # should print 4 29 | first = arr[0] 30 | print(first) # should print 1 31 | dict = { 32 | 'a': 'b', 33 | } 34 | print(dict['a']) # should print "b" 35 | i = 0 36 | for w in range(0, 10): 37 | i = i + 1 38 | print(str(i)) # should print 10 39 | list2 = [1, 2, 3, 4, 5] 40 | list2.reverse() 41 | print(list2[0]) # should print 5 42 | #should delete key from dict 43 | dict2 = { 44 | 'a': 1, 45 | 'b': 2, 46 | } 47 | del dict2['a'] 48 | dict_keys = list(dict2.keys()) 49 | print(len(dict_keys)) # should print 1 50 | print(dict_keys[0]) # should print "b" 51 | first_concat = ['a', 'b'] 52 | second_concat = ['c', 'd'] 53 | both = first_concat + second_concat 54 | print(len(both)) # should print 4 55 | print(both[2]) # should print "c" 56 | -------------------------------------------------------------------------------- /tests/integration/source/init.ts: -------------------------------------------------------------------------------- 1 | import { Test } from "./transpilable.js"; 2 | 3 | var instance = new Test(); 4 | instance.test(); -------------------------------------------------------------------------------- /tests/integration/source/transpilable.ts: -------------------------------------------------------------------------------- 1 | 2 | class Second { 3 | 4 | myClassProperty: string = "classProp"; 5 | myBoolProp: boolean = false; 6 | public stringifyNumber(arg: number) { 7 | return arg.toString(); 8 | } 9 | } 10 | 11 | class Test { 12 | 13 | public test() { 14 | var a = 1; 15 | var b = 2; 16 | var c = a + b; 17 | console.log(c); // should print 3 18 | var s1 = "a"; 19 | var s2 = "b"; 20 | var s3 = s1 + s2; 21 | 22 | let stringVar: string; 23 | stringVar = "hello"; 24 | console.log(stringVar); // should print "hello" 25 | console.log(s3); // should print "ab" 26 | let x = false; 27 | if (x) { 28 | console.log("x is true"); 29 | } else { 30 | console.log("x is false"); // should print "x is false" 31 | } 32 | 33 | var instance = new Second(); 34 | console.log(instance.stringifyNumber(4)); // should print 4 35 | 36 | console.log(instance.myClassProperty); // should print "classProp" 37 | 38 | if (instance.myBoolProp == false) { 39 | console.log("myBoolProp is false"); // should print "myBoolProp is false" 40 | } 41 | 42 | const arr = [1,2,3,4]; 43 | 44 | console.log(arr.length); // should print 4 45 | 46 | const first = arr[0]; 47 | console.log(first); // should print 1 48 | 49 | const dict = {"a": "b"}; 50 | console.log(dict["a"]); // should print "b" 51 | 52 | let i = 0; 53 | for (let w = 0; w < 10; w++) { 54 | i = i + 1; 55 | } 56 | console.log(i.toString()); // should print 10 57 | 58 | const list2 = [1,2,3,4,5]; 59 | list2.reverse(); 60 | console.log(list2[0]); // should print 5 61 | 62 | //should delete key from dict 63 | const dict2 = {"a": 1, "b": 2}; 64 | delete dict2["a"]; 65 | const dictKeys = Object.keys(dict2); 66 | console.log(dictKeys.length); // should print 1 67 | console.log(dictKeys[0]); // should print "b" 68 | 69 | const firstConcat = ["a", "b"]; 70 | const secondConcat = ["c", "d"]; 71 | const both = firstConcat.concat(secondConcat); 72 | console.log(both.length); // should print 4 73 | console.log(both[2]); // should print "c" 74 | 75 | const baseString = "aabba"; 76 | const replacedAllString = baseString.replaceAll("a", ""); 77 | console.log(replacedAllString); // should print "bb" 78 | } 79 | } 80 | 81 | export { 82 | Test 83 | } -------------------------------------------------------------------------------- /tests/integration/test.ts: -------------------------------------------------------------------------------- 1 | import { Transpiler } from '../../src/transpiler.js'; 2 | import * as fs from 'fs'; 3 | import { exec } from "node:child_process"; 4 | import { green, yellow, red, blue } from "colorette"; 5 | const { readFileSync, writeFileSync } = fs; 6 | 7 | const TS_TRANSPILABLE_FILE = "./tests/integration/source/transpilable.ts"; 8 | const PY_TRANSPILABLE_FILE = "./tests/integration/py/transpilable.py"; 9 | const PHP_TRANSPILABLE_FILE = "./tests/integration/php/transpilable.php"; 10 | const CS_TRANSPILABLE_FILE = "./tests/integration/cs/transpilable.cs"; 11 | const GO_TRANSPILABLE_FILE = "./tests/integration/go/transpilable.go"; 12 | 13 | 14 | const TS_FILE = "./tests/integration/source/init.ts"; 15 | const PY_FILE = "./tests/integration/py/init.py"; 16 | const PHP_FILE = "./tests/integration/php/init.php"; 17 | const CS_FILE = "./tests/integration/cs"; 18 | const GO_FILE = "./tests/integration/go"; 19 | 20 | const langConfig = [ 21 | { 22 | language: "csharp", 23 | async: true 24 | }, 25 | 26 | { 27 | language: "python", 28 | async: true 29 | }, 30 | { 31 | language: "php", 32 | async: true 33 | }, 34 | { 35 | language: "go", 36 | async: true 37 | }, 38 | ] 39 | 40 | function transpileTests() { 41 | const parseConfig = { 42 | "verbose": false, 43 | "csharp": { 44 | "parser": { 45 | "ELEMENT_ACCESS_WRAPPER_OPEN": "getValue(", 46 | "ELEMENT_ACCESS_WRAPPER_CLOSE": ")", 47 | } 48 | }, 49 | } 50 | const transpiler = new Transpiler(parseConfig); 51 | const result = transpiler.transpileDifferentLanguagesByPath(langConfig as any, TS_TRANSPILABLE_FILE); 52 | 53 | let phpRes = `` as string; 54 | phpRes = (phpRes as any).replaceAll('var_dump', 'custom_echo'); 55 | const pythonAsync = result[1].content; 56 | let csharp = 'namespace tests;\n' + result[0].content; 57 | csharp = csharp.replace('class Test', 'partial class Test'); 58 | 59 | 60 | const goImports = [ 61 | '\n', 62 | 'import (', 63 | ' "fmt"', 64 | ')', 65 | '\n' 66 | ].join('\n'); 67 | const go = 'package main\n' + goImports + result[3].content; 68 | 69 | writeFileSync(PHP_TRANSPILABLE_FILE, phpRes.toString()); 70 | writeFileSync(PY_TRANSPILABLE_FILE, pythonAsync); 71 | writeFileSync(CS_TRANSPILABLE_FILE, csharp); 72 | writeFileSync(GO_TRANSPILABLE_FILE, go); 73 | } 74 | 75 | function runCommand(command) { 76 | return new Promise((resolve, reject) => { 77 | exec(command, (error, stdout, stderr) => { 78 | if (stderr !== undefined || stderr !== null) { 79 | stderr = stderr.replace('Debugger attached.\nWaiting for the debugger to disconnect...\n', ''); 80 | } 81 | if (stderr.startsWith("Debugger listening") && stderr.includes("For help, see: https://nodejs.org/en/docs/inspector")) { 82 | stderr = undefined; 83 | } 84 | if (error) { 85 | reject(error); 86 | return; 87 | } 88 | if (stderr) { 89 | reject(stderr); 90 | return; 91 | } 92 | resolve(stdout.trim()); 93 | }); 94 | }); 95 | } 96 | 97 | async function runTS() { 98 | const command = "node --no-warnings --loader ts-node/esm " + TS_FILE; 99 | const result = await runCommand(command); 100 | console.log(blue("Executed TS")) 101 | return result; 102 | } 103 | 104 | async function runPHP() { 105 | const command = "php " + PHP_FILE; 106 | const result = await runCommand(command); 107 | console.log(blue("Executed PHP")) 108 | return result; 109 | } 110 | 111 | async function runPy() { 112 | const command = "python3 " + PY_FILE; 113 | const result = await runCommand(command); 114 | console.log(blue("Executed PY")) 115 | return result; 116 | } 117 | 118 | async function runCS() { 119 | const buildCommand = "dotnet build " + CS_FILE; 120 | await runCommand(buildCommand); 121 | const command = "dotnet run --project " + CS_FILE + '/cs.csproj'; 122 | const result = await runCommand(command); 123 | console.log(blue("Executed CS")) 124 | return result; 125 | } 126 | 127 | async function runGO() { 128 | const buildCommand = "go build " + GO_FILE; 129 | await runCommand(buildCommand); 130 | const command = "go run " + GO_FILE; 131 | const result = await runCommand(command); 132 | console.log(blue("Executed GO")) 133 | return result; 134 | } 135 | 136 | async function main() { 137 | transpileTests(); 138 | 139 | const promises = [ 140 | runTS(), 141 | runPHP(), 142 | runPy(), 143 | runCS(), 144 | runGO() 145 | ]; 146 | const results = await Promise.all(promises); 147 | const [ts, php, py, cs, go]: any = results; 148 | 149 | let success = true; 150 | if (php !== ts) { 151 | success = false; 152 | compareOutputs("PHP", ts, php); 153 | } 154 | if (py !== ts) { 155 | success = false; 156 | compareOutputs("PY", ts, py); 157 | } 158 | if (cs !== ts) { 159 | success = false; 160 | compareOutputs("CS", ts, cs); 161 | } 162 | 163 | if (go !== ts) { 164 | success = false; 165 | compareOutputs("GO", ts, go); 166 | } 167 | 168 | if (success) { 169 | console.log(green("Integration tests passed!")); 170 | } 171 | 172 | } 173 | 174 | 175 | function compareOutputs(language: string, tsOutput: string, output: string) { 176 | if (tsOutput !== output) { 177 | console.log(red(`${language} and TS outputs are not equal`)); 178 | console.log(yellow("TS output:")); 179 | console.log(tsOutput); 180 | console.log(yellow(`${language} output:`)); 181 | console.log(output); 182 | } 183 | } 184 | 185 | main() -------------------------------------------------------------------------------- /tmp.ts: -------------------------------------------------------------------------------- 1 | if (typeof this.proxy === 'function') { 2 | proxyUrl = proxy (url, method, headers, body); 3 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./src", 4 | "module": "Node16", 5 | "outDir": "./dist", 6 | "allowJs": true, 7 | "target": "es2021", 8 | "allowSyntheticDefaultImports": true, 9 | "esModuleInterop": true, 10 | "declaration": true 11 | }, 12 | "include": [ 13 | "./src/**/*" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /worker.ts: -------------------------------------------------------------------------------- 1 | import { Transpiler } from './src/transpiler.js' 2 | 3 | // expected files config 4 | // const filesConfig = [ 5 | // { 6 | // name: string, ???? 7 | // content: string, 8 | // [config: { 9 | // language: string, 10 | // async: boolean 11 | // }] 12 | // } 13 | // ]; 14 | 15 | export default async ({transpilerConfig, filesConfig}) => { 16 | const transpiler = new Transpiler(transpilerConfig); 17 | 18 | const result = [] as any[]; 19 | for (const fileConfig of filesConfig) { 20 | const transpiled = transpiler.transpileDifferentLanguages(fileConfig.config, fileConfig.content); 21 | const transpiledFile = { 22 | name: fileConfig.name, 23 | result: transpiled 24 | }; 25 | result.push(transpiledFile); 26 | } 27 | return result; 28 | } --------------------------------------------------------------------------------