Source: lib/core/expression/fallback.js

/**
 * @file fallback.js
 * @description Where the expression interpreter plugs in, when it is present.
 *
 * ## Why this indirection exists
 *
 * Every expression an application contains is compiled to a closure at build
 * time. When that succeeds for all of them — which is the normal case, and the
 * build says so when it is not — the interpreter has nothing to do, and a
 * production bundle should not carry a JavaScript parser and a tree-walking
 * evaluator that can never run.
 *
 * A bundler cannot work that out from a conditional `import`, so the dependency
 * is inverted instead. Nothing in the evaluation path imports the interpreter;
 * it registers itself here, and only the *development* entry pulls in the
 * module that does the registering. In a production build nothing references
 * it, so the parser, the evaluator and the old source-text sandbox are shaken
 * out of the graph entirely.
 *
 * This is the same mechanism the trace recorder uses, for the same reason: the
 * difference between a development and a production bundle should be which
 * modules are reachable, not a flag consulted at run time.
 *
 * ## What happens in production when an expression did not compile
 *
 * It throws, naming the expression, rather than silently doing something else.
 * The build already reported it as AVX_W48 and refused it outright if the
 * reason was a security one, so this is the last of three chances to notice —
 * and a loud failure is better than a bundle that quietly carries an
 * interpreter because one template used a construct nobody meant to use.
 * @module lib/core/expression/fallback
 */

/**
 * The interpreter, when a build has installed one.
 * @type {{evaluate: function(string, object): any, execute: function(string, object): any}|null}
 */
let interpreter = null;

/**
 * Installs the interpreter.
 *
 * Called by the development-only module the compiler adds to the entry. Nothing
 * else should call it: a production application that wants an expression to
 * work should compile it, not reinstate the interpreter.
 * @param {object} implementation - The interpreter.
 * @param {function(string, object): any} implementation.evaluate - Evaluates an expression.
 * @param {function(string, object): any} implementation.execute - Runs a statement body.
 */
export function installExpressionInterpreter(implementation) {
  interpreter = implementation || null;
}

/**
 * The installed interpreter, or null.
 * @returns {object|null} The interpreter.
 */
export function getExpressionInterpreter() {
  return interpreter;
}

/**
 * Whether an interpreter is available.
 *
 * Exposed so a diagnostic can tell "this build has no interpreter" apart from
 * "this expression did not compile", which are very different things to a
 * developer reading an error.
 * @returns {boolean} True when one is installed.
 */
export function hasExpressionInterpreter() {
  return interpreter !== null;
}