LINUX.ORG.RU

Lug 0.5.0

 , , , ,

Lug 0.5.0

3

4

19 марта состоялся выпуск C++ библиотеки Lug, реализующей встраиваемый предметно-ориентированный язык для выражения синтаксических анализаторов в виде расширенных грамматик синтаксических выражений, и распространяемой по лицензии MIT.

Возможности библиотеки:

  • Естественный синтаксис, напоминающий языки внешних генераторов парсеров, с поддержкой атрибутов и семантических действий.
  • Возможность работы с контекстно-зависимыми грамматиками с таблицами символов, условиями и синтаксическими предикатами.
  • Сгенерированные парсеры компилируются в байткод и выполняются в виртуальной машине синтаксического анализа.
  • Чёткое разделение синтаксических и лексических правил с возможностью настройки неявного пропуска пробельных символов.
  • Поддержка прямой и косвенной левой рекурсии, с уровнями старшинства для разграничения подвыражений со смешанными левой и правой рекурсиями.
  • Полная поддержка разбора текста в формате UTF-8, включая уровень 1 и частичное соответствие уровню 2 технического стандарта UTS #18 Unicode Regular Expressions.
  • Обработка ошибок и восстановление с помощью помеченных сбоев, правил восстановления и обработчиков ошибок.
  • Автоматическое отслеживание номеров строк и колонок, настраиваемая ширина и выравнивание табуляции.
  • Header-only-библиотека, использующая только стандартную библиотеку и возможности стандарта C++17. Перспективно совместима со стандартами C++20 и C++23.
  • Относительно небольшой размер библиотеки, с целью содержания общего количества строк во всех заголовочных файлах на уровне менее 6000 строк лаконичного кода.

Список изменений:

  • Реализованы директивы коллекций и атрибутов объектов. Новая директива collect<C>[e] синтезирует последовательность или ассоциативный контейнер типа C, состоящий из элементов, собранных из унаследованных или синтезированных атрибутов в выражении e. Аналогично, появились новые директивы synthesize<C,A...>[e], synthesize_shared<C,A...>[e] и synthesize_unique<C,A...>[e] для синтеза объектов, общих указателей и уникальных указателей соответственно, построенных из атрибутов компонентов в выражении e.
  • Реализована директива synthesize_collect, которая объединяет директивы collect и synthesize для улучшения читаемости кода и уменьшения количества шаблонов при построении сложных структур данных из разобранных элементов. Это особенно полезно для создания вложенных коллекций, таких как массивы объектов или ассоциативные контейнеры со сложными типами значений.
  • Добавлен шаблонный класс lug::recursive_wrapper для обработки циклических зависимостей в деревьях абстрактного синтаксиса, в частности, использующих std::variant.
  • Реализована поддержка стандарта Unicode 16.0.0 и добавлена поддержка инструментов сборки в CMakeLists.txt.
  • Оптимизировано сопоставление диапазона и набора ASCII-символов, что привело к значительному увеличению производительности при выполнении распространённых операций обработки текста. Добавлены специализированные быстрые способы для обработки только ASCII-символов, которые значительно быстрее кода обработки Unicode.
  • Реализованы опкоды test для оптимизации ошибок и опкоды repeat для оптимизации пропуска пробельных символов. Эти оптимизации будут полностью включены в следующем выпуске после запланированных преобразований дерева выражений.
  • Улучшена обработка источников ввода с улучшенной буферизацией и отчетом об ошибках для std::istream, а также улучшена поддержка интерактивного режима, которая корректно обрабатывает построчный ввод для терминальных сессий или для линейно-ориентированных грамматик.
  • Поддержка std::istream перемещена в отдельный заголовочный файл <lug/iostream.hpp>. Это сокращает время компиляции и минимизирует зависимости от заголовков для проектов, которым не требуется функциональность потокового ввода/вывода.
  • Переработана логика фиксации парсера путем инлайнинга инструкций в lug::basic_parser для лучшего соответствия изменениям архитектуры стекового фрейма, представленным в версии 0.4.0, что улучшило организацию кода и производительность.
  • Исправлена проблема в примере парсера BASIC, когда пользовательские функции (например, FNA(X)) завершались во время оценки. Это было вызвано изменениями в версии 0.4.0, которые сбрасывали lug::environment во время вложенных операций синтаксического анализа. Добавлена новая функция lug::environment::should_reset_on_parse для обеспечения тонкого контроля над этим поведением, позволяя окружению сохраняться во время вложенных операций разбора, когда это необходимо.
  • Добавлена комплексная инфраструктура тестирования примеров программ.
  • Перестроена иерархия каталогов include.
  • Для обеспечения более широкой совместимости в GitHub CI добавлена поддержка дополнительных компиляторов (GCC 9/10/11/12, Clang 14/15/16/17).
  • В GitHub CI добавлены статические анализаторы Clang и MSVC.
  • В GitHub CI добавлены Address Sanitizer (ASan), Undefined Behavior Sanitizer (UBSan) и Memory Sanitizer (MSan).
  • В GitHub CI добавлена интеграция clang-tidy.
  • В GitHub CI удалено использование Ubuntu 20.04.

