UNPKG

aran

Version:
832 lines (813 loc) 20.6 kB
/* eslint-disable no-use-before-define */ import { AranTypeError } from "../../error.mjs"; import { isParameter, unpackPrimitive, isDeclareHeader, isModuleHeader, } from "../../lang/index.mjs"; import { EMPTY, concatXX, concat_X, filterNarrow, flat, flatenTree, isTreeEmpty, map, mapTree, } from "../../util/index.mjs"; import { makeApplyExpression, makeAwaitExpression, makeBlockStatement, makeBreakStatement, makeClosureExpression, makeConditionalEffect, makeConditionalExpression, makeSegmentBlock, makeDebuggerStatement, makeEffectStatement, makeEvalExpression, makeExportEffect, makeExpressionEffect, makeIfStatement, makeImportExpression, makeIntrinsicExpression, makePrimitiveExpression, makeProgram, makeReadExpression, makeRoutineBlock, makeSequenceExpression, makeTryStatement, makeWhileStatement, makeWriteEffect, makeYieldExpression, } from "../node.mjs"; import { ADVICE_VARIABLE, COMPLETION_VARIABLE, FRAME_VARIABLE, mangleOriginalVariable, mangleStateVariable, } from "../variable.mjs"; import { incrementDepth } from "../depth.mjs"; import { listParameter, makeProgramKind } from "../parametrization.mjs"; import { trapApplyAround, trapAwaitAfter, trapAwaitBefore, trapSegmentBlockAfter, trapRoutineBlockBefore, trapSegmentBlockBefore, trapBlockDeclaration, trapBlockThrowing, trapBlockDeclarationOverwrite, trapBlockTeardown, trapBreakBefore, trapClosureAfter, trapConstructAround, trapDropBefore, trapEvalAfter, trapEvalBefore, trapExportBefore, trapImportAfter, trapIntrinsicAfter, trapPrimitiveAfter, trapReadAfter, trapTestBefore, trapWriteBefore, trapYieldAfter, trapYieldBefore, trapGeneratorBlockSuspension, trapGeneratorBlockResumption, trapRoutineBlockAfter, trapBlockSetup, } from "./trap.mjs"; import { CLOSURE_PARENT, CONTROL_PARENT } from "./parent.mjs"; import { weaveRoutineHead } from "../prelude.mjs"; import { makeJsonExpression } from "../json.mjs"; /** * @type {[ * import("../atom.d.ts").ResVariable, * import("../../lang/syntax.d.ts").Intrinsic, * ]} */ const FRAME_BINDING = [FRAME_VARIABLE, "undefined"]; /** * @type {[ * import("../atom.d.ts").ResVariable, * import("../../lang/syntax.d.ts").Intrinsic, * ]} */ const ADVICE_BINDING = [ADVICE_VARIABLE, "undefined"]; /** * @type {[ * import("../atom.d.ts").ResVariable, * import("../../lang/syntax.d.ts").Intrinsic, * ]} */ const COMPLETION_BINDING = [COMPLETION_VARIABLE, "undefined"]; ///////////// // Program // ///////////// /** * @type {( * node: import("../atom.d.ts").ArgProgram, * routine: { * initial: import("../../util/util.d.ts").Json, * advice: string, * }, * context: import("./context.d.ts").Context, * ) => import("../atom.d.ts").ResProgram} */ export const weaveProgram = (node, { initial, advice }, context) => makeProgram( node.kind, node.situ, node.head, weaveRoutineBlock( node.body, { type: "program", kind: makeProgramKind(node), head: concatXX( filterNarrow(node.head, isModuleHeader), filterNarrow(node.head, isDeclareHeader), ), advice, initial, }, context, ), node.tag, ); /////////// // Block // /////////// /** * @type {( * binding: [ * import("../atom.d.ts").ArgVariable, * import("../../lang/syntax.d.ts").Intrinsic, * ], * ) => import("../atom.d.ts").ArgVariable} */ const getBindingVariable = ([variable, _intrinsic]) => variable; /** * @type {( * binding: [ * import("../atom.d.ts").ArgVariable, * import("../../lang/syntax.d.ts").Intrinsic, * ], * ) => [ * import("../atom.d.ts").ResVariable, * import("../../lang/syntax.d.ts").Intrinsic, * ]} */ const mangleBinding = ([variable, intrinsic]) => [ mangleOriginalVariable(variable), intrinsic, ]; /** * @type {( * depth: import("../depth.d.ts").Depth, * ) => [ * import("../atom.d.ts").ResVariable, * import("../../lang/syntax.d.ts").Intrinsic, * ]} */ const makeStateBinding = (depth) => [mangleStateVariable(depth), "undefined"]; /** * @type {( * node: import("../atom.d.ts").ArgSegmentBlock, * parent: import("./parent.d.ts").SegmentParent, * context: import("./context.d.ts").Context, * ) => import("../atom.d.ts").ResSegmentBlock} */ const weaveSegmentBlock = (node, parent, context1) => { const { tag } = node; const parameters = listParameter(parent.kind); const variables = map(node.bindings, getBindingVariable); const setup = trapBlockSetup( context1.pointcut, context1.depth, parent.kind, tag, ); const context2 = isTreeEmpty(setup) ? context1 : { ...context1, depth: incrementDepth(context1.depth) }; const declaration = trapBlockDeclarationOverwrite( context2.pointcut, context2.depth, parent.kind, parameters, variables, tag, ); // Cannot use Tree because nodes are arrays themselves. /** * @type {import("../atom.d.ts").ResBinding[]} */ const bindings = flat([ isTreeEmpty(setup) ? EMPTY : [makeStateBinding(context2.depth)], isTreeEmpty(declaration) ? EMPTY : [FRAME_BINDING], map(node.bindings, mangleBinding), ]); const body = [ mapTree(setup, (node) => makeEffectStatement(node, tag)), mapTree( trapSegmentBlockBefore( context2.pointcut, context2.depth, parent.kind, node.labels, tag, ), (node) => makeEffectStatement(node, tag), ), mapTree( trapBlockDeclaration( context2.pointcut, context2.depth, parent.kind, parameters, variables, tag, ), (node) => makeEffectStatement(node, tag), ), mapTree(declaration, (node) => makeEffectStatement(node, tag)), map(node.body, (child) => weaveStatement(child, context2)), mapTree( trapSegmentBlockAfter( context2.pointcut, context2.depth, parent.kind, tag, ), (node) => makeEffectStatement(node, tag), ), ]; const throwing = trapBlockThrowing( context2.pointcut, context2.depth, parent.kind, makeReadExpression("catch.error", tag), tag, ); const teardown = trapBlockTeardown( context2.pointcut, context2.depth, parent.kind, tag, ); if ( throwing.type === "ReadExpression" && throwing.variable === "catch.error" && isTreeEmpty(teardown) ) { return makeSegmentBlock(node.labels, bindings, flatenTree(body), tag); } else { return makeSegmentBlock( node.labels, bindings, [ makeTryStatement( makeSegmentBlock(EMPTY, EMPTY, flatenTree(body), tag), makeSegmentBlock( EMPTY, EMPTY, [ makeEffectStatement( makeExpressionEffect( makeApplyExpression( makeIntrinsicExpression("aran.throwException", tag), makeIntrinsicExpression("undefined", tag), [throwing], tag, ), tag, ), tag, ), ], tag, ), makeSegmentBlock( EMPTY, EMPTY, mapTree(teardown, (node) => makeEffectStatement(node, tag)), tag, ), tag, ), ], tag, ); } }; /** * @type {( * node: import("../atom.d.ts").ArgRoutineBlock, * parent: import("./parent.d.ts").RoutineParent, * context: import("./context.d.ts").Context, * ) => import("../atom.d.ts").ResRoutineBlock} */ const weaveRoutineBlock = (node, parent, context1) => { const { tag } = node; const parameters = listParameter(parent.kind); const variables = map(node.bindings, getBindingVariable); const setup = trapBlockSetup( context1.pointcut, context1.depth, parent.kind, tag, ); const context2 = isTreeEmpty(setup) ? context1 : { ...context1, depth: incrementDepth(context1.depth) }; const declaration = trapBlockDeclarationOverwrite( context2.pointcut, context2.depth, parent.kind, parameters, variables, tag, ); // Cannot use Tree because nodes are arrays themselves. /** * @type {import("../atom.d.ts").ResBinding[]} */ const bindings = flat([ parent.type === "program" ? [ADVICE_BINDING] : EMPTY, parent.type === "program" ? [makeStateBinding(context1.depth)] : EMPTY, isTreeEmpty(setup) ? EMPTY : [makeStateBinding(context2.depth)], isTreeEmpty(declaration) ? EMPTY : [FRAME_BINDING], map(node.bindings, mangleBinding), ]); const head = [ parent.type === "program" ? makeWriteEffect( ADVICE_VARIABLE, makeApplyExpression( makeIntrinsicExpression("aran.readGlobalVariable", tag), makeIntrinsicExpression("undefined", tag), [makePrimitiveExpression(parent.advice, tag)], tag, ), tag, ) : null, parent.type === "program" ? makeWriteEffect( mangleStateVariable(context1.depth), makeJsonExpression(parent.initial, tag), tag, ) : null, setup, trapRoutineBlockBefore( context2.pointcut, context2.depth, parent.kind, parent.type === "program" ? parent.head : EMPTY, tag, ), trapBlockDeclaration( context2.pointcut, context2.depth, parent.kind, parameters, variables, tag, ), declaration, node.head === null ? null : map(node.head, (child) => weaveEffect(child, context2)), parent.kind === "generator" || parent.kind === "async-generator" ? trapGeneratorBlockSuspension( context2.pointcut, context2.depth, parent.kind, tag, ) : null, ]; const body = [ parent.kind === "generator" || parent.kind === "async-generator" ? mapTree( trapGeneratorBlockResumption( context2.pointcut, context2.depth, parent.kind, tag, ), (node) => makeEffectStatement(node, tag), ) : null, map(node.body, (child) => weaveStatement(child, context2)), ]; const tail = trapRoutineBlockAfter( context2.pointcut, context2.depth, parent.kind, weaveExpression(node.tail, context2), tag, ); const throwing = trapBlockThrowing( context2.pointcut, context2.depth, parent.kind, makeReadExpression("catch.error", tag), tag, ); const teardown = trapBlockTeardown( context2.pointcut, context2.depth, parent.kind, tag, ); if ( throwing.type === "ReadExpression" && throwing.variable === "catch.error" && isTreeEmpty(teardown) ) { return makeRoutineBlock( bindings, node.head === null ? null : flatenTree(head), flatenTree( node.head === null ? [mapTree(head, (node) => makeEffectStatement(node, tag)), body] : body, ), tail, tag, ); } else { return makeRoutineBlock( concat_X(COMPLETION_BINDING, bindings), node.head === null ? null : flatenTree(weaveRoutineHead(head, throwing, teardown, tag)), [ makeTryStatement( makeSegmentBlock( EMPTY, EMPTY, flatenTree([ node.head === null ? mapTree(head, (node) => makeEffectStatement(node, tag)) : null, body, makeEffectStatement( makeWriteEffect(COMPLETION_VARIABLE, tail, tag), tag, ), ]), tag, ), makeSegmentBlock( EMPTY, EMPTY, [ makeEffectStatement( makeExpressionEffect( makeApplyExpression( makeIntrinsicExpression("aran.throwException", tag), makeIntrinsicExpression("undefined", tag), [throwing], tag, ), tag, ), tag, ), ], tag, ), makeSegmentBlock( EMPTY, EMPTY, mapTree(teardown, (node) => makeEffectStatement(node, tag)), tag, ), tag, ), ], makeReadExpression(COMPLETION_VARIABLE, tag), tag, ); } }; /////////////// // Statement // /////////////// /** * @type {( * node: import("../atom.d.ts").ArgStatement, * context: import("./context.d.ts").Context, * ) => import("../../util/tree.d.ts").Tree<import("../atom.d.ts").ResStatement>} */ const weaveStatement = (node, context) => { const { tag } = node; switch (node.type) { case "EffectStatement": { return makeEffectStatement(weaveEffect(node.inner, context), tag); } case "DebuggerStatement": { return makeDebuggerStatement(tag); } case "BreakStatement": { return [ mapTree( trapBreakBefore(context.pointcut, context.depth, node.label, tag), (node) => makeEffectStatement(node, tag), ), makeBreakStatement(node.label, tag), ]; } case "BlockStatement": { return makeBlockStatement( weaveSegmentBlock(node.body, CONTROL_PARENT.bare, context), tag, ); } case "IfStatement": { return makeIfStatement( trapTestBefore( context.pointcut, context.depth, "if", weaveExpression(node.test, context), tag, ), weaveSegmentBlock(node.then, CONTROL_PARENT.then, context), weaveSegmentBlock(node.else, CONTROL_PARENT.else, context), tag, ); } case "WhileStatement": { return makeWhileStatement( trapTestBefore( context.pointcut, context.depth, "while", weaveExpression(node.test, context), tag, ), weaveSegmentBlock(node.body, CONTROL_PARENT.while, context), tag, ); } case "TryStatement": { return makeTryStatement( weaveSegmentBlock(node.try, CONTROL_PARENT.try, context), weaveSegmentBlock(node.catch, CONTROL_PARENT.catch, context), weaveSegmentBlock(node.finally, CONTROL_PARENT.finally, context), tag, ); } default: { throw new AranTypeError(node); } } }; //////////// // Effect // //////////// /** * @type {( * node: import("../atom.d.ts").ArgEffect, * context: import("./context.d.ts").Context, * ) => import("../atom.d.ts").ResEffect} */ const weaveEffect = (node, context) => { const { tag } = node; switch (node.type) { case "ExpressionEffect": { return makeExpressionEffect( trapDropBefore( context.pointcut, context.depth, weaveExpression(node.discard, context), tag, ), tag, ); } case "WriteEffect": { return makeWriteEffect( isParameter(node.variable) ? node.variable : mangleOriginalVariable(node.variable), trapWriteBefore( context.pointcut, context.depth, node.variable, weaveExpression(node.value, context), tag, ), tag, ); } case "ExportEffect": { return makeExportEffect( node.export, trapExportBefore( context.pointcut, context.depth, node.export, weaveExpression(node.value, context), tag, ), tag, ); } case "ConditionalEffect": { return makeConditionalEffect( trapTestBefore( context.pointcut, context.depth, "conditional", weaveExpression(node.test, context), tag, ), map(node.positive, (child) => weaveEffect(child, context)), map(node.negative, (child) => weaveEffect(child, context)), tag, ); } default: { throw new AranTypeError(node); } } }; //////////////// // Expression // //////////////// /** * @type {( * node: import("../atom.d.ts").ArgExpression, * context: import("./context.d.ts").Context, * ) => import("../atom.d.ts").ResExpression} */ const weaveExpression = (node, context) => { const { tag } = node; switch (node.type) { case "PrimitiveExpression": { return trapPrimitiveAfter( context.pointcut, context.depth, unpackPrimitive(node.primitive), makePrimitiveExpression(node.primitive, tag), tag, ); } case "IntrinsicExpression": { return trapIntrinsicAfter( context.pointcut, context.depth, node.intrinsic, makeIntrinsicExpression(node.intrinsic, tag), tag, ); } case "ReadExpression": { return trapReadAfter( context.pointcut, context.depth, node.variable, makeReadExpression( isParameter(node.variable) ? node.variable : mangleOriginalVariable(node.variable), tag, ), tag, ); } case "ImportExpression": { return trapImportAfter( context.pointcut, context.depth, node.source, node.import, makeImportExpression(node.source, node.import, tag), tag, ); } case "ClosureExpression": { /** @type {import("../parametrization.d.ts").ClosureKind} */ const kind = node.asynchronous ? `async-${node.kind}` : node.kind; return trapClosureAfter( context.pointcut, context.depth, kind, makeClosureExpression( node.kind, node.asynchronous, weaveRoutineBlock(node.body, CLOSURE_PARENT[kind], context), tag, ), tag, ); } case "SequenceExpression": { return makeSequenceExpression( map(node.head, (header) => weaveEffect(header, context)), weaveExpression(node.tail, context), tag, ); } case "ConditionalExpression": { return makeConditionalExpression( trapTestBefore( context.pointcut, context.depth, "conditional", weaveExpression(node.test, context), tag, ), weaveExpression(node.consequent, context), weaveExpression(node.alternate, context), tag, ); } case "YieldExpression": { return trapYieldAfter( context.pointcut, context.depth, node.delegate, makeYieldExpression( node.delegate, trapYieldBefore( context.pointcut, context.depth, node.delegate, weaveExpression(node.item, context), tag, ), tag, ), tag, ); } case "AwaitExpression": { return trapAwaitAfter( context.pointcut, context.depth, makeAwaitExpression( trapAwaitBefore( context.pointcut, context.depth, weaveExpression(node.promise, context), tag, ), tag, ), tag, ); } case "EvalExpression": { return trapEvalAfter( context.pointcut, context.depth, makeEvalExpression( trapEvalBefore( context.pointcut, context.depth, weaveExpression(node.code, context), tag, ), tag, ), tag, ); } case "ApplyExpression": { return trapApplyAround( context.pointcut, context.depth, weaveExpression(node.callee, context), weaveExpression(node.this, context), map(node.arguments, (argument) => weaveExpression(argument, context)), tag, ); } case "ConstructExpression": { return trapConstructAround( context.pointcut, context.depth, weaveExpression(node.callee, context), map(node.arguments, (argument) => weaveExpression(argument, context)), tag, ); } default: { throw new AranTypeError(node); } } };