Глава 21: Функция DoParseProgram и генерация AST

Добро пожаловать в другие главы Давайте разберемся с Chrome V8

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

1. Сканер и токен

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

Строка 3 — инициализация сканера, ниже — исходный код:

В строке 3 функция Init() инициализирует сканер. В строке 5 Scan() генерирует только токен, помните, что сканер управляется синтаксическим анализатором, этот токен используется, чтобы сначала разбудить синтаксический анализатор, а затем вскоре вызвать промах кеширования токенов, что в конечном итоге запускает конвейер компилятора. . Давайте рассмотрим Init() более подробно.

В строке 2 Advance() извлекает символ из кода JavaScript, который вы, как разработчики, пишете на самом деле.

В строке 11 показан исходный код Advance(). В строке 15 ключевое слово c0_ указывает на следующий символ, который будет удален функцией Advance(). Определение source_->Advance находится в строке 18. Пожалуйста, посмотрите на строку 24, Peek() — это фактическая функция, которая извлекает символ из вашего JavaScript, поскольку в строках 25–28 она считывает буферный поток, который содержит ваш JavaScript.

Вернемся к void Scanner::Initialize(), давайте углубимся в Scan().

В строке 4 ScanSingleToken() использует Advance() для извлечения символов один за другим, пока не встретится терминатор.

В нашем случае начальным символом является f, мы знаем, что f может означать ключевое слово function или обычную определяемую пользователем переменную, в зависимости от следующих символов. Поскольку в нашем случае это ключевое слово function, перейдите к строкам 16–17.

Основой сканера является автомат с конечным состоянием, V8 использует предопределенный шаблон макроса и регистр переключения для сканирования символов, а затем объединяет связанные символы в виде токена, который будет заполнен в кэше.

Давайте взглянем на ScanIdentifierOrKeyword():

ScanIdentifierOrKeywordInner() генерирует один токен за одно выполнение.

2. Создать AST

DoParseProgram() отвечает за создание дерева AST.

В строке 2 выберите ленивый вариант, по умолчанию ленивый имеет значение true и определен в flag-definitions.h. Строка 8 создает пустое тело AST.

В строке 16 ParseStatementList() анализирует токены и генерирует AST.

В строке 4 peek() возвращает тип текущего токена. В нашем случае первым токеном является Token::FUNCTION, который не является ни Token::STRING, ни end_token, поэтому перейдите в ParseStatementListItem().

В ParseStatementListItem в соответствии с типом Token выполните следующие действия. Точнее, ParseStatementListItem также является автоматом с конечным состоянием, который использует предопределенные шаблоны макросов и регистр переключения для сопоставления и анализа символов.

На рис. 1 показан стек вызовов.

В JavaScript оператор может быть определением переменной, функцией или блоком операторов, поэтому ParseStatementListItem() часто рекурсивно вызывает сам себя.

Выводы

(1) В основе компилятора лежит использование конечного автомата для анализа символов. В частности, V8 использует шаблоны макросов и регистр переключателей.

(2) Минимальная степень детализации компилятора — это функция JavaScript.

(3) Поскольку здесь присутствует ленивая компиляция, функция компилируется, когда она скоро будет выполняться.

(4) FunctionLiteral содержит дерево AST, представляющее вашу функцию JavaScript.

(5) Ниже приведен шаблон макроса, определяющий токен:

Хорошо, на этом мы заканчиваем. Увидимся в следующий раз, берегите себя!

Пожалуйста, свяжитесь со мной, если у вас есть какие-либо проблемы. WeChat: qq9123013 Электронная почта: [email protected]

Дополнительные материалы на PlainEnglish.io. Подпишитесь на нашу бесплатную еженедельную рассылку новостей. Подпишитесь на нас в Twitter, LinkedIn, YouTube и Discord.