« giao-js » использует js для написания интерпретатора js.

внешний интерфейс JavaScript
« giao-js » использует js для написания интерпретатора js.

предисловие

В этой статье мы создадим собственный JS-интерпретатор через JS и напишем JS на JS, что может показаться странным, но при этом мы лучше познакомимся с JS, а также узнаем, как работают JS-движки!

Что такое интерпретатор?

Интерпретатор — это оценщик языка, который запускается во время выполнения и динамически выполняет исходный код программы. Интерпретатор анализирует исходный код, генерирует AST (абстрактное синтаксическое дерево) из исходного кода, проходит по AST и вычисляет их один за другим.

Как работает интерпретатор

Interpreter

  • Лексический анализ (токенизация)

  • Парсинг (Парсинг)

  • Оценка

Лексический анализ (токенизация)

Процесс декомпозиции и организации исходного кода в набор осмысленных слов называется лексическим анализом (Token).

В английском языке, когда мы сталкиваемся с таким предложением:

Javascript is the best language in the world

Мы подсознательно разбиваем предложения на слова:

+----------------------------------------------------------+
| Javascript | is | the | best | language | in |the |world |
+----------------------------------------------------------+

Это первый этап разбора и понимания предложений.

Лексический анализ проводитсялексический анализаторПо завершении лексер сканирует код и извлекает лексические единицы.

var a = 1;

[
  ("var": "keyword"),
  ("a": "identifier"),
  ("=": "assignment"),
  ("1": "literal"),
  (";": "separator"),
];

После того, как лексический анализатор разложит код на токены, он передаст токен синтаксическому анализатору для синтаксического анализа.Давайте посмотрим, как работает этап синтаксического анализа.

Парсинг (Парсинг)

Токен, сгенерированный на этапе лексического анализа, преобразуется в абстрактное синтаксическое дерево (Abstract Syntax Tree), что называется парсингом.

На английском языке Javascript — лучший язык, разбитый на следующие слова:

+------------------------------------------+
| Javascript | is | the | best | language  |
+------------------------------------------+

Таким образом, мы можем подбирать слова и формировать грамматические конструкции:

"Javascript": Subject
"is the best language": Predicate
"language": Object

Яваскрипт — это существительное в грамматике, остальное — малозначительное предложение, называемое сказуемым, язык — приемник действия, то есть объект. Структура такая:

Subject(Noun) -> Predicate -> Object

Разбор синтаксиса выполняетсяпарсерПо завершении он преобразует токен, сгенерированный на предыдущем шаге, в абстрактное синтаксическое дерево (AST) в соответствии с правилами грамматики.

{
  type: "Program",
  body: [
    {
      type: "VariableDeclaration",
      declarations: [
        {
          type: "VariableDeclarator",
          id: {
            type: "Identifier",
            name: "sum"
          },
          init: {
            type: "Literal",
            value: 30,
            raw: "30"
          }
        }
      ],
      kind: "var"
    }
  ],
}

Оценка

Интерпретатор будет проходить через AST и вычислять каждый узел. - этап оценки

1 + 2
|
    |
    v
+---+  +---+
| 1 |  | 2 |
+---+  +---+
  \     /
   \   /
    \ /
   +---+
   | + |
   +---+
{
    lhs: 1,
    op: '+'.
    rhs: 2
}

Интерпретатор разбирает Ast, получает узел LHS, затем собирает узел оператора +, оператор + указывает, что требуется операция сложения, и он должен иметь второй узел для операции сложения, затем собирает узел RHS. Он собирает ценную информацию и выполняет сложение для получения результата, 3.

{
  type: "Program",
  body: [
    {
      type: "ExpressionStatement",
      expression: {
        type: "BinaryExpression",
        left: {
          type: "Literal",
          value: 1,
          raw: "1"
        },
        operator: "+",
        right: {
          type: "Literal",
          value: 2,
          raw: "2"
        }
      }
    }
  ],
}

