Parsing Expression Grammar Template Library (PEGTL)

repository·main·Indexed 24 days ago

https://github.com/taocpp/pegtl

A header-only, zero-dependency C++ library for building parsers using Parsing Expression Grammars (PEG). It utilizes template programming to define grammars as nested C++ types, offering high performance and customizable parsing. PEGTL supports various C++ standards: C++20 for the main branch, C++17 for versions 3.x and 4.x, and C++11 for versions 1.x and 2.x. The library provides a comprehensive set of parser rules, mechanisms for grammar analysis, and a system for attaching semantic actions and states to rules.

Tokens
60.5K
Snippets
95
Records
226
Agent score
77%

What's inside PEGTL

  1. Overview of PEGTL Rules

    main

    PEGTL provides a vast library of rules categorized by their behavior and the character sets they target. Rules are grouped into several functional categories:

    • Atomic: Basic building blocks like eof, bol (beginning of line), eot (end of text), and everything.
    • ASCII: Rules specifically targeting the ASCII character set, such as alnum, digit, blank, and string<C...>.
    • Unicode/ICU: Rules based on Unicode properties and ICU rules, including alphabetic, id_start, id_continue, and various grapheme properties.
    • Combinators: Rules that combine other rules, such as seq<R...> (sequence), opt<R> (optional), star<R> (zero or more), and plus<R> (one or more).
    • Controlling: Rules that manage the parsing state or flow, such as action<A, R...>, control<C, R...>, and state<S, R...>.
    • Convenience: High-level rules for common patterns, like list<R, S>, separated<S, R...>, and rep<Num, R...>.
    • Exceptional: Rules for error handling and conditional logic, such as must<R...>, raise<T>, and various try_catch variants.
    • Member: Rules that operate on members of a set, such as not_one<M, U...> or string<M, U...>.
  2. Overview of PEGTL

    main

    The Parsing Expression Grammar Template Library (PEGTL) is a zero-dependency, header-only C++ parser combinator library. It allows you to create parsers based on Parsing Expression Grammars (PEG) by writing grammars as regular C++ code using nested template instantiations.

    Key features include:

    • A comprehensive set of parser rules that can be combined and extended.
    • Mechanisms for debugging grammars and performing grammar analysis (e.g., detecting left recursion).
    • Support for attaching user-defined actions to grammar rules.
    • High performance through template programming, where the compiler optimizes the grammar rules.
  3. What is a Control in PEGTL?

    main

    A control is a class template that manages the behind-the-scenes details of a parsing run. It adheres to an informal interface and is responsible for handling rule lifecycle events, error propagation, and action application.

    Developers use custom controls to:

    • Obtain debug or trace information from a parsing run.
    • Customize or extend parsing behavior (e.g., changing how exceptions are thrown or how error messages are generated).

    Most additional controls provided by PEGTL are adapters that wrap an existing control (eventually leading back to tao::pegtl::normal) to add specific functionality.

  4. Unicode rule namespaces and encoding requirements

    main

    Unicode rules are categorized by their encoding and endianness. The compatibility with input data_t varies:

    NamespaceInput data_t Type
    tao::pegtl::utf88-bit integer or enum
    tao::pegtl::utf168-bit or 16-bit integer or enum
    tao::pegtl::utf328-bit or 32-bit integer or enum
    tao::pegtl::utf16_be / le8-bit or 16-bit integer or enum
    tao::pegtl::utf32_be / le8-bit or 32-bit integer or enum

    Encoding Details:

    • UTF-8: Multi-byte-sequence-aware (N is 1, 2, 3, or 4).
    • UTF-16: Surrogate-pair-aware (N is 2 or 4 for 8-bit inputs; 1 or 2 for 16-bit inputs). Only matches surrogates as part of a valid pair.
    • UTF-32: N is 4 for 8-bit inputs; 1 for 32-bit inputs.
  5. Understand PEGTL Rule and Combinator implementation details

    main

    Rules and combinators in PEGTL reside in the tao::pegtl namespace (or a sub-namespace). You can change the default namespace using the TAO_PEGTL_NAMESPACE macro in tao/pegtl/config.hpp.

    Key concepts for interpreting rule documentation:

    • Equivalence: Some rules are documented as equivalent to other combinations. This refers to matching behavior, not necessarily the underlying implementation.
    • Parameter Packs: Documentation uses R... to indicate zero-or-more or one-or-more template parameters (e.g., seq< R... >).
    • End Of Line (EOL) Rules: Rules available in scan or lazy sub-namespaces are designed for end-of-line scanning. The default EOL behavior can be modified by defining TAO_PEGTL_DEFAULT_EOL before including tao/pegtl/system.hpp or by providing a custom EOL rule to the input type.
    • Control: The default control is tao::pegtl::normal, which does not call control functions for rules in the tao::pegtl::internal namespace.
  6. How container deduction works for Copy Inputs

    main

    When using copy_input or text_copy_input, PEGTL uses deduction guides to select a container for the data being copied. The selection depends on how you construct the input:

    1. Constructing from raw data, arrays, or initializer lists: PEGTL uses internal::container_for_data_t< Data > to choose the container:

    • If the data type is char, it selects std::string.
    • For any other data type, it selects std::vector< Data >.

    2. Constructing from an existing container: PEGTL uses internal::container_for_container_t< Container >. This preserves the original container type, with one exception: data referenced by a std::string_view is copied into a std::string.

  7. Ensure compatibility for custom rules in parse trees

    main

    To use custom rules with the parse_tree builder, your rules must correctly implement metadata so the builder can optimize tree construction. Specifically, the builder uses subs_t rule metadata to avoid creating internal stack nodes for branches that cannot contain selected descendants.

    Requirement: Custom rules must provide correct rule_t and subs_t type aliases.

  8. Understand the PEGTL header structure

    main

    PEGTL is a header-only library. The headers are organized into several categories:

    • Core Library: Included via <tao/pegtl.hpp>.
    • Additional Library: Located in sub-directories like action/, binary/, control/, stream/, and unicode/. These are part of the official public API.
    • Experimental/Niche: Located in example/ and extra/. These are not subject to semantic versioning and may change.
    • Deprecated: Located in deprecated/.
    • Private: Any headers in an internal/ directory or within an internal namespace are private to the library and not part of the stable API.
  9. Core PEGTL Terminology and Definitions

    main

    To use PEGTL effectively, it is important to understand its core abstractions:

    • Rule: A class that models a production rule of a formal grammar or a parser combinator.
    • Grammar: A set of one or more related rules, with one or more designated top-level rules as entry points.
    • Input: A class representing input data (often a sequence of char) that adheres to an informal input interface.
    • Action: A class used for semantic processing. It must provide a static apply() or apply0() function. Advanced actions may also provide a static match() function.
    • Control: A class that manages behind-the-scenes details of a parsing run.
    • States: User-defined objects passed to all rules, actions, and control functions to maintain context.
    • Parsing Run: The execution of tao::pegtl::parse() (or tao::pegtl::parse_nested() for nested runs).
    • Position: An object indicating a specific location in the input data, often including metadata like filename and line number.

    Parsing Outcomes

    • Success: Occurs when a rule's match() function returns true.
    • Local Failure: Occurs when a match() function returns false, typically triggering backtracking.
    • Global Failure: Occurs when a match() function throws an exception, usually aborting the entire parsing run.
  10. Use Exceptional Rules in PEGTL

    main

    PEGTL provides a set of rules that throw and/or catch exceptions. These rules are located in the tao::pegtl namespace and are only available when compiling with exception support enabled.

    These rules allow you to control the flow of parsing by converting local failures into global failures (exceptions) or by catching exceptions to convert them back into local failures.

  11. Use States to pass data to actions

    main

    States are user-defined objects passed to tao::pegtl::parse() and subsequently forwarded to every action's apply() or apply0() function.

    Key characteristics:

    • They act as a list of additional arguments for all actions.
    • All actions in a parsing run must accept the same list of states (the same argument signature).
    • They allow actions to operate on shared data (e.g., an AST builder or a symbol table) during the parsing process.
  12. Create a partial parse tree with selectors

    main

    To create a more compact AST-like tree, use a selector. A selector determines which grammar rules produce tree nodes.

    Custom Selector

    Define a template specializing for specific rules:

    template< typename Rule >
    struct my_selector : std::false_type {};
    
    template<> 
    struct my_selector< number > : std::true_type {};

    Using tao::pegtl::parse_tree::selector

    The selector class template allows combining multiple rule collections using .on< Rules... >. This allows you to specify different behaviors (like storing content or removing it) for different sets of rules.

    template< typename Rule >
    using my_selector = tao::pegtl::parse_tree::selector<
       Rule,
       tao::pegtl::parse_tree::store_content::on< number, identifier >,
       tao::pegtl::parse_tree::remove_content::on< plus, minus > >;
    
    auto root = tao::pegtl::parse_tree::parse< my_grammar, my_selector >( in );

    In this example:

    • number and identifier nodes keep their matched input in data.
    • plus and minus nodes are structural (they keep children but have empty data).
    • Rules not mentioned do not produce nodes, but their selected descendants are attached to the nearest selected ancestor.
    template< typename Rule >
    struct my_selector
       : std::false_type
    {};
    
    template<> 
    struct my_selector< number >
       : std::true_type
    {};
    
    // Or using the selector utility:
    template< typename Rule >
    using my_selector = tao::pegtl::parse_tree::selector<
       Rule,
       tao::pegtl::parse_tree::store_content::on< number, identifier >,
       tao::pegtl::parse_tree::remove_content::on< plus, minus > >;
    
    auto root = tao::pegtl::parse_tree::parse< my_grammar, my_selector >( in );