Comparing version 1.1.0 to 1.2.0
@@ -12,8 +12,13 @@ import Visitor from './visitor.js'; | ||
scope: Map<string, any>; | ||
constructor(input: string); | ||
constructor(); | ||
defineObject<T>(name: string, obj: T): void; | ||
/** | ||
* Interprets the program. | ||
* Generates an AST from the input. | ||
*/ | ||
parseScript(input: string): import("../acorn.js").ExtendNode<import("estree").Program>; | ||
/** | ||
* Evaluates the program. | ||
* @returns The result of the last statement in the program. | ||
*/ | ||
interpret(): any; | ||
evaluate(input: string): any; | ||
} |
@@ -15,59 +15,54 @@ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { | ||
import { parse } from 'acorn'; | ||
import { JinterError } from './utils/index.js'; | ||
export default class Jinter { | ||
constructor(input) { | ||
_Jinter_ast.set(this, void 0); | ||
const program = parse(input, { ecmaVersion: 2020 }); | ||
__classPrivateFieldSet(this, _Jinter_ast, program.body, "f"); | ||
this.visitor = new Visitor(__classPrivateFieldGet(this, _Jinter_ast, "f")); | ||
constructor() { | ||
_Jinter_ast.set(this, []); | ||
this.visitor = new Visitor(); | ||
this.scope = this.visitor.scope; | ||
this.scope.set('print', (args) => console.log(...args)); | ||
this.visitor.on('console', (node, visitor) => { | ||
this.defineObject('console', console); | ||
this.defineObject('Math', Math); | ||
this.defineObject('String', String); | ||
this.defineObject('Array', Array); | ||
this.defineObject('Date', Date); | ||
} | ||
defineObject(name, obj) { | ||
this.visitor.on(name, (node, visitor) => { | ||
if (node.type === 'Identifier') | ||
return console; | ||
return obj; | ||
if (node.type === 'CallExpression' && node.callee.type === 'MemberExpression') { | ||
const prop = visitor.visitNode(node.callee.property); | ||
const args = node.arguments.map((arg) => visitor.visitNode(arg)); | ||
const console_prop = console[prop]; | ||
if (!console_prop) | ||
return 'proceed'; | ||
return console_prop(...args); | ||
const callable = obj[prop]; | ||
if (!callable) | ||
return '__continue_exec'; | ||
return callable.apply(obj, args); | ||
} | ||
return 'proceed'; | ||
return '__continue_exec'; | ||
}); | ||
this.visitor.on('Math', (node, visitor) => { | ||
if (node.type === 'Identifier') | ||
return Math; | ||
if (node.type === 'CallExpression' && node.callee.type === 'MemberExpression') { | ||
const prop = visitor.visitNode(node.callee.property); | ||
const args = node.arguments.map((arg) => visitor.visitNode(arg)); | ||
const math_prop = Math[prop]; | ||
if (!math_prop) | ||
return 'proceed'; | ||
return math_prop(...args); | ||
} | ||
return 'proceed'; | ||
}); | ||
this.visitor.on('String', (node, visitor) => { | ||
if (node.type === 'Identifier') | ||
return String; | ||
if (node.type === 'CallExpression' && node.callee.type === 'MemberExpression') { | ||
const prop = visitor.visitNode(node.callee.property); | ||
const args = node.arguments.map((arg) => visitor.visitNode(arg)); | ||
const string_prop = String[prop]; | ||
if (!string_prop) | ||
return 'proceed'; | ||
return string_prop(args); | ||
} | ||
return 'proceed'; | ||
}); | ||
this.visitor.on('Date', (node) => { | ||
if (node.type === 'Identifier') | ||
return Date; | ||
}); | ||
} | ||
/** | ||
* Interprets the program. | ||
* Generates an AST from the input. | ||
*/ | ||
parseScript(input) { | ||
try { | ||
return parse(input, { ecmaVersion: 2020 }); | ||
} | ||
catch (e) { | ||
throw new JinterError(e.message); | ||
} | ||
} | ||
/** | ||
* Evaluates the program. | ||
* @returns The result of the last statement in the program. | ||
*/ | ||
interpret() { | ||
evaluate(input) { | ||
try { | ||
const program = parse(input, { ecmaVersion: 2020 }); | ||
__classPrivateFieldSet(this, _Jinter_ast, program.body, "f"); | ||
} | ||
catch (e) { | ||
throw new JinterError(e.message); | ||
} | ||
this.visitor.setAST(__classPrivateFieldGet(this, _Jinter_ast, "f")); | ||
return this.visitor.run(); | ||
@@ -74,0 +69,0 @@ } |
@@ -6,4 +6,5 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { | ||
}; | ||
var _CallExpression_instances, _CallExpression_throwError; | ||
var _CallExpression_instances, _CallExpression_throwError, _CallExpression_getCalleeString; | ||
import BaseJSNode from './BaseJSNode.js'; | ||
import { JinterError } from '../utils/index.js'; | ||
export default class CallExpression extends BaseJSNode { | ||
@@ -27,3 +28,3 @@ constructor() { | ||
const cb = this.visitor.listeners[exp_object](this.node, this.visitor); | ||
if (cb !== 'proceed') { | ||
if (cb !== '__continue_exec') { | ||
return cb; | ||
@@ -35,3 +36,3 @@ } | ||
const cb = this.visitor.listeners[exp_property](this.node, this.visitor); | ||
if (cb !== 'proceed') { | ||
if (cb !== '__continue_exec') { | ||
return cb; | ||
@@ -47,2 +48,4 @@ } | ||
const args = this.node.arguments.map((arg) => this.visitor.visitNode(arg)); | ||
if (!obj) | ||
__classPrivateFieldGet(this, _CallExpression_instances, "m", _CallExpression_throwError).call(this); | ||
if (typeof obj[prop] !== 'function') | ||
@@ -62,8 +65,6 @@ __classPrivateFieldGet(this, _CallExpression_instances, "m", _CallExpression_throwError).call(this); | ||
_CallExpression_instances = new WeakSet(), _CallExpression_throwError = function _CallExpression_throwError() { | ||
if (this.node.callee.type === 'MemberExpression') { | ||
throw new Error(`${this.node.callee.object.type === 'Identifier' ? this.node.callee.object.name : '<object>'}.${this.node.callee.property.type === 'Identifier' ? this.node.callee.property.name : '?'} is not a function`); | ||
if (this.node.callee.type === 'MemberExpression' || this.node.callee.type === 'Identifier') { | ||
const callee_string = __classPrivateFieldGet(this, _CallExpression_instances, "m", _CallExpression_getCalleeString).call(this, this.node.callee); | ||
throw new JinterError(`${callee_string} is not a function`); | ||
} | ||
else if (this.node.callee.type === 'Identifier') { | ||
throw new Error(`${this.node.callee.name} is not a function`); | ||
} | ||
else if (this.node.callee.type === 'SequenceExpression') { | ||
@@ -91,4 +92,14 @@ const call = []; | ||
call.push(')'); | ||
throw new Error(`${call.join('')} is not a function`); | ||
throw new JinterError(`${call.join('')} is not a function`); | ||
} | ||
}, _CallExpression_getCalleeString = function _CallExpression_getCalleeString(node) { | ||
if (node.type === 'Identifier') { | ||
return node.name; | ||
} | ||
else if (node.type === 'MemberExpression') { | ||
const object_string = __classPrivateFieldGet(this, _CallExpression_instances, "m", _CallExpression_getCalleeString).call(this, node.object); | ||
const property_string = node.computed ? `[${this.visitor.getName(node.property) || '...'}]` : `.${this.visitor.getName(node.property)}`; | ||
return `${object_string}${property_string}`; | ||
} | ||
return '<unknown>'; | ||
}; | ||
@@ -95,0 +106,0 @@ class Builtins { |
@@ -6,3 +6,3 @@ import BaseJSNode from './BaseJSNode.js'; | ||
const cb = this.visitor.listeners[this.node.name](this.node, this.visitor); | ||
if (cb !== 'proceed') { | ||
if (cb !== '__continue_exec') { | ||
return cb; | ||
@@ -9,0 +9,0 @@ } |
@@ -27,2 +27,3 @@ export { default as ArrayExpression } from './ArrayExpression.js'; | ||
export { default as SwitchStatement } from './SwitchStatement.js'; | ||
export { default as TemplateLiteral } from './TemplateLiteral.js'; | ||
export { default as ThisExpression } from './ThisExpression.js'; | ||
@@ -29,0 +30,0 @@ export { default as ThrowStatement } from './ThrowStatement.js'; |
@@ -28,2 +28,3 @@ // This file is auto-generated by ./scripts/build-nodes-map.js. | ||
export { default as SwitchStatement } from './SwitchStatement.js'; | ||
export { default as TemplateLiteral } from './TemplateLiteral.js'; | ||
export { default as ThisExpression } from './ThisExpression.js'; | ||
@@ -30,0 +31,0 @@ export { default as ThrowStatement } from './ThrowStatement.js'; |
@@ -10,3 +10,3 @@ import BaseJSNode from './BaseJSNode.js'; | ||
const cb = this.visitor.listeners[prop](this.node, this.visitor); | ||
if (cb !== 'proceed') { | ||
if (cb !== '__continue_exec') { | ||
return cb; | ||
@@ -13,0 +13,0 @@ } |
@@ -6,4 +6,4 @@ import BaseJSNode from './BaseJSNode.js'; | ||
const args = this.node.arguments.map((arg) => this.visitor.visitNode(arg)); | ||
return new callee(args); | ||
return args.length ? new callee(args) : new callee(); | ||
} | ||
} |
@@ -11,1 +11,5 @@ import ESTree from 'estree'; | ||
} | ||
export declare class JinterError extends Error { | ||
info?: any; | ||
constructor(message: string, info?: any); | ||
} |
export const namedFunction = (name, fn) => Object.defineProperty(fn, 'name', { value: name }); | ||
export class JinterError extends Error { | ||
constructor(message, info) { | ||
super(message); | ||
if (info) { | ||
this.info = info; | ||
} | ||
} | ||
} |
@@ -11,3 +11,3 @@ import type { Node } from 'estree'; | ||
ast: Node[]; | ||
constructor(ast: Node[]); | ||
setAST(ast: Node[]): void; | ||
run(): any; | ||
@@ -14,0 +14,0 @@ /** |
@@ -9,6 +9,9 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { | ||
export default class Visitor { | ||
constructor(ast) { | ||
constructor() { | ||
_Visitor_instances.add(this); | ||
this.scope = new Map(); | ||
this.listeners = {}; | ||
this.ast = []; | ||
} | ||
setAST(ast) { | ||
this.ast = ast; | ||
@@ -15,0 +18,0 @@ } |
{ | ||
"name": "jintr", | ||
"version": "1.1.0", | ||
"version": "1.2.0", | ||
"description": "A tiny JavaScript interpreter written in TypeScript.", | ||
@@ -5,0 +5,0 @@ "type": "module", |
@@ -21,3 +21,3 @@ [actions]: https://github.com/LuanRT/Jinter/actions | ||
- [API](#api) | ||
- [`interpret()`](#interpret) | ||
- [`evaluate(input: string)`](#evaluateinput-string) | ||
- [`visitor`](#visitor) | ||
@@ -47,17 +47,31 @@ - [`scope`](#scope) | ||
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 'jintr'; | ||
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(); | ||
@@ -67,13 +81,14 @@ jinter.visitor.on('toArray', (node, visitor) => { | ||
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); | ||
``` | ||
@@ -84,9 +99,9 @@ | ||
## 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. | ||
@@ -93,0 +108,0 @@ ### `visitor` |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
175280
85
2488
116