упражняться

Как мы ввели принцип работы переводчика, давайте переместим руку к костям, реализуйте интерпретатор Mini JS ~

Подготовка

  • Acorn.js

Крошечный, быстрый анализатор JavaScript, полностью написанный на JavaScript Небольшой и быстрый парсер JavaScript, полностью реализованный на JavaScript

В этой практике мы будем использовать acorn.js, который поможет нам выполнить лексический анализ, разбор грамматики и преобразование в абстрактные синтаксические деревья.

Сторонние библиотеки, такие как Webpack/Rollup/Babel(@babel/parser), также используют acorn.js в качестве базовой библиотеки для своего собственного парсера. (Встать на плечи гигантов!)

  • The Estree Spec

самое началоMozilla JS Parser APIЭто документ спецификации для вывода JavaScript AST движком SpiderMonkey, созданным инженерами Mozilla в Firefox Формат, описанный в документе, используется в качестве общего языка для манипулирования исходным кодом JavaScript.

По мере развития JavaScript добавлялось больше новых синтаксисов, чтобы помочь развивать формат, чтобы идти в ногу с эволюцией языка JavaScript.The ESTree Specродился как стандарт сообщества для тех, кто занимается созданием и использованием этих инструментов.

Возвращаемое значение синтаксического анализа acorn.js соответствует объекту AST, описанному в спецификации ESTree Здесь мы используем @types/estree в качестве определения типа.

  • Jest

Утверждает, что это восхитительное тестирование JavaScript... мы используем его для модульного тестирования.

  • Rollup

Rollup — это сборщик модулей JavaScript, который мы используем для упаковки и предоставления модулей в соответствии со спецификацией UMD.

Инициализация проекта

// visitor.ts 创建一个Visitor类,并提供一个方法操作ES节点。
import * as ESTree from "estree";
class Visitor {
  visitNode(node: ESTree.Node) {
    // ...
  }
}
export default Visitor;
// interpreter.ts 创建一个Interpreter类,用于运行ES节点树。
// 创建一个Visitor实例,并使用该实例来运行ESTree节点
import Visitor from "./visitor";
import * as ESTree from "estree";
class Interpreter {
  private visitor: Visitor;
  constructor(visitor: Visitor) {
    this.visitor = visitor;
  }
  interpret(node: ESTree.Node) {
    this.visitor.visitNode(node);
  }
}
export default Interpreter;
// vm.ts 对外暴露run方法,并使用acorn code->ast后,交给Interpreter实例进行解释。
const acorn = require("acorn");
import Visitor from "./visitor";
import Interpreter from "./interpreter";

const jsInterpreter = new Interpreter(new Visitor());

export function run(code: string) {
  const root = acorn.parse(code, {
    ecmaVersion: 8,
    sourceType: "script",
  });
  return jsInterpreter.interpret(root);
}

Практическая пуля 1: 1+1= ?

Мы достигли этого раздела до 1 + 1 добавления добавления. Сначала мы пройдемAST explorer, и посмотрите на структуру AST после преобразования кода 1+1.

1+1 ast

Мы видим, что в этом коде есть 4 типа узлов, давайте кратко представим их:

Program

Корневой узел представляет все абстрактное синтаксическое дерево, а атрибут body представляет собой массив, содержащий несколько узлов Statement.

interface Program {
  type: "Program";
  sourceType: "script" | "module";
  body: Array<Directive | Statement | ModuleDeclaration>;
  comments?: Array<Comment>;
}

ExpressionStatement

Узел оператора выражения, атрибут выражения указывает на объект узла выражения

interface ExpressionStatement {
  type: "ExpressionStatement";
  expression: Expression;
}

BinaryExpression

Узел выражения бинарной операции, слева и справа, представляет два выражения слева и справа от оператора, а оператор представляет бинарный оператор. Суть этого раздела проста для понимания: нам нужно только получить тип оператора operator и реализовать его, а затем оценить левое и правое значения.