>>> Подробности на github.com

★★★★★

Проверено: CrX ()
Последнее исправление: dataman (всего исправлений: 1)

Пример калькулятора:

// lug - Embedded DSL for PE grammar parser combinators in C++
// Copyright (c) 2017-2025 Jesse W. Towner
// See LICENSE.md file for license details

#include <lug/lug.hpp>
#include <lug/iostream.hpp>

#include <cstdlib>

namespace samples::calc {

using namespace lug::language;

int i;
double e, l, n, r, s;
double v[26];

extern rule Expr;

implicit_space_rule BLANK = lexeme[ *"[ \t]"_rx ];

rule EOL    = lexeme[ "[\n\r;]"_rx ];
rule ID     = lexeme[ "[a-zA-Z]"_rx  <[](syntax m) -> int { return static_cast<int>(lug::unicode::tolower(static_cast<char32_t>(m.str().at(0))) - U'a'); } ];
rule NUMBER = lexeme[ ( ~"[-+]"_rx > +"[0-9]"_rx > ~('.' > +"[0-9]"_rx) )
                                     <[](syntax m) -> double { return std::stod(std::string{m}); } ];
rule Value  = n%NUMBER               <[]{ return n; }
            | i%ID > !"="_sx         <[]{ return v[i]; }
            | '(' > e%Expr > ')'     <[]{ return e; };
rule Prod   = l%Value > *(
                  '*' > r%Value      <[]{ l *= r; }
                | '/' > r%Value      <[]{ l /= r; }
            )                        <[]{ return l; };
rule Sum    = l%Prod > *(
                  '+' > r%Prod       <[]{ l += r; }
                | '-' > r%Prod       <[]{ l -= r; }
            )                        <[]{ return l; };
rule Expr   = i%ID > '=' > s%Sum     <[]{ return v[i] = s; }
            | s%Sum                  <[]{ return s; };
rule Stmt   = ( (   "exit"_isx
                  | "quit"_isx )     <[]{ std::exit(EXIT_SUCCESS); }
                | e%Expr             <[]{ std::cout << e << "\n"; }
            ) > EOL
            | *( !EOL > any ) > EOL  <[]{ std::cout << "SYNTAX ERROR\n"; };

grammar Grammar = start(Stmt > eoi);

} // namespace samples::calc

int main()
try {
    while (lug::parse(samples::calc::Grammar, lug::source_options::interactive)) ;
    return 0;
} catch (std::exception const& e) {
    std::cerr << "ERROR: " << e.what() << "\n";
    return 1;
} catch (...) {
    std::cerr << "UNKNOWN ERROR\n";
    return 1;
}
dataman ★★★★★
() автор топика

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

