Table of contents

Interceptor

Onto the last but not least argument of metaesEval - config. Provided config will be passed around during execution of given script inside metaES, until the execution ends. It may contain many things, but most interesting is interceptor.

Think of an interceptor as a function that is called every time metaES enters or exits AST node. It's a basic building block for many metaES use cases.

Example:

let start = new Date().getTime();
let padding = 0;
let source = "a+2";
metaesEval(
  source,
  console.log, // 4
  console.error,
  { values: { a: 2 } },
  {
    interceptor({ phase, timestamp, e, value, config }) {
      if (phase === "exit") {
        padding--;
      }
      console.log(
        `[${timestamp - start}ms] script${config.script.scriptId}:${
          e.loc ? e.loc.start.line + "," + e.loc.start.column : "*"
        }`,
        "\t",
        " ".repeat(padding),
        e.type,
        e.range ? `"${source.substring(e.range[0], e.range[1])}"` : "",
        `(${value})`
      );
      if (phase === "enter") {
        padding++;
      }
    }
  }
);

will output:

1ms enter Program
1ms enter ExpressionStatement
1ms enter BinaryExpression
2ms enter Identifier
2ms enter GetValue
2ms exit GetValue
3ms exit Identifier
3ms enter Literal
4ms exit Literal
5ms exit BinaryExpression
5ms exit ExpressionStatement
6ms exit Program

That kind of log is useful for all sorts of instrumentation tools. enter/exit phases implicitly create tree structure, let's try to visualize it better and add some more metadata:

let start = new Date().getTime();
let padding = 0;
let source = `var a = 2;
var b = 3;
b+a;`;
metaesEval(
  source,
  console.log, // 4
  console.error,
  { values: { a: 2 } },
  {
    interceptor({ phase, timestamp, e, value, config }) {
      if (phase === "exit") {
        padding--;
      }
      console.log(
        `[${timestamp - start}ms] script${config.script.scriptId}:${
          e.loc ? e.loc.start.line + "," + e.loc.start.column : "*"
        }`,
        "\t",
        " ".repeat(padding),
        `${phase === "enter" ? "↓" : "↑"}${e.type}:`,
        e.range ? `"${source.substring(e.range[0], e.range[1]).replace(/\n/g, "\\n")}"` : "",
        `=> ${value}`
      );
      if (phase === "enter") {
        padding++;
      }
    }
  }
);

Output is more verbose:

[8ms] script0:1,0       ↓Program: "var a = 2;\nvar b = 3;\nb+a;" => undefined
[12ms] script0:1,0        ↓VariableDeclaration: "var a = 2;" => undefined
[12ms] script0:1,4         ↓VariableDeclarator: "a = 2" => undefined
[13ms] script0:1,8          ↓Literal: "2" => undefined
[13ms] script0:1,8          ↑Literal: "2" => 2
[13ms] script0:*          ↓SetValue:  => undefined
[13ms] script0:*          ↑SetValue:  => 2
[13ms] script0:1,4         ↑VariableDeclarator: "a = 2" => 2
[13ms] script0:1,0        ↑VariableDeclaration: "var a = 2;" => 2
[14ms] script0:2,0        ↓VariableDeclaration: "var b = 3;" => undefined
[14ms] script0:2,4         ↓VariableDeclarator: "b = 3" => undefined
[14ms] script0:2,8          ↓Literal: "3" => undefined
[14ms] script0:2,8          ↑Literal: "3" => 3
[14ms] script0:*          ↓SetValue:  => undefined
[14ms] script0:*          ↑SetValue:  => 3
[14ms] script0:2,4         ↑VariableDeclarator: "b = 3" => 3
[14ms] script0:2,0        ↑VariableDeclaration: "var b = 3;" => 3
[14ms] script0:3,0        ↓ExpressionStatement: "b+a;" => undefined
[14ms] script0:3,0         ↓BinaryExpression: "b+a" => undefined
[15ms] script0:3,0          ↓Identifier: "b" => undefined
[15ms] script0:*           ↓GetValue:  => undefined
[15ms] script0:*           ↑GetValue:  => 3
[15ms] script0:3,0          ↑Identifier: "b" => 3
[15ms] script0:3,2          ↓Identifier: "a" => undefined
[15ms] script0:*           ↓GetValue:  => undefined
[15ms] script0:*           ↑GetValue:  => 2
[15ms] script0:3,2          ↑Identifier: "a" => 2
[15ms] script0:3,0         ↑BinaryExpression: "b+a" => 5
[16ms] script0:3,0        ↑ExpressionStatement: "b+a;" => 5
[16ms] script0:1,0       ↑Program: "var a = 2;\nvar b = 3;\nb+a;" => 5

This way of using interceptor can be a base for flame graph tool.