interface BinaryExpression {
  type: "BinaryExpression";
  operator: BinaryOperator;
  left: Expression;
  right: Expression;
}

Literal

Литерал здесь относится не к [] или {}, а к литералам, которые семантически представляют значение, например 1, "hello", true, и регулярным выражениям, таким как /\d?/.

type Literal = SimpleLiteral | RegExpLiteral;

interface SimpleLiteral {
  type: "Literal";
  value: string | boolean | number | null;
  raw?: string;
}

interface RegExpLiteral {
  type: "Literal";
  value?: RegExp | null;
  regex: {
    pattern: string;
    flags: string;
  };
  raw?: string;
}

Не говори глупостей, поехали!!!

// standard/es5.ts 实现以上节点方法

import Scope from "../scope";
import * as ESTree from "estree";
import { AstPath } from "../types/index";

const es5 = {
  // 根节点的处理很简单,我们只要对它的body属性进行遍历,然后访问该节点即可。
  Program(node: ESTree.Program) {
    node.body.forEach((bodyNode) => this.visitNode(bodyNode));
  },
  // 表达式语句节点的处理,同样访问expression 属性即可。
  ExpressionStatement(node: ESTree.ExpressionStatement>) {
    return this.visitNode(node.expression);
  },
  // 字面量节点处理直接求值,这里对正则表达式类型进行了特殊处理,其他类型直接返回value值即可。
  Literal(node: ESTree.Literal>) {
    if ((<ESTree.RegExpLiteral>node).regex) {
      const { pattern, flags } = (<ESTree.RegExpLiteral>node).regex;
      return new RegExp(pattern, flags);
    } else return node.value;
  },
  // 二元运算表达式节点处理
  // 对left/node两个节点(Literal)进行求值,然后实现operator类型运算,返回结果。
  BinaryExpression(node: ESTree.BinaryExpression>) {
    const leftNode = this.visitNode(node.left);
    const operator = node.operator;
    const rightNode = this.visitNode(node.right);
    return {
      "+": (l, r) => l + r,
      "-": (l, r) => l - r,
      "*": (l, r) => l * r,
      "/": (l, r) => l / r,
      "%": (l, r) => l % r,
      "<": (l, r) => l < r,
      ">": (l, r) => l > r,
      "<=": (l, r) => l <= r,
      ">=": (l, r) => l >= r,
      "==": (l, r) => l == r,
      "===": (l, r) => l === r,
      "!=": (l, r) => l != r,
      "!==": (l, r) => l !== r,
    }[operator](leftNode, rightNode);
  },
};
export default es5;
// visitor.ts
import Scope from "./scope";
import * as ESTree from "estree";
import es5 from "./standard/es5";

const VISITOR = {
  ...es5,
};
class Visitor {
  // 实现访问节点方法,通过节点类型访问对应的节点方法
  visitNode(node: ESTree.Node) {
    return {
      visitNode: this.visitNode,
      ...VISITOR,
    }[node.type](node);
  }
}
export default Visitor;

Таким образом выполняются обычные бинарные операции!!!

Практическое занятие 2: Как найти переменные?

Концепция области действия Javascript и цепочки областей видимости должна быть знакома всем, поэтому я не буду повторять ее здесь~

Да, нам нужно реализовать область действия для доступа к переменным и реализовать цепочку областей видимости для поиска идентификаторов.

Перед этим мы сначала реализуем класс Variable и реализуем метод доступа к переменной.

// variable.ts
export enum Kind {
  var = "var",
  let = "let",
  const = "const",
}
export type KindType = "var" | "let" | "const";
export class Variable {
  private _value: any;
  constructor(public kind: Kind, val: any) {
    this._value = val;
  }
  get value() {
    return this._value;
  }
  set value(val: any) {
    this._value = val;
  }
}
import { Variable, Kind, KindType } from "./variable";

