pt::peg::import::peg(3tcl)

πŸ“¦
version 2.0 Β· 2 versions
, in debian trixie, ubuntu questing, ubuntu resolute, ubuntu stonking

πŸ’– pt::peg::import::peg β€” the PEG Import Plugin πŸ’–

aka pt::peg::import::peg(3tcl), part of Parser Tools. Bestie. Sit down. We're reading PEG markup and we're gonna have a time. ✨


🌸 NAME 🌸

pt::peg::import::peg β€” PEG Import Plugin. Read PEG format. That's it, that's the whole job, and she does it flawlessly. πŸ’…


πŸŽ€ SYNOPSIS πŸŽ€

package require Tcl 8.5 9

package require pt::peg::import::peg ?1.1?

package require pt::peg::to::peg

import text

okay so like, four lines and the whole vibe is set. slay.


πŸ¦‹ DESCRIPTION πŸ¦‹

Are you lost? 😭 Do you have trouble understanding this document? Fr fr, if that's you, please go read the overview in the Introduction to Parser Tools. That doc is the entrypoint to the whole system this package lives in β€” it's the group chat invite link, basically. πŸ‘‘

This package implements the parsing expression grammar import plugin that processes PEG markup.

She resides in the Import section of the Core Layer of Parser Tools, and her whole purpose in life is to be used by pt::peg::import, the import manager β€” she sits right between that manager and the matching core conversion functionality that pt::peg::from::peg provides. Like, she's the middle bestie. The connector. The 🫢 of the architecture.

(There's a picture in the original here β€” arch_core_iplugins β€” showing exactly where she lives in that stack. It's giving diagram energy. πŸ“Š)

Now β€” and I need you to hear me on this one β€” while it is technically possible to use this package directly with a regular interpreter, that is strongly disrecommended 😬 and requires a whole bunch of contortions to set up the environment it expects. Do not do that to yourself. The proper way depends on your situation:

  1. In an untrusted environment, the proper access is through the package pt::peg::import and the import manager objects it provides.
  2. In a trusted environment though? Just use the package pt::peg::from::peg and hit the core conversion functionality directly. No middleman needed, she's not mad, she gets it. πŸ’œ

⭐ API ⭐

The API this package provides satisfies the spec of the Plugin API found in the Parser Tools Import API specification. Yes, she's certified. No cap.

  • import text β€” this command takes the PEG markup encoding a parsing expression grammar (contained in text) and generates the canonical serialization of said grammar, exactly as specified in the section PEG serialization format. That created value is then returned as the result of the command. Hand it markup, get back the glow-up version. ✨

🌈 PEG SPECIFICATION LANGUAGE 🌈

peg β€” a language for the specification of parsing expression grammars β€” is meant to be human readable and writable, while still being strict enough that a machine can process it. Like any computer language, bestie. She was defined to make writing the specification of a grammar easy, which is something the other formats found in Parser Tools really do not lend themselves to. Not naming names. πŸ™ˆ

She's formally specified by the grammar shown below β€” written in herself, which is the most main character thing a language can do. πŸ‘‘ For a tutorial / introduction to the language, go read the PEG Language Tutorial.

PEG pe_grammar_for_peg (Grammar)

  # --------------------------------------------------------------------
        # Syntactical constructs

        Grammar         <- WHITESPACE Header Definition* Final EOF ;

        Header          <- PEG Identifier StartExpr ;
        Definition      <- Attribute? Identifier IS Expression SEMICOLON ;
        Attribute       <- (VOID / LEAF) COLON ;
        Expression      <- Sequence (SLASH Sequence)* ;
        Sequence        <- Prefix+ ;
        Prefix          <- (AND / NOT)? Suffix ;
        Suffix          <- Primary (QUESTION / STAR / PLUS)? ;
        Primary         <- ALNUM / ALPHA / ASCII / CONTROL / DDIGIT / DIGIT
                        /  GRAPH / LOWER / PRINTABLE / PUNCT / SPACE / UPPER
                        /  WORDCHAR / XDIGIT
                        / Identifier
                        /  OPEN Expression CLOSE
                        /  Literal
                        /  Class
                        /  DOT
                        ;
        Literal         <- APOSTROPH  (!APOSTROPH  Char)* APOSTROPH  WHITESPACE
                        /  DAPOSTROPH (!DAPOSTROPH Char)* DAPOSTROPH WHITESPACE ;
        Class           <- OPENB (!CLOSEB Range)* CLOSEB WHITESPACE ;
        Range           <- Char TO Char / Char ;

        StartExpr       <- OPEN Expression CLOSE ;
