jintr
Version:
A tiny JavaScript interpreter written in TypeScript.
78 lines (77 loc) • 3.63 kB
JavaScript
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
if (kind === "m") throw new TypeError("Private method is not writable");
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var _Jinter_ast;
import Visitor from './visitor.js';
import { parse } from 'acorn';
import { JinterError } from './utils/index.js';
export default class Jinter {
constructor() {
_Jinter_ast.set(this, []);
this.visitor = new Visitor();
this.scope = this.visitor.scope;
this.scope.set('print', (args) => console.log(...args));
this.defineObject('console', console);
this.defineObject('Math', Math);
this.defineObject('String', String);
this.defineObject('Number', Number);
this.defineObject('Array', Array);
this.defineObject('Date', Date);
}
defineObject(name, obj) {
this.visitor.on(name, (node, visitor) => {
if (node.type === 'Identifier')
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 callable = obj[prop];
if (!callable)
return '__continue_exec';
return callable.apply(obj, args);
}
return '__continue_exec';
});
}
/**
* Evaluates the program.
* @returns The result of the last statement in the program.
*/
evaluate(input) {
const program = Jinter.parseScript(input);
__classPrivateFieldSet(this, _Jinter_ast, program.body, "f");
this.visitor.setAST(__classPrivateFieldGet(this, _Jinter_ast, "f"));
return this.visitor.run();
}
/**
* Generates an AST from the input.
*/
static parseScript(input, options) {
try {
return parse(input, { ecmaVersion: 2020, ...(options || {}) });
}
catch (e) {
const match = e.message.match(/\((\d+):(\d+)\)/);
if (match) {
const line = parseInt(match[1], 10);
const column = parseInt(match[2], 10);
const lines = input.split('\n');
const errorLine = lines[line - 1];
const snippet = errorLine ? errorLine.substring(Math.max(0, column - 10), column + 10) : '';
throw new JinterError(`${e.message.replace(/\(.*\)/, '').trim()} at line ${line}, column ${column}: ${snippet}`, { errorLine });
}
else {
throw new JinterError(e.message);
}
}
}
}
_Jinter_ast = new WeakMap();