class Scope {
  // 父作用域
  private parent: Scope | null;
  // 当前作用域
  private targetScope: { [key: string]: any };
  constructor(public readonly type, parent?: Scope) {
    this.parent = parent || null;
    this.targetScope = new Map();
  }
  // 是否已定义
  private hasDefinition(rawName: string): boolean {
    return Boolean(this.search(rawName));
  }
  // var类型变量定义
  public defineVar(rawName: string, value: any) {
    let scope: Scope = this;
    // 如果不是全局作用域且不是函数作用域,找到全局作用域,存储变量
    // 这里就是我们常说的Hoisting (变量提升)
    while (scope.parent && scope.type !== "function") {
      scope = scope.parent;
    }
    // 存储变量
    scope.targetScope.set(rawName, new Variable(Kind.var, value));
  }
  // let类型变量定义
  public defineLet(rawName: string, value: any) {
    this.targetScope.set(rawName, new Variable(Kind.let, value));
  }
  // const类型变量定义
  public defineConst(rawName: string, value: any) {
    this.targetScope.set(rawName, new Variable(Kind.const, value));
  }
  // 作用域链实现,向上查找标识符
  public search(rawName: string): Variable | null {
    if (this.targetScope.get(rawName)) {
      return this.targetScope.get(rawName);
    } else if (this.parent) {
      return this.parent.search(rawName);
    } else {
      return null;
    }
  }
  // 变量声明方法,变量已定义则抛出语法错误异常
  public declare(kind: Kind | KindType, rawName: string, value: any) {
    if (this.hasDefinition(rawName)) {
      console.error(
        `Uncaught SyntaxError: Identifier '${rawName}' has already been declared`
      );
      return true;
    }
    return {
      [Kind.var]: () => this.defineVar(rawName, value),
      [Kind.let]: () => this.defineLet(rawName, value),
      [Kind.const]: () => this.defineConst(rawName, value),
    }[kind]();
  }
}

export default Scope;

Выше приведена базовая реализация переменных объектов, областей видимости и цепочек областей видимости, после чего мы можем определить и получить доступ к переменным.

Тренировочный раунд 3: var age = 18

var

Из синтаксического дерева мы можем увидеть три незнакомых типа узлов, чтобы понять, что они означают:

VariableDeclaration

Для объявлений переменных атрибут kind указывает тип объявления, потому что ES6 представил const/let. объявления означают несколько описаний объявлений, потому что мы можем сделать это: пусть a = 1, b = 2;.

interface VariableDeclaration {
  type: "VariableDeclaration";
  declarations: Array<VariableDeclarator>;
  kind: "var" | "let" | "const";
}

VariableDeclarator

Описание объявления переменной, id представляет узел имени переменной, а init представляет выражение начального значения, которое может быть нулевым.

interface VariableDeclarator {
  type: "VariableDeclarator";
  id: Pattern;
  init?: Expression | null;
}

Identifier

Как следует из названия, узел идентификатора, имя переменной, имя функции и имя атрибута, определенные при написании JS, классифицируются как идентификаторы.

interface Identifier {
  type: "Identifier";
  name: string;
}

Поняв значение соответствующего узла, давайте его реализуем:

// standard/es5.ts 实现以上节点方法

import Scope from "../scope";
import * as ESTree from "estree";

type AstPath<T> = {
  node: T;
  scope: Scope;
};

