From 6b2055eeceebc99d1effa3f9cc54444b46ea4fcd Mon Sep 17 00:00:00 2001 From: Luan Date: Wed, 10 Jul 2024 01:38:27 -0300 Subject: [PATCH] refactor(main.ts): Simplify logic for defining objects (#14) * Add support for `Array`. * refactor(CallExpression): Improve error handling * fix(NewExpression): Don't pass args if not needed * chore: Misc updates * chore: Update tests * chore: Update readme * chore: lint * refactor: Move evaluation logic out of the constructor * chore: Update readme --- README.md | 57 +++--- examples/test-code.js | 342 ++++++++++++++++++---------------- jest.config.js | 4 +- src/main.ts | 103 +++------- src/nodes/CallExpression.ts | 30 ++- src/nodes/Identifier.ts | 2 +- src/nodes/MemberExpression.ts | 2 +- src/nodes/NewExpression.ts | 2 +- src/utils/index.ts | 12 ++ src/visitor.ts | 4 +- test.mjs | 11 ++ test/main.test.ts | 197 +++++++++++--------- 12 files changed, 403 insertions(+), 363 deletions(-) create mode 100644 test.mjs diff --git a/README.md b/README.md index 9c9f8ad..b0aa0fe 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ - [Installation](#installation) - [Usage](#usage) - [API](#api) - - [`interpret()`](#interpret) + - [`evaluate(input: string)`](#evaluateinput-string) - [`visitor`](#visitor) - [`scope`](#scope) - [License](#license) @@ -43,48 +43,63 @@ const code = ` sayHiTo('mom'); ` -const jinter = new Jinter(code); -jinter.interpret(); +const jinter = new Jinter(); +jinter.evaluate(code); ``` --- -Inject your own functions and variables into the interpreter: +Inject your own functions, objects, etc: ```ts -// ... +import { Jinter } from './dist/index.js'; -jinter.visitor.on('println', (node, visitor) => { - if (node.type === 'CallExpression' && node.callee.type === 'MemberExpression') { - const args = node.arguments.map((arg) => visitor.visitNode(arg)); - return console.log(...args); +const jinter = new Jinter(); + +const code = ` + console.log(new SomeClass().a); + console.log('hello'.toArray()); + + function myFn() { + console.log('[myFn]: Who called me?'); } -}); + + myFn(); +`; + +class SomeClass { + constructor() { + this.a = 'this is a test'; + } +} + +jinter.defineObject('SomeClass', SomeClass); // Ex: str.toArray(); jinter.visitor.on('toArray', (node, visitor) => { if (node.type === 'CallExpression' && node.callee.type === 'MemberExpression') { const obj = visitor.visitNode(node.callee.object); - return obj.split(''); - } + return obj.split(''); + } }); -// Or you can just intercept access to specific nodes; -jinter.visitor.on('myFn', (node, visitor) => { - console.info('MyFn node just got accessed:', node); - return 'proceed'; // tells the interpreter to continue execution +// Intercept function calls +jinter.visitor.on('myFn', (node) => { + if (node.type == 'CallExpression') + console.info('myFn was called!'); + return '__continue_exec'; }); -jinter.interpret(); +jinter.evaluate(code); ``` For more examples see [`/test`](https://github.com/LuanRT/Jinter/tree/main/test) and [`/examples`](https://github.com/LuanRT/Jinter/tree/main/examples). ## API -* Jinter(code: string) - * [`interpret()`](#interpret) +* Jinter() + * [`evaluate(input: string)`](#evaluate) * [`visitor`](#visitor) * [`scope`](#scope) -### `interpret()` -Interprets the code passed to the constructor. +### `evaluate(input: string)` +Evaluates the given JavaScript code. ### `visitor` The node visitor. This is responsible for walking the AST and executing the nodes. diff --git a/examples/test-code.js b/examples/test-code.js index 98ac721..ac6870d 100644 --- a/examples/test-code.js +++ b/examples/test-code.js @@ -1,189 +1,211 @@ +/* eslint-disable */ // YouTube's nsig cipher function transform(a) { - let b = a.split(""), - c = [null, -467929786, 20693572, function (d, e, f, h, l, m) { - return e(h, l, m) - }, - function (d, e) { - 0 != d.length && (e = (e % d.length + d.length) % d.length, d.splice(0, 1, d.splice(e, 1, d[0])[0])) - }, - b, 263517048, -1768090663, -159579355, -220717995, 1402181206, -1960874026, -541505454, - function () { - for (var d = 64, e = []; ++d - e.length - 32;) { - switch (d) { - case 91: - d = 44; - continue; - case 123: - d = 65; - break; - case 65: - d -= 18; - continue; - case 58: - d = 96; - continue; - case 46: - d = 95 - } - e.push(String.fromCharCode(d)) - } - return e - }, - -1156421728, -898813806, - function (d, e) { - d.push(e) - }, - "throw", 63940368, 909799102, 1036396083, -678760656, -823261927, - function (d, e) { - e = (e % d.length + d.length) % d.length; - d.splice(e, 1) - }, - "\u22bc{]", - function (d, e, f, h, l, m, n, p) { - return e(f, h, l, m, n, p) - }, - 1610130803, 2136823640, null, -468866158, 826740834, 617227783, 544797833, - function (d) { - d.reverse() - }, - -1107841705, b, 700278204, 2102866214, b, -1427608738, 1434467991, 805983840, "'}}]", - function (d, e, f) { - var h = f.length; - d.forEach(function (l, m, n) { - this.push(n[m] = f[(f.indexOf(l) - f.indexOf(this[m]) + m + h--) % f.length]) - }, e.split("")) - }, - 287016001, - function () { - for (var d = 64, e = []; ++d - e.length - 32;) switch (d) { - case 58: - d = 96; - continue; + let b = String.prototype.split.call(a, ""), + c = [-1451594444, 1301161877, function () { + for (var d = 64, e = []; ++d - e.length - 32;) { + switch (d) { case 91: d = 44; + continue; + case 123: + d = 65; break; case 65: - d = 47; + d -= 18; + continue; + case 58: + d = 96; continue; case 46: - d = 153; - case 123: - d -= 58; - default: - e.push(String.fromCharCode(d)) - } - return e - }, - -774661505, -654005396, 410973967, -170640037, - function (d, e) { - e = (e % d.length + d.length) % d.length; - d.splice(-e).reverse().forEach(function (f) { - d.unshift(f) - }) - }, - 161493801, -1113374319, -1314155185, -1475572784, 1674445585, 106972131, -472560708, - function () { - for (var d = 64, e = []; ++d - e.length - 32;) { - switch (d) { - case 58: - d -= 14; - case 91: - case 92: - case 93: - continue; - case 123: - d = 47; - case 94: - case 95: - case 96: - continue; - case 46: - d = 95 - } - e.push(String.fromCharCode(d)) - } - return e - }, - function (d) { - for (var e = d.length; e;) d.push(d.splice(--e, 1)[0]) - }, - 1810055978, -593884348, - function (d, e) { - for (d = (d % e.length + e.length) % e.length; d--;) e.unshift(e.pop()) - }, - "',;,", -654005396, -334104963, - function (d, e, f, h, l) { - return e(f, h, l) - }, - 1264794349, - function (d, e) { - if (0 != d.length) { - e = (e % d.length + d.length) % d.length; - var f = d[0]; - d[0] = d[e]; - d[e] = f + d = 95 } - }, - null, -994950726, 323679202, 2115002765, - function (d, e, f, h, l, m, n) { - return d(l, m, n) - }, - 492882935, 909933502, -1673096547, 1729335177, - function () { - for (var d = 64, e = []; ++d - e.length - 32;) switch (d) { - case 46: - d = 95; - default: - e.push(String.fromCharCode(d)); + e.push(String.fromCharCode(d)) + } + return e + }, + 649805386, -1685245837, "catch", 681943953, 583860904, 1296172818, 425880877, 287270362, 315751560, + function (d) { + for (var e = d.length; e;) d.push(d.splice(--e, 1)[0]) + }, + function (d, e) { + d.splice(d.length, 0, e) + }, + 1275414153, + function () { + for (var d = 64, e = []; ++d - e.length - 32;) { + switch (d) { + case 58: + d -= 14; + case 91: + case 92: + case 93: + continue; + case 123: + d = 47; case 94: case 95: case 96: - break; + continue; + case 46: + d = 95 + } + e.push(String.fromCharCode(d)) + } + return e + }, + 498111650, 1202497635, -1995710382, -1938677291, -889657924, 175902055, 2017394798, 195881378, -1693441371, -1975261179, -248934245, 1670929676, -466078905, + function (d, e, f, h, l, m) { + return e(h, l, m) + }, + -505676905, + function (d, e, f, h, l) { + return e(f, h, l) + }, + -1965308853, + function (d, e) { + d.length != 0 && (e = (e % d.length + d.length) % d.length, d.splice(0, 1, d.splice(e, 1, d[0])[0])) + }, + -333520374, -94023361, -1505266190, -2094368029, + function (d) { + throw d; + }, + 569341060, + function (d, e) { + for (d = (d % e.length + e.length) % e.length; d--;) e.unshift(e.pop()) + }, + 1053915394, b, -94023361, + function (d, e, f, h, l, m, n, p) { + return e(f, h, l, m, n, p) + }, + 1597737293, b, null, + function (d, e) { + e = (e % d.length + d.length) % d.length; + d.splice(e, 1) + }, + function () { + for (var d = 64, e = []; ++d - e.length - 32;) switch (d) { + case 46: + d = 95; + default: + e.push(String.fromCharCode(d)); + case 94: + case 95: + case 96: + break; + case 123: + d -= 76; + case 92: + case 93: + continue; + case 58: + d = 44; + case 91: + } + return e + }, + -2007431109, -2134753269, -69827996, 554723023, 1603647478, 1083969154, -238723852, + function (d, e) { + e = (e % d.length + d.length) % d.length; + d.splice(-e).reverse().forEach(function (f) { + d.unshift(f) + }) + }, + 230292577, + function (d) { + d.reverse() + }, + 1474273583, b, + function (d, e) { + e.push(d) + }, + function () { + for (var d = 64, e = []; ++d - e.length - 32;) { + switch (d) { + case 91: + d = 44; + continue; case 123: - d -= 76; - case 92: - case 93: + d = 65; + break; + case 65: + d -= 18; continue; case 58: - d = 44; - case 91: + d = 96; + continue; + case 46: + d = 95 } - return e - }, - -1325829678, -2019708971 + e.push(String.fromCharCode(d)) + } + return e + }, + -384262400, null, + function () { + for (var d = 64, e = []; ++d - e.length - 32;) switch (d) { + case 58: + d = 96; + continue; + case 91: + d = 44; + break; + case 65: + d = 47; + continue; + case 46: + d = 153; + case 123: + d -= 58; + default: + e.push(String.fromCharCode(d)) + } + return e + }, + 1556991112, + function (d, e, f) { + var h = e.length; + d.forEach(function (l, m, n) { + this.push(n[m] = e[(e.indexOf(l) - e.indexOf(this[m]) + m + h--) % e.length]) + }, f.split("")) + }, + function (d, e) { + if (e.length != 0) { + d = (d % e.length + e.length) % e.length; + var f = e[0]; + e[0] = e[d]; + e[d] = f + } + }, + null, 1056973316, + function (d, e, f, h, l, m, n, p, q, r) { + return e(l, m, n, p, q, r) + }, + 50789005, 1362852578, -253339780, ",55];c[52]=c;", "9SsPP" ]; - c[0] = c; - c[28] = c; - c[69] = c; + c[47] = c; + c[65] = c; + c[70] = c; try { try { - -3 != c[67] && (8 < c[7] ? (((0, c[43])(c[5], c[17], (0, c[45])()), (0, c[60 + Math.pow(7, 1) + -44])(c[0], c[8]), c[33])(c[38]), ((0, c[4])(c[69], c[new Date("1969-12-31T18:45:19.000-05:15") / 1E3]), c[43])(c[38], c[17], (0, c[13])()), c[43])(c[35], c[17], (0, c[45])()) : ((0, c[66])(((0, c[4])(c[28], c[31]), c[68])(c[35], c[9]), c[50], c[69], c[11]), c[41])((0, c[60])(c[61], c[71]), c[43], c[10], c[77]) & (0, c[25])(c[10], c[26])), c[6] >= 38 + Math.pow(8, 5) - 32811 && (-7 != c[57] || ((0, c[new Date("1970-01-01T04:00:25.000+04:00") / - 1E3])(c[44], c[251 + Math.pow(8, 1) - 238]), 0)) && (0, c[72])(c[44], c[51]), 9 !== c[66] && (-8 === c[28] || ((0, c[43])(c[157 - Math.pow(3, 5) + 99], c[24]), 0)) && (0, c[79])(c[61], c[74]), -4 > c[19] ? (0, c[37])(c[50], c[61]) : (0, c[25])(c[3], c[12]), (-1 !== c[30] || ((0, c[35])((0, c[42])(c[12], (0, c[35])((0, c[54])(c[55], c[64]), c[2], c[4]), ((0, c[Math.pow(6, 5) - 5 + -7743])(c[55]), (0, c[12])(c[55], c[67], (0, c[14])())), (0, c[53])((0, c[73])(c[7], c[Math.pow(3, 2) + 135 + -134]), c[12], (0, c[19])(c[79], c[13]), c[55], c[67], (0, c[47])()), c[55], c[67], (0, c[63])()), - c[12], c[7], c[Math.pow(5, 2) + 20 - -22], (0, c[47])()), null)) && ((0, c[new Date("1970-01-01T10:01:16.000+10:00") / 1E3])((0, c[66])(c[38], c[12]), c[53], (0, c[37])(c[4], c[52]), c[19], ((((0, c[54])(c[4], c[21]), c[28])(c[7]), c[19])(c[38], c[5]), c[20])(c[70], c[17]), c[38], c[5]), ((0, c[11])(c[Math.pow(4, 1) + -7220 - -7254]), c[37])(c[73], c[43])) - } catch (d) { - (0, c[36])((0, c[56])(c[38], c[8]), c[65], (0, c[65])(c[73], c[50], (0, c[30])()), c[73], c[50], (0, c[30])()), ((0, c[14])(c[29], c[new Date("1970-01-01T08:15:21.000+08:15") / 1E3]), c[56])(c[73], - c[28]) - } finally { - 4 > c[31] && (0, c[-140 - Math.pow(3, 1) + new Date("1969-12-31T13:32:41.000-10:30") / 1E3])((0, c[37])(c[70], c[23]), c[68], c[70]) - } - try { - 2 > c[41] && (c[35] >= 5 - Math.pow(2, 3) % 8 ? (((0, c[11])(c[40]), (0, c[Math.pow(3, new Date("1970-01-01T02:45:03.000+02:45") / 1E3) - 13 - -31])(c[42], c[22]), c[62])(c[42], c[51]), ((0, c[45])(c[12], c[13]), c[17])(c[44], c[32], (0, c[72])())) : ((((0, c[71])(c[20]), c[45])(c[9], c[82]), (0, c[14])(c[20]), c[26])(c[61], c[18]), c[26])(c[9], c[15])), (1 < c[81] || ((0, c[80])(c[42], c[60]), 0)) && (0, c[5])(c[32], - c[20], (0, c[24])()), (-2 === c[70] || (((((0, c[2])(c[32]), c[56])(c[15], c[80]), (0, c[33])(c[30], c[46]), c[50])(c[new Date("1969-12-31T17:01:20.000-07:00") / 1E3], c[37]), c[59])(c[8]), 0)) && (((0, c[68])(c[2], c[73]), c[49])(c[50], c[65]), (0, c[249 % Math.pow(7, 1) + 64])(c[74], c[38]), c[30])((0, c[68])(c[80], c[3]), c[77], c[2], c[62], (0, c[58])()), 10 == c[12] && ((0, c[7])(c[80], c[62], (0, c[42])()), 10) || (0, c[49])(c[Math.pow(5, 3) - 33998 + 33906], c[44]) + c[0] < -4 && (c[8] < -9 || (((0, c[48])(c[61], c[64]), c[33])(c[42], c[11]), (0, c[40])(c[20], c[47]), (0, c[73])(c[75]), (0, c[47])(c[56], c[42]), 0)) && ((((0, c[4])(c[60], (0, c[2])(), c[19]), c[4])(c[56], (0, c[16])(), c[13]), (0, c[54])(c[14], c[56]), c[5])(c[7], c[6]), c[62])(c[1], c[67]), c[28] !== -10 && ((0, c[62])(c[56], c[33]), c[5])(c[64], c[-54718 + Math.pow(1, 1) - -54792]), c[44] > -3 ? (0, c[45])((0, c[62])(c[75], c[9]), c[47], c[56], c[57]) : (0, c[45])((0, c[54])(c[10212 - + Math.pow(7, 4) - 7790], c[61]), c[70], c[Math.pow(1, new Date("1970-01-01T02:45:01.000+02:45") / 1E3) * -192 + 261], c[13]), c[71] > 5 ? ((0, c[11])(c[76]), c[42])(c[76]) : (0, c[61])((0, c[20])(c[13], (0, c[1])(), c[35]), c[0], c[22], c[66]), c[62] < 10 && (0, c[59])(((((0, c[42])(c[17]), c[77])(c[1], c[20]), c[57])(c[5], (0, c[45])(), c[48]), c[Math.pow(2, 3) % 313 + 27])(c[5]), c[Math.pow(3, 4) + 26245 - 26319], (0, c[57])(c[1], (0, c[59])(), c[Math.pow(8, 2) + 23958 - 23980]), c[71], c[60]), c[55] != 5 && (0, c[18])(c[71 - Math.pow(7, 1) % 115], c[47]), c[69] !== -2 && (0, c[7])((0, c[new Date("1970-01-01T07:15:57.000+07:15") / + 1E3])((((0, c[72])(c[64], c[17]), ((0, c[21])(c[65]), c[0])(c[65], c[3]), c[21])(c[new Date("1969-12-31T22:15:50.000-01:45") / 1E3]), c[0])(c[50], c[23]), c[2], (0, c[60])(c[24], c[64]), ((0, c[1])(c[26], c[6]), c[19])(c[60], c[20]), (0, c[Math.pow(new Date("1970-01-01T01:30:07.000+01:30") / 1E3, 5) - -13272 - 30018])(c[38], c[319 % Math.pow(8, 5) + -259]), c[0], c[60], c[30]) | (0, c[19])(c[65], c[11]), c[21], (0, c[19])(c[69], c[34]), c[19], c[5], c[36]) } catch (d) { - (0, c[30])((0, c[14])(c[2], c[24]), c[7], c[2], c[62], (0, c[78 % Math.pow(3, 5) - 56])()), - (0, c[14])(c[2], c[28]), (0, c[87 - Math.pow(3, 3) + 17])(c[new Date("1969-12-31T18:15:02.000-05:45") / 1E3], c[62], (0, c[9])()) + (0, c[Math.pow(5, 3) - 229 - -166])(c[10], (0, c[3])(), c[53]), ((0, c[1])(c[60], c[0]), c[4])(c[6], + c[12]), (0, c[4])(c[10], c[46]), (0, c[4])(c[69], c[49]), (0, c[1])(c[31], c[10]), (0, c[4])(c[69], c[74]) } try { - -4 !== c[73] && (((0, c[32])(c[52], c[25]), c[49])(c[0], c[18]), 1) || (0, c[30])((0, c[23])(c[33]), c[32], c[Math.pow(1, 2) + 251 + -250], c[4]), -3 != c[57] && (3 == c[19] && ((0, c[82])(c[50]), 1) || (0, c[26])(c[19], c[33])), c[-117 * Math.pow(6, 4) + 151632] <= new Date("1970-01-01T11:14:55.000+11:15") / 1E3 && (-3 >= c[76] ? (((0, c[4])((0, c[42])(c[73], c[68]), c[38], (0, c[22])(c[62], c[39]), c[18], c[73]), c[40])(c[11]), c[51])(c[73], - c[-181 * Math.pow(8, 3) - -92737]) : ((((0, c[33])(c[Math.pow(8, 1) - 6138 - -6201], c[70]), c[13])(c[16], c[81], (0, c[0])()), (0, c[68])(c[16], c[11]), c[42])(c[69]), c[18])(c[73])) + c[55] != -3 && ((((0, c[1])(c[7], c[10]), c[73])(c[0], c[70]), c[13])(c[78], c[33]), "1") || (0, c[15])(((0, c[56])(c[63], (0, c[76])(), c[41]), c[77])(c[40], c[50]), c[13], c[40], c[45]), (0, c[15])(((0, c[8])(), c[56])(c[63], (0, c[31])(), c[41]), c[77], c[4], c[24]) } catch (d) { - (((0, c[4])(c[16], c[40]), c[42])(c[69]), c[13])(c[69], c[81], (0, c[41])()) + c[70] != -10 && (c[75] != 6 && ((0, c[67])(c[40], c[21]), "undefined") || (0, c[235 * Math.pow(5, 3) - 29298])(c[73], c[69])), c[Math.pow(1, 4) + 42 + -4] > 3 && (c[57] == -4 ? (((0, c[56])(c[4], + (0, c[58])(), c[47]), c[65])(c[73]), c[77])(c[40], c[57]) : ((0, c[55])(c[38], c[2457 - Math.pow(4, 2) * 149]), c[new Date("1969-12-31T13:45:15.000-10:15") / 1E3])((0, c[55])(c[22], c[59]), c[74], c[49], c[73])), c[5] >= 297 % Math.pow(6, 4) + -303 && (c[53] == 1 && ((0, c[144 % Math.pow(3, 1) + 42])((((0, c[4])(c[67], (0, c[6])(), c[74]), c[4])(c[Math.pow(4, 3) * 110 - 7009], (0, c[9])(), c[68]), c[3])(c[50], c[31]), c[4], c[31], (0, c[6])(), c[68]), /,"(),/) || ((0, c[3])(c[56], c[26]) <= (0, c[13])(c[26]), (0, c[36])((0, c[75])(c[64], c[71]), c[68], c[1], c[71]))) } } catch (d) { - return "enhanced_except_vZcBwuj-_w8_" + a + return "enhanced_except_yJsBt-T-_w8_" + + a } - return b.join(""); + return Array.prototype.join.call(b, ""); } transform(ntoken); \ No newline at end of file diff --git a/jest.config.js b/jest.config.js index f77904d..ccd4c87 100644 --- a/jest.config.js +++ b/jest.config.js @@ -4,9 +4,9 @@ export default { displayName: 'node', roots: [ '/test' ], transform: { - "^.+\\.(ts|tsx)$": "ts-jest", + '^.+\\.(ts|tsx)$': 'ts-jest' }, - moduleFileExtensions: ["ts", "tsx", "js"], + moduleFileExtensions: [ 'ts', 'tsx', 'js' ], testMatch: [ '**/*.test.ts' ], setupFiles: [] } diff --git a/src/main.ts b/src/main.ts index a23746f..58c76d8 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,107 +1,64 @@ import Visitor from './visitor.js'; import { parse } from 'acorn'; import type { Node } from 'estree'; +import { JinterError } from './utils/index.js'; export default class Jinter { - #ast: Node[]; + #ast: Node[] = []; /** * The node visitor. This is responsible for walking the AST and executing the nodes. */ public visitor: Visitor; - /** * The global scope of the program. */ public scope: Map; - constructor(input: string) { - const program = parse(input, { ecmaVersion: 2020 }); - - this.#ast = program.body; - - this.visitor = new Visitor(this.#ast); + constructor() { + this.visitor = new Visitor(); this.scope = this.visitor.scope; - this.scope.set('print', (args: any[]) => console.log(...args)); + this.defineObject('console', console); + this.defineObject('Math', Math); + this.defineObject('String', String); + this.defineObject('Array', Array); + this.defineObject('Date', Date); + } - this.visitor.on('console', (node, visitor) => { - if (node.type === 'Identifier') - return console; - - if (node.type === 'CallExpression' && node.callee.type === 'MemberExpression') { - const prop: keyof Console = visitor.visitNode(node.callee.property); - const args = node.arguments.map((arg) => visitor.visitNode(arg)); - - const console_prop = console[prop] as Function; - - if (!console_prop) - return 'proceed'; - - return console_prop(...args); - } return 'proceed'; - }); - - this.visitor.on('Math', (node, visitor) => { - if (node.type === 'Identifier') - return Math; - - if (node.type === 'CallExpression' && node.callee.type === 'MemberExpression') { - const prop: keyof Math = visitor.visitNode(node.callee.property); - const args = node.arguments.map((arg) => visitor.visitNode(arg)); - const math_prop = Math[prop] as Function; - - if (!math_prop) - return 'proceed'; - - return math_prop(...(args as [number, number])); - } return 'proceed'; - }); - - this.visitor.on('String', (node, visitor) => { - if (node.type === 'Identifier') - return String; - - if (node.type === 'CallExpression' && node.callee.type === 'MemberExpression') { - const prop: keyof typeof String = visitor.visitNode(node.callee.property); - const args = node.arguments.map((arg) => visitor.visitNode(arg)); - const string_prop = String[prop] as Function; - - if (!string_prop) - return 'proceed'; - - return string_prop(args); - } return 'proceed'; - }); - - this.visitor.on('Array', (node, visitor) => { + public defineObject(name: string, obj: T) { + this.visitor.on(name, (node, visitor) => { if (node.type === 'Identifier') - return Array; + return obj; if (node.type === 'CallExpression' && node.callee.type === 'MemberExpression') { - const prop: keyof typeof Array = visitor.visitNode(node.callee.property); + const prop: keyof T = visitor.visitNode(node.callee.property); const args = node.arguments.map((arg) => visitor.visitNode(arg)); - const array_prop = Array[prop] as Function; - - if (!array_prop) - return 'proceed'; + const callable = obj[prop] as Function | undefined; - return array_prop(args); - } return 'proceed'; - }); + if (!callable) + return '__continue_exec'; - this.visitor.on('Date', (node) => { - if (node.type === 'Identifier') - return Date; + return callable.apply(obj, args); + } return '__continue_exec'; }); } /** - * Interprets the program. + * Evaluates the program. * @returns The result of the last statement in the program. */ - public interpret() { + public evaluate(input: string) { + try { + const program = parse(input, { ecmaVersion: 2020 }); + this.#ast = program.body; + } catch (e: any) { + throw new JinterError(`Syntax error: ${e.message}`); + } + + this.visitor.setAST(this.#ast); + return this.visitor.run(); } } diff --git a/src/nodes/CallExpression.ts b/src/nodes/CallExpression.ts index 527b625..b18d520 100644 --- a/src/nodes/CallExpression.ts +++ b/src/nodes/CallExpression.ts @@ -1,6 +1,7 @@ import type Visitor from '../visitor.js'; import type ESTree from 'estree'; import BaseJSNode from './BaseJSNode.js'; +import { JinterError } from '../utils/index.js'; export default class CallExpression extends BaseJSNode { public run() { @@ -17,7 +18,7 @@ export default class CallExpression extends BaseJSNode { // Obj.fn(...); if (exp_object && this.visitor.listeners[exp_object]) { const cb = this.visitor.listeners[exp_object](this.node, this.visitor); - if (cb !== 'proceed') { + if (cb !== '__continue_exec') { return cb; } } @@ -25,7 +26,7 @@ export default class CallExpression extends BaseJSNode { // ?.fn(...); if (exp_property && exp_property !== 'toString' && this.visitor.listeners[exp_property]) { const cb = this.visitor.listeners[exp_property](this.node, this.visitor); - if (cb !== 'proceed') { + if (cb !== '__continue_exec') { return cb; } } @@ -39,6 +40,9 @@ export default class CallExpression extends BaseJSNode { const prop = this.node.callee.computed ? this.visitor.visitNode(this.node.callee.property) : this.visitor.getName(this.node.callee.property); const args = this.node.arguments.map((arg) => this.visitor.visitNode(arg)); + if (!obj) + this.#throwError(); + if (typeof obj[prop] !== 'function') this.#throwError(); @@ -58,10 +62,9 @@ export default class CallExpression extends BaseJSNode { } #throwError() { - if (this.node.callee.type === 'MemberExpression') { - throw new Error(`${this.node.callee.object.type === 'Identifier' ? this.node.callee.object.name : ''}.${this.node.callee.property.type === 'Identifier' ? this.node.callee.property.name : '?'} is not a function`); - } else if (this.node.callee.type === 'Identifier') { - throw new Error(`${this.node.callee.name} is not a function`); + if (this.node.callee.type === 'MemberExpression' || this.node.callee.type === 'Identifier') { + const callee_string = this.#getCalleeString(this.node.callee); + throw new JinterError(`${callee_string} is not a function`); } else if (this.node.callee.type === 'SequenceExpression') { const call: string[] = []; const items: string[] = []; @@ -83,9 +86,20 @@ export default class CallExpression extends BaseJSNode { call.push(items.join(', ')); call.push(')'); - throw new Error(`${call.join('')} is not a function`); + throw new JinterError(`${call.join('')} is not a function`); } } + + #getCalleeString(node: ESTree.MemberExpression | ESTree.Identifier): string { + if (node.type === 'Identifier') { + return node.name; + } else if (node.type === 'MemberExpression') { + const object_string = this.#getCalleeString(node.object as ESTree.MemberExpression | ESTree.Identifier); + const property_string = node.computed ? `[${this.visitor.getName(node.property) || '...'}]` : `.${this.visitor.getName(node.property)}`; + return `${object_string}${property_string}`; + } + return ''; + } } class Builtins { @@ -111,8 +125,6 @@ class Builtins { } else { console.warn('Unhandled callee type:', node.callee.type); } - - }, // Also override the toString method so that it stringifies the correct object toString: (node: ESTree.CallExpression, visitor: Visitor) => { diff --git a/src/nodes/Identifier.ts b/src/nodes/Identifier.ts index e1760c4..69f0e6b 100644 --- a/src/nodes/Identifier.ts +++ b/src/nodes/Identifier.ts @@ -5,7 +5,7 @@ export default class Identifier extends BaseJSNode { public run() { if (this.visitor.listeners[this.node.name]) { const cb = this.visitor.listeners[this.node.name](this.node, this.visitor); - if (cb !== 'proceed') { + if (cb !== '__continue_exec') { return cb; } } diff --git a/src/nodes/MemberExpression.ts b/src/nodes/MemberExpression.ts index 3fb8531..f9d304d 100644 --- a/src/nodes/MemberExpression.ts +++ b/src/nodes/MemberExpression.ts @@ -11,7 +11,7 @@ export default class MemberExpression extends BaseJSNode { public run() { const callee = this.visitor.visitNode(this.node.callee); const args = this.node.arguments.map((arg: any) => this.visitor.visitNode(arg)); - return new callee(args); + return args.length ? new callee(args) : new callee(); } } \ No newline at end of file diff --git a/src/utils/index.ts b/src/utils/index.ts index 33ca78e..34b6b98 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -10,4 +10,16 @@ export interface JSNode extends BaseJSNode { export interface JSNodeConstructor { new(node: ESTree.Node, visitor: Visitor): JSNode; +} + +export class JinterError extends Error { + info?: any; + + constructor(message: string, info?: any) { + super(message); + + if (info) { + this.info = info; + } + } } \ No newline at end of file diff --git a/src/visitor.ts b/src/visitor.ts index 6ed7be4..505125f 100644 --- a/src/visitor.ts +++ b/src/visitor.ts @@ -8,9 +8,9 @@ export type Listener = (node: Node, visitor: Visitor) => any; export default class Visitor { public scope: Map = new Map(); public listeners: { [key: string]: Listener } = {}; - public ast: Node[]; + public ast: Node[] = []; - constructor(ast: Node[]) { + public setAST(ast: Node[]) { this.ast = ast; } diff --git a/test.mjs b/test.mjs new file mode 100644 index 0000000..4150761 --- /dev/null +++ b/test.mjs @@ -0,0 +1,11 @@ +import { Jinter } from './dist/index.js'; +const code = ` + const a = "this is a test"; + const arr = String.prototype.split.call(a, ""); + const joined = Array.prototype.join.call(arr, ""); + console.log(new Date("2021-01-01").getTime()); + `; + +const jinter = new Jinter(code); + +console.log(jinter.interpret()); diff --git a/test/main.test.ts b/test/main.test.ts index e76892f..02a2256 100644 --- a/test/main.test.ts +++ b/test/main.test.ts @@ -8,8 +8,8 @@ describe('Jinter Tests', () => { const result = true && false; `; - const jinter = new Jinter(code); - jinter.interpret(); + const jinter = new Jinter(); + jinter.evaluate(code) expect(jinter.scope.get('result')).toEqual(false); }); @@ -19,8 +19,8 @@ describe('Jinter Tests', () => { const result = true || false; `; - const jinter = new Jinter(code); - jinter.interpret(); + const jinter = new Jinter(); + jinter.evaluate(code) expect(jinter.scope.get('result')).toEqual(true); }); @@ -30,8 +30,8 @@ describe('Jinter Tests', () => { const result = null ?? false; `; - const jinter = new Jinter(code); - jinter.interpret(); + const jinter = new Jinter(); + jinter.evaluate(code) expect(jinter.scope.get('result')).toEqual(false); }); @@ -44,8 +44,8 @@ describe('Jinter Tests', () => { const result = num++; `; - const jinter = new Jinter(code); - jinter.interpret(); + const jinter = new Jinter(); + jinter.evaluate(code) expect(jinter.scope.get('result')).toEqual(10); expect(jinter.scope.get('num')).toEqual(11); @@ -57,8 +57,8 @@ describe('Jinter Tests', () => { const result = num--; `; - const jinter = new Jinter(code); - jinter.interpret(); + const jinter = new Jinter(); + jinter.evaluate(code) expect(jinter.scope.get('result')).toEqual(10); expect(jinter.scope.get('num')).toEqual(9); @@ -70,8 +70,8 @@ describe('Jinter Tests', () => { const result = ++num; `; - const jinter = new Jinter(code); - jinter.interpret(); + const jinter = new Jinter(); + jinter.evaluate(code) expect(jinter.scope.get('result')).toEqual(11); expect(jinter.scope.get('num')).toEqual(11); @@ -83,8 +83,8 @@ describe('Jinter Tests', () => { const result = --num; `; - const jinter = new Jinter(code); - jinter.interpret(); + const jinter = new Jinter(); + jinter.evaluate(code) expect(jinter.scope.get('result')).toEqual(9); expect(jinter.scope.get('num')).toEqual(9); @@ -98,8 +98,8 @@ describe('Jinter Tests', () => { const result = -num; `; - const jinter = new Jinter(code); - jinter.interpret(); + const jinter = new Jinter(); + jinter.evaluate(code) expect(jinter.scope.get('result')).toEqual(-10); }); @@ -110,8 +110,8 @@ describe('Jinter Tests', () => { const result = +num; `; - const jinter = new Jinter(code); - jinter.interpret(); + const jinter = new Jinter(); + jinter.evaluate(code) expect(jinter.scope.get('result')).toEqual(10); }); @@ -122,8 +122,8 @@ describe('Jinter Tests', () => { const result = !bool; `; - const jinter = new Jinter(code); - jinter.interpret(); + const jinter = new Jinter(); + jinter.evaluate(code) expect(jinter.scope.get('result')).toEqual(false); }); @@ -134,8 +134,8 @@ describe('Jinter Tests', () => { const result = ~num; `; - const jinter = new Jinter(code); - jinter.interpret(); + const jinter = new Jinter(); + jinter.evaluate(code) expect(jinter.scope.get('result')).toEqual(-11); }); @@ -146,8 +146,8 @@ describe('Jinter Tests', () => { const result = typeof num; `; - const jinter = new Jinter(code); - jinter.interpret(); + const jinter = new Jinter(); + jinter.evaluate(code) expect(jinter.scope.get('result')).toEqual('number'); }); @@ -157,8 +157,8 @@ describe('Jinter Tests', () => { const result = void 0; `; - const jinter = new Jinter(code); - jinter.interpret(); + const jinter = new Jinter(); + jinter.evaluate(code) expect(jinter.scope.get('result')).toEqual(undefined); }); @@ -169,8 +169,8 @@ describe('Jinter Tests', () => { const result = delete obj.a; `; - const jinter = new Jinter(code); - jinter.interpret(); + const jinter = new Jinter(); + jinter.evaluate(code) expect(jinter.scope.get('result')).toEqual(true); expect(jinter.scope.get('obj')).toEqual({ b: 2, c: 3 }); @@ -184,8 +184,8 @@ describe('Jinter Tests', () => { const num_2 = 5; const result = num_1 == num_2; `; - const jinter = new Jinter(code); - jinter.interpret(); + const jinter = new Jinter(); + jinter.evaluate(code) expect(jinter.scope.get('result')).toEqual(false); }); @@ -196,8 +196,8 @@ describe('Jinter Tests', () => { const num_2 = '10'; const result = num_1 === num_2; `; - const jinter = new Jinter(code); - jinter.interpret(); + const jinter = new Jinter(); + jinter.evaluate(code) expect(jinter.scope.get('result')).toEqual(false); }); @@ -208,8 +208,8 @@ describe('Jinter Tests', () => { const num_2 = 5; const result = num_1 != num_2; `; - const jinter = new Jinter(code); - jinter.interpret(); + const jinter = new Jinter(); + jinter.evaluate(code) expect(jinter.scope.get('result')).toEqual(true); }); @@ -220,8 +220,8 @@ describe('Jinter Tests', () => { const num_2 = '10'; const result = num_1 !== num_2; `; - const jinter = new Jinter(code); - jinter.interpret(); + const jinter = new Jinter(); + jinter.evaluate(code) expect(jinter.scope.get('result')).toEqual(true); }); @@ -233,8 +233,8 @@ describe('Jinter Tests', () => { const result = num_1 > num_2; `; - const jinter = new Jinter(code); - jinter.interpret(); + const jinter = new Jinter(); + jinter.evaluate(code) expect(jinter.scope.get('result')).toEqual(true); }); @@ -246,8 +246,8 @@ describe('Jinter Tests', () => { const result = num_1 >= num_2; `; - const jinter = new Jinter(code); - jinter.interpret(); + const jinter = new Jinter(); + jinter.evaluate(code) expect(jinter.scope.get('result')).toEqual(true); }); @@ -259,8 +259,8 @@ describe('Jinter Tests', () => { const result = num_1 < num_2; `; - const jinter = new Jinter(code); - jinter.interpret(); + const jinter = new Jinter(); + jinter.evaluate(code) expect(jinter.scope.get('result')).toEqual(false); }); @@ -272,8 +272,8 @@ describe('Jinter Tests', () => { const result = num_1 + num_2; `; - const jinter = new Jinter(code); - jinter.interpret(); + const jinter = new Jinter(); + jinter.evaluate(code) expect(jinter.scope.get('result')).toEqual(15); }); @@ -285,8 +285,8 @@ describe('Jinter Tests', () => { const result = num_1 - num_2; `; - const jinter = new Jinter(code); - jinter.interpret(); + const jinter = new Jinter(); + jinter.evaluate(code) expect(jinter.scope.get('result')).toEqual(5); }); @@ -298,8 +298,8 @@ describe('Jinter Tests', () => { const result = num_1 * num_2; `; - const jinter = new Jinter(code); - jinter.interpret(); + const jinter = new Jinter(); + jinter.evaluate(code) expect(jinter.scope.get('result')).toEqual(50); }); @@ -311,8 +311,8 @@ describe('Jinter Tests', () => { const result = num_1 / num_2; `; - const jinter = new Jinter(code); - jinter.interpret(); + const jinter = new Jinter(); + jinter.evaluate(code) expect(jinter.scope.get('result')).toEqual(2); }); @@ -324,8 +324,8 @@ describe('Jinter Tests', () => { const result = num_1 ** num_2; `; - const jinter = new Jinter(code); - jinter.interpret(); + const jinter = new Jinter(); + jinter.evaluate(code) expect(jinter.scope.get('result')).toEqual(8); }); @@ -337,8 +337,8 @@ describe('Jinter Tests', () => { const result = num_1 % num_2; `; - const jinter = new Jinter(code); - jinter.interpret(); + const jinter = new Jinter(); + jinter.evaluate(code) expect(jinter.scope.get('result')).toEqual(1); }); @@ -349,8 +349,8 @@ describe('Jinter Tests', () => { const num_2 = 3; const result = num_1 << num_2; `; - const jinter = new Jinter(code); - jinter.interpret(); + const jinter = new Jinter(); + jinter.evaluate(code) expect(jinter.scope.get('result')).toEqual(80); }); @@ -361,8 +361,8 @@ describe('Jinter Tests', () => { const num_2 = 3; const result = num_1 >> num_2; `; - const jinter = new Jinter(code); - jinter.interpret(); + const jinter = new Jinter(); + jinter.evaluate(code) expect(jinter.scope.get('result')).toEqual(1); }); @@ -373,8 +373,8 @@ describe('Jinter Tests', () => { const num_2 = 3; const result = num_1 >>> num_2; `; - const jinter = new Jinter(code); - jinter.interpret(); + const jinter = new Jinter(); + jinter.evaluate(code) expect(jinter.scope.get('result')).toEqual(1); }); @@ -385,8 +385,8 @@ describe('Jinter Tests', () => { const num_2 = 3; const result = num_1 & num_2; `; - const jinter = new Jinter(code); - jinter.interpret(); + const jinter = new Jinter(); + jinter.evaluate(code) expect(jinter.scope.get('result')).toEqual(2); }); @@ -397,8 +397,8 @@ describe('Jinter Tests', () => { const num_2 = 3; const result = num_1 ^ num_2; `; - const jinter = new Jinter(code); - jinter.interpret(); + const jinter = new Jinter(); + jinter.evaluate(code) expect(jinter.scope.get('result')).toEqual(9); }); @@ -409,8 +409,8 @@ describe('Jinter Tests', () => { const num_2 = 3; const result = num_1 | num_2; `; - const jinter = new Jinter(code); - jinter.interpret(); + const jinter = new Jinter(); + jinter.evaluate(code) expect(jinter.scope.get('result')).toEqual(11); }); @@ -420,8 +420,8 @@ describe('Jinter Tests', () => { const obj = { a: 1, b: 2 }; const result = 'a' in obj; `; - const jinter = new Jinter(code); - jinter.interpret(); + const jinter = new Jinter(); + jinter.evaluate(code) expect(jinter.scope.get('result')).toBeTruthy(); }); @@ -432,8 +432,8 @@ describe('Jinter Tests', () => { const result = that_date instanceof Date; `; - const jinter = new Jinter(code); - jinter.interpret(); + const jinter = new Jinter(); + jinter.evaluate(code) expect(jinter.scope.get('result')).toBeTruthy(); }); @@ -447,8 +447,8 @@ describe('Jinter Tests', () => { let num = 24; `; - const jinter = new Jinter(code); - jinter.interpret(); + const jinter = new Jinter(); + jinter.evaluate(code) expect(jinter.scope.has('greeting')).toBeTruthy(); expect(jinter.scope.has('num')).toBeTruthy(); @@ -459,8 +459,8 @@ describe('Jinter Tests', () => { function fn(arg) { return arg; } `; - const jinter = new Jinter(code); - jinter.interpret(); + const jinter = new Jinter(); + jinter.evaluate(code) expect(jinter.scope.has('fn')).toBeTruthy(); }); @@ -474,7 +474,7 @@ describe('Jinter Tests', () => { greet('Jacob'); ` - const jinter = new Jinter(code); + const jinter = new Jinter(); jinter.visitor.on('print', (node: any, visitor: any) => { const args = node.arguments.map((arg: any) => visitor.visitNode(arg)); @@ -482,7 +482,7 @@ describe('Jinter Tests', () => { return; }); - jinter.interpret(); + jinter.evaluate(code) }); it('should create objects', () => { @@ -499,8 +499,8 @@ describe('Jinter Tests', () => { const comparison = myobj.level_1.prop === myobj.level_2.prop; `; - const jinter = new Jinter(code); - jinter.interpret(); + const jinter = new Jinter(); + jinter.evaluate(code) expect(jinter.scope.get('comparison')).toBeFalsy(); }); @@ -518,8 +518,8 @@ describe('Jinter Tests', () => { run(); `; - const jinter = new Jinter(code); - const result = jinter.interpret(); + const jinter = new Jinter(); + const result = jinter.evaluate(code) expect(result).toEqual(50); }); @@ -534,8 +534,8 @@ describe('Jinter Tests', () => { } `; - const jinter = new Jinter(code); - jinter.interpret(); + const jinter = new Jinter(); + jinter.evaluate(code) expect(jinter.scope.get('sum')).toEqual(15); }); @@ -550,8 +550,8 @@ describe('Jinter Tests', () => { } `; - const jinter = new Jinter(code); - jinter.interpret(); + const jinter = new Jinter(); + jinter.evaluate(code) expect(jinter.scope.get('num_1')).toEqual(200); }); @@ -569,8 +569,8 @@ describe('Jinter Tests', () => { } `; - const jinter = new Jinter(code); - jinter.interpret(); + const jinter = new Jinter(); + jinter.evaluate(code) expect(jinter.scope.get('result')).toBe('It is smaller'); }); @@ -581,8 +581,8 @@ describe('Jinter Tests', () => { fn('hello'); `; - const jinter = new Jinter(code); - const result = jinter.interpret(); + const jinter = new Jinter(); + const result = jinter.evaluate(code) expect(result).toBe('hello'); }); @@ -590,12 +590,23 @@ describe('Jinter Tests', () => { it('should interpret a small program', async () => { const nsig_code = fs.readFileSync('./examples/test-code.js').toString(); - const jinter = new Jinter(nsig_code); - jinter.scope.set('ntoken', 'vPIcacaohWtfY_'); - - const result = jinter.interpret(); + const jinter = new Jinter(); + jinter.scope.set('ntoken', 'b6HcntHGkvBLk_FRf'); + const result = jinter.evaluate(nsig_code) - expect(result).toBe('-pK0vFvet_mXoA'); + expect(result).toBe('kNPW6A7FyP2l8A'); + }); + + it('should support regex', () => { + const code = ` + const regex = /hello/; + const result = regex.test('hello world'); + `; + + const jinter = new Jinter(); + jinter.evaluate(code) + + expect(jinter.scope.get('result')).toBeTruthy(); }); }); });