Gepars: универсальный парсер
Я написал общий парсер на JavaScript, gepars. В настоящее время я использую его в своих проектах вместе с gelex (универсальный лексер) и geast (универсальный абстрактный синтаксический древо). Написанный с использованием TDD (Test-Driven Development), интересная часть заключается в том, что я нашел его очень полезным: я мог написать несколько интерпретаторов и компиляторов с помощью нескольких строк кода. Обычно общий синтаксический анализатор генерирует представление, абстрактное синтаксическое дерево. Трудная часть состоит в том, чтобы интерпретировать или преобразовать AST в машинный код. А вот парсить исходный код теперь стало проще.
Простой код для создания константного узла в универсальном AST:
const gepars = require('gepars'); const geast = require('geast'); // generic AST // parser definition const pdef = gepars.definition(); // define integer node pdef.define('integer', 'integer:', function (value) { return geast.constant(parseInt(value)); });
Обозначение integer:
относится к целочисленному детектору анализатора. Функция получает value
в качестве строкового значения этого токена. И возвращает постоянный узел AST.
Разбор реального числа:
pdef.define('real', 'real:', function (value) { return geast.constant(parseFloat(value)); });
Разбор логических констант (обратите внимание на использование токена лексера С указанным значением):
pdef.define('boolean', 'name:true', function (value) { return geast.constant(true); }); pdef.define('boolean', 'name:false', function (value) { return geast.constant(false); });
Мы можем определить string`
и другие типы констант. Переменная, на которую ссылаются по имени, может быть определена как:
pdef.define('name', 'name:', function (value) { return geast.name(value); });
Основной термин в выражении:
// terms pdef.define('term', 'integer'); pdef.define('term', 'real'); pdef.define('term', 'string'); pdef.define('term', 'boolean'); pdef.define('term', 'name'); pdef.define('term', [ 'delimiter:(', 'expression', 'delimiter:)' ], function (values) { return values[1]; });
Обратите внимание на определение массива в последнем определении: последовательность частей для анализа, описывающая общее выражение в круглых скобках.
Общее выражение может быть определено с учетом приоритета оператора и ассоциативности:
// expressions pdef.define('expression', 'expression0'); pdef.define('expression0', 'expression1'); pdef.define('expression0', [ 'expression0', 'binop0', 'expression1' ], function (values) { return geast.binary(values[1], values[0], values[2]); }); pdef.define('expression1', 'expression2'); pdef.define('expression1', [ 'expression1', 'binop1', 'expression2' ], function (values) { return geast.binary(values[1], values[0], values[2]); }); pdef.define('expression2', 'term'); pdef.define('expression2', [ 'expression2', 'binop2', 'term' ], function (values) { return geast.binary(values[1], values[0], values[2]); });
где бинарные операторы:
`pdef.define('binop0', 'operator:<'); pdef.define('binop0', 'operator:<='); pdef.define('binop0', 'operator:>'); pdef.define('binop0', 'operator:>='); pdef.define('binop0', 'operator:=='); pdef.define('binop0', 'operator:!='); pdef.define('binop1', 'operator:+'); pdef.define('binop1', 'operator:-'); pdef.define('binop2', 'operator:*'); pdef.define('binop2', 'operator:/');
Парсер может управлять левой рекурсией и соответствующим образом расширяться:
pdef.define('expression1', [ 'expression1', 'binop1', 'expression2' ], function (values) { return geast.binary(values[1], values[0], values[2]); });
где expression1
может иметь левое expression1
.
Имея определение лексера, я обычно разбираю текст, используя такой код:
function parseNode(type, text) { const lexer = lexers.lexer(text); const parser = pdef.parser(lexer); return parser.parse(type); }
type
— это нетерминальная часть для анализа, т. е. expression
, term
и т. д.
Сейчас я использую гепарс в следующих личных проектах:
- Walang: язык программирования для компиляции в WebAssembly
- Selang: простой язык программирования для смарт-контрактов Ethereum
- Rlie: R-подобный интерпретатор языка программирования
- Erlie: Erlang-подобный интерпретатор языка программирования
- Solcom: компилятор языка программирования Solidity в байт-код виртуальной машины Ethereum.
Похожие сообщения:
Geast, универсальное абстрактное синтаксическое дерево
Ангел «Ява» Лопес
https://github.com/ajlopez
https://twitter.com/ajlopez