void:   Final           <- "END" WHITESPACE SEMICOLON WHITESPACE ;

        # --------------------------------------------------------------------
        # Lexing constructs

        Identifier      <- Ident WHITESPACE ;
leaf:   Ident           <- ([_:] / <alpha>) ([_:] / <alnum>)* ;
        Char            <- CharSpecial / CharOctalFull / CharOctalPart
                        /  CharUnicode / CharUnescaped
                        ;

leaf:   CharSpecial     <- "\\" [nrt'"\[\]\\] ;
leaf:   CharOctalFull   <- "\\" [0-3][0-7][0-7] ;
leaf:   CharOctalPart   <- "\\" [0-7][0-7]? ;
leaf:   CharUnicode     <- "\\" 'u' HexDigit (HexDigit (HexDigit HexDigit?)?)? ;
leaf:   CharUnescaped   <- !"\\" . ;

void:   HexDigit        <- [0-9a-fA-F] ;

void:   TO              <- '-'           ;
void:   OPENB           <- "["           ;
void:   CLOSEB          <- "]"           ;
void:   APOSTROPH       <- "'"           ;
void:   DAPOSTROPH      <- '"'           ;
void:   PEG             <- "PEG" !([_:] / <alnum>) WHITESPACE ;
void:   IS              <- "<-"    WHITESPACE ;
leaf:   VOID            <- "void"  WHITESPACE ; # Implies that definition has no semantic value.
leaf:   LEAF            <- "leaf"  WHITESPACE ; # Implies that definition has no terminals.
void:   SEMICOLON       <- ";"     WHITESPACE ;
void:   COLON           <- ":"     WHITESPACE ;
void:   SLASH           <- "/"     WHITESPACE ;
leaf:   AND             <- "&"     WHITESPACE ;
leaf:   NOT             <- "!"     WHITESPACE ;
leaf:   QUESTION        <- "?"     WHITESPACE ;
leaf:   STAR            <- "*"     WHITESPACE ;
leaf:   PLUS            <- "+"     WHITESPACE ;
void:   OPEN            <- "("     WHITESPACE ;
void:   CLOSE           <- ")"     WHITESPACE ;
leaf:   DOT             <- "."     WHITESPACE ;

leaf:   ALNUM           <- "<alnum>"    WHITESPACE ;
leaf:   ALPHA           <- "<alpha>"    WHITESPACE ;
leaf:   ASCII           <- "<ascii>"    WHITESPACE ;
leaf:   CONTROL         <- "<control>"  WHITESPACE ;
leaf:   DDIGIT          <- "<ddigit>"   WHITESPACE ;
leaf:   DIGIT           <- "<digit>"    WHITESPACE ;
leaf:   GRAPH           <- "<graph>"    WHITESPACE ;
leaf:   LOWER           <- "<lower>"    WHITESPACE ;
leaf:   PRINTABLE       <- "<print>"    WHITESPACE ;
leaf:   PUNCT           <- "<punct>"    WHITESPACE ;
leaf:   SPACE           <- "<space>"    WHITESPACE ;
leaf:   UPPER           <- "<upper>"    WHITESPACE ;
leaf:   WORDCHAR        <- "<wordchar>" WHITESPACE ;
leaf:   XDIGIT          <- "<xdigit>"   WHITESPACE ;

void:   WHITESPACE      <- (" " / "\t" / EOL / COMMENT)* ;
void:   COMMENT         <- '#' (!EOL .)* EOL ;
void:   EOL             <- "\n\r" / "\n" / "\r" ;
void:   EOF             <- !. ;

        # --------------------------------------------------------------------
END;

🧚 EXAMPLE 🧚

Our example specifies the grammar for a basic 4-operation calculator. So cute. So practical. She ate. πŸ“

PEG calculator (Expression)
    Digit      <- '0'/'1'/'2'/'3'/'4'/'5'/'6'/'7'/'8'/'9'       ;
    Sign       <- '-' / '+'                                     ;
    Number     <- Sign? Digit+                                  ;
    Expression <- Term (AddOp Term)*                            ;
    MulOp      <- '*' / '/'                                     ;
    Term       <- Factor (MulOp Factor)*                        ;
    AddOp      <- '+'/'-'                                       ;
    Factor     <- '(' Expression ')' / Number                   ;
END;

Using higher-level features of the notation, i.e. the character classes (predefined and custom), this example can be rewritten as:

PEG calculator (Expression)
    Sign       <- [-+]                                          ;
    Number     <- Sign? <ddigit>+                               ;
    Expression <- Term (AddOp Term)*                            ;
    MulOp      <- [*/];
    Term       <- Factor (MulOp Factor)*                        ;
    AddOp      <- [-+];
    Factor     <- '(' Expression ')' / Number                   ;
END;

Same grammar, way less typing, highkey iconic. That's the character-class glow-up and it is so worth knowing about. πŸ’…

✨ PEG SERIALIZATION FORMAT ✨

okay bestie, so this is where we spill the tea on how Parser Tools serializes Parsing Expression Grammars as immutable values for transport, comparison, etc. πŸ’…

We distinguish between regular and canonical serializations. While a PEG may have more than one regular serialization, only exactly one of them will be canonical. periodt.


πŸ’– regular serialization πŸ’–

  1. The serialization of any PEG is a nested Tcl dictionary. 🫢
  2. This dictionary holds a single key, pt::grammar::peg, and its value. This value holds the contents of the grammar.
  3. The contents of the grammar are a Tcl dictionary holding the set of nonterminal symbols and the starting expression. The relevant keys and their values are:
    • rules β€” The value is a Tcl dictionary whose keys are the names of the nonterminal symbols known to the grammar. πŸ‘‘
      1. Each nonterminal symbol may occur only once.
      2. The empty string is not a legal nonterminal symbol.
      3. The value for each symbol is a Tcl dictionary itself. The relevant keys and their values in this dictionary are:
        • is β€” The value is the serialization of the parsing expression describing the symbols sentennial structure, as specified in the section PE serialization format.
        • mode β€” The value can be one of three values specifying how a parser should handle the semantic value produced by the symbol.
          • value β€” The semantic value of the nonterminal symbol is an abstract syntax tree consisting of a single node node for the nonterminal itself, which has the ASTs of the symbol's right hand side as its children.
          • leaf β€” The semantic value of the nonterminal symbol is an abstract syntax tree consisting of a single node node for the nonterminal, without any children. Any ASTs generated by the symbol's right hand side are discarded.
          • void β€” The nonterminal has no semantic value. Any ASTs generated by the symbol's right hand side are discarded (as well).
    • start β€” The value is the serialization of the start parsing expression of the grammar, as specified in the section PE serialization format.
  4. The terminal symbols of the grammar are specified implicitly as the set of all terminal symbols used in the start expression and on the RHS of the grammar rules.

🌈 canonical serialization 🌈

The canonical serialization of a grammar has the format as specified in the previous item, and then additionally satisfies the constraints below, which make it unique among all the possible serializations of this grammar. ✨

  1. The keys found in all the nested Tcl dictionaries are sorted in ascending dictionary order, as generated by Tcl's builtin command lsort -increasing -dict.
  2. The string representation of the value is the canonical representation of a Tcl dictionary. I.e. it does not contain superfluous whitespace.

πŸ“ EXAMPLE πŸ“

Assuming the following PEG for simple mathematical expressions

PEG calculator (Expression)
    Digit      <- '0'/'1'/'2'/'3'/'4'/'5'/'6'/'7'/'8'/'9'       ;
    Sign       <- '-' / '+'                                     ;
    Number     <- Sign? Digit+                                  ;
    Expression <- Term (AddOp Term)*                            ;
    MulOp      <- '*' / '/'                                     ;
    Term       <- Factor (MulOp Factor)*                        ;
    AddOp      <- '+'/'-'                                       ;
    Factor     <- '(' Expression ')' / Number                   ;
END;

then its canonical serialization (except for whitespace) is

pt::grammar::peg {
    rules {
        AddOp      {is {/ {t -} {t +}}                                                                mode value}
        Digit      {is {/ {t 0} {t 1} {t 2} {t 3} {t 4} {t 5} {t 6} {t 7} {t 8} {t 9}}                mode value}
        Expression {is {x {n Term} {* {x {n AddOp} {n Term}}}}                                        mode value}
        Factor     {is {/ {x {t (} {n Expression} {t )}} {n Number}}                                  mode value}
        MulOp      {is {/ {t *} {t /}}                                                                mode value}
        Number     {is {x {? {n Sign}} {+ {n Digit}}}                                                 mode value}
        Sign       {is {/ {t -} {t +}}                                                                mode value}
        Term       {is {x {n Factor} {* {x {n MulOp} {n Factor}}}}                                    mode value}
    }
    start {n Expression}
}

so like, this whole thing right here is the format spec that the Parser Tools use to serialize Parsing Expressions into immutable values β€” for transport, for comparison, for all of it. and bestie, there are two flavors: regular and canonical. a parsing expression can have more than one regular serialization, but exactly one of them is ever canonical. it's giving main character only-one-of-you energy. πŸ’…


πŸ’– Regular serialization πŸ’–

🌸 Atomic Parsing Expressions 🌸

  1. the string epsilon is an atomic parsing expression. it matches the empty string. yes, it's a whole vibe of nothing. ✨
  2. the string dot is an atomic parsing expression. it matches any character. literally anything, it does not care. πŸ¦‹
  3. the string alnum is an atomic parsing expression. it matches any Unicode alphabet or digit character. this one is a custom extension of PEs based on Tcl's builtin command string is. πŸŽ€
  4. the string alpha is an atomic parsing expression. it matches any Unicode alphabet character. custom extension of PEs, based on Tcl's builtin command string is.
  5. the string ascii is an atomic parsing expression. it matches any Unicode character below U0080. custom extension of PEs, based on Tcl's builtin command string is.
  6. the string control is an atomic parsing expression. it matches any Unicode control character. custom extension of PEs, based on Tcl's builtin command string is.
  7. the string digit is an atomic parsing expression. it matches any Unicode digit character. ⚠️ heads up bestie β€” this includes characters outside of the [0..9] range. custom extension of PEs, based on Tcl's builtin command string is.
  8. the string graph is an atomic parsing expression. it matches any Unicode printing character, except for space. custom extension of PEs, based on Tcl's builtin command string is.
  9. the string lower is an atomic parsing expression. it matches any Unicode lower-case alphabet character. custom extension of PEs, based on Tcl's builtin command string is.
  10. the string print is an atomic parsing expression. it matches any Unicode printing character, including space. custom extension of PEs, based on Tcl's builtin command string is.
  11. the string punct is an atomic parsing expression. it matches any Unicode punctuation character. custom extension of PEs, based on Tcl's builtin command string is.
  12. the string space is an atomic parsing expression. it matches any Unicode space character. custom extension of PEs, based on Tcl's builtin command string is.
  13. the string upper is an atomic parsing expression. it matches any Unicode upper-case alphabet character. custom extension of PEs, based on Tcl's builtin command string is.
  14. the string wordchar is an atomic parsing expression. it matches any Unicode word character. that means any alphanumeric character (see alnum), and any connector punctuation characters (e.g. underscore). custom extension of PEs, based on Tcl's builtin command string is.
  15. the string xdigit is an atomic parsing expression. it matches any hexadecimal digit character. custom extension of PEs, based on Tcl's builtin command string is.
  16. the string ddigit is an atomic parsing expression. it matches any decimal digit character. custom extension of PEs, based on Tcl's builtin command regexp. πŸ“
  17. the expression [list t x] is an atomic parsing expression. it matches the terminal string x.
  18. the expression [list n A] is an atomic parsing expression. it matches the nonterminal A.

✨ Combined Parsing Expressions ✨

  1. for parsing expressions e1, e2, ... the result of [list / e1 e2 ... ] is a parsing expression as well. this is the ordered choice, aka prioritized choice. πŸ‘‘
  2. for parsing expressions e1, e2, ... the result of [list x e1 e2 ... ] is a parsing expression as well. this is the sequence.
  3. for a parsing expression e, the result of [list * e] is a parsing expression as well. this is the kleene closure, describing zero or more repetitions.
  4. for a parsing expression e, the result of [list + e] is a parsing expression as well. this is the positive kleene closure, describing one or more repetitions.
  5. for a parsing expression e, the result of [list & e] is a parsing expression as well. this is the and lookahead predicate.
  6. for a parsing expression e, the result of [list ! e] is a parsing expression as well. this is the not lookahead predicate.
  7. for a parsing expression e, the result of [list ? e] is a parsing expression as well. this is the optional input.

πŸ‘‘ Canonical serialization πŸ‘‘

the canonical serialization of a parsing expression has the format specified in the previous item, and then additionally satisfies the constraints down below, which is what makes it unique among all the possible serializations of that parsing expression. no cap.

  1. the string representation of the value is the canonical representation of a pure Tcl list. i.e. it does not contain superfluous whitespace. clean. πŸ’…
  2. terminals are not encoded as ranges (where start and end of the range are identical).

🌈 EXAMPLE 🌈

say the parsing expression is the one on the right-hand side of this rule:

Expression <- Term (AddOp Term)*

then its canonical serialization (except for whitespace) is:

{x {n Term} {* {x {n AddOp} {n Term}}}}

look at that. ate. πŸ’–


πŸ› BUGS, IDEAS, FEEDBACK πŸ›

ngl, this document, and the package it describes, will undoubtedly contain bugs and other problems. 😭 please report those in the category pt of the Tcllib Trackers at http://core.tcl.tk/tcllib/reportlist. and please also report any ideas for enhancements you may have for either the package and/or the documentation, we want them fr fr. 🫢

when proposing code changes, please provide unified diffs, i.e the output of diff -u.

note further that attachments are strongly preferred over inlined patches. you can make attachments by going to the Edit form of the ticket immediately after its creation, and then using the left-most button in the secondary navigation bar. iykyk. ✨


πŸ”‘ KEYWORDS πŸ”‘

EBNF, LL(k), PEG, TDPL, context-free languages, expression, grammar, import, matching, parser, parsing expression, parsing expression grammar, plugin, push down automaton, recursive descent, serialization, state, top-down parsing languages, transducer


πŸ“‚ CATEGORY πŸ“‚

Parsing and Grammars


Copyright (c) 2009 Andreas Kupries <andreas_kupries@users.sourceforge.net>


okay bestie that's the whole page, we did it, slay πŸŽ€ this was tcllib version 1.1, aka pt::peg::import::peg(3tcl), and honestly? iconic. go write some grammars. bye πŸ’–πŸ¦‹βœ¨

πŸ“¦ where this page comes from