js-slang
Version:
Javascript-based implementations of Source, written in Typescript
171 lines • 9.85 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.StepperBlockExpression = void 0;
const __1 = require("..");
const generator_1 = require("../../generator");
const __2 = require("../..");
const utils_1 = require("../../utils");
// TODO: add docs, because this is a block expression, not a block statement, and this does not follow official estree spec
class StepperBlockExpression {
constructor(body, innerComments, leadingComments, trailingComments, loc, range) {
this.type = 'BlockStatement';
this.body = body;
this.innerComments = innerComments;
this.leadingComments = leadingComments;
this.trailingComments = trailingComments;
this.loc = loc;
this.range = range;
}
static create(node) {
return new StepperBlockExpression(node.body.map(ast => (0, generator_1.convert)(ast)));
}
isContractible() {
return (this.body.length === 0 ||
(this.body.length === 1 && !this.body[0].isOneStepPossible()) || // { 1; } -> undefined;
this.body[0].type === 'ReturnStatement');
}
isOneStepPossible() {
return this.isContractible() || this.body[0].isOneStepPossible() || this.body.length >= 2;
}
contract() {
if (this.body.length === 0 || (this.body.length === 1 && !this.body[0].isOneStepPossible())) {
__2.redex.preRedex = [this];
__2.redex.postRedex = [];
return __1.undefinedNode;
}
if (this.body[0].type === 'ReturnStatement') {
const returnStmt = this.body[0];
returnStmt.contract();
return returnStmt.argument || __1.undefinedNode;
}
throw new Error('Cannot contract block expression ' + JSON.stringify(this.isContractible()));
}
oneStep() {
if (this.isContractible()) {
return this.contract();
}
// reduce the first statement
if (this.body[0].isOneStepPossible()) {
const firstStatementOneStep = this.body[0].oneStep();
const afterSubstitutedScope = this.body.slice(1);
if (firstStatementOneStep === __1.undefinedNode) {
return new StepperBlockExpression([afterSubstitutedScope].flat(), this.innerComments, this.leadingComments, this.trailingComments, this.loc, this.range);
}
return new StepperBlockExpression([firstStatementOneStep, ...afterSubstitutedScope], this.innerComments, this.leadingComments, this.trailingComments, this.loc, this.range);
}
// If the first statement is constant declaration, gracefully handle it!
if (this.body[0].type == 'VariableDeclaration') {
const declarations = (0, utils_1.assignMuTerms)(this.body[0].declarations);
const afterSubstitutedScope = this.body
.slice(1)
.map(current => declarations
.filter(declarator => declarator.init)
.reduce((statement, declarator) => statement.substitute(declarator.id, declarator.init), current));
const substitutedProgram = new StepperBlockExpression(afterSubstitutedScope, this.innerComments, this.leadingComments, this.trailingComments, this.loc, this.range);
__2.redex.preRedex = [this.body[0]];
__2.redex.postRedex = declarations.map(x => x.id);
return substitutedProgram;
}
// If the first statement is function declaration, also gracefully handle it!
if (this.body[0].type == 'FunctionDeclaration') {
const arrowFunction = this.body[0].getArrowFunctionExpression();
const functionIdentifier = this.body[0].id;
const afterSubstitutedScope = this.body
.slice(1)
.map(statement => statement.substitute(functionIdentifier, arrowFunction));
const substitutedProgram = new StepperBlockExpression(afterSubstitutedScope, this.innerComments, this.leadingComments, this.trailingComments, this.loc, this.range);
__2.redex.preRedex = [this.body[0]];
__2.redex.postRedex = afterSubstitutedScope;
return substitutedProgram;
}
const firstValueStatement = this.body[0];
// After this stage, the first statement is a value statement. Now, proceed until getting the second value statement.
// if the second statement is return statement, remove the first statement
if (this.body.length >= 2 && this.body[1].type == 'ReturnStatement') {
__2.redex.preRedex = [this.body[0]];
const afterSubstitutedScope = this.body.slice(1);
__2.redex.postRedex = [];
return new StepperBlockExpression(afterSubstitutedScope, this.innerComments, this.leadingComments, this.trailingComments, this.loc, this.range);
}
if (this.body.length >= 2 && this.body[1].isOneStepPossible()) {
const secondStatementOneStep = this.body[1].oneStep();
const afterSubstitutedScope = this.body.slice(2);
if (secondStatementOneStep === __1.undefinedNode) {
return new StepperBlockExpression([firstValueStatement, afterSubstitutedScope].flat(), this.innerComments, this.leadingComments, this.trailingComments, this.loc, this.range);
}
return new StepperBlockExpression([
firstValueStatement,
secondStatementOneStep,
afterSubstitutedScope
].flat(), this.innerComments, this.leadingComments, this.trailingComments, this.loc, this.range);
}
// If the second statement is constant declaration, gracefully handle it!
if (this.body.length >= 2 && this.body[1].type == 'VariableDeclaration') {
const declarations = (0, utils_1.assignMuTerms)(this.body[1].declarations);
const afterSubstitutedScope = this.body
.slice(2)
.map(current => declarations
.filter(declarator => declarator.init)
.reduce((statement, declarator) => statement.substitute(declarator.id, declarator.init), current));
const substitutedProgram = new StepperBlockExpression([firstValueStatement, afterSubstitutedScope].flat(), this.innerComments, this.leadingComments, this.trailingComments, this.loc, this.range);
__2.redex.preRedex = [this.body[1]];
__2.redex.postRedex = declarations.map(x => x.id);
return substitutedProgram;
}
// If the second statement is function declaration, also gracefully handle it!
if (this.body.length >= 2 && this.body[1].type == 'FunctionDeclaration') {
const arrowFunction = this.body[1].getArrowFunctionExpression();
const functionIdentifier = this.body[1].id;
const afterSubstitutedScope = this.body
.slice(2)
.map(statement => statement.substitute(functionIdentifier, arrowFunction));
const substitutedProgram = new StepperBlockExpression([firstValueStatement, afterSubstitutedScope].flat(), this.innerComments, this.leadingComments, this.trailingComments, this.loc, this.range);
__2.redex.preRedex = [this.body[1]];
__2.redex.postRedex = afterSubstitutedScope;
return substitutedProgram;
}
// After this stage, we have two value inducing statement. Remove the first one.
this.body[0].contractEmpty(); // update the contracted statement onto redex
return new StepperBlockExpression(this.body.slice(1), this.innerComments, this.leadingComments, this.trailingComments, this.loc, this.range);
}
substitute(id, value) {
const valueFreeNames = value.freeNames();
const scopeNames = this.scanAllDeclarationNames();
const repeatedNames = valueFreeNames.filter(name => scopeNames.includes(name));
let protectedNamesSet = new Set(this.allNames());
repeatedNames.forEach(name => protectedNamesSet.delete(name));
const protectedNames = Array.from(protectedNamesSet);
const newNames = (0, utils_1.getFreshName)(repeatedNames, protectedNames);
const currentBlockExpression = newNames.reduce((current, name, index) => current.rename(repeatedNames[index], name), this);
if (currentBlockExpression.scanAllDeclarationNames().includes(id.name)) {
return currentBlockExpression;
}
return new StepperBlockExpression(currentBlockExpression.body.map(statement => statement.substitute(id, value)), currentBlockExpression.innerComments, currentBlockExpression.leadingComments, currentBlockExpression.trailingComments, currentBlockExpression.loc, currentBlockExpression.range);
}
scanAllDeclarationNames() {
return this.body
.filter(ast => ast.type === 'VariableDeclaration' || ast.type === 'FunctionDeclaration')
.flatMap((ast) => {
if (ast.type === 'VariableDeclaration') {
return ast.declarations.map(ast => ast.id.name);
}
else {
// Function Declaration
return [ast.id.name];
}
});
}
freeNames() {
const names = new Set(this.body.flatMap(ast => ast.freeNames()));
this.scanAllDeclarationNames().forEach(name => names.delete(name));
return Array.from(names);
}
allNames() {
return Array.from(new Set(this.body.flatMap(ast => ast.allNames())));
}
rename(before, after) {
return new StepperBlockExpression(this.body.map(statement => statement.rename(before, after)), this.innerComments, this.leadingComments, this.trailingComments, this.loc, this.range);
}
}
exports.StepperBlockExpression = StepperBlockExpression;
//# sourceMappingURL=BlockExpression.js.map