const es5 = {
  // ...
  // 这里我们定义了astPath,新增了scope作用域参数
  VariableDeclaration(astPath: AstPath<ESTree.VariableDeclaration>) {
    const { node, scope } = astPath;
    const { declarations, kind } = node;
    // 上面提到,生声明可能存在多个描述(let a = 1, b = 2;),所以我们这里对它进行遍历:
    // 这里遍历出来的每个item是VariableDeclarator节点
    declarations.forEach((declar) => {
      const { id, init } = <ESTree.VariableDeclarator>declar;
      // 变量名称节点,这里拿到的是age
      const key = (<ESTree.Identifier>id).name;
      // 判断变量是否进行了初始化 ? 查找init节点值(Literal类型直接返回值:18) : 置为undefined;
      const value = init ? this.visitNode(init, scope) : undefined;
      // 根据不同的kind(var/const/let)声明进行定义,即var age = 18
      scope.declare(kind, key, value);
    });
  },
  // 标识符节点,我们只要通过访问作用域,访问该值即可。
  Identifier(astPath: AstPath<ESTree.Identifier>) {
    const { node, scope } = astPath;
    const name = node.name;
    // walk identifier
    // 这个例子中查找的是age变量
    const variable = scope.search(name);
    // 返回的是定义的变量对象(age)的值,即18
    if (variable) return variable.value;
  },
};
export default es5;

Практика №4: module.exports = 6

Сначала посмотрим на AST, соответствующую Module.exports = 6.

module-exports

Из синтаксического дерева мы видим два незнакомых типа узлов, давайте посмотрим, что они означают:

AssignmentExpression

В узле выражения присваивания атрибут operator представляет оператор присваивания, а left и right — это выражения слева и справа от оператора присваивания.

interface AssignmentExpression {
  type: "AssignmentExpression";
  operator: AssignmentOperator;
  left: Pattern | MemberExpression;
  right: Expression;
}

MemberExpression

Узел выражения члена, то есть оператор, который ссылается на член объекта, объект — это узел выражения, который ссылается на объект, свойство — это имя свойства, если вычислено — false, это означает, что для ссылки на член , свойство должно быть узлом идентификатора, если вычисляемое свойство имеет значение true, оно [] для ссылки, то есть свойство является узлом выражения, а имя является результирующим значением выражения.

interface MemberExpression {
  type: "MemberExpression";
  object: Expression | Super;
  property: Expression;
  computed: boolean;
  optional: boolean;
}

Давайте сначала определим переменную module.exports.

import Scope from "./scope";
import Visitor from "./visitor";
import * as ESTree from "estree";
class Interpreter {
  private scope: Scope;
  private visitor: Visitor;
  constructor(visitor: Visitor) {
    this.visitor = visitor;
  }
  interpret(node: ESTree.Node) {
    this.createScope();
    this.visitor.visitNode(node, this.scope);
    return this.exportResult();
  }
  createScope() {
    // 创建全局作用域
    this.scope = new Scope("root");
    // 定义module.exports
    const $exports = {};
    const $module = { exports: $exports };
    this.scope.defineConst("module", $module);
    this.scope.defineVar("exports", $exports);
  }
  // 模拟commonjs,对外暴露结果
  exportResult() {
    // 查找module变量
    const moduleExport = this.scope.search("module");
    // 返回module.exports值
    return moduleExport ? moduleExport.value.exports : null;
  }
}
export default Interpreter;

хорошо, давайте реализуем вышеуказанную функцию узла~

// standard/es5.ts 实现以上节点方法

import Scope from "../scope";
import * as ESTree from "estree";

type AstPath<T> = {
  node: T;
  scope: Scope;
};

