All files / lib/core/expression interpreter.js

100% Statements 81/81
100% Branches 11/11
100% Functions 2/2
100% Lines 81/81

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82256x 256x 256x 256x 256x 256x 256x 256x 256x 256x 256x 256x 256x 256x 256x 256x 256x 256x 256x 256x 256x 256x 256x 256x 256x 256x 256x 256x 256x 256x 256x 256x 256x 256x 256x 256x 256x 256x 256x 256x 1607x 1607x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 1604x 256x 256x 256x 256x 256x 256x 256x 256x 256x 256x 256x 256x 256x 173x 173x 138x 138x 35x 35x 42x 30x 30x 30x 30x 34x 256x 256x  
/**
 * @file interpreter.js
 * @description The expression interpreter, as a development-only plug-in.
 *
 * This is the only module that reaches the parser, the tree-walking evaluator
 * and the old source-text sandbox, and the only thing that imports *it* is the
 * module the compiler adds to a development entry. That is what keeps all three
 * out of a production bundle: they are unreachable there, so the bundler drops
 * them.
 *
 * It exists because a development build should keep working while a developer
 * is still writing the template — an expression mid-edit, or one using a
 * construct the generator does not cover, should render rather than fail, with
 * the build's AVX_W48 warning saying which. A production build makes the
 * opposite trade: an uncompiled expression is a defect to fix, not a cost to
 * absorb on every page load.
 * @module lib/core/expression/interpreter
 */
 
import { compileExpression, compileStatements, describeParseFailure } from './compile.js';
import { evaluate as evaluateAst } from './evaluator.js';
import { installExpressionInterpreter } from './fallback.js';
import { AvenxSandbox } from '../security/sandbox.js';
import { AvenxError, AvenxErrorCodes } from '../runtime/AvenxError.js';
import { LruCache } from '../utils/LruCache.js';
 
/**
 * Function bodies built for statement sources the AST evaluator cannot run.
 * @type {LruCache}
 */
const statementCache = new LruCache(1000);
 
installExpressionInterpreter({
  /**
   * Evaluates an expression the compiler did not compile.
   * @param {string} source - The expression source.
   * @param {object} scope - The evaluation scope.
   * @returns {any} The value.
   */
  evaluate(source, scope) {
    const ast = compileExpression(source);
    if (!ast) {
      // Refused, not handed to the engine. The interpreter exists to evaluate
      // what the *parser* understands; an expression outside the language is
      // outside it in a development build too, and gets the same AVX_R32 it
      // always did -- there is no path from a parse failure to `new Function`
      // on the expression side, in either mode.
      throw new AvenxError(
        AvenxErrorCodes.EXPRESSION_UNSUPPORTED,
        source,
        describeParseFailure(source),
      );
    }
    return evaluateAst(ast, scope);
  },
 
  /**
   * Runs a statement body the compiler did not compile.
   *
   * This is the last `new Function` in the framework, and it is reachable only
   * from a development build. A body that gets here is one the action compiler
   * refused, which the build reported.
   * @param {string} source - The statement source.
   * @param {object} scope - The evaluation scope.
   * @returns {any} Whatever the body returned.
   */
  execute(source, scope) {
    const ast = compileStatements(source);
    if (ast) {
      return evaluateAst(ast, scope);
    }
 
    let fn = statementCache.get(source);
    if (!fn) {
      AvenxSandbox.validateSource(source);
      fn = new Function(`with(this) { ${source} }`);
      statementCache.set(source, fn);
    }
    return fn.call(AvenxSandbox.createProxy(scope, scope));
  },
});