From 87a7b476c2a0015e5dca33de850c1e24d4c6378a Mon Sep 17 00:00:00 2001 From: alaa-eddine Date: Tue, 27 Jan 2026 01:56:52 +0100 Subject: [PATCH 1/2] Fixed wrong warning message appearing with valid code Fixed multiline Pine Script conditions parsing (Indent error) Fixed transpiler switch syntax conversion --- package-lock.json | 275 +++++++++--------- package.json | 1 + src/namespaces/ta/methods/hma.ts | 2 +- src/namespaces/ta/methods/macd.ts | 8 +- src/namespaces/ta/methods/mom.ts | 2 +- src/transpiler/pineToJS/codegen.ts | 58 ++-- src/transpiler/pineToJS/lexer.ts | 2 +- src/transpiler/pineToJS/parser.ts | 114 ++++++-- .../transformers/ExpressionTransformer.ts | 7 +- .../transformers/MainTransformer.ts | 24 ++ .../transformers/StatementTransformer.ts | 76 ++++- src/transpiler/utils/ASTFactory.ts | 5 +- tests/core/pagination.test.ts | 7 +- tests/indicators/macd-pinescript.test.ts | 74 +++-- tests/transpiler/pinescript-to-js.test.ts | 2 +- tests/transpiler/pinets-source-to-js.test.ts | 6 +- tests/utils.ts | 5 +- 17 files changed, 431 insertions(+), 237 deletions(-) diff --git a/package-lock.json b/package-lock.json index 53f8213..1647d17 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pinets", - "version": "0.3.0", + "version": "0.8.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pinets", - "version": "0.3.0", + "version": "0.8.4", "license": "AGPL-3.0", "dependencies": { "acorn": "^8.14.0", @@ -25,6 +25,7 @@ "rollup-plugin-esbuild": "^6.1.1", "rollup-plugin-sourcemaps": "^0.6.3", "rollup-plugin-typescript-paths": "^1.5.0", + "tsx": "^4.21.0", "vite-tsconfig-paths": "^4.3.2", "vitest": "^2.0.0" } @@ -101,9 +102,9 @@ "license": "MIT" }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz", - "integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", "cpu": [ "ppc64" ], @@ -113,15 +114,14 @@ "os": [ "aix" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/android-arm": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.2.tgz", - "integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", "cpu": [ "arm" ], @@ -131,15 +131,14 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/android-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz", - "integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", "cpu": [ "arm64" ], @@ -149,15 +148,14 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/android-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.2.tgz", - "integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", "cpu": [ "x64" ], @@ -167,15 +165,14 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz", - "integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", "cpu": [ "arm64" ], @@ -185,15 +182,14 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz", - "integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", "cpu": [ "x64" ], @@ -203,15 +199,14 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz", - "integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", "cpu": [ "arm64" ], @@ -221,15 +216,14 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz", - "integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", "cpu": [ "x64" ], @@ -239,15 +233,14 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/linux-arm": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz", - "integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", "cpu": [ "arm" ], @@ -257,15 +250,14 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz", - "integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", "cpu": [ "arm64" ], @@ -275,15 +267,14 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz", - "integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", "cpu": [ "ia32" ], @@ -293,15 +284,14 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz", - "integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", "cpu": [ "loong64" ], @@ -311,15 +301,14 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz", - "integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", "cpu": [ "mips64el" ], @@ -329,15 +318,14 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz", - "integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", "cpu": [ "ppc64" ], @@ -347,15 +335,14 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz", - "integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", "cpu": [ "riscv64" ], @@ -365,15 +352,14 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz", - "integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", "cpu": [ "s390x" ], @@ -383,15 +369,14 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/linux-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz", - "integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", "cpu": [ "x64" ], @@ -401,15 +386,14 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz", - "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", "cpu": [ "arm64" ], @@ -419,15 +403,14 @@ "os": [ "netbsd" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz", - "integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", "cpu": [ "x64" ], @@ -437,15 +420,14 @@ "os": [ "netbsd" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz", - "integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", "cpu": [ "arm64" ], @@ -455,15 +437,14 @@ "os": [ "openbsd" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz", - "integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", "cpu": [ "x64" ], @@ -473,15 +454,31 @@ "os": [ "openbsd" ], - "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], "engines": { "node": ">=18" } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz", - "integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", "cpu": [ "x64" ], @@ -491,15 +488,14 @@ "os": [ "sunos" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz", - "integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", "cpu": [ "arm64" ], @@ -509,15 +505,14 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz", - "integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", "cpu": [ "ia32" ], @@ -527,15 +522,14 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/win32-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz", - "integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", "cpu": [ "x64" ], @@ -545,7 +539,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=18" } @@ -1637,13 +1630,12 @@ "license": "MIT" }, "node_modules/esbuild": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz", - "integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -1651,31 +1643,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.24.2", - "@esbuild/android-arm": "0.24.2", - "@esbuild/android-arm64": "0.24.2", - "@esbuild/android-x64": "0.24.2", - "@esbuild/darwin-arm64": "0.24.2", - "@esbuild/darwin-x64": "0.24.2", - "@esbuild/freebsd-arm64": "0.24.2", - "@esbuild/freebsd-x64": "0.24.2", - "@esbuild/linux-arm": "0.24.2", - "@esbuild/linux-arm64": "0.24.2", - "@esbuild/linux-ia32": "0.24.2", - "@esbuild/linux-loong64": "0.24.2", - "@esbuild/linux-mips64el": "0.24.2", - "@esbuild/linux-ppc64": "0.24.2", - "@esbuild/linux-riscv64": "0.24.2", - "@esbuild/linux-s390x": "0.24.2", - "@esbuild/linux-x64": "0.24.2", - "@esbuild/netbsd-arm64": "0.24.2", - "@esbuild/netbsd-x64": "0.24.2", - "@esbuild/openbsd-arm64": "0.24.2", - "@esbuild/openbsd-x64": "0.24.2", - "@esbuild/sunos-x64": "0.24.2", - "@esbuild/win32-arm64": "0.24.2", - "@esbuild/win32-ia32": "0.24.2", - "@esbuild/win32-x64": "0.24.2" + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" } }, "node_modules/estree-walker": { @@ -3238,6 +3231,26 @@ } } }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, "node_modules/typescript": { "version": "5.7.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", diff --git a/package.json b/package.json index cec3eb6..68eb8f1 100644 --- a/package.json +++ b/package.json @@ -92,6 +92,7 @@ "rollup-plugin-esbuild": "^6.1.1", "rollup-plugin-sourcemaps": "^0.6.3", "rollup-plugin-typescript-paths": "^1.5.0", + "tsx": "^4.21.0", "vite-tsconfig-paths": "^4.3.2", "vitest": "^2.0.0" }, diff --git a/src/namespaces/ta/methods/hma.ts b/src/namespaces/ta/methods/hma.ts index bd2e230..4492e59 100644 --- a/src/namespaces/ta/methods/hma.ts +++ b/src/namespaces/ta/methods/hma.ts @@ -11,7 +11,7 @@ export function hma(context: any) { const sqrtPeriod = Math.floor(Math.sqrt(period)); // Get wma function from context.ta - const wmaFn = context.ta.wma; + const wmaFn = context.pine.ta.wma; // Pass derived call IDs to internal WMA calls to avoid state collision const wma1 = wmaFn(source, halfPeriod, _callId ? `${_callId}_wma1` : undefined); diff --git a/src/namespaces/ta/methods/macd.ts b/src/namespaces/ta/methods/macd.ts index d6ef9a2..233bdc9 100644 --- a/src/namespaces/ta/methods/macd.ts +++ b/src/namespaces/ta/methods/macd.ts @@ -38,9 +38,9 @@ export function macd(context: any) { const signalEmaId = `${baseId}_signal`; // Calculate Fast and Slow EMAs - // context.ta.ema returns the current EMA value - const fastMA = context.ta.ema(source, fastLength, fastEmaId); - const slowMA = context.ta.ema(source, slowLength, slowEmaId); + // context.pine.ta.ema returns the current EMA value + const fastMA = context.pine.ta.ema(source, fastLength, fastEmaId); + const slowMA = context.pine.ta.ema(source, slowLength, slowEmaId); // Calculate MACD Line // Handle NaN cases if EMAs are not yet valid @@ -55,7 +55,7 @@ export function macd(context: any) { // We must ensure we don't pass NaN to EMA, as it might corrupt the state (initSum). let signalLine = NaN; if (!isNaN(macdLine)) { - signalLine = context.ta.ema(macdLine, signalLength, signalEmaId); + signalLine = context.pine.ta.ema(macdLine, signalLength, signalEmaId); } // Calculate Histogram diff --git a/src/namespaces/ta/methods/mom.ts b/src/namespaces/ta/methods/mom.ts index 0c3832e..4d60bf9 100644 --- a/src/namespaces/ta/methods/mom.ts +++ b/src/namespaces/ta/methods/mom.ts @@ -7,6 +7,6 @@ export function mom(context: any) { const length = Series.from(_length).get(0); // Momentum is same as change - return context.ta.change(source, length, _callId); + return context.pine.ta.change(source, length, _callId); }; } diff --git a/src/transpiler/pineToJS/codegen.ts b/src/transpiler/pineToJS/codegen.ts index 339002d..5a72a5a 100644 --- a/src/transpiler/pineToJS/codegen.ts +++ b/src/transpiler/pineToJS/codegen.ts @@ -1289,40 +1289,44 @@ export class CodeGenerator { this.write('}'); } - // Generate SwitchExpression (convert to ternary chain) + // Generate SwitchExpression (convert to IIFE with switch statement) generateSwitchExpression(node) { - // switch discriminant => chain of ternary operators - // switch x - // A => result1 - // B => result2 - // => defaultResult - // becomes: (x == A ? result1 : x == B ? result2 : defaultResult) - this.write('('); - - for (let i = 0; i < node.cases.length; i++) { - const c = node.cases[i]; - + this.write('(() => {\n'); + this.indent++; + this.write(this.indentStr.repeat(this.indent)); + + this.write('switch ('); + this.generateExpression(node.discriminant); + this.write(') {\n'); + + this.indent++; + + for (const c of node.cases) { + this.write(this.indentStr.repeat(this.indent)); + if (c.test) { - // Compare discriminant to test value - this.generateExpression(node.discriminant); - this.write(' == '); + this.write('case '); this.generateExpression(c.test); - this.write(' ? '); - this.generateExpression(c.consequent); - this.write(' : '); + this.write(':\n'); } else { - // Default case (no test) - just the consequent - this.generateExpression(c.consequent); + this.write('default:\n'); } + + this.indent++; + this.write(this.indentStr.repeat(this.indent)); + this.write('return '); + this.generateExpression(c.consequent); + this.write(';\n'); + this.indent--; } + + this.indent--; + this.write(this.indentStr.repeat(this.indent)); + this.write('}\n'); // end switch - // If no default case was provided, add undefined - const hasDefault = node.cases.some((c) => !c.test); - if (!hasDefault) { - this.write('undefined'); - } - - this.write(')'); + this.indent--; + this.write(this.indentStr.repeat(this.indent)); + this.write('})()'); } // Generate SequenceExpression diff --git a/src/transpiler/pineToJS/lexer.ts b/src/transpiler/pineToJS/lexer.ts index fce7c04..79deda2 100644 --- a/src/transpiler/pineToJS/lexer.ts +++ b/src/transpiler/pineToJS/lexer.ts @@ -152,7 +152,7 @@ export class Lexer { // Check if this is a blank line (only whitespace followed by newline or EOF) // If so, skip indentation processing and keep position at whitespace - if (this.peek() === '\n' || this.peek() === '\0') { + if (this.peek() === '\n' || this.peek() === '\r' || this.peek() === '\0') { // Don't process indentation for blank lines // The whitespace will be skipped in the main loop return; diff --git a/src/transpiler/pineToJS/parser.ts b/src/transpiler/pineToJS/parser.ts index c9ced4c..3322482 100644 --- a/src/transpiler/pineToJS/parser.ts +++ b/src/transpiler/pineToJS/parser.ts @@ -72,10 +72,45 @@ export class Parser { return this.advance(); } - skipNewlines() { + // Match a token, optionally ignoring NEWLINE and INDENT (for line continuation) + matchEx(type, value = null, allowLineContinuation = false) { + if (!allowLineContinuation) { + return this.match(type, value); + } + + let offset = 0; + let token = this.peek(offset); + + // Skip NEWLINE and subsequent INDENT + if (token.type === TokenType.NEWLINE) { + offset++; + token = this.peek(offset); + + // Optional INDENT after NEWLINE + if (token.type === TokenType.INDENT) { + offset++; + token = this.peek(offset); + } + } + + if (token.type !== type) return false; + if (value !== null && token.value !== value) return false; + + // Consume skipped tokens + for (let i = 0; i < offset; i++) { + this.advance(); + } + + return true; + } + + skipNewlines(allowIndent = false) { while (this.match(TokenType.NEWLINE)) { this.advance(); } + if (allowIndent && this.match(TokenType.INDENT)) { + this.advance(); + } } // Main parse method @@ -84,6 +119,13 @@ export class Parser { while (!this.match(TokenType.EOF)) { this.skipNewlines(); + + // Handle DEDENTs at top level (from line continuations) + if (this.match(TokenType.DEDENT)) { + this.advance(); + continue; + } + if (this.match(TokenType.EOF)) break; const stmt = this.parseStatement(); @@ -170,7 +212,7 @@ export class Parser { const op = this.peek().value; if (['=', ':=', '+=', '-=', '*=', '/=', '%='].includes(op)) { this.advance(); - this.skipNewlines(); + this.skipNewlines(true); const right = this.parseExpression(); // Simple assignment with = creates variable declaration @@ -344,7 +386,7 @@ export class Parser { } this.expect(TokenType.OPERATOR, '='); - this.skipNewlines(); + this.skipNewlines(true); const init = this.parseExpression(); const id = new Identifier(name); @@ -366,7 +408,7 @@ export class Parser { const name = this.expect(TokenType.IDENTIFIER).value; this.expect(TokenType.OPERATOR, '='); - this.skipNewlines(); + this.skipNewlines(true); const init = this.parseExpression(); const id = new Identifier(name); @@ -598,7 +640,7 @@ export class Parser { const op = this.peek().value; if (['=', ':=', '+=', '-=', '*=', '/=', '%='].includes(op)) { this.advance(); - this.skipNewlines(); + this.skipNewlines(true); const right = this.parseExpression(); // Simple assignment with = creates variable declaration @@ -774,19 +816,38 @@ export class Parser { return new BlockStatement(stmt ? [stmt] : []); } + const blockIndent = this.peek().indent; this.advance(); // consume INDENT const statements = []; - while (!this.match(TokenType.DEDENT) && !this.match(TokenType.EOF)) { + while (!this.match(TokenType.EOF)) { this.skipNewlines(); - if (this.match(TokenType.DEDENT)) break; + + // Check for DEDENT + if (this.match(TokenType.DEDENT)) { + const dedentLevel = this.peek().indent; + if (dedentLevel < blockIndent) { + // Dedenting out of this block + break; + } else { + // Dedenting from a deeper level back to this block (or deeper) + // Consume spurious DEDENT + this.advance(); + continue; + } + } + + if (this.match(TokenType.EOF)) break; const stmt = this.parseStatement(); if (stmt) statements.push(stmt); } if (this.match(TokenType.DEDENT)) { - this.advance(); + const dedentLevel = this.peek().indent; + if (dedentLevel < blockIndent) { + this.advance(); + } } return new BlockStatement(statements); @@ -846,7 +907,7 @@ export class Parser { this.expect(TokenType.RBRACKET); this.skipNewlines(); this.expect(TokenType.OPERATOR, '='); - this.skipNewlines(); + this.skipNewlines(true); const init = this.parseExpression(); return new VariableDeclaration([new VariableDeclarator(new ArrayPattern(elements), init)], VariableDeclarationKind.CONST); @@ -860,12 +921,19 @@ export class Parser { parseTernary() { let expr = this.parseLogicalOr(); - if (this.match(TokenType.OPERATOR, '?')) { + if (this.matchEx(TokenType.OPERATOR, '?', true)) { this.advance(); - this.skipNewlines(); + this.skipNewlines(true); const consequent = this.parseExpression(); - this.expect(TokenType.COLON); - this.skipNewlines(); + + // Handle : with line continuation + if (this.matchEx(TokenType.COLON, null, true)) { + this.advance(); // Consume : + } else { + this.expect(TokenType.COLON); + } + + this.skipNewlines(true); const alternate = this.parseExpression(); return new ConditionalExpression(expr, consequent, alternate); } @@ -876,9 +944,9 @@ export class Parser { parseLogicalOr() { let left = this.parseLogicalAnd(); - while (this.match(TokenType.KEYWORD, 'or') || (this.match(TokenType.OPERATOR) && this.peek().value === '||')) { + while (this.matchEx(TokenType.KEYWORD, 'or', true) || (this.matchEx(TokenType.OPERATOR, null, true) && this.peek().value === '||')) { this.advance(); - this.skipNewlines(); + this.skipNewlines(true); const right = this.parseLogicalAnd(); left = new BinaryExpression('||', left, right); } @@ -889,7 +957,7 @@ export class Parser { parseLogicalAnd() { let left = this.parseEquality(); - while (this.match(TokenType.KEYWORD, 'and') || (this.match(TokenType.OPERATOR) && this.peek().value === '&&')) { + while (this.matchEx(TokenType.KEYWORD, 'and', true) || (this.matchEx(TokenType.OPERATOR, null, true) && this.peek().value === '&&')) { this.advance(); this.skipNewlines(); const right = this.parseEquality(); @@ -902,12 +970,12 @@ export class Parser { parseEquality() { let left = this.parseComparison(); - while (this.match(TokenType.OPERATOR)) { + while (this.matchEx(TokenType.OPERATOR, null, true)) { const op = this.peek().value; if (!['==', '!='].includes(op)) break; this.advance(); - this.skipNewlines(); + this.skipNewlines(true); const right = this.parseComparison(); left = new BinaryExpression(op, left, right); } @@ -918,7 +986,7 @@ export class Parser { parseComparison() { let left = this.parseAdditive(); - while (this.match(TokenType.OPERATOR)) { + while (this.matchEx(TokenType.OPERATOR, null, true)) { const op = this.peek().value; if (!['<', '>', '<=', '>='].includes(op)) break; @@ -934,12 +1002,12 @@ export class Parser { parseAdditive() { let left = this.parseMultiplicative(); - while (this.match(TokenType.OPERATOR)) { + while (this.matchEx(TokenType.OPERATOR, null, true)) { const op = this.peek().value; if (!['+', '-'].includes(op)) break; this.advance(); - this.skipNewlines(); + this.skipNewlines(true); const right = this.parseMultiplicative(); left = new BinaryExpression(op, left, right); } @@ -950,12 +1018,12 @@ export class Parser { parseMultiplicative() { let left = this.parseUnary(); - while (this.match(TokenType.OPERATOR)) { + while (this.matchEx(TokenType.OPERATOR, null, true)) { const op = this.peek().value; if (!['*', '/', '%'].includes(op)) break; this.advance(); - this.skipNewlines(); + this.skipNewlines(true); const right = this.parseUnary(); left = new BinaryExpression(op, left, right); } diff --git a/src/transpiler/transformers/ExpressionTransformer.ts b/src/transpiler/transformers/ExpressionTransformer.ts index 0b7ceaa..7de96ae 100644 --- a/src/transpiler/transformers/ExpressionTransformer.ts +++ b/src/transpiler/transformers/ExpressionTransformer.ts @@ -233,8 +233,11 @@ export function transformIdentifier(node: any, scopeManager: ScopeManager): void if (isContextBoundVar) { const isFunctionArg = node.parent && node.parent.type === 'CallExpression' && node.parent.arguments.includes(node); - if (!isFunctionArg) { - // Return early if it's not a function arg that needs unwrapping + const isSwitchDiscriminant = node.parent && node.parent.type === 'SwitchStatement' && node.parent.discriminant === node; + const isSwitchCaseTest = node.parent && node.parent.type === 'SwitchCase' && node.parent.test === node; + + if (!isFunctionArg && !isSwitchDiscriminant && !isSwitchCaseTest) { + // Return early if it's not a function arg or switch test that needs unwrapping return; } } diff --git a/src/transpiler/transformers/MainTransformer.ts b/src/transpiler/transformers/MainTransformer.ts index caae84a..48912e0 100644 --- a/src/transpiler/transformers/MainTransformer.ts +++ b/src/transpiler/transformers/MainTransformer.ts @@ -127,6 +127,30 @@ export function runTransformationPass( IfStatement(node: any, state: ScopeManager, c: any) { transformIfStatement(node, state, c); }, + SwitchStatement(node: any, state: ScopeManager, c: any) { + node.discriminant.parent = node; + c(node.discriminant, state); + node.cases.forEach((caseNode: any) => { + caseNode.parent = node; + c(caseNode, state); + }); + }, + SwitchCase(node: any, state: ScopeManager, c: any) { + if (node.test) { + node.test.parent = node; + c(node.test, state); + } + const newConsequent: any[] = []; + node.consequent.forEach((stmt: any) => { + state.enterHoistingScope(); + // stmt.parent = node; // Not strictly necessary for statements, but good for consistency + c(stmt, state); + const hoistedStmts = state.exitHoistingScope(); + newConsequent.push(...hoistedStmts); + newConsequent.push(stmt); + }); + node.consequent = newConsequent; + }, AwaitExpression(node: any, state: ScopeManager, c: any) { // Mark the argument as being inside an await so transformCallExpression knows not to add another await if (node.argument) { diff --git a/src/transpiler/transformers/StatementTransformer.ts b/src/transpiler/transformers/StatementTransformer.ts index 6d67782..e5fd519 100644 --- a/src/transpiler/transformers/StatementTransformer.ts +++ b/src/transpiler/transformers/StatementTransformer.ts @@ -407,11 +407,6 @@ export function transformVariableDeclaration(varNode: any, scopeManager: ScopeMa // Transform the body of arrow functions scopeManager.pushScope('fn'); walk.recursive(decl.init.body, scopeManager, { - BlockStatement(node: any, state: ScopeManager, c: any) { - //state.pushScope('block'); - node.body.forEach((stmt: any) => c(stmt, state)); - //state.popScope(); - }, IfStatement(node: any, state: ScopeManager, c: any) { state.pushScope('if'); c(node.consequent, state); @@ -431,6 +426,40 @@ export function transformVariableDeclaration(varNode: any, scopeManager: ScopeMa AssignmentExpression(node: any, state: ScopeManager) { transformAssignmentExpression(node, state); }, + SwitchStatement(node: any, state: ScopeManager, c: any) { + node.discriminant.parent = node; + c(node.discriminant, state); + node.cases.forEach((caseNode: any) => { + caseNode.parent = node; + c(caseNode, state); + }); + }, + SwitchCase(node: any, state: ScopeManager, c: any) { + if (node.test) { + node.test.parent = node; + c(node.test, state); + } + const newConsequent: any[] = []; + node.consequent.forEach((stmt: any) => { + state.enterHoistingScope(); + c(stmt, state); + const hoistedStmts = state.exitHoistingScope(); + newConsequent.push(...hoistedStmts); + newConsequent.push(stmt); + }); + node.consequent = newConsequent; + }, + BlockStatement(node: any, state: ScopeManager, c: any) { + const newBody: any[] = []; + node.body.forEach((stmt: any) => { + state.enterHoistingScope(); + c(stmt, state); + const hoistedStmts = state.exitHoistingScope(); + newBody.push(...hoistedStmts); + newBody.push(stmt); + }); + node.body = newBody; + }, }); scopeManager.popScope(); } @@ -728,6 +757,9 @@ export function transformReturnStatement(node: any, scopeManager: ScopeManager): }, // c is the callback function for recursion (acorn-walk) CallExpression(node: any, state: ScopeManager, c: any) { + if (node.callee.type === 'ArrowFunctionExpression' || node.callee.type === 'FunctionExpression') { + c(node.callee, state); + } transformCallExpression(node, state); if (node.type === 'CallExpression') { node.arguments.forEach((arg: any) => c(arg, state)); @@ -737,6 +769,40 @@ export function transformReturnStatement(node: any, scopeManager: ScopeManager): c(node.left, state); c(node.right, state); }, + SwitchStatement(node: any, state: ScopeManager, c: any) { + node.discriminant.parent = node; + c(node.discriminant, state); + node.cases.forEach((caseNode: any) => { + caseNode.parent = node; + c(caseNode, state); + }); + }, + SwitchCase(node: any, state: ScopeManager, c: any) { + if (node.test) { + node.test.parent = node; + c(node.test, state); + } + const newConsequent: any[] = []; + node.consequent.forEach((stmt: any) => { + state.enterHoistingScope(); + c(stmt, state); + const hoistedStmts = state.exitHoistingScope(); + newConsequent.push(...hoistedStmts); + newConsequent.push(stmt); + }); + node.consequent = newConsequent; + }, + BlockStatement(node: any, state: ScopeManager, c: any) { + const newBody: any[] = []; + node.body.forEach((stmt: any) => { + state.enterHoistingScope(); + c(stmt, state); + const hoistedStmts = state.exitHoistingScope(); + newBody.push(...hoistedStmts); + newBody.push(stmt); + }); + node.body = newBody; + }, }); } diff --git a/src/transpiler/utils/ASTFactory.ts b/src/transpiler/utils/ASTFactory.ts index 13b7308..660ec2c 100644 --- a/src/transpiler/utils/ASTFactory.ts +++ b/src/transpiler/utils/ASTFactory.ts @@ -134,9 +134,10 @@ export const ASTFactory = { return this.createCallExpression(setMethod, [target, value]); }, - // Create $.math.__eq(left, right) + // Create $.pine.math.__eq(left, right) createMathEqCall(left: any, right: any): any { - const mathObj = this.createMemberExpression(this.createContextIdentifier(), this.createIdentifier('math'), false); + const pineObj = this.createMemberExpression(this.createContextIdentifier(), this.createIdentifier('pine'), false); + const mathObj = this.createMemberExpression(pineObj, this.createIdentifier('math'), false); const eqMethod = this.createMemberExpression(mathObj, this.createIdentifier('__eq'), false); return this.createCallExpression(eqMethod, [left, right]); }, diff --git a/tests/core/pagination.test.ts b/tests/core/pagination.test.ts index 0deed91..0c11e1b 100644 --- a/tests/core/pagination.test.ts +++ b/tests/core/pagination.test.ts @@ -9,8 +9,7 @@ describe('Pagination', () => { const sourceCode = (context) => { const { close } = context.data; - const { plotchar } = context.core; - const ta = context.ta; + const { plotchar, ta } = context.pine; const sma = ta.sma(close, 14); plotchar(sma, 'data'); @@ -173,7 +172,7 @@ describe('Pagination', () => { const sourceCode = (context) => { const { close } = context.data; - const ta = context.ta; + const { ta } = context.pine; const sma = ta.sma(close, 5); @@ -235,7 +234,7 @@ describe('Pagination', () => { const sourceCode = (context) => { const { close } = context.data; - const ta = context.ta; + const { ta } = context.pine; const sma = ta.sma(close, 5); return { sma }; }; diff --git a/tests/indicators/macd-pinescript.test.ts b/tests/indicators/macd-pinescript.test.ts index 1f7e08d..b90c4bb 100644 --- a/tests/indicators/macd-pinescript.test.ts +++ b/tests/indicators/macd-pinescript.test.ts @@ -5,34 +5,7 @@ import { Provider } from '@pinets/marketData/Provider.class'; describe('Indicators', () => { it('MACD from Pine Script source', async () => { - const pineTS = new PineTS(Provider.Binance, 'BTCUSDT', '1d', 100, 0, new Date('Dec 25 2024').getTime() - 1); - const code0 = ` -//@version=6 -indicator(title="Moving Average Convergence Divergence", shorttitle="MACD", timeframe="", timeframe_gaps=true) -// Getting inputs -fast_length = input(title = "Fast Length", defval = 12) -slow_length = input(title = "Slow Length", defval = 26) -src = input(title = "Source", defval = close) -signal_length = input.int(title = "Signal Smoothing", minval = 1, maxval = 50, defval = 9, display = display.data_window) -sma_source = input.string(title = "Oscillator MA Type", defval = "EMA", options = ["SMA", "EMA"], display = display.data_window) -sma_signal = input.string(title = "Signal Line MA Type", defval = "EMA", options = ["SMA", "EMA"], display = display.data_window) -// Calculating -fast_ma = sma_source == "SMA" ? ta.sma(src, fast_length) : ta.ema(src, fast_length) -slow_ma = sma_source == "SMA" ? ta.sma(src, slow_length) : ta.ema(src, slow_length) -macd = fast_ma - slow_ma -signal = sma_signal == "SMA" ? ta.sma(macd, signal_length) : ta.ema(macd, signal_length) -hist = macd - signal - -alertcondition(hist[1] >= 0 and hist < 0, title = 'Rising to falling', message = 'The MACD histogram switched from a rising to falling state') -alertcondition(hist[1] <= 0 and hist > 0, title = 'Falling to rising', message = 'The MACD histogram switched from a falling to rising state') - -hline(0, "Zero Line", color = color.new(#787B86, 50)) -plot(hist, title = "Histogram", style = plot.style_columns, color = (hist >= 0 ? (hist[1] < hist ? #26A69A : #B2DFDB) : (hist[1] < hist ? #FFCDD2 : #FF5252))) -plot(macd, title = "MACD", color = #2962FF) -plot(signal, title = "Signal", color = #FF6D00) -plotchar(hist >= 0 and hist[1] < hist, title = "Bullish", style = shape.triangleup, location = location.belowbar, color = #26A69A) -plotshape(hist >= 0 and hist[1] < hist, title = "Bullish", style = shape.triangleup, location = location.belowbar, color = #26A69A) -`; + const pineTS = new PineTS(Provider.Binance, 'BTCUSDT', '1d', 100, 0, new Date('Dec 25 2024').getTime() - 1); const code = ` //@version=6 @@ -68,4 +41,49 @@ plot(signal, "Signal line", #ff6d00) console.log(context.plots); expect(context.plots).toBeDefined(); }); + + + it('MACD from Pine Script source with switch/case statements', async () => { + const pineTS = new PineTS(Provider.Binance, 'BTCUSDT', '1d', 100, 0, new Date('Dec 25 2024').getTime() - 1); + + const code = ` +//@version=6 +indicator("Moving Average Convergence Divergence", "MACD", timeframe = "", timeframe_gaps = true) + +// Inputs +float sourceInput = input.source(close, "Source") +int fastLenInput = input.int(12, "Fast length", 1) +int slowLenInput = input.int(26, "Slow length", 1) +int sigLenInput = input.int(9, "Signal length", 1) +string oscTypeInput = input.string("EMA", "Oscillator MA type", ["EMA", "SMA"], display = display.data_window) +string sigTypeInput = input.string("EMA", "Signal MA type", ["EMA", "SMA"], display = display.data_window) + +// @function Calculates an EMA or SMA of a 'source' series. +ma(float source, int length, simple string maType) => + switch maType + "EMA" => ta.ema(source, length) + "SMA" => ta.sma(source, length) + +// Calculate and plot the MACD, signal, and histogram values. +float maFast = ma(sourceInput, fastLenInput, oscTypeInput) +float maSlow = ma(sourceInput, slowLenInput, oscTypeInput) +float macd = maFast - maSlow +float signal = ma(macd, sigLenInput, sigTypeInput) +float hist = macd - signal +color hColor = hist >= 0 ? hist > hist[1] ? #26a69a : #b2dfdb : hist > hist[1] ? #ffcdd2 : #ff5252 + +hline(0, "Zero", #787b8680) +plot(hist, "Histogram", hColor, style = plot.style_columns) +plot(macd, "MACD") +plot(signal, "Signal line", #ff6d00) + +// Create alert conditions. +alertcondition(hist[1] >= 0 and hist < 0, "Rising to falling", "MACD histogram switched from a rising to falling state") +alertcondition(hist[1] <= 0 and hist > 0, "Falling to rising", "MACD histogram switched from a falling to rising state") +`; + const context = await pineTS.run(code); + + console.log(context.plots); + expect(context.plots).toBeDefined(); + }); }); diff --git a/tests/transpiler/pinescript-to-js.test.ts b/tests/transpiler/pinescript-to-js.test.ts index 29aa22b..12a3632 100644 --- a/tests/transpiler/pinescript-to-js.test.ts +++ b/tests/transpiler/pinescript-to-js.test.ts @@ -375,7 +375,7 @@ plot(gt ? 1 : 0) const result = transpile(code); const jsCode = result.toString(); - expect(jsCode).toContain('$.math.__eq('); + expect(jsCode).toContain('$.pine.math.__eq('); expect(jsCode).toContain('>'); expect(jsCode).toContain('<'); expect(jsCode).toContain('>='); diff --git a/tests/transpiler/pinets-source-to-js.test.ts b/tests/transpiler/pinets-source-to-js.test.ts index 95bc559..38b01b9 100644 --- a/tests/transpiler/pinets-source-to-js.test.ts +++ b/tests/transpiler/pinets-source-to-js.test.ts @@ -245,7 +245,7 @@ let src_open = input.any({ title: 'Open Source', defval: open }); const p1 = ta.param(14, undefined, 'p1'); const temp_1 = ta.sma(p0, p1, "_ta0"); $.const.glb1_sma = $.init($.const.glb1_sma, temp_1); - if ($.math.__eq($.get(low, 0), NaN)) { + if ($.pine.math.__eq($.get(low, 0), NaN)) { $.const.if2_data3 = $.init($.const.if2_data3, $.get(high, 0)); } }`; @@ -929,7 +929,7 @@ let src_open = input.any({ title: 'Open Source', defval: open }); for (let i = 1; i <= $.get(avg_len, 0); i++) { $.set($$.let.fn2_ret_val, $.get($$.let.fn2_ret_val, 0) + $.get(avg_src, i)); } - if ($.math.__eq($.get(avg_len, 0), 0)) { + if ($.pine.math.__eq($.get(avg_len, 0), 0)) { $.set($$.let.fn2_ret_val, $.get($$.let.fn2_cc, 1)); } return $.precision($.get($$.let.fn2_ret_val, 0) / $.get(avg_len, 0)); @@ -1120,7 +1120,7 @@ let src_open = input.any({ title: 'Open Source', defval: open }); for (let i = 1; i <= $.get(avg_len, 0); i++) { $.set($$.let.fn1_ret_val, $.get($$.let.fn1_ret_val, 0) + $.get(avg_src, i)); } - if ($.math.__eq($.get(avg_len, 0), 0)) { + if ($.pine.math.__eq($.get(avg_len, 0), 0)) { $.set($$.let.fn1_ret_val, $.get($$.let.fn1_cc, 1)); } return $.precision($.get($$.let.fn1_ret_val, 0) / $.get(avg_len, 0)); diff --git a/tests/utils.ts b/tests/utils.ts index 8cece25..146ebb8 100644 --- a/tests/utils.ts +++ b/tests/utils.ts @@ -54,10 +54,7 @@ export async function runNSFunctionWithArgs(klines: any[], ns: string, fn: strin let sourceCode = `(context) =>{ const { close, open, high, low, hlc3, volume, hl2, ohlc4 } = context.data; - const { plotchar, plot, na } = context.core; - const ta = context.ta; - const math = context.math; - const input = context.input; + const { plotchar, plot, na, ta, math, input } = context.pine; const values = ${ns}.${fn}(${args.join(',')}); From bf553c8d1a332162c4751b90bb357f723b4c537a Mon Sep 17 00:00:00 2001 From: alaa-eddine Date: Tue, 27 Jan 2026 02:09:47 +0100 Subject: [PATCH 2/2] v0.8.5 Transpiler Hotfixes --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 530855b..d353d22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Change Log +## [0.8.5] - 2026-01-27 - Transpiler Hotfixes + +### Fixed +- **Deprecation Warnings**: Fixed wrong warning message appearing with valid code. +- **Pine Script Parser**: Fixed multiline Pine Script conditions parsing (indent error). +- **Transpiler**: Fixed `switch` statement syntax conversion. + ## [0.8.4] - 2026-01-24 - Math Namespace Enhancements & Critical Fixes ### Added diff --git a/package.json b/package.json index 68eb8f1..e840941 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pinets", - "version": "0.8.4", + "version": "0.8.5", "description": "Run Pine Script anywhere. PineTS is an open-source transpiler and runtime that brings Pine Script logic to Node.js and the browser with 1:1 syntax compatibility. Reliably write, port, and run indicators or strategies on your own infrastructure.", "keywords": [ "Pine Script",