├── src ├── index.ts ├── report-uncovered-ast-types.ts ├── nimhelpers.ts ├── nim.ts ├── types.ts ├── utils.ts ├── analyzer.ts ├── cli.ts └── transpiler.ts ├── .npmignore ├── .gitignore ├── .editorconfig ├── .travis.yml ├── __tests__ ├── analyzer_data.ts ├── typeof-spec.ts ├── operation-spec.ts ├── type-predicate-spec.ts ├── unknown-type-spec.ts ├── tuple-spec.ts ├── import-spec.ts ├── regex-spec.ts ├── type-spec.ts ├── string-trans-spec.ts ├── func-param-assign-spec.ts ├── switch-spec.ts ├── forloop-spec.ts ├── bridge-spec.ts ├── analyzer-spec.ts ├── ArrayPattern-spec.ts ├── if-branches-spec.ts ├── common-tranform-spec.ts ├── string-spec.ts ├── enum-spec.ts ├── string-template_data.ts ├── loop-spec.ts ├── class-spec.ts ├── ident-name-spec.ts ├── comment-spec.ts ├── computed-prop-spec.ts ├── interface-spec.ts ├── complex-spec.ts └── function-spec.ts ├── tslint.json ├── tsconfig.json ├── LICENSE ├── package.json └── README.md /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './transpiler'; 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # gitignore 2 | 3 | coverage/ 4 | node_modules/ 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | .nyc_output 9 | lib 10 | lib_test 11 | 12 | # npmignore 13 | 14 | src/ 15 | __tests__/ 16 | .vscode/ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | node_modules/ 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | .nyc_output 7 | package-lock.json 8 | lib 9 | lib_test 10 | local_test.ts 11 | src/analyzer_test.ts 12 | *.nim 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 'node' # use latest stable nodejs version 4 | script: 5 | - npm run coverage # jest test with coverage flag does coverage too 6 | after_script: 7 | - 'cat coverage/lcov.info | ./node_modules/.bin/coveralls' # sends the coverage report to coveralls 8 | -------------------------------------------------------------------------------- /__tests__/analyzer_data.ts: -------------------------------------------------------------------------------- 1 | var b = 1; 2 | function add(a: number, d: any[]): void { 3 | const c = 5, 4 | f = []; 5 | if (a === b) { 6 | a + 1; 7 | } 8 | if (!d) { 9 | } 10 | if (f) { 11 | } 12 | if (!c) { 13 | } 14 | } 15 | 16 | var a = 1; 17 | var e; 18 | add(a, e); 19 | -------------------------------------------------------------------------------- /__tests__/typeof-spec.ts: -------------------------------------------------------------------------------- 1 | import { transpile } from '../src/transpiler'; 2 | import { fs } from 'memfs'; 3 | test('Should ignore typeof', done => { 4 | const typedef = ` 5 | export type URI = typeof URI 6 | 7 | `; 8 | const expected = ''; 9 | const { writer } = transpile(undefined, typedef); 10 | 11 | writer.on('close', () => { 12 | expect(fs.readFileSync(writer.path).toString()).toBe(expected); 13 | done(); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /src/report-uncovered-ast-types.ts: -------------------------------------------------------------------------------- 1 | import * as realfs from 'fs'; 2 | import * as path from 'path'; 3 | import * as parser from '@typescript-eslint/typescript-estree'; 4 | import * as process from 'process'; 5 | const dataPath = path.join(__dirname, 'transpiler.ts'); 6 | const source = realfs.readFileSync(dataPath).toString(); 7 | for (const typ in parser.AST_NODE_TYPES) { 8 | if (!source.includes(typ)) { 9 | process.stdout.write(`\x1b[32m${typ}\n\x1b[0m`); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "tslint:latest", 4 | "tslint-config-prettier" 5 | ], 6 | "rules":{ 7 | "ordered-imports":false, 8 | "no-submodule-imports":false, 9 | "member-access":false, 10 | "no-shadowed-variable":false, 11 | "no-console":false, 12 | "no-conditional-assignment":false, 13 | "prefer-conditional-expression":false, 14 | "object-literal-sort-keys":false, 15 | "interface-name":false, 16 | "one-variable-per-declaration":false 17 | } 18 | } -------------------------------------------------------------------------------- /__tests__/operation-spec.ts: -------------------------------------------------------------------------------- 1 | import { transpile } from '../src/transpiler'; 2 | import { fs } from 'memfs'; 3 | test('Should handle three operands', done => { 4 | const typedef = `const height = 256 <= png.height ? 0 : png.height`; 5 | const expected = `var height = if 256 <= png.height: 0 else: png.height 6 | `; 7 | const { writer } = transpile(undefined, typedef); 8 | 9 | writer.on('close', () => { 10 | expect(fs.readFileSync(writer.path).toString()).toBe(expected); 11 | done(); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /__tests__/type-predicate-spec.ts: -------------------------------------------------------------------------------- 1 | import { transpile } from '../src/transpiler'; 2 | import { fs } from 'memfs'; 3 | test('Should replace generic through type predicate', done => { 4 | const typedef = ` 5 | export default function createValidator(): (value: unknown) => value is T; 6 | 7 | `; 8 | const expected = `proc createValidator[T](): proc [T](value:T): auto 9 | `; 10 | const { writer } = transpile(undefined, typedef); 11 | 12 | writer.on('close', () => { 13 | expect(fs.readFileSync(writer.path).toString()).toBe(expected); 14 | done(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /__tests__/unknown-type-spec.ts: -------------------------------------------------------------------------------- 1 | import { transpile } from '../src/transpiler'; 2 | import { fs } from 'memfs'; 3 | test('Should handle one unknown type param', done => { 4 | const typedef = ` 5 | function applyAttr(el: Element, name: string, value: unknown){} 6 | 7 | `; 8 | const expected = `proc applyAttr[V](el:Element, name:string, value:V): auto = discard 9 | 10 | `; 11 | const { writer } = transpile(undefined, typedef); 12 | 13 | writer.on('close', () => { 14 | expect(fs.readFileSync(writer.path).toString()).toBe(expected); 15 | done(); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /__tests__/tuple-spec.ts: -------------------------------------------------------------------------------- 1 | import { transpile } from '../src/transpiler'; 2 | import { fs } from 'memfs'; 3 | test('Should handle tuple', done => { 4 | const typedef = ` 5 | // Declare a tuple type 6 | let x: [string, number]; 7 | // Initialize it 8 | x = ['hello', 10]; // OK 9 | `; 10 | const expected = `## Declare a tuple type 11 | var x:(string,float) 12 | ## Initialize it 13 | x = ("hello",10) 14 | `; 15 | const { writer } = transpile(undefined, typedef); 16 | 17 | writer.on('close', () => { 18 | expect(fs.readFileSync(writer.path).toString()).toBe(expected); 19 | done(); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /__tests__/import-spec.ts: -------------------------------------------------------------------------------- 1 | import { transpile } from '../src/transpiler'; 2 | import { fs } from 'memfs'; 3 | test('Should handle relative import ignore package', done => { 4 | const typedef = ` 5 | import fs from 'fs' 6 | import path from 'path' 7 | import generateICO from './ico' 8 | import { ImageInfo, filterImagesBySizes } from './png' 9 | import Logger from './logger' 10 | `; 11 | const expected = `import ./ico 12 | import ./png 13 | import ./logger 14 | `; 15 | const { writer } = transpile(undefined, typedef); 16 | 17 | writer.on('close', () => { 18 | expect(fs.readFileSync(writer.path).toString()).toBe(expected); 19 | done(); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /__tests__/regex-spec.ts: -------------------------------------------------------------------------------- 1 | import { transpile } from '../src/transpiler'; 2 | import { fs } from 'memfs'; 3 | test('Should regex', done => { 4 | const typedef = ` 5 | const _schemePattern = /^\\w[\\w\\d+.-]*$/; 6 | const _singleSlashStart = /^\\//; 7 | const _doubleSlashStart = /^\\/\\//; 8 | 9 | `; 10 | const expected = `import regex 11 | 12 | const schemePattern = re"^\\w[\\w\\d+.-]*$" 13 | 14 | const singleSlashStart = re"^\\/" 15 | 16 | const doubleSlashStart = re"^\\/\\/" 17 | 18 | `; 19 | const { writer } = transpile(undefined, typedef); 20 | 21 | writer.on('close', () => { 22 | expect(fs.readFileSync(writer.path).toString()).toBe(expected); 23 | done(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/nimhelpers.ts: -------------------------------------------------------------------------------- 1 | // http://rosettacode.org/wiki/Loops/Do-while#Nim 2 | /** 3 | * template doWhile(a, b: untyped): untyped = 4 | * b 5 | * while a: 6 | * b 7 | * var val = 1 8 | * doWhile val mod 6 != 0: 9 | * val += 1 10 | * echo val 11 | */ 12 | export const doWhile = `template doWhile(a, b: untyped): untyped = 13 | b 14 | while a: 15 | b 16 | `; 17 | 18 | export const brideHeader = `when not defined(js) and not defined(Nimdoc): 19 | {.error: "This module only works on the JavaScript platform".} 20 | 21 | 22 | `; 23 | 24 | export const seqFind = `proc find[T](self:var seq[T],ele:T):int = 25 | for i,e in self: 26 | if e == ele: 27 | return i 28 | return -1 29 | 30 | `; 31 | -------------------------------------------------------------------------------- /__tests__/type-spec.ts: -------------------------------------------------------------------------------- 1 | import { transpile } from '../src/transpiler'; 2 | import { fs } from 'memfs'; 3 | test('Should handle ExportNamedDeclaration', done => { 4 | const typedef = ` 5 | export type ImageInfo = { 6 | /** Image size (width/height). */ 7 | size: number 8 | /** Path of an image file. */ 9 | filePath: string 10 | } 11 | `; 12 | const expected = `type ImageInfo* = ref object of RootObj 13 | size*:float ## ## Image size (width/height). 14 | filePath*:string ## ## Path of an image file. 15 | 16 | `; 17 | const { writer } = transpile(undefined, typedef); 18 | 19 | writer.on('close', () => { 20 | expect(fs.readFileSync(writer.path).toString()).toBe(expected); 21 | done(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /__tests__/string-trans-spec.ts: -------------------------------------------------------------------------------- 1 | import { transpile } from '../src/transpiler'; 2 | import { fs } from 'memfs'; 3 | test('Should transform basic string methods', done => { 4 | const typedef = ` 5 | authority.substr(0, idx) 6 | authority.substr(idx) 7 | uri.path.charCodeAt(1) 8 | path.substring(2); 9 | authority = path.substring(2, idx); 10 | String.fromCharCode(2333) 11 | `; 12 | const expected = `import unicode 13 | 14 | authority.runeSubStr(0,idx) 15 | authority.runeSubStr(idx) 16 | ord(uri.path[1]) 17 | path.runeSubStr(2) 18 | authority = path.runeSubStr(2,idx - 2) 19 | "\\u2333" 20 | `; 21 | const { writer } = transpile(undefined, typedef); 22 | 23 | writer.on('close', () => { 24 | expect(fs.readFileSync(writer.path).toString()).toBe(expected); 25 | done(); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /__tests__/func-param-assign-spec.ts: -------------------------------------------------------------------------------- 1 | import { transpile } from '../src/transpiler'; 2 | import { fs } from 'memfs'; 3 | test('Should handle param left AssignmentPattern right ObjectExpression', done => { 4 | const typedef = ` 5 | declare module "fs" { 6 | async function move( 7 | src: string, 8 | dest: string, 9 | { overwrite = false }: MoveOptions = {} 10 | ): Promise ; 11 | } 12 | 13 | `; 14 | const expected = `import asyncdispatch 15 | 16 | proc move(src:string, dest:string, moveOptions:MoveOptions = newMoveOptions(overwrite = false)): Future[void] {.async.} 17 | `; 18 | const { writer } = transpile(undefined, typedef); 19 | 20 | writer.on('close', () => { 21 | expect(fs.readFileSync(writer.path).toString()).toBe(expected); 22 | done(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "lib": [ 7 | "esnext" 8 | ], 9 | "target": "es6", 10 | "outDir": "./lib", 11 | "allowSyntheticDefaultImports": false, 12 | "removeComments": true, 13 | "inlineSourceMap": false, 14 | "inlineSources": false, 15 | "preserveConstEnums": true, 16 | "experimentalDecorators": true, 17 | "emitDecoratorMetadata": true, 18 | "strict": true, 19 | "esModuleInterop":false, 20 | // "noUnusedLocals": true, 21 | // "noUnusedParameters": true, 22 | "noImplicitReturns": true, 23 | "noFallthroughCasesInSwitch": true 24 | }, 25 | "include": [ 26 | "src/**/*" 27 | ], 28 | "exclude": [ 29 | "node_modules", 30 | "**/*-spec.ts" 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /__tests__/switch-spec.ts: -------------------------------------------------------------------------------- 1 | import { transpile } from '../src/transpiler'; 2 | import { fs } from 'memfs'; 3 | test('Should handle switch', done => { 4 | const typedef = `switch (theNode.callee.object.type) { 5 | case parser.AST_NODE_TYPES.CallExpression: 6 | obj = this.convertCallExpression(theNode.callee.object); 7 | break; 8 | default: 9 | obj = theNode.callee.object.name; 10 | break; 11 | }`; 12 | const expected = `case theNode.callee.\`object\`.\`type\`: 13 | of parser.AST_NODE_TYPES.CallExpression: 14 | obj = self.convertCallExpression(theNode.callee.\`object\`) 15 | else: 16 | obj = theNode.callee.\`object\`.name 17 | `; 18 | const { writer } = transpile(undefined, typedef); 19 | 20 | writer.on('close', () => { 21 | expect(fs.readFileSync(writer.path).toString()).toBe(expected); 22 | done(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /__tests__/forloop-spec.ts: -------------------------------------------------------------------------------- 1 | import { transpile } from '../src/transpiler'; 2 | import { fs } from 'memfs'; 3 | test('Should handle for of', done => { 4 | const typedef = ` 5 | export function eulerPhi(x: number) { 6 | if (x <= 0) throw Error('Number should be greater than zero'); 7 | 8 | let n = x; 9 | for (const p of primeFactors(x)) n *= (p - 1) / p; 10 | return n; 11 | }`; 12 | const expected = `proc eulerPhi(x:float): auto = 13 | if x <= 0: 14 | raise Exception("Number should be greater than zero") 15 | var n = x 16 | for p in primeFactors(x).mitems: 17 | n *= p - 1 / p 18 | return n 19 | 20 | `; 21 | const { writer } = transpile(undefined, typedef); 22 | 23 | writer.on('close', () => { 24 | expect(fs.readFileSync(writer.path).toString()).toBe(expected); 25 | done(); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /__tests__/bridge-spec.ts: -------------------------------------------------------------------------------- 1 | import { transpile } from '../src/transpiler'; 2 | import { fs } from 'memfs'; 3 | test('Should handle .d.ts', done => { 4 | const typedef = ` 5 | interface Console { 6 | Console: NodeJS.ConsoleConstructor; 7 | 8 | assert(value: any, message?: string, ...optionalParams: any[]): void; 9 | } 10 | declare var console: Console; 11 | `; 12 | const expected = `when not defined(js) and not defined(Nimdoc): 13 | {.error: "This module only works on the JavaScript platform".} 14 | 15 | 16 | type Console* = ref object of RootObj 17 | ## Console*:NodeJS.ConsoleConstructor 18 | 19 | 20 | proc assert*(self:Console){.importcpp,varargs.} 21 | 22 | var console* {.importc, nodecl.}:Console 23 | `; 24 | const { writer } = transpile('global.d.ts', typedef); 25 | 26 | writer.on('close', () => { 27 | expect(fs.readFileSync(writer.path).toString()).toBe(expected); 28 | done(); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /__tests__/analyzer-spec.ts: -------------------------------------------------------------------------------- 1 | import { transpile } from '../src/transpiler'; 2 | import { fs } from 'memfs'; 3 | import * as realfs from 'fs'; 4 | import { Analyzer } from '../src/analyzer'; 5 | import * as path from 'path'; 6 | 7 | test('Should inffer type through anayzer', done => { 8 | const expected = `var b = 1 9 | proc add(a:float, d:seq[any]) = 10 | const c = 5 11 | var f = @[] 12 | if a == b: 13 | a + 1 14 | if not d.len > 0: 15 | discard 16 | if f.len > 0: 17 | discard 18 | if not c == 0: 19 | discard 20 | 21 | var a = 1 22 | var e 23 | add(a,e) 24 | `; 25 | const dataPath = path.join(__dirname, 'analyzer_data.ts'); 26 | const anayzer = new Analyzer([dataPath]); 27 | anayzer.annalize(); 28 | 29 | const { writer, logger } = transpile( 30 | dataPath, 31 | realfs.readFileSync(dataPath).toString(), 32 | undefined, 33 | undefined, 34 | anayzer.symbols 35 | ); 36 | 37 | writer.once('close', () => { 38 | expect(fs.readFileSync(writer.path).toString()).toBe(expected); 39 | done(); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /src/nim.ts: -------------------------------------------------------------------------------- 1 | export const reserved = [ 2 | 'addr', 3 | 'and', 4 | 'as', 5 | 'asm', 6 | 'atomic', 7 | 'bind', 8 | 'block', 9 | 'break', 10 | 'case', 11 | 'cast', 12 | 'concept', 13 | 'const', 14 | 'continue', 15 | 'converter', 16 | 'defer', 17 | 'discard', 18 | 'distinct', 19 | 'div', 20 | 'do', 21 | 'elif', 22 | 'else', 23 | 'end', 24 | 'enum', 25 | 'except', 26 | 'export', 27 | 'finally', 28 | 'for', 29 | 'from', 30 | 'func', 31 | 'generic', 32 | 'if', 33 | 'import', 34 | 'in', 35 | 'include', 36 | 'interface', 37 | 'is', 38 | 'isnot', 39 | 'iterator', 40 | 'let', 41 | 'macro', 42 | 'method', 43 | 'mixin', 44 | 'mod', 45 | 'nil', 46 | 'not', 47 | 'notin', 48 | 'object', 49 | 'of', 50 | 'or', 51 | 'out', 52 | 'proc', 53 | 'ptr', 54 | 'raise', 55 | 'ref', 56 | 'return', 57 | 'shl', 58 | 'shr', 59 | 'static', 60 | 'template', 61 | 'try', 62 | 'tuple', 63 | 'type', 64 | 'using', 65 | 'var', 66 | 'when', 67 | 'while', 68 | 'with', 69 | 'without', 70 | 'xor', 71 | 'yield', 72 | ]; 73 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /__tests__/ArrayPattern-spec.ts: -------------------------------------------------------------------------------- 1 | import { transpile } from '../src/transpiler'; 2 | import { fs } from 'memfs'; 3 | test('Should handle ArrayPatten assignment from ArrayExpression', done => { 4 | const typedef = ` 5 | 6 | export namespace Random { 7 | 8 | /** Randomly shuffles the elements in an array a. */ 9 | export function shuffle(a: T[]): T[] { 10 | a = a.slice(0); // create copy 11 | for (let i = a.length - 1; i > 0; --i) { 12 | let j = Math.floor(Math.random() * (i + 1)); 13 | [a[i], a[j]] = [a[j], a[i]]; 14 | } 15 | return a; 16 | } 17 | }`; 18 | const expected = `proc shuffle[T](a:seq[T]): seq[T] = 19 | ## Randomly shuffles the elements in an array a. 20 | a = a.slice(0) 21 | var i = a.len - 1 22 | while i > 0: 23 | var j = Math.floor(Math.random() * i + 1) 24 | a[i] = a[j] 25 | a[j] = a[i] 26 | 27 | return a 28 | 29 | `; 30 | const { writer } = transpile(undefined, typedef); 31 | 32 | writer.on('close', () => { 33 | expect(fs.readFileSync(writer.path).toString()).toBe(expected); 34 | done(); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /__tests__/if-branches-spec.ts: -------------------------------------------------------------------------------- 1 | import { transpile } from '../src/transpiler'; 2 | import { fs } from 'memfs'; 3 | 4 | test('Should handle if,else if,else', done => { 5 | const typedef = `function transCommonMemberExpression( 6 | obj: string, 7 | mem: string, 8 | args: any[] = [] 9 | ): string { 10 | let result = ''; 11 | let func = ''; 12 | if (obj === 'fs' && mem === 'readFileSync') { 13 | func = \`readFile \${mem\}\`; 14 | nimModules().add('os'); 15 | } else if (obj === 'path' && mem === 'join') { 16 | nimModules().add('os'); 17 | } else{} 18 | 19 | return result; 20 | }`; 21 | const expected = `import strformat 22 | 23 | proc transCommonMemberExpression(obj:string, mem:string, args:seq[any] = newSeq[any]()): string = 24 | var result = "" 25 | var \`func\` = "" 26 | if obj == "fs" and mem == "readFileSync": 27 | \`func\` = fmt"readFile {mem}" 28 | nimModules().add("os") 29 | elif obj == "path" and mem == "join": 30 | nimModules().add("os") 31 | else: 32 | discard 33 | return result 34 | 35 | `; 36 | const { writer } = transpile(undefined, typedef); 37 | 38 | writer.on('close', () => { 39 | expect(fs.readFileSync(writer.path).toString()).toBe(expected); 40 | done(); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | // https://github.com/estree/estree/blob/master/es5.md 2 | 3 | export const primitiveTypes = [ 4 | 'number', 5 | 'bigInt', // TODO: Check if this fits assertBuiltinType 6 | 'string', 7 | 'boolean', 8 | // "null", 9 | // "object", 10 | // "any", 11 | // "undefined", 12 | // "unknown", 13 | ]; 14 | 15 | export const BinaryOperators = [ 16 | '==', 17 | '!=', 18 | '===', 19 | '!==', 20 | '<', 21 | '<=', 22 | '>', 23 | '>=', 24 | '<<', 25 | '>>', 26 | '>>>', 27 | '+', 28 | '-', 29 | '*', 30 | '/', 31 | '%', 32 | '|', 33 | '^', 34 | '&', 35 | 'in', 36 | 'instanceof', 37 | ]; 38 | export const BinaryOperatorsReturnsBoolean = [ 39 | '==', 40 | '!=', 41 | '===', 42 | '!==', 43 | '<', 44 | '<=', 45 | '>', 46 | '>=', 47 | '|', 48 | '^', 49 | '&', 50 | 'in', 51 | 'instanceof', 52 | ]; 53 | export const UnaryOperators = ['-', '+', '!', '~', 'typeof', 'void', 'delete']; 54 | // UpdateExpression 55 | export const UpdateOperators = ['++', '--']; 56 | export const AssignmentOperators = [ 57 | '=', 58 | '+=', 59 | '-=', 60 | '*=', 61 | '/=', 62 | '%=', 63 | '<<=', 64 | '>>=', 65 | '>>>=', 66 | '|=', 67 | '^=', 68 | '&=', 69 | ]; 70 | // LogicalExpression 71 | export const LogicalOperators = ['||', '&&']; 72 | -------------------------------------------------------------------------------- /__tests__/common-tranform-spec.ts: -------------------------------------------------------------------------------- 1 | import { transpile } from '../src/transpiler'; 2 | import { fs } from 'memfs'; 3 | test('Should transform path.join', done => { 4 | const typedef = `const dest = path.join(dir, opt.name + FILE_EXTENSION)`; 5 | const expected = `import os 6 | 7 | var dest = dir / opt.name & FILE_EXTENSION 8 | `; 9 | const { writer } = transpile(undefined, typedef); 10 | 11 | writer.on('close', () => { 12 | expect(fs.readFileSync(writer.path).toString()).toBe(expected); 13 | done(); 14 | }); 15 | }); 16 | 17 | test('Should transform .length', done => { 18 | const typedef = `stream.write(createFileHeader(pngs.length), 'binary')`; 19 | const expected = `stream.write(createFileHeader(pngs.len),"binary") 20 | `; 21 | const { writer } = transpile(undefined, typedef); 22 | 23 | writer.on('close', () => { 24 | expect(fs.readFileSync(writer.path).toString()).toBe(expected); 25 | done(); 26 | }); 27 | }); 28 | 29 | test('Should transform raise', done => { 30 | const typedef = `throw new Error('There was no PNG file matching the specified size.')`; 31 | const expected = `raise newException(Exception,"There was no PNG file matching the specified size.") 32 | `; 33 | const { writer } = transpile(undefined, typedef); 34 | 35 | writer.on('close', () => { 36 | expect(fs.readFileSync(writer.path).toString()).toBe(expected); 37 | done(); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /__tests__/string-spec.ts: -------------------------------------------------------------------------------- 1 | import { transpile } from '../src/transpiler'; 2 | import { fs } from 'memfs'; 3 | test('Should string concat', done => { 4 | const typedef = `/** 5 | * Makes sure that the caller is where attributes are expected. 6 | * @param functionName The name of the caller, for the error message. 7 | */ 8 | function assertInAttributes(functionName: string) { 9 | if (!inAttributes) { 10 | throw new Error( 11 | functionName + 12 | "() can only be called after calling " + 13 | "elementOpenStart()." 14 | ); 15 | } 16 | }`; 17 | const expected = `proc assertInAttributes(functionName:string): auto = 18 | ## Makes sure that the caller is where attributes are expected. 19 | ## @param functionName The name of the caller, for the error message. 20 | 21 | if not inAttributes: 22 | raise newException(Exception,functionName & "() can only be called after calling " & "elementOpenStart().") 23 | 24 | `; 25 | const { writer } = transpile(undefined, typedef); 26 | 27 | writer.on('close', () => { 28 | expect(fs.readFileSync(writer.path).toString()).toBe(expected); 29 | done(); 30 | }); 31 | }); 32 | 33 | test('Should handle escaped string', done => { 34 | const typedef = `const sep: '\\\\' | '/';`; 35 | const expected = `var sep:"\\\\"|"/" 36 | `; 37 | const { writer } = transpile(undefined, typedef); 38 | 39 | writer.on('close', () => { 40 | expect(fs.readFileSync(writer.path).toString()).toBe(expected); 41 | done(); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /__tests__/enum-spec.ts: -------------------------------------------------------------------------------- 1 | import { transpile } from '../src/transpiler'; 2 | import { fs } from 'memfs'; 3 | test('Should handle enum', done => { 4 | const typedef = ` 5 | enum Color {Red = 1, Green, Blue} 6 | enum Color {Red, Green, Blue} 7 | enum Color {Red = 1, Green = 2, Blue = 4} 8 | `; 9 | const expected = `type Color = enum 10 | Red = 1 11 | Green 12 | Blue 13 | 14 | 15 | type Color = enum 16 | Red 17 | Green 18 | Blue 19 | 20 | 21 | type Color = enum 22 | Red = 1 23 | Green = 2 24 | Blue = 4 25 | 26 | 27 | `; 28 | const { writer } = transpile(undefined, typedef); 29 | 30 | writer.on('close', () => { 31 | expect(fs.readFileSync(writer.path).toString()).toBe(expected); 32 | done(); 33 | }); 34 | }); 35 | 36 | test('Should handle enum with comment', done => { 37 | const typedef = ` 38 | const enum CharCode { 39 | Null = 0, 40 | /** 41 | * The \`\\t\` character. 42 | */ 43 | Tab = 9, 44 | /** 45 | * The \`\\n\` character. 46 | */ 47 | LineFeed = 10, 48 | /** 49 | * The \`\\r\` character. 50 | */ 51 | CarriageReturn = 13 52 | } 53 | `; 54 | const expected = `type CharCode = enum 55 | Null = 0 56 | Tab = 9 ## The \`\\t\` character. 57 | LineFeed = 10 ## The \`\\n\` character. 58 | CarriageReturn = 13 ## The \`\\r\` character. 59 | 60 | 61 | `; 62 | const { writer } = transpile(undefined, typedef); 63 | 64 | writer.on('close', () => { 65 | expect(fs.readFileSync(writer.path).toString()).toBe(expected); 66 | done(); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import * as indentString from 'indent-string'; 2 | const indentSpaces = 2; 3 | export function arraysEqual(a: any[], b: any[]) { 4 | if (a === b) { 5 | return true; 6 | } 7 | if (a == null || b == null) { 8 | return false; 9 | } 10 | if (a.length !== b.length) { 11 | return false; 12 | } 13 | 14 | for (let i = 0; i < a.length; ++i) { 15 | if (a[i] !== b[i]) { 16 | return false; 17 | } 18 | } 19 | return true; 20 | } 21 | 22 | export function getLine(value: string, indentLevel = 0): string { 23 | if (value.length === 0) { 24 | return ''; 25 | } 26 | // @ts-ignore 27 | return indentString(value, indentSpaces * indentLevel) + '\n'; 28 | } 29 | export function indented(indentLevel: number) { 30 | // @ts-ignore 31 | return (value: string) => indentString(value, indentSpaces * indentLevel); 32 | } 33 | 34 | export function getIndented(value: string, indentLevel: number) { 35 | // @ts-ignore 36 | return indentString(value, indentSpaces * indentLevel); 37 | } 38 | 39 | export function skip(arr: any[], index: number): any[] { 40 | const a = []; 41 | let i = 0; 42 | while (i < index) { 43 | a.push(arr[i]); 44 | i++; 45 | } 46 | return a; 47 | } 48 | 49 | /** 50 | * add backslshes to double quoted string 51 | */ 52 | export function addslashes(str: string) { 53 | return ( 54 | str 55 | .replace(/\\/g, '\\\\') 56 | .replace(/\u0008/g, '\\b') 57 | .replace(/\t/g, '\\t') 58 | .replace(/\n/g, '\\n') 59 | .replace(/\f/g, '\\f') 60 | .replace(/\r/g, '\\r') 61 | // replace(/'/g, '\\\''). 62 | .replace(/"/g, '\\"') 63 | ); 64 | } 65 | -------------------------------------------------------------------------------- /__tests__/string-template_data.ts: -------------------------------------------------------------------------------- 1 | export const ErrorBase = { 2 | ValidatorNoTypeParameter: `Failed to find type parameter. createValidator should be called with a type parameter. Example: "createValidator()"`, 3 | NotCalledAsFunction: `Macro should be called as function but was called as a`, 4 | MoreThanOneTypeParameter: `Macro should be called with 1 type parameter but was called with`, 5 | RegisterInvalidCall: stripIndent` 6 | "register" should be called right below the type 7 | declaration it is registering: 8 | \`\`\` 9 | interface Foo { 10 | bar: string 11 | } 12 | // no return value 13 | register() 14 | \`\`\` 15 | `, 16 | UnregisteredType: `Tried to generate a validator for an unregistered type with name`, 17 | // TODO: These will need to be updated once register accepts an options object 18 | RegisterInvalidNumberParams: `register should be called with 1 argument, but it was called with`, 19 | RegisterParam1NotStringLiteral: `register's first (and only) parameter should be a string literal, which is the name of the type to register, but it was a`, 20 | 21 | TypeDoesNotAcceptGenericParameters: `types don't accept generic parameters`, 22 | TooManyTypeParameters: `even though it only accepts`, 23 | NotEnoughTypeParameters: `type parameters even though it requires at least`, 24 | InvalidTypeParameterReference: `tried to reference the default type parameter in position:`, 25 | }; 26 | -------------------------------------------------------------------------------- /__tests__/loop-spec.ts: -------------------------------------------------------------------------------- 1 | import { transpile } from '../src/transpiler'; 2 | import { fs } from 'memfs'; 3 | test('Should handle do while and union type', done => { 4 | const typedef = `/** 5 | * Finds the matching node, starting at \`node\` and looking at the subsequent 6 | * siblings if a key is used. 7 | * @param matchNode The node to start looking at. 8 | * @param nameOrCtor The name or constructor for the Node. 9 | * @param key The key used to identify the Node. 10 | * @returns The matching Node, if any exists. 11 | */ 12 | function getMatchingNode( 13 | matchNode: Node | null, 14 | nameOrCtor: NameOrCtorDef, 15 | key: Key 16 | ): Node | null { 17 | if (!matchNode) { 18 | return null; 19 | } 20 | 21 | let cur: Node | null = matchNode; 22 | 23 | do { 24 | if (matches(cur, nameOrCtor, key)) { 25 | return cur; 26 | } 27 | } while (key && (cur = cur.nextSibling)); 28 | 29 | return null; 30 | }`; 31 | const expected = `template doWhile(a, b: untyped): untyped = 32 | b 33 | while a: 34 | b 35 | 36 | proc getMatchingNode(matchNode:Node, nameOrCtor:NameOrCtorDef, key:Key): Node = 37 | ## Finds the matching node, starting at \`node\` and looking at the subsequent 38 | ## siblings if a key is used. 39 | ## @param matchNode The node to start looking at. 40 | ## @param nameOrCtor The name or constructor for the Node. 41 | ## @param key The key used to identify the Node. 42 | ## @returns The matching Node, if any exists. 43 | 44 | if not matchNode: 45 | return nil 46 | var cur:Node = matchNode 47 | doWhile key and cur = cur.nextSibling: 48 | if matches(cur,nameOrCtor,key): 49 | return cur 50 | return nil 51 | 52 | `; 53 | const { writer } = transpile(undefined, typedef); 54 | 55 | writer.on('close', () => { 56 | expect(fs.readFileSync(writer.path).toString()).toBe(expected); 57 | done(); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /__tests__/class-spec.ts: -------------------------------------------------------------------------------- 1 | import { transpile } from '../src/transpiler'; 2 | import { fs } from 'memfs'; 3 | test('Should handle class', done => { 4 | const typedef = `class Transpiler { 5 | noAccMember:string 6 | private privateMember:string 7 | constructor(public ast: Program, protected writer: IWriteStream,noMember:boolean) { 8 | modules = new Set(); 9 | helpers = new Set(); 10 | } 11 | methodA(){} 12 | }`; 13 | const expected = `type Transpiler* = ref object of RootObj 14 | ast*:Program 15 | writer:IWriteStream 16 | noAccMember*:string 17 | privateMember:string 18 | 19 | 20 | proc newTranspiler*(ast:Program, writer:IWriteStream, noMember:bool): Transpiler = 21 | self.ast = ast 22 | self.writer = writer 23 | modules = Set() 24 | helpers = Set() 25 | 26 | proc methodA*(self:Transpiler): auto = discard 27 | 28 | `; 29 | const { writer } = transpile(undefined, typedef); 30 | 31 | writer.on('close', () => { 32 | expect(fs.readFileSync(writer.path).toString()).toBe(expected); 33 | done(); 34 | }); 35 | }); 36 | 37 | test('Should handle static method', done => { 38 | const typedef = `export class BufWriter extends AbstractBufBase implements Writer { 39 | /** return new BufWriter unless writer is BufWriter */ 40 | static create(writer: Writer, size: number = DEFAULT_BUF_SIZE): BufWriter { 41 | return writer instanceof BufWriter ? writer : new BufWriter(writer, size); 42 | } 43 | }`; 44 | const expected = `type BufWriter* = ref object of AbstractBufBase 45 | 46 | 47 | proc create*(self:typedesc[BufWriter], writer:Writer, size:float = DEFAULT_BUF_SIZE): BufWriter = 48 | ## return new BufWriter unless writer is BufWriter 49 | return if writer instanceof BufWriter: writer else: BufWriter(writer,size) 50 | 51 | `; 52 | const { writer } = transpile(undefined, typedef); 53 | 54 | writer.on('close', () => { 55 | expect(fs.readFileSync(writer.path).toString()).toBe(expected); 56 | done(); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /src/analyzer.ts: -------------------------------------------------------------------------------- 1 | import * as ts from 'typescript'; 2 | import * as path from 'path'; 3 | 4 | // https://github.com/Microsoft/TypeScript/wiki/Using-the-Compiler-API 5 | export interface Sym { 6 | name: string; 7 | type: string; 8 | loc: { 9 | pos: number; 10 | end: number; 11 | }; 12 | } 13 | 14 | export class Analyzer { 15 | program: ts.Program; 16 | public checker: ts.TypeChecker; 17 | public symbols: { [index: string]: Sym[] } = {}; 18 | private sourceFiles: ts.SourceFile[]; 19 | private idx = ''; 20 | constructor(fileNames: string[], compilerOptions: ts.CompilerOptions = {}) { 21 | this.program = ts.createProgram(fileNames, compilerOptions); 22 | this.checker = this.program.getTypeChecker(); 23 | this.sourceFiles = this.program 24 | .getSourceFiles() 25 | .filter( 26 | (sourceFile: ts.SourceFile) => 27 | !sourceFile.isDeclarationFile && !sourceFile.fileName.includes('node_modules' + path.sep) 28 | ); 29 | } 30 | 31 | annalize() { 32 | for (const sourceFile of this.sourceFiles) { 33 | const idx = this.sourceFile2pathWithoutExt(sourceFile); 34 | this.idx = idx; 35 | this.symbols[idx] = []; 36 | ts.forEachChild(sourceFile, this.visit.bind(this)); 37 | } 38 | } 39 | 40 | serializeSymbol(symbol: ts.Symbol, loc: { pos: number; end: number }): Sym { 41 | return { 42 | loc, 43 | name: symbol.getName(), 44 | type: this.checker.typeToString( 45 | this.checker.getTypeOfSymbolAtLocation(symbol, symbol.valueDeclaration!) 46 | // ,undefined 47 | // ,ts.TypeFormatFlags. 48 | ), 49 | }; 50 | } 51 | 52 | sourceFile2pathWithoutExt(sourceFile: ts.SourceFile): string { 53 | const filePath = sourceFile.fileName; 54 | const ext = path.extname(filePath); 55 | const dir = path.dirname(filePath); 56 | const basename = path.basename(filePath, ext); 57 | const changed = path.join(dir, basename); 58 | return changed; 59 | } 60 | 61 | visit(node: ts.Node) { 62 | let symbol; 63 | try { 64 | // @ts-ignore 65 | symbol = this.checker.getSymbolAtLocation(node.name); 66 | } catch (e) { 67 | console.error(e); 68 | } 69 | if (symbol) { 70 | this.symbols[this.idx].push(this.serializeSymbol(symbol, { pos: node.pos, end: node.end })); 71 | } 72 | node.forEachChild(this.visit.bind(this)); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /__tests__/ident-name-spec.ts: -------------------------------------------------------------------------------- 1 | import { transpile } from '../src/transpiler'; 2 | import { fs } from 'memfs'; 3 | test('Should avoid ident name start with _,$', done => { 4 | const typedef = ` 5 | export class Ng1LocationServices implements LocationConfig, LocationServices { 6 | private $locationProvider: ILocationProvider; 7 | private $location: ILocationService; 8 | private $sniffer: any; 9 | private $browser: any; 10 | private $window: IWindowService; 11 | _runtimeServices($rootScope, $location: ILocationService, $sniffer, $browser, $window: IWindowService) { 12 | this.$location = $location; 13 | this.$sniffer = $sniffer; 14 | this.$browser = $browser; 15 | this.$window = $window; 16 | 17 | // Bind $locationChangeSuccess to the listeners registered in LocationService.onChange 18 | $rootScope.$on('$locationChangeSuccess', evt => this._urlListeners.forEach(fn => fn(evt))); 19 | const _loc = val($location); 20 | 21 | // Bind these LocationService functions to $location 22 | createProxyFunctions(_loc, this, _loc, ['replace', 'path', 'search', 'hash']); 23 | // Bind these LocationConfig functions to $location 24 | createProxyFunctions(_loc, this, _loc, ['port', 'protocol', 'host']); 25 | } 26 | } 27 | `; 28 | const expected = `type Ng1LocationServices* = ref object of RootObj 29 | locationProvider:ILocationProvider 30 | location:ILocationService 31 | sniffer:any 32 | browser:any 33 | window:IWindowService 34 | 35 | 36 | proc runtimeServices(self:Ng1LocationServices, rootScope:auto, location:ILocationService, sniffer:auto, browser:auto, window:IWindowService): auto = 37 | self.location = location 38 | self.sniffer = sniffer 39 | self.browser = browser 40 | self.window = window 41 | ## Bind $locationChangeSuccess to the listeners registered in LocationService.onChange 42 | rootScope.on("$locationChangeSuccess",proc (evt:auto): auto = 43 | ) 44 | var loc = val(location) 45 | ## Bind these LocationService functions to $location 46 | createProxyFunctions(loc,self,loc,@["replace","path","search","hash"]) 47 | ## Bind these LocationConfig functions to $location 48 | createProxyFunctions(loc,self,loc,@["port","protocol","host"]) 49 | 50 | `; 51 | const { writer } = transpile(undefined, typedef); 52 | 53 | writer.on('close', () => { 54 | expect(fs.readFileSync(writer.path).toString()).toBe(expected); 55 | done(); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /__tests__/comment-spec.ts: -------------------------------------------------------------------------------- 1 | import { transpile } from '../src/transpiler'; 2 | import { fs } from 'memfs'; 3 | 4 | test('Should handle inline comment', done => { 5 | const typedef = ` 6 | /** 7 | * Create the Icon entry. 8 | * @param png PNG image. 9 | * @param offset The offset of directory data from the beginning of the ICO/CUR file 10 | * @return Directory data. 11 | * 12 | * @see https://msdn.microsoft.com/en-us/library/ms997538.aspx 13 | */ 14 | const createDirectory = (png:PNG, offset:number) => { 15 | const b = Buffer.alloc(ICO_DIRECTORY_SIZE) 16 | const size = png.data.length + BITMAPINFOHEADER_SIZE 17 | const width = 256 <= png.width ? 0 : png.width 18 | const height = 256 <= png.height ? 0 : png.height 19 | const bpp = BPP_ALPHA * 8 20 | 21 | b.writeUInt8(width, 0) // 1 BYTE Image width 22 | b.writeUInt8(height, 1) // 1 BYTE Image height 23 | b.writeUInt8(0, 2) // 1 BYTE Colors 24 | b.writeUInt8(0, 3) // 1 BYTE Reserved 25 | b.writeUInt16LE(1, 4) // 2 WORD Color planes 26 | b.writeUInt16LE(bpp, 6) // 2 WORD Bit per pixel 27 | b.writeUInt32LE(size, 8) // 4 DWORD Bitmap (DIB) size 28 | b.writeUInt32LE(offset, 12) // 4 DWORD Offset 29 | 30 | return b 31 | } 32 | `; 33 | const expected = `proc createDirectory(png:PNG, offset:float): auto = 34 | ## Create the Icon entry. 35 | ## @param png PNG image. 36 | ## @param offset The offset of directory data from the beginning of the ICO/CUR file 37 | ## @return Directory data. 38 | ## @see https://msdn.microsoft.com/en-us/library/ms997538.aspx 39 | 40 | var b = Buffer.alloc(ICO_DIRECTORY_SIZE) 41 | var size = png.data.len + BITMAPINFOHEADER_SIZE 42 | var width = if 256 <= png.width: 0 else: png.width 43 | var height = if 256 <= png.height: 0 else: png.height 44 | var bpp = BPP_ALPHA * 8 45 | b.writeUInt8(width,0) 46 | ## 1 BYTE Image width 47 | b.writeUInt8(height,1) 48 | ## 1 BYTE Image height 49 | b.writeUInt8(0,2) 50 | ## 1 BYTE Colors 51 | b.writeUInt8(0,3) 52 | ## 1 BYTE Reserved 53 | b.writeUInt16LE(1,4) 54 | ## 2 WORD Color planes 55 | b.writeUInt16LE(bpp,6) 56 | ## 2 WORD Bit per pixel 57 | b.writeUInt32LE(size,8) 58 | ## 4 DWORD Bitmap (DIB) size 59 | b.writeUInt32LE(offset,12) 60 | return b 61 | 62 | `; 63 | const { writer } = transpile(undefined, typedef); 64 | 65 | writer.on('close', () => { 66 | expect(fs.readFileSync(writer.path).toString()).toBe(expected); 67 | done(); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ts2nim", 3 | "version": "0.0.31", 4 | "description": "typescript to Nim transpiler", 5 | "license": "MIT", 6 | "repository": "https://github.com/bung87/ts2nim", 7 | "author": { 8 | "name": "", 9 | "email": "", 10 | "url": "" 11 | }, 12 | "keywords": [ 13 | "typescript", 14 | "Nim", 15 | "transpiler" 16 | ], 17 | "files": [ 18 | "lib" 19 | ], 20 | "bin": { 21 | "ts2nim": "lib/cli.js" 22 | }, 23 | "publishConfig": { 24 | "access": "public", 25 | "registry": "https://registry.npmjs.com" 26 | }, 27 | "main": "lib/index", 28 | "types": "lib/index", 29 | "scripts": { 30 | "clean": "rimraf lib && rimraf coverage", 31 | "format": "prettier --print-width 100 --write \"{src,__tests__}/**/*.ts\" --single-quote --trailing-comma es5", 32 | "lint": "tslint --fix --force --format verbose \"src/**/*.ts\"", 33 | "uglifyjs": "uglifyjs lib/*.js --ecma=5 -c drop_console=true -o lib/*.js ", 34 | "prepublishOnly": "npm run build", 35 | "prebuild": "npm run clean && npm run format && npm run lint", 36 | "build": "npm run prebuild && tsc -p .", 37 | "report": "ts-node src/report-uncovered-ast-types.ts", 38 | "test": "jest --runInBand", 39 | "coverage": "jest --coverage ", 40 | "watch": "npm run build -- --watch", 41 | "watch:test": "jest --watch" 42 | }, 43 | "dependencies": { 44 | "@typescript-eslint/experimental-utils": "^3.5.0", 45 | "@typescript-eslint/typescript-estree": "^3.5.0", 46 | "fast-glob": "^3.2.4", 47 | "indent-string": "^4.0.0", 48 | "inflection2": "^2.0.1", 49 | "memfs": "^3.1.2", 50 | "mkdirp": "^1.0.4", 51 | "rxjs": "^6.5.5", 52 | "typescript": "^3.1.1", 53 | "yargs": "^15.3.1" 54 | }, 55 | "devDependencies": { 56 | "@types/eslint-visitor-keys": "^1.0.0", 57 | "@types/jest": "^23.3.3", 58 | "@types/mkdirp": "^1.0.0", 59 | "@types/node": "^10.11.4", 60 | "@types/yargs": "^15.0.4", 61 | "coveralls": "^3.0.2", 62 | "jest": "^23.6.0", 63 | "prettier": "^1.14.3", 64 | "rimraf": "^2.6.2", 65 | "ts-jest": "^23.10.3", 66 | "ts-node": "^7.0.1", 67 | "tslint": "^5.11.0", 68 | "tslint-config-prettier": "^1.15.0", 69 | "uglify-es": "^3.3.9" 70 | }, 71 | "engines": { 72 | "node": ">=10.0.0" 73 | }, 74 | "jest": { 75 | "testPathIgnorePatterns": [ 76 | "/node_modules/", 77 | "__tests__/analyzer_data.ts", 78 | "__tests__/string-template_data.ts" 79 | ], 80 | "preset": "ts-jest" 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/cli.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import * as yargs from 'yargs'; 3 | // @ts-ignore 4 | import * as glob from 'fast-glob'; 5 | import * as realfs from 'fs'; 6 | import * as path from 'path'; 7 | import { fs as memfs } from 'memfs'; 8 | import { transpile } from './transpiler'; 9 | import * as mkdirp from 'mkdirp'; 10 | import { Analyzer } from './analyzer'; 11 | const argv = yargs 12 | .option('src', { 13 | alias: 'i', 14 | description: 'input file or source dir', 15 | type: 'string', 16 | }) 17 | .option('numberAs', { 18 | alias: 'n', 19 | description: 'number as float or int?', 20 | type: 'string', 21 | default: 'float', 22 | }) 23 | .option('dest', { 24 | alias: 'o', 25 | description: 'dest file or dest dir', 26 | type: 'string', 27 | }) 28 | .help() 29 | .alias('help', 'h').argv; 30 | const src = argv.src ? path.resolve(argv.src) : process.cwd(); 31 | const dest = argv.dest ? path.resolve(argv.dest) : process.cwd(); 32 | if (realfs.lstatSync(src).isDirectory()) { 33 | const files = glob.sync('(*.ts|*.js)', { 34 | // @ts-ignore 35 | root: src, 36 | matchBase: true, 37 | ignore: ['**/node_modules/**'], 38 | }); 39 | const anayzer = new Analyzer(files); 40 | anayzer.annalize(); 41 | files.forEach((file: string) => { 42 | const ext = path.extname(file); 43 | const relativePath = path.relative(src, file); 44 | 45 | const relativeDir = path.dirname(relativePath); 46 | const basename = path.basename(file, ext); 47 | const writePath = path.join(dest, relativeDir, basename + '.nim'); 48 | const code = realfs.readFileSync(file).toString(); 49 | const { writer } = transpile( 50 | writePath, 51 | code, 52 | { 53 | numberAs: (argv.numberAs as unknown) as any, 54 | isProject: true, 55 | }, 56 | undefined, 57 | anayzer.symbols 58 | ); 59 | writer.on('close', () => { 60 | console.log(writer.path); 61 | const content = memfs.readFileSync(writer.path).toString(); 62 | if (!realfs.existsSync(path.dirname(writer.path))) { 63 | mkdirp.sync(path.dirname(writer.path)); 64 | } 65 | realfs.writeFileSync(writer.path, content); 66 | }); 67 | }); 68 | } else { 69 | let writePath: string; 70 | const anayzer = new Analyzer([src]); 71 | anayzer.annalize(); 72 | const ext = path.extname(src); 73 | const basename = path.basename(src, ext); 74 | if (dest.endsWith('.nim')) { 75 | writePath = dest; 76 | } else { 77 | writePath = path.join(dest, basename + '.nim'); 78 | } 79 | const code = realfs.readFileSync(src).toString(); 80 | const { writer } = transpile( 81 | writePath, 82 | code, 83 | { 84 | numberAs: (argv.numberAs as unknown) as any, 85 | isProject: false, 86 | }, 87 | undefined, 88 | anayzer.symbols 89 | ); 90 | writer.on('close', () => { 91 | console.log(writer.path); 92 | const content = memfs.readFileSync(writer.path).toString(); 93 | if (!realfs.existsSync(path.dirname(writer.path))) { 94 | mkdirp.sync(path.dirname(writer.path)); 95 | } 96 | realfs.writeFileSync(writer.path, content); 97 | }); 98 | } 99 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ts2nim 2 | [![Npm Version](https://badgen.net/npm/v/ts2nim)](https://www.npmjs.com/package/ts2nim) ![npm: total downloads](https://badgen.net/npm/dt/ts2nim) ![Types](https://badgen.net/npm/types/ts2nim) ![license](https://badgen.net/npm/license/ts2nim) 3 | 4 | Typescript to Nim transpiler 5 | 6 | It also can translate js code with the Nim type `auto` as `typescript-estree` compatible with the js estree. 7 | 8 | It identified the `.d.ts` file if the ast node is `TSMethodSignature` it will add `importcpp` pragma to translated `proc`, so it could be used as a wrapper generator. 9 | 10 | ## Motivation 11 | 12 | Transpile nodejs modules, write in Typescript and transpile it to Nim, expand the Nim-Javascript backend ecosystem and so on. 13 | 14 | Current goal is translating Typescript syntax into valid and pretty looking nim code. you may manually modify nim sources after translation. it just translate source code exclude dependency,even modules in nodejs std. 15 | 16 | demo transpiled project: 17 | [vscode-uri](https://github.com/bung87/vscode-uri) 18 | 19 | It can be easy to translate library processes like string manipulation, images, bytes and all these things. 20 | 21 | ## RoadMap 22 | 23 | This project has two routes 24 | 1. Generate nim js bridge through typescript type difinition file. 25 | 2. Generate nim source code through typescript source file. 26 | 27 | ### Todos 28 | - [ ] Inferring js type (object or others) 29 | - [ ] Inferring native type (eg. number is int or float) 30 | 31 | ## Limitations 32 | 33 | [assemblyscript basics](https://docs.assemblyscript.org/basics) described well, share same theory. 34 | 35 | ## Installation 36 | 37 | `npm i ts2nim` or 38 | `yarn add ts2nim` 39 | 40 | ## Usage 41 | 42 | `ts2nim -i inputFileOrDir -o outFileOrDir` 43 | 44 | without param it will transpile current directory in place with extension `.nim` 45 | 46 | ## Translation 47 | 48 | | origin | to | description | 49 | | :-------------: | :----------: | :----------- | 50 | | number | float | | 51 | | boolean | bool | | 52 | | interface,type,class | type | | 53 | | Example() | newExample() | constructor for a class | 54 | | let,var,const | var | const no-object -> const,others var | 55 | | this | self | | 56 | | null,undefinded | nil | | 57 | | optinal param | none(T) | options module | 58 | | T\|null,T\|undefinded | T | ref type| 59 | | RestElement param | openArray[T] | | 60 | | switch | case of | | 61 | | Array | seq | | 62 | | StringTemplate | fmt | strformat module | 63 | | do while | doWhile | doWhile template | 64 | | raise new T() | raise newException(T) | | 65 | | .length | .len | | 66 | | .push | .add | | 67 | | fs.readFileSync | readFile | os module | 68 | | path.join | / | os module | 69 | | .some | .any | sequtils module | 70 | | .sort | .sorted | algorithm module | 71 | | async | {.async.} | asyncdispatch or asyncjs module | 72 | | === | == | | 73 | | !== | != | | 74 | | && | and | | 75 | | \|\| | or | | 76 | | ! | not | | 77 | | + | & or + | depends on a literal string or number found | 78 | | delete | assign to nil | | 79 | | extends A,B| ref object of A,B | Multiple Inheritance not supported in Nim | 80 | 81 | 82 | ## Related Projects 83 | 84 | [mcclure/dts2nim](https://github.com/mcclure/dts2nim) 85 | 86 | ## Projects use ts2nim 87 | 88 | *Add by you* 89 | -------------------------------------------------------------------------------- /__tests__/computed-prop-spec.ts: -------------------------------------------------------------------------------- 1 | import { transpile } from '../src/transpiler'; 2 | import { fs } from 'memfs'; 3 | const typedef = `/** 4 | * Applies the statics. When importing an Element, any existing attributes that 5 | * match a static are converted into a static attribute. 6 | * @param node The Element to apply statics for. 7 | * @param data The NodeData associated with the Element. 8 | * @param statics The statics array. 9 | */ 10 | function diffStatics(node: Element, data: NodeData, statics: Statics) { 11 | if (data.staticsApplied) { 12 | return; 13 | } 14 | 15 | data.staticsApplied = true; 16 | 17 | if (!statics || !statics.length) { 18 | return; 19 | } 20 | 21 | if (data.hasEmptyAttrsArr()) { 22 | for (let i = 0; i < statics.length; i += 2) { 23 | updateAttribute(node, statics[i] as string, statics[i + 1]); 24 | } 25 | return; 26 | } 27 | 28 | for (let i = 0; i < statics.length; i += 2) { 29 | prevAttrsMap[statics[i] as string] = i + 1; 30 | } 31 | 32 | const attrsArr = data.getAttrsArr(0); 33 | let j = 0; 34 | for (let i = 0; i < attrsArr.length; i += 2) { 35 | const name = attrsArr[i]; 36 | const value = attrsArr[i + 1]; 37 | const staticsIndex = prevAttrsMap[name]; 38 | 39 | if (staticsIndex) { 40 | // For any attrs that are static and have the same value, make sure we do 41 | // not set them again. 42 | if (statics[staticsIndex] === value) { 43 | delete prevAttrsMap[name]; 44 | } 45 | 46 | continue; 47 | } 48 | 49 | // For any attrs that are dynamic, move them up to the right place. 50 | attrsArr[j] = name; 51 | attrsArr[j + 1] = value; 52 | j += 2; 53 | } 54 | // Anything after \`j\` was either moved up already or static. 55 | truncateArray(attrsArr, j); 56 | 57 | for (const name in prevAttrsMap) { 58 | updateAttribute(node, name, statics[prevAttrsMap[name]]); 59 | delete prevAttrsMap[name]; 60 | } 61 | }`; 62 | const expected = `proc diffStatics(node:Element, data:NodeData, statics:Statics): auto = 63 | ## Applies the statics. When importing an Element, any existing attributes that 64 | ## match a static are converted into a static attribute. 65 | ## @param node The Element to apply statics for. 66 | ## @param data The NodeData associated with the Element. 67 | ## @param statics The statics array. 68 | 69 | if data.staticsApplied: 70 | return void 71 | data.staticsApplied = true 72 | if not statics or not statics.len: 73 | return void 74 | if data.hasEmptyAttrsArr(): 75 | var i = 0 76 | while i < statics.len: 77 | updateAttribute(node,statics[i],statics[i + 1]) 78 | return void 79 | var i = 0 80 | while i < statics.len: 81 | prevAttrsMap[statics[i]] = i + 1 82 | var attrsArr = data.getAttrsArr(0) 83 | var j = 0 84 | var i = 0 85 | while i < attrsArr.len: 86 | var name = attrsArr[i] 87 | var value = attrsArr[i + 1] 88 | var staticsIndex = prevAttrsMap[name] 89 | if staticsIndex: 90 | if statics[staticsIndex] == value: 91 | prevAttrsMap[name] = nil 92 | continue 93 | ## For any attrs that are dynamic, move them up to the right place. 94 | attrsArr[j] = name 95 | attrsArr[j + 1] = value 96 | j += 2 97 | ## Anything after \`j\` was either moved up already or static. 98 | truncateArray(attrsArr,j) 99 | for name,_ in prevAttrsMap: 100 | updateAttribute(node,name,statics[prevAttrsMap[name]]) 101 | prevAttrsMap[name] = nil 102 | 103 | `; 104 | test('Should handle computed prop', done => { 105 | const { writer } = transpile(undefined, typedef); 106 | 107 | writer.on('close', () => { 108 | expect(fs.readFileSync(writer.path).toString()).toBe(expected); 109 | done(); 110 | }); 111 | }); 112 | -------------------------------------------------------------------------------- /__tests__/interface-spec.ts: -------------------------------------------------------------------------------- 1 | import { transpile } from '../src/transpiler'; 2 | import { fs } from 'memfs'; 3 | test('Should handle interface', done => { 4 | const typedef = ` 5 | interface SquareConfig { 6 | color?: string; 7 | width?: number; 8 | } 9 | `; 10 | const expected = `type SquareConfig* = ref object of RootObj 11 | color*:string 12 | width*:float 13 | 14 | 15 | `; 16 | const { writer } = transpile(undefined, typedef); 17 | 18 | writer.on('close', () => { 19 | expect(fs.readFileSync(writer.path).toString()).toBe(expected); 20 | done(); 21 | }); 22 | }); 23 | 24 | test('Should handle interface with index signature', done => { 25 | const typedef = ` 26 | interface SquareConfig { 27 | color?: string; 28 | width?: number; 29 | [propName: string]: any; 30 | } 31 | `; 32 | const expected = `type SquareConfig* = ref object of RootObj 33 | color*:string 34 | width*:float 35 | 36 | 37 | `; 38 | const { writer } = transpile(undefined, typedef); 39 | 40 | writer.on('close', () => { 41 | expect(fs.readFileSync(writer.path).toString()).toBe(expected); 42 | done(); 43 | }); 44 | }); 45 | 46 | test('Should handle function interface', done => { 47 | const typedef = ` 48 | interface SearchFunc { 49 | (source: string, subString: string): boolean; 50 | } 51 | `; 52 | const expected = `type SearchFunc* = proc (source:string, subString:string): bool 53 | 54 | 55 | `; 56 | const { writer } = transpile(undefined, typedef); 57 | 58 | writer.on('close', () => { 59 | expect(fs.readFileSync(writer.path).toString()).toBe(expected); 60 | done(); 61 | }); 62 | }); 63 | test('Should handle interface with prop and method', done => { 64 | const typedef = ` 65 | interface ClockInterface { 66 | currentTime: Date; 67 | setTime(d: Date); 68 | } 69 | `; 70 | const expected = `type ClockInterface* = ref object of RootObj 71 | currentTime*:Date 72 | 73 | 74 | proc setTime*(self:ClockInterface, d:Date): auto 75 | 76 | `; 77 | const { writer } = transpile(undefined, typedef); 78 | 79 | writer.on('close', () => { 80 | expect(fs.readFileSync(writer.path).toString()).toBe(expected); 81 | done(); 82 | }); 83 | }); 84 | 85 | test('Should handle interface with new factor', done => { 86 | const typedef = ` 87 | interface ClockConstructor { 88 | new (hour: number, minute: number); 89 | } 90 | `; 91 | const expected = `type ClockConstructor* = proc (hour:float, minute:float): auto 92 | 93 | 94 | `; 95 | const { writer } = transpile(undefined, typedef); 96 | 97 | writer.on('close', () => { 98 | expect(fs.readFileSync(writer.path).toString()).toBe(expected); 99 | done(); 100 | }); 101 | }); 102 | 103 | test('Should handle class implements interface ', done => { 104 | const typedef = ` 105 | class Clock implements ClockConstructor { 106 | currentTime: Date; 107 | constructor(h: number, m: number) { } 108 | } 109 | `; 110 | const expected = `type Clock* = ref object of RootObj 111 | currentTime*:Date 112 | 113 | 114 | proc newClock*(h:float, m:float): Clock = discard 115 | 116 | `; 117 | const { writer } = transpile(undefined, typedef); 118 | 119 | writer.on('close', () => { 120 | expect(fs.readFileSync(writer.path).toString()).toBe(expected); 121 | done(); 122 | }); 123 | }); 124 | 125 | test('Should handle interface extends', done => { 126 | const typedef = ` 127 | interface Square extends Shape { 128 | sideLength: number; 129 | } 130 | interface Square extends Shape, PenStroke { 131 | sideLength: number; 132 | } 133 | `; 134 | const expected = `type Square* = ref object of Shape 135 | sideLength*:float 136 | 137 | 138 | type Square* = ref object of Shape,PenStroke 139 | sideLength*:float 140 | 141 | 142 | `; 143 | const { writer } = transpile(undefined, typedef); 144 | 145 | writer.on('close', () => { 146 | expect(fs.readFileSync(writer.path).toString()).toBe(expected); 147 | done(); 148 | }); 149 | }); 150 | -------------------------------------------------------------------------------- /__tests__/complex-spec.ts: -------------------------------------------------------------------------------- 1 | import { transpile } from '../src/transpiler'; 2 | import { fs } from 'memfs'; 3 | test('Should handle generic,spread,try', done => { 4 | const typedef = `/** 5 | * Returns a patcher function that sets up and restores a patch context, 6 | * running the run function with the provided data. 7 | * @param run The function that will run the patch. 8 | * @param patchConfig The configuration to use for the patch. 9 | * @returns The created patch function. 10 | */ 11 | function createPatcher( 12 | run: PatchFunction, 13 | patchConfig: PatchConfig = {} 14 | ): PatchFunction { 15 | const { matches = defaultMatchFn } = patchConfig; 16 | 17 | const f: PatchFunction = (node, fn, data) => { 18 | const prevContext = context; 19 | const prevDoc = doc; 20 | const prevFocusPath = focusPath; 21 | const prevArgsBuilder = argsBuilder; 22 | const prevAttrsBuilder = attrsBuilder; 23 | const prevCurrentNode = currentNode; 24 | const prevCurrentParent = currentParent; 25 | const prevMatchFn = matchFn; 26 | let previousInAttributes = false; 27 | let previousInSkip = false; 28 | 29 | doc = node.ownerDocument; 30 | context = new Context(); 31 | matchFn = matches; 32 | argsBuilder = []; 33 | attrsBuilder = []; 34 | currentNode = null; 35 | currentParent = node.parentNode; 36 | focusPath = getFocusedPath(node, currentParent); 37 | 38 | if (DEBUG) { 39 | previousInAttributes = setInAttributes(false); 40 | previousInSkip = setInSkip(false); 41 | updatePatchContext(context); 42 | } 43 | 44 | try { 45 | const retVal = run(node, fn, data); 46 | if (DEBUG) { 47 | assertVirtualAttributesClosed(); 48 | } 49 | 50 | return retVal; 51 | } finally { 52 | context.notifyChanges(); 53 | 54 | doc = prevDoc; 55 | context = prevContext; 56 | matchFn = prevMatchFn; 57 | argsBuilder = prevArgsBuilder; 58 | attrsBuilder = prevAttrsBuilder; 59 | currentNode = prevCurrentNode; 60 | currentParent = prevCurrentParent; 61 | focusPath = prevFocusPath; 62 | 63 | // Needs to be done after assertions because assertions rely on state 64 | // from these methods. 65 | if (DEBUG) { 66 | setInAttributes(previousInAttributes); 67 | setInSkip(previousInSkip); 68 | updatePatchContext(context); 69 | } 70 | } 71 | }; 72 | return f; 73 | }`; 74 | const expected = `proc createPatcher[T,R](run:PatchFunction[T,R], patchConfig:PatchConfig = newPatchConfig()): PatchFunction[T,R] = 75 | ## Returns a patcher function that sets up and restores a patch context, 76 | ## running the run function with the provided data. 77 | ## @param run The function that will run the patch. 78 | ## @param patchConfig The configuration to use for the patch. 79 | ## @returns The created patch function. 80 | 81 | var matches = patchConfig.matches 82 | if isNil(matches): 83 | matches = defaultMatchFn 84 | var f:PatchFunction[T,R] = proc (node:auto, fn:auto, data:auto): auto = 85 | var prevContext = context 86 | var prevDoc = doc 87 | var prevFocusPath = focusPath 88 | var prevArgsBuilder = argsBuilder 89 | var prevAttrsBuilder = attrsBuilder 90 | var prevCurrentNode = currentNode 91 | var prevCurrentParent = currentParent 92 | var prevMatchFn = matchFn 93 | var previousInAttributes = false 94 | var previousInSkip = false 95 | doc = node.ownerDocument 96 | context = Context() 97 | matchFn = matches 98 | argsBuilder = @[] 99 | attrsBuilder = @[] 100 | currentNode = nil 101 | currentParent = node.parentNode 102 | focusPath = getFocusedPath(node,currentParent) 103 | if DEBUG: 104 | previousInAttributes = setInAttributes(false) 105 | previousInSkip = setInSkip(false) 106 | updatePatchContext(context) 107 | try: 108 | var retVal = run(node,fn,data) 109 | if DEBUG: 110 | assertVirtualAttributesClosed() 111 | return retVal 112 | finally: 113 | context.notifyChanges() 114 | doc = prevDoc 115 | context = prevContext 116 | matchFn = prevMatchFn 117 | argsBuilder = prevArgsBuilder 118 | attrsBuilder = prevAttrsBuilder 119 | currentNode = prevCurrentNode 120 | currentParent = prevCurrentParent 121 | focusPath = prevFocusPath 122 | if DEBUG: 123 | setInAttributes(previousInAttributes) 124 | setInSkip(previousInSkip) 125 | updatePatchContext(context) 126 | return f 127 | 128 | `; 129 | const { writer } = transpile(undefined, typedef); 130 | 131 | writer.on('close', () => { 132 | expect(fs.readFileSync(writer.path).toString()).toBe(expected); 133 | done(); 134 | }); 135 | }); 136 | -------------------------------------------------------------------------------- /__tests__/function-spec.ts: -------------------------------------------------------------------------------- 1 | import { transpile } from '../src/transpiler'; 2 | import { fs } from 'memfs'; 3 | 4 | test('Should handle async function', done => { 5 | const typedef = ` 6 | /** 7 | * Generate the PNG files. 8 | * @param src Path of SVG file. 9 | * @param dir Output destination The path of directory. 10 | * @param sizes Required PNG image size. 11 | * @param logger Logger. 12 | */ 13 | export const generatePNG = async ( 14 | src: string, 15 | dir: string, 16 | sizes: number[], 17 | logger: Logger 18 | ): Promise => { 19 | logger.log('SVG to PNG:') 20 | 21 | const svg = fs.readFileSync(src) 22 | const images: ImageInfo[] = [] 23 | for (const size of sizes) { 24 | images.push(await generate(svg, size, dir, logger)) 25 | } 26 | 27 | return images 28 | } 29 | `; 30 | const expected = `import asyncdispatch,os 31 | 32 | proc generatePNG*(src:string, dir:string, sizes:seq[float], logger:Logger): Future[seq[ImageInfo]] {.async.} = 33 | ## Generate the PNG files. 34 | ## @param src Path of SVG file. 35 | ## @param dir Output destination The path of directory. 36 | ## @param sizes Required PNG image size. 37 | ## @param logger Logger. 38 | 39 | logger.log("SVG to PNG:") 40 | var svg = readFile(src) 41 | var images:seq[ImageInfo] = @[] 42 | for size in sizes.mitems: 43 | images.add(await generate(svg,size,dir,logger)) 44 | return images 45 | 46 | `; 47 | const { writer } = transpile(undefined, typedef); 48 | 49 | writer.on('close', () => { 50 | expect(fs.readFileSync(writer.path).toString()).toBe(expected); 51 | done(); 52 | }); 53 | }); 54 | 55 | test('Should handle chained function', done => { 56 | const typedef = ` 57 | /** 58 | * Filter by size to the specified image informations. 59 | * @param images Image file informations. 60 | * @param sizes Required sizes. 61 | * @return Filtered image informations. 62 | */ 63 | export const filterImagesBySizes = (images: ImageInfo[], sizes: number[]) => { 64 | return images 65 | .filter((image) => { 66 | return sizes.some((size) => { 67 | return image.size === size 68 | }) 69 | }) 70 | .sort((a, b) => { 71 | return a.size - b.size 72 | }) 73 | } 74 | `; 75 | const expected = `import sequtils,algorithm 76 | 77 | proc filterImagesBySizes*(images:seq[ImageInfo], sizes:seq[float]): auto = 78 | ## Filter by size to the specified image informations. 79 | ## @param images Image file informations. 80 | ## @param sizes Required sizes. 81 | ## @return Filtered image informations. 82 | 83 | images.filter(proc (image:auto): auto = 84 | sizes.any(proc (size:auto): bool = 85 | image.size == size 86 | ) 87 | ).sorted(proc (a:auto, b:auto): float = 88 | a.size - b.size 89 | ) 90 | 91 | `; 92 | const { writer } = transpile(undefined, typedef); 93 | 94 | writer.on('close', () => { 95 | expect(fs.readFileSync(writer.path).toString()).toBe(expected); 96 | done(); 97 | }); 98 | }); 99 | 100 | test('Should handle for statement', done => { 101 | const typedef = ` 102 | /** 103 | * Convert a PNG of the byte array to the DIB (Device Independent Bitmap) format. 104 | * PNG in color RGBA (and more), the coordinate structure is the Top/Left to Bottom/Right. 105 | * DIB in color BGRA, the coordinate structure is the Bottom/Left to Top/Right. 106 | * @param src Target image. 107 | * @param width The width of the image. 108 | * @param height The height of the image. 109 | * @param bpp The bit per pixel of the image. 110 | * @return Converted image 111 | * @see https://en.wikipedia.org/wiki/BMP_file_format 112 | */ 113 | const convertPNGtoDIB = ( 114 | src: Buffer, 115 | width: number, 116 | height: number, 117 | bpp: number 118 | ) => { 119 | const cols = width * bpp 120 | const rows = height * cols 121 | const rowEnd = rows - cols 122 | const dest = Buffer.alloc(src.length) 123 | 124 | for (let row = 0; row < rows; row += cols) { 125 | for (let col = 0; col < cols; col += bpp) { 126 | // RGBA: Top/Left -> Bottom/Right 127 | let pos = row + col 128 | const r = src.readUInt8(pos) 129 | const g = src.readUInt8(pos + 1) 130 | const b = src.readUInt8(pos + 2) 131 | const a = src.readUInt8(pos + 3) 132 | 133 | // BGRA: Right/Left -> Top/Right 134 | pos = rowEnd - row + col 135 | dest.writeUInt8(b, pos) 136 | dest.writeUInt8(g, pos + 1) 137 | dest.writeUInt8(r, pos + 2) 138 | dest.writeUInt8(a, pos + 3) 139 | } 140 | } 141 | 142 | return dest 143 | } 144 | `; 145 | const expected = `proc convertPNGtoDIB(src:Buffer, width:float, height:float, bpp:float): auto = 146 | ## Convert a PNG of the byte array to the DIB (Device Independent Bitmap) format. 147 | ## PNG in color RGBA (and more), the coordinate structure is the Top/Left to Bottom/Right. 148 | ## DIB in color BGRA, the coordinate structure is the Bottom/Left to Top/Right. 149 | ## @param src Target image. 150 | ## @param width The width of the image. 151 | ## @param height The height of the image. 152 | ## @param bpp The bit per pixel of the image. 153 | ## @return Converted image 154 | ## @see https://en.wikipedia.org/wiki/BMP_file_format 155 | 156 | var cols = width * bpp 157 | var rows = height * cols 158 | var rowEnd = rows - cols 159 | var dest = Buffer.alloc(src.len) 160 | var row = 0 161 | while row < rows: 162 | var col = 0 163 | while col < cols: 164 | ## RGBA: Top/Left -> Bottom/Right 165 | var pos = row + col 166 | var r = src.readUInt8(pos) 167 | var g = src.readUInt8(pos + 1) 168 | var b = src.readUInt8(pos + 2) 169 | var a = src.readUInt8(pos + 3) 170 | ## BGRA: Right/Left -> Top/Right 171 | pos = rowEnd - row + col 172 | dest.writeUInt8(b,pos) 173 | dest.writeUInt8(g,pos + 1) 174 | dest.writeUInt8(r,pos + 2) 175 | dest.writeUInt8(a,pos + 3) 176 | return dest 177 | 178 | `; 179 | const { writer } = transpile(undefined, typedef); 180 | 181 | writer.on('close', () => { 182 | expect(fs.readFileSync(writer.path).toString()).toBe(expected); 183 | done(); 184 | }); 185 | }); 186 | 187 | test('Should handle TSFunctionType', done => { 188 | const typedef = `const patchOuter: ( 189 | node: Element | DocumentFragment, 190 | template: (a: T | undefined) => void, 191 | data?: T | undefined 192 | ) => Node | null = createPatchOuter();`; 193 | const expected = `import options 194 | 195 | var patchOuter:proc [T](node:Element|DocumentFragment, \`template\`:proc (a:T): auto, data = none(T)): auto = createPatchOuter() 196 | `; 197 | const { writer } = transpile(undefined, typedef); 198 | 199 | writer.on('close', () => { 200 | expect(fs.readFileSync(writer.path).toString()).toBe(expected); 201 | done(); 202 | }); 203 | }); 204 | 205 | test('Should handle rest and optional param', done => { 206 | const typedef = `function elementVoid( 207 | nameOrCtor: NameOrCtorDef, 208 | key?: Key, 209 | // Ideally we could tag statics and varArgs as an array where every odd 210 | // element is a string and every even element is any, but this is hard. 211 | statics?: Statics, 212 | ...varArgs: Array 213 | ) { 214 | elementOpen.apply(null, arguments as any); 215 | return elementClose(nameOrCtor); 216 | }`; 217 | const expected = `import options 218 | 219 | proc elementVoid(nameOrCtor:NameOrCtorDef, key = none(Key), statics = none(Statics), varArgs:openArray[any]): auto = 220 | elementOpen.apply(nil,arguments) 221 | elementClose(nameOrCtor) 222 | 223 | `; 224 | const { writer } = transpile(undefined, typedef); 225 | 226 | writer.on('close', () => { 227 | expect(fs.readFileSync(writer.path).toString()).toBe(expected); 228 | done(); 229 | }); 230 | }); 231 | 232 | test('Should handle void return type', done => { 233 | const typedef = ` 234 | function warnUser(): void { 235 | console.log("This is my warning message"); 236 | } 237 | `; 238 | const expected = `proc warnUser() = 239 | console.log("This is my warning message") 240 | 241 | `; 242 | const { writer } = transpile(undefined, typedef); 243 | 244 | writer.on('close', () => { 245 | expect(fs.readFileSync(writer.path).toString()).toBe(expected); 246 | done(); 247 | }); 248 | }); 249 | -------------------------------------------------------------------------------- /src/transpiler.ts: -------------------------------------------------------------------------------- 1 | import * as parser from '@typescript-eslint/typescript-estree'; 2 | import { fs } from 'memfs'; 3 | import { IWriteStream } from 'memfs/lib/volume'; 4 | import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/experimental-utils'; 5 | import { doWhile, brideHeader, seqFind } from './nimhelpers'; 6 | import * as path from 'path'; 7 | import { arraysEqual, getLine, skip, indented, getIndented, addslashes } from './utils'; 8 | import { BinaryOperatorsReturnsBoolean, primitiveTypes } from './types'; 9 | import { Subject } from 'rxjs'; 10 | import { performance } from 'perf_hooks'; 11 | import { reserved } from './nim'; 12 | import { Sym } from './analyzer'; 13 | import { camelize } from 'inflection2'; 14 | 15 | type NumberAs = 'float' | 'int'; 16 | 17 | interface TranspilerOptions { 18 | isProject: boolean; 19 | numberAs: NumberAs; 20 | } 21 | const { 22 | TSCallSignatureDeclaration, 23 | BlockStatement, 24 | TSConstructSignatureDeclaration, 25 | TSParameterProperty, 26 | TSPropertySignature, 27 | TSIndexSignature, 28 | TSInterfaceHeritage, 29 | TSFunctionType, 30 | TSMethodSignature, 31 | TSVoidKeyword, 32 | TSNeverKeyword, 33 | TSTypePredicate, 34 | TSAnyKeyword, 35 | MethodDefinition, 36 | ImportDeclaration, 37 | ForInStatement, 38 | ForOfStatement, 39 | TSUnknownKeyword, 40 | ArrayExpression, 41 | ObjectExpression, 42 | AssignmentPattern, 43 | ObjectPattern, 44 | TSTypeQuery, 45 | } = AST_NODE_TYPES; 46 | 47 | function isConstructor(node: any): boolean { 48 | return node.type === MethodDefinition && node.kind === 'constructor'; 49 | } 50 | 51 | /** 52 | * function interface 53 | * @param node 54 | */ 55 | function isFunctionInterface(node: any): boolean { 56 | const body = node.body.body; 57 | if (!body) { 58 | return false; 59 | } 60 | if (body.length === 0) { 61 | return false; 62 | } 63 | const typ = body[0].type; 64 | const isFunctionSignature = 65 | body.length === 1 && 66 | (typ === TSCallSignatureDeclaration || typ === TSConstructSignatureDeclaration); 67 | return isFunctionSignature; 68 | } 69 | 70 | function isParamProp(node: any): boolean { 71 | return node.type === TSParameterProperty; 72 | } 73 | 74 | function notBreak(node: any): boolean { 75 | return node.type !== AST_NODE_TYPES.BreakStatement; 76 | } 77 | 78 | interface FuncMeta { 79 | isGenerator: boolean; 80 | isAsync: boolean; 81 | isExpression: boolean; 82 | isGeneric: boolean; 83 | } 84 | 85 | function getFunctionMeta(node: any): FuncMeta { 86 | return { 87 | isGenerator: node.generator, 88 | isAsync: node.async, 89 | isExpression: node.expression, 90 | isGeneric: node.typeParameters, 91 | }; 92 | } 93 | 94 | function convertIdentName(name: string): string { 95 | if (!name) { 96 | return ''; 97 | } 98 | let result = ''; 99 | if (name.startsWith('_')) { 100 | result = name.substring(1); 101 | } else if (name.startsWith('$')) { 102 | result = name.substring(1); 103 | } else if (name === 'length') { 104 | result = 'len'; 105 | } else if (name === 'Error') { 106 | result = 'Exception'; 107 | } else if (/([_[a-z0-9]]+)/.test(name)) { 108 | result = camelize(name, true); 109 | } else { 110 | result = name; 111 | } 112 | if (reserved.includes(name)) { 113 | result = '`' + name + '`'; 114 | } 115 | return result; 116 | } 117 | 118 | function convertTypeName(name: string): string { 119 | let result = ''; 120 | if (name === 'Promise') { 121 | result = 'Future'; 122 | } else if (name === 'undefined') { 123 | result = 'nil'; 124 | } else if (name === 'Error') { 125 | result = 'Exception'; 126 | } else { 127 | result = name; 128 | } 129 | return result; 130 | } 131 | 132 | class Transpiler { 133 | public isD = false; 134 | public modules = new Set(); 135 | public helpers = new Set(); 136 | public logger: Subject; 137 | public lastNode: any; 138 | protected pathWithoutExt: string; 139 | constructor( 140 | protected ast: TSESTree.Program, 141 | protected writer: IWriteStream, 142 | protected transpilerOptions: TranspilerOptions, 143 | public symbols: { [index: string]: Sym[] } = {} 144 | ) { 145 | const filePath = writer.path; 146 | const ext = path.extname(filePath); 147 | const dir = path.dirname(filePath); 148 | const basename = path.basename(filePath, ext); 149 | const changed = path.join(dir, basename); 150 | this.pathWithoutExt = changed; 151 | this.logger = new Subject(); 152 | } 153 | 154 | log(...args: any) { 155 | this.logger.next(args); 156 | } 157 | 158 | typeof2type(typ: string): string { 159 | if (typ === 'number') { 160 | return this.transpilerOptions.numberAs; 161 | } else { 162 | return typ; 163 | } 164 | } 165 | 166 | transCommonMemberExpression(obj: string, mem: string, args: any[] = [], isCall = true): string { 167 | let result = ''; 168 | let func = ''; 169 | if (mem === 'push') { 170 | func = `${obj}.add`; 171 | } else if (mem === 'length') { 172 | func = `${obj}.len`; 173 | } else if (obj === 'fs' && mem === 'readFileSync') { 174 | func = `readFile`; 175 | this.modules.add('os'); 176 | } else if (obj === 'path' && mem === 'join') { 177 | this.modules.add('os'); 178 | return `${args.map((x: string) => x.replace('+', '&')).join(' / ')}`; 179 | } else if (mem === 'some') { 180 | this.modules.add('sequtils'); 181 | func = `${obj}.any`; 182 | } else if (mem === 'sort') { 183 | this.modules.add('algorithm'); 184 | func = `${obj}.sorted`; 185 | } else if (mem === 'charCodeAt') { 186 | // @FIXME The charCodeAt() method returns an integer between 0 and 65535 representing the UTF-16 code unit at the given index. 187 | // charAt() method returns a new string consisting of the single UTF-16 code unit located at the specified offset into the string. 188 | func = `ord(${obj}[${args[0]}])`; 189 | return func; 190 | } else if (mem === 'toLowerCase') { 191 | this.modules.add('unicode'); 192 | func = `${obj}.toLower`; 193 | } else if (mem === 'toUpperCase') { 194 | this.modules.add('unicode'); 195 | func = `${obj}.toUpper`; 196 | } else if (mem === 'substr') { 197 | this.modules.add('unicode'); 198 | func = `${obj}.runeSubStr`; 199 | } else if (mem === 'substring') { 200 | // @FIXME maybe negtive 201 | // && args[1] > 0 202 | if (args.length === 2) { 203 | const arg = `${args[0]},${args[1]} - ${args[0]}`; 204 | return `${obj}.runeSubStr(${arg})`; 205 | } 206 | func = `${obj}.runeSubStr`; 207 | } else if (obj === 'String' && mem === 'fromCharCode') { 208 | return `"\\u${args[0]}"`; 209 | } // indexOf strutils.find 210 | else { 211 | func = `${obj}.${mem}`; 212 | } 213 | if (isCall) { 214 | result = `${func}(${args.join(',')})`; 215 | } else { 216 | result = `${func}`; 217 | } 218 | 219 | return result; 220 | } 221 | 222 | getComment(node: any, indentLevel = 1): string { 223 | const originComent = this.getOriginalComment(node); 224 | const comment = originComent?.trim()?.replace(/^([#\s\*]*)*/gm, '') || ''; 225 | if (comment.length > 0) { 226 | const end = originComent?.includes('\n') ? '\n' : ''; 227 | const value = '## ' + comment.split('\n').join('\n## ') + end; 228 | return getLine(value, indentLevel); 229 | } else { 230 | return ''; 231 | } 232 | } 233 | 234 | getProcMeta(node: any): any[] { 235 | const { 236 | // isGenerator, 237 | isAsync, 238 | // isExpression, 239 | isGeneric, 240 | } = getFunctionMeta(node); 241 | const name = node.id?.name; 242 | let generics: string[] = []; 243 | if (isGeneric) { 244 | const gen = node.typeParameters.params.map((x: any) => x.name.name); 245 | generics = gen; 246 | } 247 | let skipIndex = -1; 248 | 249 | if (this.isD) { 250 | skipIndex = node.params.findIndex((x: any) => { 251 | if (x.optional) { 252 | return true; 253 | } 254 | const isAny = x.typeAnnotation.typeAnnotation.type === TSAnyKeyword; 255 | if (isAny) { 256 | return true; 257 | } 258 | const isRest = x.type === AST_NODE_TYPES.RestElement; 259 | if (isRest) { 260 | return true; 261 | } 262 | return this.tsType2nimType(x.typeAnnotation.typeAnnotation).includes('any'); 263 | }); 264 | } 265 | 266 | const params = skipIndex !== -1 ? skip(node.params, skipIndex) : node.params; 267 | // mutate the param if it is unknow type 268 | const availableT = ['T', 'S', 'U', 'V']; 269 | const used: Set = new Set(); 270 | for (const p of params) { 271 | if (p.typeAnnotation?.typeAnnotation.type === TSUnknownKeyword) { 272 | p.typeAnnotation.typeAnnotation.type = AST_NODE_TYPES.Identifier; 273 | let key = (p.name as string).charAt(0).toUpperCase(); 274 | if (!used.has(key)) { 275 | const index = availableT.indexOf(key); 276 | if (-1 !== index) { 277 | availableT.splice(index, 1); 278 | used.add(key); 279 | } 280 | } else { 281 | if (availableT.length > 0) { 282 | key = (availableT.shift() as unknown) as string; 283 | used.add(key); 284 | } 285 | } 286 | generics.push(key); 287 | p.typeAnnotation.typeAnnotation.name = key; 288 | } 289 | } 290 | let hasName = 0; 291 | if (name) { 292 | hasName = 1; 293 | } 294 | const isTSMethodSignature = node.type === TSMethodSignature; 295 | const pragmas = this.isD && Boolean(hasName + Number(isTSMethodSignature)) ? ['importcpp'] : []; 296 | if (isAsync) { 297 | pragmas.push('async'); 298 | if (!this.isD) { 299 | this.modules.add('asyncdispatch'); 300 | } else { 301 | this.modules.add('asyncjs'); 302 | } 303 | } 304 | if (this.isD && skipIndex !== -1) { 305 | pragmas.push('varargs'); 306 | } 307 | return [generics, params, pragmas]; 308 | } 309 | 310 | handleFunction( 311 | node: any, 312 | pname: string, 313 | isExport = true, 314 | self: any = null, 315 | isStatic = false, 316 | indentLevel = 0 317 | ): string { 318 | const name = node?.id?.name || pname; 319 | const returnTypeNode = node.returnType; 320 | let returnType; 321 | const isVoid = returnTypeNode?.typeAnnotation.type === AST_NODE_TYPES.TSVoidKeyword; 322 | const isNever = returnTypeNode?.typeAnnotation.type === AST_NODE_TYPES.TSNeverKeyword; 323 | let noReturnTypeNode = isVoid || isNever; 324 | 325 | if (returnTypeNode?.typeAnnotation) { 326 | returnType = this.tsType2nimType(returnTypeNode.typeAnnotation); 327 | } else if (pname === `new${self}`) { 328 | noReturnTypeNode = false; 329 | returnType = self; 330 | } /*else if(returnTypeNode.type === AST_NODE_TYPES.TSThisType) { 331 | returnType = self 332 | }*/ else { 333 | noReturnTypeNode = false; 334 | returnType = 'auto'; 335 | } 336 | 337 | const [generics, params, pragmas] = this.getProcMeta(node); 338 | const body = node.body; 339 | const nimpa = params?.map(this.mapParam, this) || []; 340 | 341 | if (self && pname !== `new${self}`) { 342 | if (isStatic) { 343 | nimpa.unshift(`self:typedesc[${self}]`); 344 | } else { 345 | nimpa.unshift(`self:${self}`); 346 | } 347 | } 348 | 349 | const exportMark = isExport && !name?.startsWith('_') ? '*' : ''; 350 | const pragmaStr = pragmas.length > 0 ? `{.${pragmas.join(',')}.}` : undefined; 351 | const genericsStr = generics.length > 0 ? `[${generics.join(',')}]` : ''; 352 | const returnTypeStr = !noReturnTypeNode ? ': ' + returnType : ''; 353 | const paramStr = nimpa?.join(', '); 354 | let result = ''; 355 | 356 | const isSignature = -1 !== node.type.indexOf('Signature'); 357 | const hasBody = typeof body !== 'undefined' && body !== null; 358 | const emptyBody = hasBody && body.body && body.body.length === 0; 359 | const rp = [returnTypeStr, pragmaStr].filter(x => x && x !== ' ').join(' '); 360 | result += getLine( 361 | `proc ${convertIdentName(name)}${exportMark}${genericsStr}(${paramStr})${rp}${ 362 | isSignature ? '' : hasBody ? (emptyBody ? ' = discard' : ' = ') : ' = discard' 363 | }`, 364 | indentLevel 365 | ); 366 | result += this.getComment(node, indentLevel + 1); 367 | // @TODO remove top level return variable 368 | let current: any; 369 | if (self && params) { 370 | params.filter(isParamProp).forEach((x: any) => { 371 | const id = x.parameter.name; 372 | result += getLine(`self.${id} = ${id}`, indentLevel + 1); 373 | }); 374 | } 375 | if (hasBody) { 376 | while ((current = body.body?.shift())) { 377 | result += this.tsType2nimType(current, indentLevel + 1); 378 | } 379 | } 380 | result += '\n'; 381 | return result; 382 | } 383 | 384 | handleDeclaration(declaration: any, isExport = true, indentLevel = 0): string { 385 | let result = ''; 386 | if (!declaration) { 387 | return ''; 388 | } 389 | if ( 390 | declaration.type === AST_NODE_TYPES.TSTypeAliasDeclaration && 391 | declaration.typeAnnotation.type === TSTypeQuery 392 | ) { 393 | // ignore kind like: type URI = typeof URI 394 | return ''; 395 | } 396 | if ( 397 | declaration.type === AST_NODE_TYPES.TSTypeAliasDeclaration || 398 | declaration.type === AST_NODE_TYPES.TSInterfaceDeclaration 399 | ) { 400 | const typeName = declaration.id.name; 401 | 402 | let members: string[] = []; 403 | if (declaration.typeAnnotation?.type === AST_NODE_TYPES.TSTypeLiteral) { 404 | members = declaration.typeAnnotation.members.map(this.mapDecl.bind(this, isExport)); 405 | } else if (declaration.type === AST_NODE_TYPES.TSInterfaceDeclaration) { 406 | members = declaration.body.body.map(this.mapDecl.bind(this, isExport)); 407 | } 408 | result += `type ${typeName}* = ref object of RootObj\n`; 409 | 410 | result += members.map(indented(1)).join('\n'); 411 | result += '\n\n'; 412 | } else if (declaration.type === AST_NODE_TYPES.VariableDeclaration) { 413 | if (declaration.declarations) { 414 | outer: for (const m of declaration.declarations) { 415 | if (m.init) { 416 | switch (m.init.type) { 417 | case AST_NODE_TYPES.ArrowFunctionExpression: 418 | case AST_NODE_TYPES.FunctionExpression: 419 | { 420 | if (indentLevel === 0) { 421 | result = this.handleFunction(m.init, m.id.name, isExport, indentLevel); 422 | } else { 423 | result = this.convertVariableDeclaration(declaration, indentLevel); 424 | break outer; 425 | } 426 | } 427 | break; 428 | case AST_NODE_TYPES.ConditionalExpression: 429 | result = getLine(this.convertVariableDeclaration(declaration, indentLevel)); 430 | break outer; 431 | case ObjectExpression: 432 | case ArrayExpression: 433 | const isPlainEmptyObj = 434 | m.init.type === ObjectExpression && m.init.properties.length === 0; 435 | const isPlainEmptyArr = 436 | m.init.type === ArrayExpression && m.init.elements.length === 0; 437 | let typ = ''; 438 | if (m.id.typeAnnotation) { 439 | typ = this.tsType2nimType(m.id.typeAnnotation.typeAnnotation); 440 | } 441 | const props = m.init.properties || m.init.elements; 442 | const newName = typ.charAt(0).toUpperCase() + typ.slice(1); 443 | 444 | if (isPlainEmptyObj) { 445 | result += getLine(`var ${this.tsType2nimType(m.id)} = new${newName}()`); 446 | } else if (isPlainEmptyArr) { 447 | result += getLine(`var ${this.tsType2nimType(m.id)} = @[]`, indentLevel); 448 | } else if (ArrayExpression) { 449 | const elements = m.init.elements.map((e: any) => e.raw).join(','); 450 | result += getLine( 451 | `var ${this.tsType2nimType(m.id)} = @[${elements}]`, 452 | indentLevel 453 | ); 454 | } else { 455 | result += `var ${this.tsType2nimType(m.id)} = new${newName}(${props 456 | .map(this.tsType2nimType, this) 457 | .join(',')})`; 458 | if (m.init.type === ObjectExpression) { 459 | this.log('tsType2nimType:declaration.declarations.map:else', m.properties); 460 | } else { 461 | this.log('tsType2nimType:declaration.declarations.map:else', m); 462 | } 463 | } 464 | break; 465 | 466 | default: 467 | result += this.getComment(m, indentLevel); 468 | result += getLine(this.convertVariableDeclaration(declaration, indentLevel)); 469 | break outer; 470 | // this.log('handleDeclaration:VariableDeclaration:default', m); 471 | // break; 472 | } 473 | } else { 474 | result += this.getComment(m, indentLevel); 475 | result += getLine(this.convertVariableDeclaration(declaration, indentLevel)); 476 | } 477 | } 478 | } 479 | } else if (declaration.type === AST_NODE_TYPES.FunctionDeclaration) { 480 | result += this.handleFunction(declaration, '', false, indentLevel); 481 | } else if (declaration.type === AST_NODE_TYPES.ClassDeclaration) { 482 | result = this.tsType2nimType(declaration); 483 | } else if (declaration.type === AST_NODE_TYPES.TSDeclareFunction) { 484 | result = this.tsType2nimType(declaration); 485 | } else if (declaration.type === AST_NODE_TYPES.TSModuleDeclaration) { 486 | declaration.body.body.forEach((node: any) => { 487 | result += this.tsType2nimType(node, 0); 488 | }); 489 | } 490 | 491 | return result; 492 | } 493 | 494 | convertConditionalExpression(expression: any): string { 495 | let result = ''; 496 | result = `if ${this.tsType2nimType(expression.test)}: ${this.tsType2nimType( 497 | expression.consequent 498 | )} else: ${this.tsType2nimType(expression.alternate)}`; 499 | return result; 500 | } 501 | 502 | convertCallExpression(node: any): string { 503 | let result = ''; 504 | const theNode = node.expression || node.init || node.argument || node; 505 | 506 | switch (theNode.callee.type) { 507 | case AST_NODE_TYPES.MemberExpression: 508 | { 509 | let mem: string = ''; 510 | 511 | switch (theNode.callee.property.type) { 512 | case AST_NODE_TYPES.CallExpression: 513 | mem = this.convertCallExpression(theNode.callee.property); 514 | break; 515 | default: 516 | mem = this.tsType2nimType(theNode.callee.property); 517 | break; 518 | } 519 | let obj: string = ''; 520 | switch (theNode.callee.object.type) { 521 | case AST_NODE_TYPES.CallExpression: 522 | obj = this.convertCallExpression(theNode.callee.object); 523 | break; 524 | default: 525 | obj = this.tsType2nimType(theNode.callee.object); 526 | break; 527 | } 528 | if (mem === 'indexOf') { 529 | if (this.isString(theNode.callee.object)) { 530 | this.modules.add('strutils'); 531 | mem = 'find'; 532 | } else if (this.isArray(theNode.callee.object)) { 533 | this.helpers.add(seqFind); 534 | mem = 'find'; 535 | } 536 | } 537 | const args = theNode.arguments.map(this.tsType2nimType, this); 538 | result = this.transCommonMemberExpression(obj, mem, args); 539 | } 540 | break; 541 | 542 | default: 543 | const func = this.tsType2nimType(theNode.callee); 544 | const args = theNode.arguments.map(this.tsType2nimType, this); 545 | result = `${func}(${args.join(',')})`; 546 | this.log('convertCallExpression:default', node); 547 | break; 548 | } 549 | return result; 550 | } 551 | 552 | isNil(node: any) { 553 | const name = node.id?.name || node.name; 554 | const end = node.range[1]; 555 | const sym = this.symbols[this.pathWithoutExt]?.find( 556 | (x: any) => x.name === name && x.loc.end <= end && x.loc.pos <= node.range[0] 557 | ); 558 | if (sym) { 559 | return sym.type === 'null' || sym.type === 'undefined'; 560 | } 561 | return ( 562 | node.type === AST_NODE_TYPES.Literal && (node.raw === 'null' || node.raw === 'undefined') 563 | ); 564 | } 565 | 566 | isObj(node: any) { 567 | const name = node.id?.name || node.name; 568 | const end = node.range[1]; 569 | const sym = this.symbols[this.pathWithoutExt]?.find( 570 | (x: any) => x.name === name && x.loc.end <= end && x.loc.pos <= node.range[0] 571 | ); 572 | if (sym) { 573 | return !primitiveTypes.includes(sym.type); 574 | } 575 | return ( 576 | node.type === AST_NODE_TYPES.Literal && (node.raw === 'null' || node.raw === 'undefined') 577 | ); 578 | } 579 | 580 | isBoolean(node: any) { 581 | const name = node.id?.name || node.name; 582 | const value = node.init?.value || node.value; 583 | if (typeof value === 'boolean') { 584 | return true; 585 | } 586 | const end = node.range[1]; 587 | const sym = this.symbols[this.pathWithoutExt]?.find( 588 | (x: any) => x.name === name && x.loc.end <= end && x.loc.pos <= node.range[0] 589 | ); 590 | if (sym) { 591 | return sym.type === 'boolean'; 592 | } 593 | return false; 594 | } 595 | 596 | isNumber(node: any) { 597 | const name = node.id?.name || node.name; 598 | const value = node.init?.value || node.value; 599 | if (typeof value === 'number') { 600 | return true; 601 | } 602 | const end = node.range[1]; 603 | const sym = this.symbols[this.pathWithoutExt]?.find( 604 | (x: any) => x.name === name && x.loc.end <= end && x.loc.pos <= node.range[0] 605 | ); 606 | if (sym) { 607 | // @ts-ignore 608 | return sym.type === 'number' || !isNaN(sym.type); 609 | } 610 | return false; 611 | } 612 | 613 | isRegExp(node: any) { 614 | // const name = node.id?.name || node.name; 615 | const value = node.init?.value || node.value; 616 | if (value instanceof RegExp) { 617 | return true; 618 | } 619 | // const end = node.range[1]; 620 | // const sym = this.symbols[this.pathWithoutExt]?.find( 621 | // (x: any) => x.name === name && x.loc.end <= end && x.loc.pos <= node.range[0] 622 | // ); 623 | // console.log(sym) 624 | // if (sym) { 625 | // // @ts-ignore 626 | // return sym.type === 'number' ; 627 | // } 628 | return false; 629 | } 630 | 631 | isString(node: any) { 632 | const name = node.id?.name || node.name; 633 | const value = node.init?.value || node.value; 634 | if (typeof value === 'string') { 635 | return true; 636 | } 637 | const end = node.range[1]; 638 | const sym = this.symbols[this.pathWithoutExt]?.find( 639 | (x: any) => x.name === name && x.loc.end <= end && x.loc.pos <= node.range[0] 640 | ); 641 | if (sym) { 642 | return sym.type === 'string'; 643 | } 644 | return false; 645 | } 646 | 647 | isArray(node: any) { 648 | const name = node.id?.name || node.name; 649 | const value = node.init?.value || node.value; 650 | const end = node.range[1]; 651 | const sym = this.symbols[this.pathWithoutExt]?.find( 652 | (x: any) => x.name === name && x.loc.end <= end && x.loc.pos <= node.range[0] 653 | ); 654 | if (sym) { 655 | return sym.type.endsWith('[]'); 656 | } 657 | return Array.isArray(value); 658 | } 659 | 660 | convertBinaryExpression(expression: any): string { 661 | let result = ''; 662 | let op = ''; 663 | switch (expression.operator) { 664 | case '===': 665 | case '==': 666 | if (this.isNil(expression.right)) { 667 | return `isNil(${this.tsType2nimType(expression.left)})`; 668 | } 669 | op = '=='; 670 | break; 671 | case '!==': 672 | case '!=': 673 | if (this.isNil(expression.right)) { 674 | return `not isNil(${this.tsType2nimType(expression.left)})`; 675 | } 676 | op = '!='; 677 | break; 678 | case '+': 679 | const leftIsString = typeof expression.left.value === 'string'; 680 | const rightIsString = typeof expression.right.value === 'string'; 681 | const hasString = leftIsString || rightIsString; 682 | op = hasString ? '&' : '+'; 683 | break; 684 | case '<<': 685 | op = 'shl'; 686 | break; 687 | case '>>': 688 | op = 'shr'; 689 | break; 690 | case '%': 691 | op = 'mod'; 692 | break; 693 | case '^': 694 | op = 'xor'; 695 | break; 696 | case '&': 697 | op = 'and'; 698 | break; 699 | case '|': 700 | op = 'or'; 701 | break; 702 | case '>>>': { 703 | const left = this.tsType2nimType(expression.left); 704 | const right = this.tsType2nimType(expression.right); 705 | return `abs(${left} shr 1 shr ${right})`; 706 | } 707 | case '~': 708 | op = 'not'; 709 | break; 710 | default: 711 | op = expression.operator; 712 | } 713 | const left = this.tsType2nimType(expression.left); 714 | const right = this.tsType2nimType(expression.right); 715 | result = `${left} ${op} ${right}`; 716 | return result; 717 | } 718 | 719 | convertVariableDeclarator(node: any, indentLevel = 0): string { 720 | let result = ''; 721 | if (!node.init) { 722 | return node.id.name; 723 | } 724 | if (node.id.type === ObjectPattern) { 725 | node.id.properties.forEach((prop: any) => { 726 | const name = convertIdentName(prop.key.name); 727 | result += getLine(`${name} = ${this.tsType2nimType(node.init)}.${name}`, indentLevel); 728 | if (prop.value && prop.value.type === AssignmentPattern) { 729 | result += getLine(`if isNil(${name}):`, indentLevel); 730 | result += getIndented( 731 | `${name} = ${this.tsType2nimType(prop.value.right)}`, 732 | indentLevel + 1 733 | ); 734 | } 735 | }); 736 | return result; 737 | } 738 | result = this.tsType2nimType(node.init, indentLevel); 739 | return result; 740 | } 741 | 742 | convertNodeInTest(node: any): string { 743 | if (node.type === AST_NODE_TYPES.Identifier) { 744 | const prefix = ''; 745 | if (this.isArray(node)) { 746 | return `${prefix}${this.tsType2nimType(node)}.len > 0`; 747 | } else if (this.isString(node)) { 748 | return `${prefix}${this.tsType2nimType(node)}.len > 0`; 749 | } else if (this.isNumber(node)) { 750 | return `${prefix}${this.tsType2nimType(node)} == 0`; 751 | } else if (this.isObj(node)) { 752 | return `${prefix}isNil(${this.tsType2nimType(node)})`; 753 | } else { 754 | this.log('convertNodeInTest:Identifier:else', node); 755 | return `${prefix}${this.tsType2nimType(node)}`; 756 | } 757 | } else { 758 | return this.tsType2nimType(node); 759 | } 760 | } 761 | 762 | convertUnaryExpression(node: any) { 763 | // UnaryOperators ["-", "+", "!", "~", "typeof", "void", "delete"] 764 | let result = ''; 765 | let op = ''; 766 | switch (node.operator) { 767 | case '!': 768 | const prefix = 'not '; 769 | if (this.isArray(node.argument)) { 770 | return `${prefix}${this.tsType2nimType(node.argument)}.len > 0`; 771 | } else if (this.isString(node.argument)) { 772 | return `${prefix}${this.tsType2nimType(node.argument)}.len > 0`; 773 | } else if (this.isNumber(node.argument)) { 774 | return `${prefix}${this.tsType2nimType(node.argument)} == 0`; 775 | } else if (this.isObj(node.argument)) { 776 | return `${prefix}isNil(${this.tsType2nimType(node.argument)})`; 777 | } 778 | op = 'not'; 779 | break; 780 | case 'delete': 781 | return `${this.tsType2nimType(node.argument)} = nil`; 782 | default: 783 | op = node.operator; 784 | break; 785 | } 786 | result = `${op} ${this.tsType2nimType(node.argument)}`; 787 | return result; 788 | } 789 | 790 | mapVar(isDeclare: boolean, x: any): string { 791 | const hasTyp = typeof x.id.typeAnnotation !== 'undefined'; 792 | const hasInit = x.init; 793 | const name = convertIdentName(x.id.name); 794 | if (!name) { 795 | return this.convertVariableDeclarator(x); 796 | } 797 | let result = name; 798 | if (isDeclare) { 799 | result += '*'; 800 | result += ' {.importc, nodecl.}'; 801 | } 802 | if (hasTyp) { 803 | result += ':' + this.tsType2nimType(x.id.typeAnnotation.typeAnnotation); 804 | } 805 | 806 | if (hasInit) { 807 | result += ' = ' + this.convertVariableDeclarator(x); 808 | } 809 | 810 | return result; 811 | } 812 | convertVariableDeclaration(node: any, indentLevel = 0): string { 813 | let result = ''; 814 | const isDeclare = node.declare; 815 | const nimKind = node.kind === 'const' ? 'var' : 'var'; 816 | let constVars = []; 817 | if (!node.declarations) { 818 | return result; 819 | } 820 | if (node.kind === 'const') { 821 | constVars = node.declarations.filter( 822 | (x: any) => this.isNumber(x) || this.isRegExp(x) || this.isString(x) || this.isBoolean(x), 823 | this 824 | ); 825 | } 826 | if (constVars.length) { 827 | const consts: string = constVars.map(this.mapVar.bind(this, isDeclare)).join(','); 828 | const v = `const ${consts}`; 829 | result += getLine(v, indentLevel); 830 | } 831 | const vars = node.declarations.slice(constVars.length).map(this.mapVar.bind(this, isDeclare)); 832 | if (vars.length) { 833 | const value = `${nimKind} ${vars.join(',')}`; 834 | result += getIndented(value, indentLevel); 835 | } 836 | 837 | return result; 838 | } 839 | 840 | convertLogicalExpression(expression: any): string { 841 | let result = ''; 842 | let op = ''; 843 | switch (expression.operator) { 844 | case '&&': 845 | op = 'and'; 846 | break; 847 | case '||': 848 | op = 'or'; 849 | break; 850 | default: 851 | op = expression.operator; 852 | } 853 | result = `${this.tsType2nimType(expression.left)} ${op} ${this.tsType2nimType( 854 | expression.right 855 | )}`; 856 | return result; 857 | } 858 | 859 | tsType2nimType(node: any, indentLevel = 0): string { 860 | this.lastNode = node; 861 | let result: string = ''; 862 | const typ = node?.type; 863 | switch (typ) { 864 | case ImportDeclaration: 865 | const notName = node.source.value.includes('/'); 866 | if (notName) { 867 | const isAbs = path.isAbsolute(node.source.value); 868 | const imp = `import ${node.source.value}`; 869 | if (!isAbs) { 870 | result = getLine(imp, 0); 871 | } 872 | } 873 | 874 | break; 875 | case AST_NODE_TYPES.TSInterfaceDeclaration: 876 | { 877 | // @TODO real isExport 878 | const ex = node.extends; 879 | const hasSuper = ex ? true : false; 880 | const className = this.tsType2nimType(node.id); 881 | const body = node.body.body; 882 | const isFunctionSignature = isFunctionInterface(node); 883 | 884 | if (isFunctionSignature) { 885 | const node = body[0]; 886 | const procSignature = this.handleFunction(node, '', false, null, false, indentLevel); 887 | return `type ${className}* = ${procSignature}\n`; 888 | } 889 | const ctrIndex = body.findIndex((x: any) => x.type === TSConstructSignatureDeclaration); 890 | const hasCtr = -1 !== ctrIndex; 891 | 892 | if (hasSuper) { 893 | const supers = ex.map(this.tsType2nimType, this).join(','); 894 | result += `type ${className}* = ref object of ${supers}\n`; 895 | } else { 896 | result += `type ${className}* = ref object of RootObj\n`; 897 | } 898 | let ctrl; 899 | if (hasCtr) { 900 | ctrl = body[ctrIndex]; 901 | body.splice(ctrIndex, 1); 902 | const ctrlProps = ctrl.params.filter((x: any) => x.type === TSParameterProperty); 903 | 904 | const members = ctrlProps.map(this.mapMember, this); 905 | if (members.length > 0) { 906 | result += members.map(indented(1)).join('\n') + '\n'; 907 | } 908 | } 909 | const propsIndexes = body.reduce((p: any, cur: any, i: number) => { 910 | if (cur.type === TSPropertySignature) { 911 | return [...p, i]; 912 | } 913 | return p; 914 | }, []); 915 | if (propsIndexes.length > 0) { 916 | const props = body.filter((x: any, i: number) => propsIndexes.includes(i)); 917 | propsIndexes.reverse().forEach((v: number, i: number) => { 918 | body.splice(v, 1); 919 | }); 920 | // @TODO handle TSTypeQuery 921 | const propsStrs = props.map(this.mapMember, this); 922 | if (propsStrs.length > 0) { 923 | result += propsStrs.map(indented(1)).join('\n') + '\n'; 924 | } 925 | } 926 | 927 | result += '\n\n'; 928 | 929 | // write constructor 930 | if (hasCtr) { 931 | result += this.handleFunction( 932 | ctrl, 933 | `new${className}`, 934 | true, 935 | className, 936 | false, 937 | indentLevel 938 | ); 939 | } 940 | // write methods 941 | 942 | body 943 | .filter((x: any) => x.type !== TSIndexSignature) 944 | .forEach((v: any) => { 945 | result += this.handleFunction(v, v.key?.name, true, className, v.static, indentLevel); 946 | }); 947 | } 948 | break; 949 | case TSInterfaceHeritage: 950 | result = this.tsType2nimType(node.expression); 951 | break; 952 | case TSIndexSignature: 953 | { 954 | // const params = node.parameters; 955 | } 956 | break; 957 | case AST_NODE_TYPES.ExportNamedDeclaration: 958 | result = this.handleDeclaration(node.declaration, true, indentLevel); 959 | break; 960 | case AST_NODE_TYPES.VariableDeclaration: 961 | result = this.handleDeclaration(node, false, indentLevel); 962 | break; 963 | case AST_NODE_TYPES.FunctionDeclaration: 964 | result = this.handleDeclaration(node, false, indentLevel); 965 | break; 966 | case AST_NODE_TYPES.IfStatement: 967 | { 968 | const test = this.convertNodeInTest(node.test); 969 | result = getLine(`if ${test}:`, indentLevel); 970 | 971 | if (node.consequent) { 972 | if (node.consequent.type === BlockStatement) { 973 | if (node.consequent.body && node.consequent.body.length > 0) { 974 | node.consequent.body.forEach((x: any, index: number) => { 975 | result += getIndented(this.tsType2nimType(x), indentLevel + 1); 976 | }); 977 | } else { 978 | result += getIndented('discard\n', indentLevel + 1); 979 | } 980 | } else { 981 | result += getIndented(this.tsType2nimType(node.consequent), indentLevel + 1); 982 | } 983 | } 984 | // else if , else 985 | let alternate = node.alternate; 986 | 987 | while (alternate) { 988 | const test = this.tsType2nimType(alternate.test); 989 | result += getLine(test ? `elif ${test}:` : 'else:', indentLevel); 990 | if (alternate.body && alternate.body.length > 0) { 991 | alternate.body.forEach((x: any, index: number) => { 992 | result += this.ts2nimIndented(indentLevel + 1)(x); 993 | }); 994 | } else if (alternate.consequent?.body && alternate.consequent?.body.length > 0) { 995 | alternate.consequent.body.forEach((x: any, index: number) => { 996 | result += this.ts2nimIndented(indentLevel + 1)(x); 997 | }); 998 | } else { 999 | result += getIndented('discard\n', indentLevel + 1); 1000 | } 1001 | alternate = alternate.alternate; 1002 | } 1003 | } 1004 | break; 1005 | 1006 | case AST_NODE_TYPES.ExpressionStatement: 1007 | { 1008 | if (node.directive === 'use strict') { 1009 | return ''; 1010 | } 1011 | result += this.getComment(node, indentLevel); 1012 | switch (node.expression.type) { 1013 | case AST_NODE_TYPES.CallExpression: 1014 | { 1015 | result += getLine(this.convertCallExpression(node), indentLevel); 1016 | } 1017 | break; 1018 | case AST_NODE_TYPES.AssignmentExpression: 1019 | { 1020 | const expression = this.tsType2nimType(node.expression); 1021 | result += getLine(expression, indentLevel); 1022 | } 1023 | break; 1024 | default: 1025 | { 1026 | result += getLine(this.tsType2nimType(node.expression), indentLevel); 1027 | } 1028 | this.log('tsType2nimType:ExpressionStatement:default', node.expression); 1029 | break; 1030 | } 1031 | } 1032 | break; 1033 | case AST_NODE_TYPES.TaggedTemplateExpression: 1034 | result = `${this.tsType2nimType(node.tag)}(${this.tsType2nimType(node.quasi)})`; 1035 | break; 1036 | case AST_NODE_TYPES.TemplateLiteral: 1037 | const expressions = node.expressions; 1038 | const hasLineBreak = node.quasis?.some((x: any) => { 1039 | return x.loc.start.line !== x.loc.end.line; 1040 | // return x.raw?.includes('\n') 1041 | }); 1042 | if (expressions.length > 0) { 1043 | this.modules.add('strformat'); 1044 | if (hasLineBreak) { 1045 | result = 'fmt"""'; 1046 | } else { 1047 | result = 'fmt"'; 1048 | } 1049 | 1050 | let currentQ; 1051 | while ((currentQ = node.quasis?.shift())) { 1052 | if (currentQ?.value?.cooked) { 1053 | result += addslashes(currentQ.value.cooked.replace(/\{/g, '{{').replace(/\}/g, '}}')); 1054 | } else { 1055 | result += addslashes(`{${this.tsType2nimType(expressions.shift(), indentLevel)}}`); 1056 | } 1057 | } 1058 | } else { 1059 | if (hasLineBreak) { 1060 | result = '"""'; 1061 | } else { 1062 | result = '"'; 1063 | } 1064 | let currentQ, line; 1065 | while ((currentQ = node.quasis?.shift())) { 1066 | line = currentQ.value.cooked.replace(/\{/g, '{{').replace(/\}/g, '}}'); 1067 | if (!hasLineBreak) { 1068 | result += addslashes(line); 1069 | } else { 1070 | result += line; 1071 | } 1072 | } 1073 | } 1074 | if (hasLineBreak) { 1075 | result += '"""'; 1076 | } else { 1077 | result += '"'; 1078 | } 1079 | 1080 | break; 1081 | case ForOfStatement: 1082 | case ForInStatement: 1083 | { 1084 | // const leftKind = node.left.kind; // eg. 'const' 1085 | const rightName = this.tsType2nimType(node.right); 1086 | const isForIn = node.type === ForInStatement; 1087 | const isForOf = node.type === ForOfStatement; 1088 | const mutator = isForOf ? '.mitems' : ''; 1089 | const decl = node.left.declarations; 1090 | const forVar = 1091 | isForIn && decl.length === 1 1092 | ? `${this.convertVariableDeclarator(decl[0])},_` 1093 | : decl.map(this.convertVariableDeclarator); 1094 | const forInStatement = `for ${forVar} in ${rightName}${mutator}:`; 1095 | result += getLine(forInStatement, indentLevel); 1096 | if (node.body.type === BlockStatement) { 1097 | node.body.body.forEach((x: any) => { 1098 | result += this.tsType2nimType(x, indentLevel + 1); 1099 | }); 1100 | } else { 1101 | // if(node.body.type === AST_NODE_TYPES.ExpressionStatement){ 1102 | result += this.tsType2nimType(node.body, indentLevel + 1); 1103 | } 1104 | } 1105 | break; 1106 | case AST_NODE_TYPES.Property: 1107 | result = `${this.tsType2nimType(node.key)}= ${this.tsType2nimType(node.value)}`; 1108 | break; 1109 | case AST_NODE_TYPES.ReturnStatement: 1110 | if (!node.argument) { 1111 | result = getLine('return void', indentLevel); 1112 | break; 1113 | } 1114 | switch (node?.argument?.type) { 1115 | case AST_NODE_TYPES.BinaryExpression: 1116 | result = getLine(this.convertBinaryExpression(node.argument), indentLevel); 1117 | break; 1118 | case AST_NODE_TYPES.CallExpression: 1119 | result = getLine(this.convertCallExpression(node), indentLevel); 1120 | break; 1121 | default: 1122 | result = getLine('return ' + this.tsType2nimType(node.argument), indentLevel); 1123 | this.log('this.tsType2nimType:ReturnStatement', node); 1124 | break; 1125 | } 1126 | break; 1127 | case AST_NODE_TYPES.ForStatement: 1128 | result += getLine(this.convertVariableDeclaration(node.init, indentLevel)); 1129 | const test = `while ${this.convertBinaryExpression(node.test)}:`; 1130 | result += getLine(test, indentLevel); 1131 | node.body?.body?.forEach((x: any, index: number) => { 1132 | result += getIndented(this.tsType2nimType(x), indentLevel + 1); 1133 | }); 1134 | break; 1135 | case AST_NODE_TYPES.DoWhileStatement: 1136 | { 1137 | this.helpers.add(doWhile); 1138 | const test = this.tsType2nimType(node.test); 1139 | result += getLine(`doWhile ${test}:`, indentLevel); 1140 | node.body.body.forEach((x: any) => { 1141 | result += this.tsType2nimType(x, indentLevel + 1); 1142 | }); 1143 | } 1144 | break; 1145 | case AST_NODE_TYPES.ThrowStatement: 1146 | const typedesc = this.tsType2nimType(node.argument.callee); 1147 | // const isClass = typedesc.charCodeAt(0) <= 90 1148 | const isClass = node.argument.type === AST_NODE_TYPES.NewExpression ? true : false; 1149 | let argument; 1150 | if (isClass) { 1151 | const args = node.argument.arguments.map(this.tsType2nimType, this); 1152 | argument = `newException(${typedesc},${args.join(',')})`; 1153 | } else { 1154 | argument = this.tsType2nimType(node.argument); 1155 | } 1156 | result = getLine(`raise ` + argument, indentLevel); 1157 | break; 1158 | case AST_NODE_TYPES.Identifier: 1159 | result = convertIdentName(node.name); 1160 | if (node.typeAnnotation && node.typeAnnotation.typeAnnotation) { 1161 | result += ':'; 1162 | result += this.tsType2nimType(node.typeAnnotation.typeAnnotation); 1163 | } 1164 | 1165 | break; 1166 | case AST_NODE_TYPES.RestElement: 1167 | { 1168 | const name = node.argument.name; 1169 | const primaryTyp = node.typeAnnotation.typeAnnotation.typeName?.name; 1170 | const typ = this.tsType2nimType(node.typeAnnotation.typeAnnotation); 1171 | 1172 | result = `${name}:${typ}`; 1173 | if (primaryTyp === 'Array') { 1174 | result = result.replace('Array', 'openArray'); 1175 | } 1176 | } 1177 | break; 1178 | case TSAnyKeyword: 1179 | result = 'any'; 1180 | break; 1181 | case AST_NODE_TYPES.TSTypeReference: 1182 | { 1183 | if (node.typeName.type === AST_NODE_TYPES.Identifier) { 1184 | const name = convertTypeName(node.typeName.name); 1185 | if (node.typeParameters) { 1186 | const typ = node.typeParameters.params.map(this.tsType2nimType, this).join(','); 1187 | result = `${name}[${typ}]`; 1188 | } else { 1189 | this.log('this.tsType2nimType:TSTypeReference:Identifier:else', node); 1190 | result = `${name}`; 1191 | } 1192 | } else if (node.typeName.type === AST_NODE_TYPES.TSQualifiedName) { 1193 | if (this.isD) { 1194 | result = `${this.tsType2nimType(node.typeName.left)}.${this.tsType2nimType( 1195 | node.typeName.right 1196 | )}`; 1197 | } else { 1198 | this.log('this.tsType2nimType:TSTypeReference:TSQualifiedName:else', node); 1199 | } 1200 | } 1201 | } 1202 | break; 1203 | case AST_NODE_TYPES.Literal: 1204 | if (typeof node.value === 'string') { 1205 | result = JSON.stringify(node.value); 1206 | } else if (node.value === null) { 1207 | result = 'nil'; 1208 | } else if (typeof node.value === 'undefined') { 1209 | result = 'nil'; 1210 | } else if (typeof node.regex !== 'undefined') { 1211 | const pattern = node.regex.pattern; 1212 | const flags = node.regex.flags; 1213 | this.modules.add('regex'); 1214 | result = `re"${pattern}"`; 1215 | } else { 1216 | this.log('this.tsType2nimType:Literal:else', node); 1217 | result = `${node.value}`; 1218 | } 1219 | break; 1220 | case AST_NODE_TYPES.MemberExpression: 1221 | if (node.computed) { 1222 | result = `${this.tsType2nimType(node.object)}[${this.tsType2nimType(node.property)}]`; 1223 | } else { 1224 | result = `${this.tsType2nimType(node.object)}.${this.tsType2nimType(node.property)}`; 1225 | } 1226 | break; 1227 | case AST_NODE_TYPES.TSParenthesizedType: 1228 | result = this.tsType2nimType(node.typeAnnotation); 1229 | break; 1230 | case AST_NODE_TYPES.TSNullKeyword: 1231 | // in some case handle this independently 1232 | result = 'nil'; 1233 | break; 1234 | case AST_NODE_TYPES.TSUnionType: 1235 | // TypeA || null,TypeA || undefined 1236 | const types = node.types.map((x: any) => x.type); 1237 | if (arraysEqual(types, ['TSTypeReference', 'TSNullKeyword'])) { 1238 | result = `${node.types[0].typeName.name}`; 1239 | } else if (arraysEqual(types, ['TSTypeReference', 'TSUndefinedKeyword'])) { 1240 | result = `${node.types[0].typeName.name}`; 1241 | } else { 1242 | result = `${node.types.map(this.tsType2nimType, this).join('|')}`; 1243 | } 1244 | break; 1245 | case AST_NODE_TYPES.TSNumberKeyword: 1246 | result = this.transpilerOptions.numberAs; 1247 | break; 1248 | case AST_NODE_TYPES.TSStringKeyword: 1249 | if (this.isD) { 1250 | result = 'cstring'; 1251 | } else { 1252 | result = 'string'; 1253 | } 1254 | 1255 | break; 1256 | case AST_NODE_TYPES.TSBooleanKeyword: 1257 | result = 'bool'; 1258 | break; 1259 | case AST_NODE_TYPES.TSArrayType: 1260 | result = `seq[${this.tsType2nimType(node.elementType)}]`; 1261 | break; 1262 | case AST_NODE_TYPES.AwaitExpression: 1263 | if (node.argument.type === AST_NODE_TYPES.CallExpression) { 1264 | result = `await ${this.convertCallExpression(node)}`; 1265 | } 1266 | break; 1267 | case AST_NODE_TYPES.AssignmentPattern: 1268 | { 1269 | const { left, right } = node; 1270 | let name = convertIdentName(left.name); 1271 | let typ; 1272 | const leftIsObjectPattern = left.type === ObjectPattern; 1273 | let props: string[] = []; 1274 | if (leftIsObjectPattern) { 1275 | props = props.concat( 1276 | left.properties.map((prop: any) => { 1277 | const name = convertIdentName(prop.key.name); 1278 | if (prop.value && prop.value.type === AssignmentPattern) { 1279 | return `${name} = ${this.tsType2nimType(prop.value.right)}`; 1280 | } else { 1281 | return ''; 1282 | } 1283 | }) 1284 | ); 1285 | } 1286 | if (right.type === ObjectExpression) { 1287 | props = props.concat( 1288 | right.properties.map((prop: any) => { 1289 | const name = convertIdentName(prop.key.name); 1290 | if (prop.value && prop.value.type === AssignmentPattern) { 1291 | return `${name} = ${this.tsType2nimType(prop.value.right)}`; 1292 | } else { 1293 | return ''; 1294 | } 1295 | }) 1296 | ); 1297 | } 1298 | if (left.typeAnnotation) { 1299 | typ = this.tsType2nimType(left.typeAnnotation.typeAnnotation); 1300 | } else { 1301 | typ = 'auto'; 1302 | } 1303 | // @TODO fill params 1304 | // pass by position 1305 | if (!name) { 1306 | name = typ.charAt(0).toLowerCase() + typ.slice(1); 1307 | } 1308 | // const isPlainEmptyObj = right.type === ObjectExpression && right.properties.length === 0; 1309 | const isPlainEmptyArr = right.type === ArrayExpression && right.elements.length === 0; 1310 | const newName = typ.charAt(0).toUpperCase() + typ.slice(1); 1311 | if (isPlainEmptyArr) { 1312 | result = `${name}:${typ} = new${newName}()`; 1313 | } else { 1314 | if (right.type === ObjectExpression) { 1315 | result = `${name}:${typ} = new${newName}(${props.join(',')})`; 1316 | } else if (right.type === ArrayExpression) { 1317 | this.log('tsType2nimType:AssignmentPattern:else', node); 1318 | result = `${name}:${typ} = @[${right.elements 1319 | .map(this.tsType2nimType, this) 1320 | .join(',')}]`; 1321 | } else { 1322 | result = `${name}:${typ} = ${this.tsType2nimType(right)}`; 1323 | } 1324 | } 1325 | } 1326 | break; 1327 | case AST_NODE_TYPES.ArrowFunctionExpression: 1328 | case TSFunctionType: 1329 | const [generics, params, pragmas] = this.getProcMeta(node); 1330 | const body = node.body; 1331 | const nimpa = params.map(this.mapParam, this); 1332 | let returnType = 'auto'; 1333 | const returnStatement = node.body?.body?.find( 1334 | (x: any) => x.type === AST_NODE_TYPES.ReturnStatement 1335 | ); 1336 | if (node.type === TSFunctionType) { 1337 | // node.returnType 1338 | // typeAnnotation: { 1339 | // type: 'TSTypePredicate', 1340 | // asserts: false, 1341 | if (node.returnType.typeAnnotation.type === TSTypePredicate) { 1342 | const predicateName = this.tsType2nimType(node.returnType.typeAnnotation.parameterName); 1343 | const annotation = node.returnType.typeAnnotation.typeAnnotation.typeAnnotation; 1344 | // TSTypeReference 1345 | const Tstr = this.tsType2nimType(annotation.typeName); 1346 | // replace generic through TSTypePredicate 1347 | nimpa.forEach((pa: any, index: number) => { 1348 | const [v, t] = pa.split(':'); 1349 | if (v === predicateName) { 1350 | const j = generics.findIndex((x: any) => x === t); 1351 | if (-1 !== j) { 1352 | generics[j] = Tstr; 1353 | } 1354 | nimpa[index] = pa.replace(t, Tstr); 1355 | } 1356 | }); 1357 | } 1358 | } else if (returnStatement) { 1359 | const arg = returnStatement.argument; 1360 | if (arg.type === AST_NODE_TYPES.BinaryExpression) { 1361 | if (BinaryOperatorsReturnsBoolean.includes(arg.operator)) { 1362 | returnType = 'bool'; 1363 | } else if (['+', '-', '*', '/'].includes(arg.operator)) { 1364 | const leftIsString = typeof arg.left.value === 'string'; 1365 | const rightIsString = typeof arg.right.value === 'string'; 1366 | const hasString = arg.operator === '+' && (leftIsString || rightIsString); 1367 | if (hasString) { 1368 | returnType = 'string'; 1369 | } else { 1370 | returnType = this.transpilerOptions.numberAs; 1371 | } 1372 | } 1373 | } else if (arg.type === AST_NODE_TYPES.LogicalExpression) { 1374 | returnType = 'bool'; 1375 | } 1376 | } 1377 | const pragmaStr = pragmas.length > 0 ? `{.${pragmas.join(',')}.}` : undefined; 1378 | const genericsStr = generics.length > 0 ? `[${generics.join(',')}]` : ''; 1379 | const rp = [returnType, pragmaStr].filter(x => x && x !== ' ').join(' '); 1380 | result += `proc ${genericsStr}(${nimpa.join(', ')}): ${rp}${body ? ' = \n' : ''}`; 1381 | // @TODO remove top level return variable 1382 | let current: any; 1383 | while ((current = body?.body?.shift())) { 1384 | result += this.tsType2nimType(current, indentLevel + 1); 1385 | } 1386 | if (body && body.body && body.body.length > 1) { 1387 | result += '\n'; 1388 | } 1389 | break; 1390 | case AST_NODE_TYPES.ExportDefaultDeclaration: 1391 | result = this.tsType2nimType(node.declaration); 1392 | 1393 | break; 1394 | case AST_NODE_TYPES.TSDeclareFunction: 1395 | { 1396 | const procNmae = this.tsType2nimType(node.id); 1397 | const [generics, params, pragmas] = this.getProcMeta(node); 1398 | 1399 | const nimpa = params?.map(this.mapParam, this) || []; 1400 | const returnTypeNode = node.returnType; 1401 | const noReturnTypeNode = 1402 | returnTypeNode?.typeAnnotation?.type === TSVoidKeyword || 1403 | returnTypeNode?.typeAnnotation?.type === TSNeverKeyword; 1404 | let returnType; 1405 | if (returnTypeNode?.typeAnnotation) { 1406 | returnType = this.tsType2nimType(node.returnType.typeAnnotation); 1407 | } 1408 | const pragmaStr = pragmas.length > 0 ? `{.${pragmas.join(',')}.}` : undefined; 1409 | const genericsStr = generics.length > 0 ? `[${generics.join(',')}]` : ''; 1410 | returnType = !noReturnTypeNode ? ': ' + returnType : ''; 1411 | const rp = [returnType, pragmaStr].filter(x => x).join(' '); 1412 | result += `proc ${procNmae}${genericsStr}(${nimpa.join(', ')})${rp}`; 1413 | result += '\n'; 1414 | } 1415 | break; 1416 | case TSVoidKeyword: 1417 | // only handle when it is generic type param 1418 | result = 'void'; 1419 | break; 1420 | case TSNeverKeyword: 1421 | // just ignore 1422 | break; 1423 | case AST_NODE_TYPES.TSLiteralType: 1424 | result = JSON.stringify(node.literal.value); 1425 | if (typeof node.literal.value === 'string') { 1426 | result = JSON.stringify(node.literal.value); 1427 | } else { 1428 | result = node.literal.raw; 1429 | } 1430 | break; 1431 | case AST_NODE_TYPES.TryStatement: 1432 | { 1433 | result = getLine(`try:`, indentLevel); 1434 | node.block.body.forEach((x: any, index: number) => { 1435 | result += getIndented(this.tsType2nimType(x), indentLevel + 1); 1436 | }); 1437 | if (node.handler) { 1438 | result += getLine(`except:`, indentLevel); 1439 | node.block.body.forEach((x: any) => { 1440 | result += getLine(this.tsType2nimType(x), indentLevel + 1); 1441 | }); 1442 | } 1443 | if (node.finalizer) { 1444 | result += getLine(`finally:`, indentLevel); 1445 | node.finalizer.body.forEach((x: any, index: number) => { 1446 | result += getIndented(this.tsType2nimType(x), indentLevel + 1); 1447 | }); 1448 | } 1449 | } 1450 | break; 1451 | case AST_NODE_TYPES.TSAsExpression: 1452 | // @TODO cast type? 1453 | result = this.tsType2nimType(node.expression); 1454 | break; 1455 | case AST_NODE_TYPES.ContinueStatement: 1456 | result = getLine('continue', indentLevel); 1457 | break; 1458 | case AST_NODE_TYPES.BinaryExpression: 1459 | result = this.convertBinaryExpression(node); 1460 | break; 1461 | case AST_NODE_TYPES.UnaryExpression: 1462 | result = this.convertUnaryExpression(node); 1463 | break; 1464 | case AST_NODE_TYPES.LogicalExpression: 1465 | result = this.convertLogicalExpression(node); 1466 | break; 1467 | case AST_NODE_TYPES.AssignmentExpression: 1468 | if (node.left.type === AST_NODE_TYPES.ArrayPattern && node.right.type === ArrayExpression) { 1469 | if (node.left.elements.length === node.right.elements.length) { 1470 | node.left.elements.forEach((_: any, index: number) => { 1471 | result += getLine( 1472 | `${this.tsType2nimType(node.left.elements[index])} ${ 1473 | node.operator 1474 | } ${this.tsType2nimType(node.right.elements[index])}`, 1475 | indentLevel 1476 | ); 1477 | }); 1478 | } 1479 | } else { 1480 | result = `${this.tsType2nimType(node.left)} ${node.operator} ${this.tsType2nimType( 1481 | node.right 1482 | )}`; 1483 | } 1484 | 1485 | break; 1486 | case AST_NODE_TYPES.ArrayExpression: 1487 | // @TODO inter the actual type 1488 | const eles = node.elements; 1489 | let sameType = true; 1490 | if (eles.length > 1) { 1491 | const firstType = typeof eles[0].value; 1492 | sameType = eles.slice(1).every((c: any, i: number) => { 1493 | return firstType === typeof c.value; 1494 | }); 1495 | } 1496 | if (sameType) { 1497 | result = `@[${eles.map(this.tsType2nimType, this)}]`; 1498 | } else { 1499 | result = `(${eles.map(this.tsType2nimType, this)})`; 1500 | } 1501 | 1502 | break; 1503 | case AST_NODE_TYPES.ThisExpression: 1504 | result = 'self'; 1505 | break; 1506 | case AST_NODE_TYPES.CallExpression: 1507 | result = this.convertCallExpression(node); 1508 | break; 1509 | case AST_NODE_TYPES.NewExpression: 1510 | result = `${this.convertCallExpression(node)}`; 1511 | break; 1512 | 1513 | case AST_NODE_TYPES.SwitchStatement: 1514 | const cas = `case ${this.tsType2nimType(node.discriminant)}:`; 1515 | result = getLine(cas, indentLevel); 1516 | if (Array.isArray(node.cases)) { 1517 | node.cases.forEach((cas: any, casIndex: number) => { 1518 | let statment; 1519 | if (cas.test) { 1520 | statment = `of ${this.tsType2nimType(cas.test)}:`; 1521 | } else { 1522 | statment = `else:`; 1523 | } 1524 | result += getLine(statment, indentLevel + 1); 1525 | if (cas.consequent) { 1526 | cas.consequent.filter(notBreak).forEach((x: any, index: number) => { 1527 | result += getIndented(this.tsType2nimType(x), indentLevel + 2); 1528 | }); 1529 | } else { 1530 | result += getIndented('discard\n', indentLevel + 1); 1531 | } 1532 | }); 1533 | } 1534 | 1535 | break; 1536 | case AST_NODE_TYPES.ClassDeclaration: 1537 | { 1538 | const hasSuper = node.superClass ? true : false; 1539 | const className = this.tsType2nimType(node.id); 1540 | 1541 | const body = node.body.body; 1542 | const ctrIndex = body.findIndex(isConstructor); 1543 | const hasCtr = -1 !== ctrIndex; 1544 | if (hasSuper) { 1545 | result += `type ${className}* = ref object of ${this.tsType2nimType( 1546 | node.superClass 1547 | )}\n`; 1548 | } else { 1549 | result += `type ${className}* = ref object of RootObj\n`; 1550 | } 1551 | let ctrl; 1552 | if (hasCtr) { 1553 | ctrl = body[ctrIndex]; 1554 | body.splice(ctrIndex, 1); 1555 | const ctrlProps = ctrl.value.params.filter(isParamProp); 1556 | const members = ctrlProps.map(this.mapMember, this); 1557 | if (members.length > 0) { 1558 | result += members.map(indented(1)).join('\n') + '\n'; 1559 | } 1560 | } 1561 | const propsIndexes = body.reduce((p: any, cur: any, i: number) => { 1562 | if (cur.type === AST_NODE_TYPES.ClassProperty) { 1563 | return [...p, i]; 1564 | } 1565 | return p; 1566 | }, []); 1567 | if (propsIndexes.length > 0) { 1568 | const props = body.filter((x: any, i: number) => propsIndexes.includes(i)); 1569 | propsIndexes.reverse().forEach((v: number, i: number) => { 1570 | body.splice(v, 1); 1571 | }); 1572 | const propsStrs = props.map(this.mapMember, this); 1573 | if (propsStrs) { 1574 | result += propsStrs.map(indented(1)).join('\n') + '\n'; 1575 | } 1576 | } 1577 | result += '\n\n'; 1578 | // write constructor 1579 | if (hasCtr) { 1580 | result += this.handleFunction( 1581 | ctrl.value, 1582 | `new${className}`, 1583 | true, 1584 | className, 1585 | false, 1586 | indentLevel 1587 | ); 1588 | } 1589 | // write methods 1590 | body.forEach((v: any) => { 1591 | result += this.handleFunction( 1592 | v.value, 1593 | v.key.name, 1594 | true, 1595 | className, 1596 | v.static, 1597 | indentLevel 1598 | ); 1599 | }); 1600 | // node.body type === 'ClassBody' 1601 | // body.body element type 'MethodDefinition' 1602 | // kind: 'constructor','method' 1603 | // static: 1604 | // value: type: 'FunctionExpression' 1605 | } 1606 | break; 1607 | case AST_NODE_TYPES.ClassProperty: 1608 | { 1609 | const isPub = this.isPub(node); 1610 | const name = convertIdentName(node.key.name); 1611 | const value = this.tsType2nimType(node.value); 1612 | const typ = this.getType(node); 1613 | const comment = this.getComment(node); 1614 | const exportMark = isPub ? '*' : ''; 1615 | result = `${name}${exportMark}:${typ}${comment}${ 1616 | value ? ` ## ts default value:${value}` : '' 1617 | }`; 1618 | } 1619 | break; 1620 | case AST_NODE_TYPES.TSUndefinedKeyword: 1621 | result = 'nil'; 1622 | break; 1623 | case AST_NODE_TYPES.TSEnumDeclaration: 1624 | { 1625 | const name = this.tsType2nimType(node.id); 1626 | result = `type ${name} = enum\n`; 1627 | const members = node.members; 1628 | for (const m of members) { 1629 | const comment = this.getComment(m); 1630 | const cc = comment 1631 | ? comment 1632 | .replace(/^\*+/, '') 1633 | .replace(/\n/g, '') 1634 | .trimEnd() 1635 | : ''; 1636 | const de = this.tsType2nimType(m, 1); 1637 | result += indented(1)(`${de}${cc ? ' ' + cc : ''}\n`); 1638 | } 1639 | result += '\n\n'; 1640 | } 1641 | break; 1642 | case AST_NODE_TYPES.TSEnumMember: 1643 | { 1644 | const name = this.tsType2nimType(node.id); 1645 | if (node.initializer) { 1646 | result = `${name} = ${this.tsType2nimType(node.initializer)}`; 1647 | } else { 1648 | result = `${name}`; 1649 | } 1650 | } 1651 | 1652 | break; 1653 | case AST_NODE_TYPES.TSTupleType: 1654 | result = `(${node.elementTypes.map(this.tsType2nimType, this)})`; 1655 | break; 1656 | case AST_NODE_TYPES.ConditionalExpression: 1657 | result = this.convertConditionalExpression(node); 1658 | break; 1659 | case AST_NODE_TYPES.TSModuleDeclaration: 1660 | if (node.body && node.body.body) { 1661 | result = node.body.body.map(this.ts2nimIndented(0)).join(''); 1662 | } 1663 | break; 1664 | case AST_NODE_TYPES.TSModuleBlock: 1665 | if (node.body) { 1666 | result = node.body.map(this.ts2nimIndented(0)).join(''); 1667 | } 1668 | 1669 | break; 1670 | default: 1671 | this.log('this.tsType2nimType:default', node); 1672 | break; 1673 | } 1674 | return result; 1675 | } 1676 | 1677 | ts2nimIndented(indentLevel: number) { 1678 | return (node: any) => indented(indentLevel)(this.tsType2nimType(node)); 1679 | } 1680 | 1681 | isPub(prop: any): boolean { 1682 | const name = prop.key?.name || prop.id?.name || prop.parameter.name; 1683 | if (name.startsWith('_')) { 1684 | return false; 1685 | } 1686 | const accessibility = prop.accessibility; 1687 | return accessibility === 'public' || !accessibility; 1688 | } 1689 | 1690 | mapParam(p: any): string { 1691 | if (p.type === AST_NODE_TYPES.AssignmentPattern) { 1692 | return this.tsType2nimType(p); 1693 | } else if (p.type === AST_NODE_TYPES.RestElement) { 1694 | return this.tsType2nimType(p); 1695 | } else if (p.type === AST_NODE_TYPES.TSParameterProperty) { 1696 | return this.tsType2nimType(p.parameter); 1697 | } else { 1698 | const name = convertIdentName(p.name || p.argument?.name); 1699 | const optional = p.optional; 1700 | let typ = 'auto'; 1701 | 1702 | if (p.typeAnnotation) { 1703 | if (optional) { 1704 | this.modules.add('options'); 1705 | typ = `none(${this.tsType2nimType(p.typeAnnotation.typeAnnotation)})`; 1706 | return `${name} = ${typ}`; 1707 | } else { 1708 | typ = this.tsType2nimType(p.typeAnnotation.typeAnnotation); 1709 | } 1710 | } 1711 | return `${name}:${typ}`; 1712 | } 1713 | } 1714 | 1715 | mapDecl(isExport: boolean, m: any) { 1716 | const name = convertIdentName(m.key.name); 1717 | const typ = this.tsType2nimType(m.typeAnnotation.typeAnnotation); 1718 | const comment = this.getComment(m); 1719 | const exportMark = isExport ? '*' : ''; 1720 | const cc = comment ? ' ##' + comment.replace(/^\*+/, '').trimEnd() : ''; 1721 | return `${name}${exportMark}:${typ}${cc}`; 1722 | } 1723 | getType(node: any): string { 1724 | let typ = ''; 1725 | if (node.typeAnnotation) { 1726 | typ = this.tsType2nimType(node.typeAnnotation.typeAnnotation); 1727 | } else if (node.value) { 1728 | typ = this.typeof2type(typeof node.value); 1729 | } 1730 | return typ; 1731 | } 1732 | mapMember(prop: any): string { 1733 | // readonly: undefined, 1734 | // static: undefined, 1735 | // export: undefined, 1736 | const isPub = this.isPub(prop); 1737 | const comment = this.getComment(prop); 1738 | const exportMark = isPub ? '*' : ''; 1739 | if (prop.type === TSParameterProperty) { 1740 | const parameter = prop.parameter; 1741 | const name = convertIdentName(parameter.name); 1742 | const typ = this.getType(parameter); 1743 | 1744 | return `${name}${exportMark}:${typ}${comment}`; 1745 | } else if (prop.type === TSPropertySignature) { 1746 | const name = convertIdentName(prop.key.name); 1747 | const isTSQualifiedName = 1748 | prop.typeAnnotation.typeAnnotation.typeName?.type === AST_NODE_TYPES.TSQualifiedName; 1749 | const typ = this.tsType2nimType(prop.typeAnnotation.typeAnnotation); 1750 | 1751 | return `${isTSQualifiedName ? '## ' : ''}${name}${exportMark}:${typ}${comment}`; 1752 | } else { 1753 | return this.tsType2nimType(prop); 1754 | } 1755 | } 1756 | 1757 | transpile() { 1758 | const filePath = this.writer.path; 1759 | const ext = path.extname(filePath); 1760 | const dir = path.dirname(filePath); 1761 | const basename = path.basename(filePath, ext); 1762 | const changed = path.join(dir, basename + '.ts'); 1763 | this.ast.body.forEach((node: any) => { 1764 | let content = ''; 1765 | try { 1766 | content = this.tsType2nimType(node, 0); 1767 | } catch (e) { 1768 | const start = this.lastNode.loc; 1769 | const end = this.lastNode.loc; 1770 | this.log(e); 1771 | this.log(`file://${changed}:${start.start.line}:${start.start.column}`); 1772 | this.log(`file://${changed}:${end.end.line}:${end.end.column}`); 1773 | } 1774 | 1775 | this.writer.write(content); 1776 | }); 1777 | } 1778 | 1779 | getOriginalComment(node: any): string | undefined { 1780 | // @ts-ignore 1781 | const comment = this.ast.comments.find(x => { 1782 | // this.log(x.loc,node.loc) 1783 | // @TODO could be same line,but it returns wrong 1784 | // eg. { start: { line: 23, column: 27 }, end: { line: 23, column: 55 } } { start: { line: 24, column: 2 }, end: { line: 24, column: 29 } } 1785 | return x.loc.end.line === node.loc.start.line - 1; 1786 | }); 1787 | return comment?.value; 1788 | } 1789 | } 1790 | 1791 | export function transpile( 1792 | filePath = '/unnamed.nim', 1793 | code: string, 1794 | transpilerOptions: TranspilerOptions = { isProject: false, numberAs: 'float' }, 1795 | parserOptions = { comment: true, loggerFn: false, loc: true, range: true }, 1796 | symbols: { [index: string]: Sym[] } = {} 1797 | ): { writer: IWriteStream; logger: Subject } { 1798 | if (!fs.existsSync(path.dirname(filePath))) { 1799 | fs.mkdirpSync(path.dirname(filePath)); 1800 | } 1801 | const ext = path.extname(filePath); 1802 | const dir = path.dirname(filePath); 1803 | const basename = path.basename(filePath, ext); 1804 | const changed = path.join(dir, basename + '.nim'); 1805 | const isD = -1 !== changed.indexOf('.d.'); 1806 | const writePath = changed.replace(/\.d(?=\.)/g, '_d'); 1807 | const writer = fs.createWriteStream(writePath); 1808 | const start = performance.now(); 1809 | // @ts-ignore 1810 | // loggerFn:false skip warning:"You are currently running a version of TypeScript which is not officially supported by typescript-estree SUPPORTED TYPESCRIPT VERSIONS: ~3.2.1" 1811 | const ast = parser.parse(code, parserOptions); 1812 | const duration = performance.now() - start; 1813 | const copys = Object.fromEntries( 1814 | Object.keys(symbols).map((key: string) => [key, [...symbols[key]].reverse()]) 1815 | ); 1816 | const transpiler = new Transpiler(ast, writer, transpilerOptions, copys); 1817 | transpiler.isD = isD; 1818 | writer.on('open', (fd: any) => { 1819 | transpiler.log(`parse time takes:${duration} millisecond `); 1820 | const start = performance.now(); 1821 | if (transpiler.isD) { 1822 | writer.write(brideHeader); 1823 | } 1824 | const dur = performance.now() - start; 1825 | transpiler.transpile(); 1826 | transpiler.log(`transpile time takes:${dur} millisecond `); 1827 | const start2 = performance.now(); 1828 | let preCount = 0; 1829 | if (transpiler.modules.size > 0) { 1830 | const insert = Buffer.from('import ' + Array.from(transpiler.modules).join(',') + '\n\n'); 1831 | fs.writeSync(fd, insert, 0, insert.length, 0); 1832 | preCount = insert.length; 1833 | } 1834 | if (transpiler.helpers.size > 0) { 1835 | const insert = Buffer.from(Array.from(transpiler.helpers).join('\n') + '\n'); 1836 | fs.writeSync(fd, insert, preCount, insert.length, 0); 1837 | } 1838 | const dur2 = performance.now() - start2; 1839 | transpiler.log(`extro header write time takes:${dur2} millisecond `); 1840 | writer.end(); 1841 | }); 1842 | 1843 | return { writer, logger: transpiler.logger }; 1844 | } 1845 | --------------------------------------------------------------------------------