Header-only-библиотека, использующая только стандартную библиотеку и возможности стандарта C++17. Перспективно совместима со стандартами C++20 и C++23.

Что-то у меня эти два утверждения не вяжутся. То есть, мне эту виртуальную машинку синтаксического анализа каждый раз компилить из хидеров? Не, чот тут не то, лучше как-нибудь yacc'ом c lex'ом, по старинке...

gns ★★★★★
()
Последнее исправление: gns (всего исправлений: 1)
Ответ на: комментарий от imul

Precompiled Headers в смысле? Так на них ограничений столько, что лучше этим делом не пользоваться. Мне чот стрёмно. Я почитал и про Микрософтовский компилятор, и про GCC, и что-то как-то разочаровался в их использовании. Комилятор по любому чиху все пересобрать норовит.

К тому же, Precompiled Headers, — это ж не объектный код, это только разобранные темплейты, инлайны (может быть) и глубокая замена макросов, как я понимаю.

gns ★★★★★
()
Последнее исправление: gns (всего исправлений: 1)
Ответ на: комментарий от gns

Ну вот я полез поинтересоваться, поскольку сам никогда pch не пользовался, и не очень понял, можно затолкать, или нет. Пришлось спросить.

imul ★★★★★
()
Ответ на: комментарий от imul

Только что коллегу спросил, пользовался ли он PCH. Говорит, что да, но точно ответить на вопрос, сильно ли это помогает, он не может. Я вот тоже не могу, На вскидку, заметного ускорения компиляции я не помню. Когда-то давно я пробовал еще под виндой собрать свои поделия с PCH, профита особого не получил, выключил галку в студии и больше к этому вопросу не возвращался.

Это вопрос личного процесса разработки и способа использования компилятора. Кто-то сейчас придет и скажет, что вот он работает таким-то образом и ему зашло. Ну и хорошо. Мне вот никак.

gns ★★★★★
()
Последнее исправление: gns (всего исправлений: 2)

Вызывает некоторое недоумение выбор имени, которое совпадает с очень давно известной аббревиатурой для Linux User Group.

Smacker ★★★★★
()

хорошо что ещё существуют публичные инструменты с намёком на математическую полноту/целостность/эффективность.
не без багов конечно, но это public domain.

etwrq ★★★★★
()
Ответ на: комментарий от Smacker

Очевидно тайная мечта авторов — написать на этом метациклический интерпретатор Lisp’а и тем защитить будущее С++ в эпохо подъёма Rust’а. Отсюда и буква L в названии. Осталось расшифровать остальные две.

ugoday ★★★★★
()
Ответ на: комментарий от Smacker

Линукс у меня не первая ОС. Это как с женой, жена и первая женщина очень часто являются разными женщинами. :) Но некоторым везет. Или не везет... :)

gns ★★★★★
()
Ответ на: комментарий от dataman

Очень интересно, но ничего не понятно.

rule Value  = n%NUMBER               <[]{ return n; }
            | i%ID > !"="_sx         <[]{ return v[i]; }
            | '(' > e%Expr > ')'     <[]{ return e; };

Может там надо в лямбдах делать [&]? Или как это вообще работает? И дебильная форма записи n%NUMBER. Перегрузка сравнений. Пипец.

rupert ★★★★★
()
Ответ на: комментарий от hatred

Ну я с 2003го где-то, до этого чего только не было. На десктопе FreeBSD, на работах OpenVMS, потом Solaris.

gns ★★★★★
()
Ответ на: комментарий от hatred

С компами я по молодости натрахался. С женщинами бы успеть... :)

gns ★★★★★
()

Луговский написал?

gobot ★★★★
()
Ответ на: комментарий от rupert

Перегрузка сравнений. Пипец.

Ну а что поделать, если в C++ нельзя добавлять новые операторы, как в D, Nim, Swift…
Я бы такое хотеть. :)

dataman ★★★★★
() автор топика
Ответ на: комментарий от Playermet