const es5 = {
  // ...
  // 这里我们定义了astPath,新增了scope作用域参数
  MemberExpression(astPath: AstPath<ESTree.MemberExpression>) {
    const { node, scope } = astPath;
    const { object, property, computed } = node;
    // property 是表示属性名称,computed 如果为 false,property 应该为一个 Identifier 节点,如果 computed 属性为 true,即 property 是一个 Expression 节点
    // 这里我们拿到的是exports这个key值,即属性名称
    const prop = computed
      ? this.visitNode(property, scope)
      : (<ESTree.Identifier>property).name;
    // object 表示对象,这里为module,对module进行节点访问
    const obj = this.visitNode(object, scope);
    // 访问module.exports值
    return obj[prop];
  },
  // 赋值表达式节点
  (astPath: AstPath<ESTree.>) {
    const { node, scope } = astPath;
    const { left, operator, right } = node;
    let assignVar;
    // LHS 处理
    if (left.type === "Identifier") {
      // 标识符类型 直接查找
      const value = scope.search(left.name);
      assignVar = value;
    } else if (left.type === "MemberExpression") {
      // 成员表达式类型,处理方式跟上面差不多,不同的是这边需要自定义一个变量对象的实现
      const { object, property, computed } = left;
      const obj = this.visitNode(object, scope);
      const key = computed
        ? this.visitNode(property, scope)
        : (<ESTree.Identifier>property).name;
      assignVar = {
        get value() {
          return obj[key];
        },
        set value(v) {
          obj[key] = v;
        },
      };
    }
    // RHS
    // 不同操作符处理,查询到right节点值,对left节点进行赋值。
    return {
      "=": (v) => {
        assignVar.value = v;
        return v;
      },
      "+=": (v) => {
        const value = assignVar.value;
        assignVar.value = v + value;
        return assignVar.value;
      },
      "-=": (v) => {
        const value = assignVar.value;
        assignVar.value = value - v;
        return assignVar.value;
      },
      "*=": (v) => {
        const value = assignVar.value;
        assignVar.value = v * value;
        return assignVar.value;
      },
      "/=": (v) => {
        const value = assignVar.value;
        assignVar.value = value / v;
        return assignVar.value;
      },
      "%=": (v) => {
        const value = assignVar.value;
        assignVar.value = value % v;
        return assignVar.value;
      },
    }[operator](this.visitNode(right, scope));
  },
};
export default es5;

хорошо, реализация завершена, пришло время проверить волну и перейти к шуточному Дафа.

// __test__/es5.test.ts

