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, универсальное абстрактное синтаксическое дерево

Gelex, универсальный лексер

Ангел «Ява» Лопес
https://github.com/ajlopez
https://twitter.com/ajlopez