как же это неудобно читать, аж в глазах зарябило.

Ну вот кусочек примера калькулятора (весь пример ~15Kb) из https://github.com/foonathan/lexy:

namespace grammar
{
    namespace dsl = lexy::dsl;

    constexpr auto escaped_newline = dsl::backslash >> dsl::newline;

    // An integer literal.
    // Supports hexadecimal and decimal literals.
    struct integer : lexy::token_production
    {
        static constexpr auto rule
            = LEXY_LIT("0x") >> dsl::integer<int, dsl::hex> | dsl::integer<int>;

        static constexpr auto value = lexy::forward<int>;
    };

    // A Unicode-aware identifier.
    struct name
    {
        static constexpr auto rule
            = dsl::identifier(dsl::unicode::xid_start_underscore, dsl::unicode::xid_continue);

        static constexpr auto value = lexy::as_string<std::string>;
    };

    // An expression that is nested inside another expression.
    struct nested_expr : lexy::transparent_production
    {
        // We change the whitespace rule to allow newlines:
        // as it's nested, the REPL can properly handle continuation lines.
        static constexpr auto whitespace = dsl::ascii::space | escaped_newline;
        // The rule itself just recurses back to expression, but with the adjusted whitespace now.
        static constexpr auto rule = dsl::recurse<struct expr>;

        static constexpr auto value = lexy::forward<ast::expr_ptr>;
    };

    // An arbitrary expression.
    // It uses lexy's built-in support for operator precedence parsing to automatically generate a
    // proper rule. This is done by inheriting from expression_production.
    struct expr : lexy::expression_production
    {
        struct expected_operand
        {
            static constexpr auto name = "expected operand";
        };

        // We need to specify the atomic part of an expression.
        static constexpr auto atom = [] {
            auto paren_expr = dsl::parenthesized(dsl::p<nested_expr>);
            // Functions can only have a single argument for simplicity.
            auto var_or_call = dsl::p<name> >> dsl::if_(paren_expr);
            auto literal     = dsl::p<integer>;

            return paren_expr | var_or_call | literal | dsl::error<expected_operand>;
        }();

        // Each of the nested classes defines one operation.
        // They inherit from a tag type that specify the kind of operation (prefix, infix, postfix),
        // and associativy (left, right, single (non-associative)),
        // and specify the operator rule and operand.

        // x**2
        struct math_power : dsl::infix_op_right
        {
            static constexpr auto op = dsl::op<ast::expr_binary_arithmetic::pow>(LEXY_LIT("**"));

            // math_power has highest binding power, so it's operand is the atom rule.
            using operand = dsl::atom;
        };
        // -x
        struct math_prefix : dsl::prefix_op
        {
            static constexpr auto op = dsl::op<ast::expr_unary_arithmetic::negate>(LEXY_LIT("-"));

            using operand = math_power;
        };
        // x * x, x / x
        struct math_product : dsl::infix_op_left
        {
            static constexpr auto op = [] {
                // Since we have both ** and * as possible operators, we need to ensure that * is
                // only matched when it is not followed by *. In the particular situation where **
                // has higher binding power, it doesn't actually matter here, but it's better to be
                // more resilient.
                auto star = dsl::not_followed_by(LEXY_LIT("*"), dsl::lit_c<'*'>);
                return dsl::op<ast::expr_binary_arithmetic::times>(star)
                       / dsl::op<ast::expr_binary_arithmetic::div>(LEXY_LIT("/"));
            }();
            using operand = math_prefix;
        };
        // x + x, x - x
        struct math_sum : dsl::infix_op_left
        {
            static constexpr auto op = dsl::op<ast::expr_binary_arithmetic::plus>(LEXY_LIT("+"))
                                       / dsl::op<ast::expr_binary_arithmetic::minus>(LEXY_LIT("-"));

            using operand = math_product;
        };

