js-slang
Version:
Javascript-based implementations of Source, written in Typescript
265 lines • 12.3 kB
JavaScript
/**
* The final transpiler visitor.
* Takes in expressions, yields es.Node[], so we can flatmap into a final program
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Transpiler = void 0;
const estreeBuilder = require("../../utils/estree-nodes");
// helper functions
function isExpression(node) {
return !node.type.includes("Statement") && !node.type.includes("Declaration");
}
function wrapInRest(param) {
return estreeBuilder.makeRestElement(param);
}
function wrapInStatement(expression) {
return estreeBuilder.makeExpressionStatement(expression);
}
function wrapInReturn(expression) {
return estreeBuilder.makeReturnStatement(expression);
}
class Transpiler {
static create() {
return new Transpiler();
}
transpile(program) {
// create an array of expressions
const expressions = program.flatMap(e => e.accept(this));
// then create an array of statements
const statements = expressions.map(e => isExpression(e) ? wrapInStatement(e) : e);
// then wrap the whole thing in a program
return estreeBuilder.makeProgram(statements);
}
// Atomic AST
// iife
visitSequence(node) {
const expressions = node.expressions.flatMap(e => e.accept(this));
// wrap each expression into an expression statement if required
const statements = expressions.map(e => isExpression(e) ? wrapInStatement(e) : e);
// promote the last expression to a return statement
const lastExpression = statements.at(-1);
// if the last expression is not something that emits an expression,
// the sequence should return undefined
if (lastExpression.type !== "ExpressionStatement") {
statements.push(
// always remember that undefined is an identifier
wrapInStatement(estreeBuilder.makeIdentifier("undefined", node.location)));
}
else {
// if the last expression is an expression statement, we should promote it to a return statement
statements[statements.length - 1] = wrapInReturn(lastExpression.expression);
}
// turn the statements into a block
const body = estreeBuilder.makeBlockStatement(statements);
// make the call expression
const iife = estreeBuilder.makeCallExpression(estreeBuilder.makeArrowFunctionExpression([], body, node.location), [], node.location);
// if other parts of the program want to optimize their code, eliminating
// the iife sequence, they can see that this is a sequence with this flag
iife.isSequence = true;
return [iife];
}
// literals
visitNumericLiteral(node) {
// we need to wrap the number in a call to make-number
const makeNumber = estreeBuilder.makeIdentifier("make_number", node.location);
// we turn the number into a literal
const number = estreeBuilder.makeLiteral(node.value, node.location);
return [
estreeBuilder.makeCallExpression(makeNumber, [number], node.location),
];
}
visitBooleanLiteral(node) {
return [estreeBuilder.makeLiteral(node.value, node.location)];
}
visitStringLiteral(node) {
return [estreeBuilder.makeLiteral(node.value, node.location)];
}
visitLambda(node) {
const parameters = node.params.flatMap(p => p.accept(this));
const [fnBody] = node.body.accept(this);
// if the inner body is a sequence, we can optimize it by removing the sequence
// and making the arrow function expression return the last expression
// we left a flag in the sequence to indicate that it is an iife
let finalBody = fnBody.isSequence
? // then we know that body is a sequence, stored as a call expression to an
// inner callee with an interior arrow function expression that takes no arguments
// let's steal that arrow function expression's body and use it as ours
fnBody.callee.body
: fnBody;
if (!node.rest) {
return [
estreeBuilder.makeArrowFunctionExpression(parameters, finalBody, node.location),
];
}
// there is a rest parameter to deal with
const [restParameter] = node.rest.accept(this);
// wrap it in a restElement
const restElement = wrapInRest(restParameter);
parameters.push(restElement);
// place an implicit vector-to-list conversion around the rest parameter
// this is to ensure that the rest parameter is always a list
const vectorToList = estreeBuilder.makeIdentifier("vector->list", node.location);
// we make a call to it with the rest parameter as the argument
const restParameterConversion = estreeBuilder.makeCallExpression(vectorToList, [restParameter], node.location);
// then we reassign the rest parameter to the result of the call
const restParameterAssignment = estreeBuilder.makeAssignmentExpression(restParameter, restParameterConversion, node.location);
// then we inject it into the final body
if (finalBody.type === "BlockStatement") {
finalBody.body.unshift(wrapInStatement(restParameterAssignment));
return [
estreeBuilder.makeArrowFunctionExpression(parameters, finalBody, node.location),
];
}
// otherwise, we need to wrap the final body in a block statement
// and then inject the vectorToList call
finalBody = estreeBuilder.makeBlockStatement([
wrapInStatement(restParameterAssignment),
wrapInReturn(finalBody),
]);
return [
estreeBuilder.makeArrowFunctionExpression(parameters, finalBody, node.location),
];
}
// identifiers
visitIdentifier(node) {
return [estreeBuilder.makeIdentifier(node.name, node.location)];
}
// make a verifier that prevents this from being part of an
// expression context
// turns into statement
visitDefinition(node) {
const [value] = node.value.accept(this);
const [id] = node.name.accept(this);
return [estreeBuilder.makeDeclaration("let", id, value, node.location)];
}
// expressions
visitApplication(node) {
const [operator] = node.operator.accept(this);
const operands = node.operands.flatMap(o => o.accept(this));
return [
estreeBuilder.makeCallExpression(operator, operands, node.location),
];
}
visitConditional(node) {
const [test] = node.test.accept(this);
// scheme's truthiness is different from javascript's,
// and so we must use a custom truthiness function truthy to evaluate the test
const truthy = estreeBuilder.makeIdentifier("truthy", node.location);
const schemeTest = estreeBuilder.makeCallExpression(truthy, [test], node.location);
const [consequent] = node.consequent.accept(this);
const [alternate] = node.alternate.accept(this);
return [
estreeBuilder.makeConditionalExpression(schemeTest, consequent, alternate, node.location),
];
}
// pair represented using cons call
visitPair(node) {
const [car] = node.car.accept(this);
const [cdr] = node.cdr.accept(this);
// construct the callee, cons, by hand
const cons = estreeBuilder.makeIdentifier("cons", node.location);
return [estreeBuilder.makeCallExpression(cons, [car, cdr], node.location)];
}
visitNil(node) {
return [estreeBuilder.makeLiteral(null, node.location)];
}
// generate symbols with string->symbol call
visitSymbol(node) {
// take the string out of the symbol value
const str = estreeBuilder.makeLiteral(node.value, node.location);
const stringToSymbol = estreeBuilder.makeIdentifier("string->symbol", node.location);
return [
estreeBuilder.makeCallExpression(stringToSymbol, [str], node.location),
];
}
// we are assured that this marker will always exist within a list context.
// leave a splice marker in the list that will be removed by a runtime
// call to eval-splice on a list
visitSpliceMarker(node) {
const [expr] = node.value.accept(this);
const makeSplice = estreeBuilder.makeIdentifier("make-splice", node.location);
return [estreeBuilder.makeCallExpression(makeSplice, expr, node.location)];
}
// turns into expression that returns assigned value
// maybe in the future we can make a setall! macro
visitReassignment(node) {
const [left] = node.name.accept(this);
const [right] = node.value.accept(this);
return [estreeBuilder.makeAssignmentExpression(left, right, node.location)];
}
// make a verifier that keeps these top level
// and separate from nodes
visitImport(node) {
// first we make the importDeclaration
const newIdentifiers = node.identifiers.flatMap(i => i.accept(this));
const mappedIdentifierNames = newIdentifiers.map(i => {
const copy = Object.assign({}, i);
copy.name = "imported" + copy.name;
return copy;
});
const makeSpecifiers = (importeds, locals) => importeds.map((imported, i) =>
// safe to cast as we are assured all source locations are present
estreeBuilder.makeImportSpecifier(imported, locals[i], imported.loc));
const specifiers = makeSpecifiers(newIdentifiers, mappedIdentifierNames);
const [source] = node.source.accept(this);
const importDeclaration = estreeBuilder.makeImportDeclaration(specifiers, source, node.location);
// then for each imported function, we define their proper
// names with definitions
const makeRedefinitions = (importeds, locals) => importeds.flatMap((imported, i) => estreeBuilder.makeDeclaration("let", imported, locals[i],
// we are assured that all source locations are present
imported.loc));
const redefinitions = makeRedefinitions(newIdentifiers, mappedIdentifierNames);
return [importDeclaration, ...redefinitions];
}
visitExport(node) {
const [newDefinition] = node.definition.accept(this);
return [
estreeBuilder.makeExportNamedDeclaration(newDefinition, node.location),
];
}
// turn into an array
visitVector(node) {
const newElements = node.elements.flatMap(e => e.accept(this));
return [estreeBuilder.makeArrayExpression(newElements, node.location)];
}
// Extended AST
// this is in the extended AST, but useful enough to keep.
visitList(node) {
const newElements = node.elements.flatMap(e => e.accept(this));
const [newTerminator] = node.terminator
? node.terminator.accept(this)
: [undefined];
if (newTerminator) {
// cons* or list* produces dotted lists
// we prefer list* here as it explicitly describes the
// construction of an improper list - the word LIST
const dottedList = estreeBuilder.makeIdentifier("list*", node.location);
return [
estreeBuilder.makeCallExpression(dottedList, [...newElements, newTerminator], node.location),
];
}
// a proper list
const list = estreeBuilder.makeIdentifier("list", node.location);
return [estreeBuilder.makeCallExpression(list, newElements, node.location)];
}
// if any of these are called, its an error. the simplifier
// should be called first.
visitFunctionDefinition(node) {
throw new Error("The AST should be simplified!");
}
visitLet(node) {
throw new Error("The AST should be simplified!");
}
visitCond(node) {
throw new Error("The AST should be simplified!");
}
visitBegin(node) {
throw new Error("The AST should be simplified!");
}
visitDelay(node) {
throw new Error("The AST should be simplified!");
}
}
exports.Transpiler = Transpiler;
//# sourceMappingURL=transpiler.js.map
;