import { run } from "../src/vm";
describe("giao-js es5", () => {
  test("assign", () => {
    expect(
      run(`
      module.exports = 6;
    `)
    ).toBe(6);
  });
}

jest

Практическое занятие 5: Циклы for

var result = 0;
for (var i = 0; i < 5; i++) {
  result += 2;
}
module.exports = result;

for-loop

Все обнаружили, что разные синтаксисы на самом деле соответствуют разным узлам дерева.Нам нужно только реализовать соответствующую функцию узла.Давайте посмотрим на значение этих незнакомых узлов.

ForStatement

Для узла оператора цикла атрибуты init/test/update представляют три выражения в скобках оператора for, значение инициализации, условие оценки цикла и оператор обновления переменной, выполняемый в каждом цикле (init может быть объявлением переменной или выражение). Все три свойства могут быть нулевыми, то есть for(;;){}.
Атрибут body используется для представления оператора, который должен выполняться в цикле.

interface ForStatement {
  type: "ForStatement";
  init?: VariableDeclaration | Expression | null;
  test?: Expression | null;
  update?: Expression | null;
  body: Statement;
}

UpdateExpression

Узел выражения операции обновления, а именно ++/--, аналогичен унарному оператору, но тип объекта узла, на который указывает оператор, отличается, здесь оператор обновления.

interface UpdateExpression {
  type: "UpdateExpression";
  operator: UpdateOperator;
  argument: Expression;
  prefix: boolean;
}

BlockStatement

Узел оператора блока, например: if (...) { // здесь содержимое оператора блока}, блок может содержать несколько других операторов, поэтому имеется атрибут body, представляющий собой массив, представляющий содержимое блока. блокировать несколько операторов.

interface BlockStatement {
  0;
  type: "BlockStatement";
  body: Array<Statement>;
  innerComments?: Array<Comment>;
}

Не говори глупостей, отпусти!!!

// standard/es5.ts 实现以上节点方法

import Scope from "../scope";
import * as ESTree from "estree";

type AstPath<T> = {
  node: T;
  scope: Scope;
};

const es5 = {
  // ...
  // for 循环语句节点
  ForStatement(astPath: AstPath<ESTree.ForStatement>) {
    const { node, scope } = astPath;
    const { init, test, update, body } = node;
    // 这里需要注意的是需要模拟创建一个块级作用域
    // 前面Scope类实现,var声明在块作用域中会被提升,const/let不会
    const forScope = new Scope("block", scope);
    for (
      // 初始化值
      // VariableDeclaration
      init ? this.visitNode(init, forScope) : null;
      // 循环判断条件(BinaryExpression)
      // 二元运算表达式,之前已实现,这里不再细说
      test ? this.visitNode(test, forScope) : true;
      // 变量更新语句(UpdateExpression)
      update ? this.visitNode(update, forScope) : null
    ) {
      // BlockStatement
      this.visitNode(body, forScope);
    }
  },
  // update 运算表达式节点
  // update 运算表达式节点,即 ++/--,和一元运算符类似,只是 operator 指向的节点对象类型不同,这里是 update 运算符。
  UpdateExpression(astPath: AstPath<ESTree.UpdateExpression>) {
    const { node, scope } = astPath;
    // update 运算符,值为 ++ 或 --,配合 update 表达式节点的 prefix 属性来表示前后。
    const { prefix, argument, operator } = node;
    let updateVar;
    // 这里需要考虑参数类型还有一种情况是成员表达式节点
    // 例: for (var query={count:0}; query.count < 8; query.count++)
    // LHS查找
    if (argument.type === "Identifier") {
      // 标识符类型 直接查找
      const value = scope.search(argument.name);
      updateVar = value;
    } else if (argument.type === "MemberExpression") {
      // 成员表达式的实现在前面实现过,这里不再细说,一样的套路~
      const { object, property, computed } = argument;
      const obj = this.visitNode(object, scope);
      const key = computed
        ? this.visitNode(property, scope)
        : (<ESTree.Identifier>property).name;
      updateVar = {
        get value() {
          return obj[key];
        },
        set value(v) {
          obj[key] = v;
        },
      };
    }
    return {
      "++": (v) => {
        const result = v.value;
        v.value = result + 1;
        // preifx? ++i: i++;
        return prefix ? v.value : result;
      },
      "--": (v) => {
        const result = v.value;
        v.value = result - 1;
        // preifx? --i: i--;
        return prefix ? v.value : result;
      },
    }[operator](updateVar);
  },
  // 块语句节点
  // 块语句的实现很简单,模拟创建一个块作用域,然后遍历body属性进行访问即可。
  BlockStatement(astPath: AstPath<ESTree.BlockStatement>) {
    const { node, scope } = astPath;
    const blockScope = new Scope("block", scope);
    const { body } = node;
    body.forEach((bodyNode) => {
      this.visitNode(bodyNode, blockScope);
    });
  },
};
export default es5;

Идите к Джест Дафа, чтобы убедиться в этом~

test("test for loop", () => {
  expect(
    run(`
      var result = 0;
      for (var i = 0; i < 5; i++) {
        result += 2;
      }
      module.exports = result;
    `)
  ).toBe(10);
});

for-loop-jest

Думаете, это конец? Вы когда-нибудь задумывались о том, что осталось необработанным? Оператор break цикла for?

var result = 0;
for (var i = 0; i < 5; i++) {
  result += 2;
  break; // break,continue,return
}
module.exports = result;

Заинтересованные друзья могут попробовать сами или ткнутьАдрес источника

Эпилог

giao-jsВ настоящее время реализовано всего несколько грамматик, и эта статья дает только представление.

Заинтересованные студенты могут просмотретьполный код.

Если вы считаете, что это вам поможет, нажмите звездочку, чтобы поддержать автора ❤️ ~

Ссылаться на

bramblex/jsjs

Разбираем JavaScript с помощью Acorn

Build a JS Interpreter in JavaScript Using Acorn as a Parser