        // ~x
        struct bit_prefix : dsl::prefix_op
        {
            static constexpr auto op
                = dsl::op<ast::expr_unary_arithmetic::complement>(LEXY_LIT("~"));
            using operand = dsl::atom;
        };
        // x & x
        struct bit_and : dsl::infix_op_left
        {
            static constexpr auto op = dsl::op<ast::expr_binary_arithmetic::bit_and>(LEXY_LIT("&"));
            using operand            = bit_prefix;
        };
        // x | x, x ^ x
        struct bit_or : dsl::infix_op_left
        {
            static constexpr auto op
                = dsl::op<ast::expr_binary_arithmetic::bit_or>(LEXY_LIT("|"))
                  / dsl::op<ast::expr_binary_arithmetic::bit_xor>(LEXY_LIT("^"));
            using operand = bit_and;
        };

        // Comparisons are list operators, which allows implementation of chaining.
        // x == y < z
        struct comparison : dsl::infix_op_list
        {
            // Other comparison operators omitted for simplicity.
            static constexpr auto op = dsl::op<ast::expr_comparison::equal>(LEXY_LIT("=="))
                                       / dsl::op<ast::expr_comparison::less>(LEXY_LIT("<"));

            // The use of dsl::groups ensures that an expression can either contain math or bit
            // operators. Mixing requires parenthesis.
            using operand = dsl::groups<math_sum, bit_or>;
        };

        // x ? y : z
        struct conditional : dsl::infix_op_single
        {
            // We treat a conditional operator, which has three operands,
            // as a binary operator where the operator consists of ?, the inner operator, and :.
            // The <void> ensures that `dsl::op` does not produce a value.
            static constexpr auto op
                = dsl::op<void>(LEXY_LIT("?") >> dsl::p<nested_expr> + dsl::lit_c<':'>);
            using operand = comparison;
        };

        struct assignment : dsl::infix_op_single
        {
            static constexpr auto op
                // Similar to * above, we need to prevent `=` from matching `==`.
                = dsl::op<void>(dsl::not_followed_by(LEXY_LIT("="), dsl::lit_c<'='>));
            using operand = conditional;
        };

        // An expression also needs to specify the operation with the lowest binding power.
        // The operation of everything else is determined by following the `::operand` member.
        using operation = assignment;

        static constexpr auto value =
            // We need a sink as the comparison expression generates a list.
            lexy::fold_inplace<std::unique_ptr<ast::expr_comparison>>(
                [] { return std::make_unique<ast::expr_comparison>(); },
                [](auto& node, ast::expr_ptr opr) { node->operands.push_back(LEXY_MOV(opr)); },
                [](auto& node, ast::expr_comparison::op_t op) { node->ops.push_back(op); })
            // The result of the list feeds into a callback that handles all other cases.
            >> lexy::callback(
                // atoms
                lexy::forward<ast::expr_ptr>, lexy::new_<ast::expr_literal, ast::expr_ptr>,
                lexy::new_<ast::expr_name, ast::expr_ptr>,
                lexy::new_<ast::expr_call, ast::expr_ptr>,
                // unary/binary operators
                lexy::new_<ast::expr_unary_arithmetic, ast::expr_ptr>,
                lexy::new_<ast::expr_binary_arithmetic, ast::expr_ptr>,
                // conditional and assignment
                lexy::new_<ast::expr_if, ast::expr_ptr>,
                lexy::new_<ast::expr_assignment, ast::expr_ptr>);
    };
} // namespace grammar
dataman ★★★★★
() автор топика
Ответ на: комментарий от gns

Это как если бы Михаил Боппосов решил создать Давида

buddhist ★★★★★
()

Посмотрел примеры

Expr = lhs%Term > *('+' > rhs%Term <[&]{ lhs += rhs; }) <[&]{ return lhs; }

Жесть...проще на php написать

gobot ★★★★
()
Ответ на: комментарий от gobot

А ещё проще — на стене... «В общественном парижском...»...

Somebody ★★★
()
Для того чтобы оставить комментарий войдите или зарегистрируйтесь.