PEGN: Parsing Expression Grammar Notation

The Best of PEG, ABNF, EBNF, and JSON AST

PEGN is a language for defining languages. More precisely, it is a universal notation for expressing any grammar — including natural language — in a way that is easy to parse cognitively and programmatically without any specific application or implementation in mind. It builds on the best of existing meta and data structure languages such as PEG, ABNF, EBNF, and JSON.

Motivation

As technology increases in complexity and the need for better human-computer interaction becomes more pronounced, creating language grammars quickly and simply has become a critical need. PEGN is designed to meet this need. By allowing any data to be represented as a grammar and breaking it down into a universal form data can be combined, composed, and analyzed in remarkable ways.

Whether it be simply counting all the words in a document, creating a simple query language to make searching logs easier, coding a human-friendly interface to an otherwise complicated web API, simplifying the parsing of a form of a common Markup, implementing a full programming language that leverages the LLVM to quickly create a highly-performant compiler, or developing a binary language for moisture evaporators, PEGN addresses these needs by prioritizing the creation of other language grammars without weighing them down with any specific bias about how they should be implemented. In fact, PEGN is so flexible it can be used to define spoken and written natural languages and musical notation as well.

PEG Grammars are Exploding, But Inconsistent

Since 2004 PEG grammars have exploded in popularity but the only thing that remains consistent is the wide variety of differences in their implementation and interpretation. Bryan Ford’s “example” PEG grammar is all but ignored as people continue to build their own syntaxes that have very little resemblance to the original and are more implementation code than PEG. This is demonstrated by many projects that contain both a grammar file for becoming acquainted with the syntax and another virtually identical file containing additional implementation specific code added to it so that a highly specialized code generator can use it. This redundancy and specialization are not only less sustainable but also highly rigid and counter-productive.

PEGN is a language grammar specification that does not allow implementation code so that the resulting grammar specifications stand on their own allowing the creation of any variety of linters and code generators in different language implementations, even different design variations in the same implementation language (AST, event callbacks, etc.)

Original PEG Lacks Specificity

For years ABNF and EBNF provide excruciating levels of specificity in their grammars but lack the obvious advantages of ordered priority and the simplicity of the original ASCII PEG grammar. For example, PEGN adds Count and MinMax to provide limits and adds Unicode tokens.

The hope is that the PEGN language itself can become a more explicit, better performing, and readable replacement for many grammar meta-languages and inline regular expressions. Code generators producing parsers of different types and in different implementation languages can be created from the same grammar specification expressed in PEGN. PEGN parsers and standard libraries can even provide highly optimized handling of PEGN grammars included directly in code as strings and constants much like compiled regular expressions are handled today but with much greater clarity and efficiency.

Examples

Perhaps the best example is the PEGN grammar itself, which is specified in PEGN.

Here’s another example, the JSON specification in PEGN:

# JSON-RFC-8259 (v1.0.0) gitlab.com/pegn/grammars/json

Grammar <-- ws* Value ws*

Value    <- ws* (Object / Array / Number / String 
          / True / False / Null) ws*

Object  <-- '{' ws* Member (ws* ',' ws* Member)* ws* '}'
Array   <-- '[' ws* Value (ws* ',' ws* Value)* ws* ']'
Number  <-- MINUS? Integer (DOT DIGIT+)? ('e' / 'E') sign DIGIT+ 
String  <-- DQ (Escaped / [x20-x21] / [x23-x5B] / [x5D-x10FFFF])* DQ
True    <-- 'true'
False   <-- 'false'
Null    <-- 'null'

Member  <-- String ':' Value
Integer  <- '0' / [1-9] DIGIT*
Escaped  <- BKSLASH ('b' / 'f' / 'n' / 'r' / 't' / 'u' hex{4}
          / DQ / BKSLASH / SLASH)

Abstract Syntax Tree

PEGN includes a strict JSON AST serialization format which is itself included in the grammar specification. PEGN defines an optional human-friendly version as well as the core compressed version.

AST (Short-Form)

[PEGN Specification of AST Short-Form]

The compressed, short-form of the JSON AST is optimized for the best parsing speeds and most efficient storage and transfer. It must be used whenever an AST is stored or transferred over the network in any way.

Here is the entire JSON PEGN grammar (from the earlier example) compressed into a single line of short-form AST JSON:

[1,[[2,[[13,"JSON"],[14,"RFC-8259"],[15,"1"],[16,"0"],[17,"0"],[7,"gitlab.com/pegn/grammars/json"],[55,"\n"]]],[55,"\n"],[9,[[18,"Grammar"],[21,[[25,[[29,[[19,[[56,"ws"]]],[33,"*"]]],[29,[[18,"Value"]]],[29,[[19,[[56,"ws"]]],[33,"*"]]]]]]]]],[55,"\n"],[55,"\n"],[10,[[18,"Value"],[21,[[25,[[29,[[19,[[56,"ws"]]],[33,"*"]]],[29,[[21,[[25,[[29,[[18,"Object"]]]]],[25,[[29,[[18,"Array"]]]]],[25,[[29,[[18,"Number"]]]]],[25,[[29,[[18,"String"]]]]],[55,"\n"],[25,[[29,[[18,"True"]]]]],[25,[[29,[[18,"False"]]]]],[25,[[29,[[18,"Null"]]]]]]]]],[29,[[19,[[56,"ws"]]],[33,"*"]]]]]]]]],[55,"\n"],[55,"\n"],[9,[[18,"Object"],[21,[[25,[[29,[[47,"{"]]],[29,[[19,[[56,"ws"]]],[33,"*"]]],[29,[[18,"Member"]]],[29,[[21,[[25,[[29,[[19,[[56,"ws"]]],[33,"*"]]],[29,[[47,","]]],[29,[[19,[[56,"ws"]]],[33,"*"]]],[29,[[18,"Member"]]]]]]],[33,"*"]]],[29,[[19,[[56,"ws"]]],[33,"*"]]],[29,[[47,"}"]]]]]]]]],[55,"\n"],[9,[[18,"Array"],[21,[[25,[[29,[[47,"["]]],[29,[[19,[[56,"ws"]]],[33,"*"]]],[29,[[18,"Value"]]],[29,[[21,[[25,[[29,[[19,[[56,"ws"]]],[33,"*"]]],[29,[[47,","]]],[29,[[19,[[56,"ws"]]],[33,"*"]]],[29,[[18,"Value"]]]]]]],[33,"*"]]],[29,[[19,[[56,"ws"]]],[33,"*"]]],[29,[[47,"]"]]]]]]]]],[55,"\n"],[9,[[18,"Number"],[21,[[25,[[29,[[20,[[57,"MINUS"]]],[32,"?"]]],[29,[[18,"Integer"]]],[29,[[21,[[25,[[29,[[20,[[57,"DOT"]]]]],[29,[[20,"DIGIT"],[34,"+"]]]]]]],[32,"?"]]],[29,[[21,[[25,[[29,[[47,"e"]]]]],[25,[[29,[[47,"E"]]]]]]]]],[29,[[19,[[56,"sign"]]]]],[29,[[20,"DIGIT"],[34,"+"]]]]]]]]],[55,"\n"],[9,[[18,"String"],[21,[[25,[[29,[[20,[[57,"DQ"]]]]],[29,[[21,[[25,[[29,[[18,"Escaped"]]]]],[25,[[29,[[44,[[53,"x20"],[53,"x21"]]]]]]],[25,[[29,[[44,[[53,"x23"],[53,"x5B"]]]]]]],[25,[[29,[[44,[[53,"x5D"],[53,"x10FFFF"]]]]]]]]],[33,"*"]]],[29,[[20,[[57,"DQ"]]]]]]]]]]],[55,"\n"],[9,[[18,"True"],[21,[[25,[[29,[[47,"true"]]]]]]]]],[55,"\n"],[9,[[18,"False"],[21,[[25,[[29,[[47,"false"]]]]]]]]],[55,"\n"],[9,[[18,"Null"],[21,[[25,[[29,[[47,"null"]]]]]]]]],[55,"\n"],[55,"\n"],[9,[[18,"Member"],[21,[[25,[[29,[[18,"String"]]],[29,[[47,":"]]],[29,[[18,"Value"]]]]]]]]],[55,"\n"],[10,[[18,"Integer"],[21,[[25,[[29,[[47,"0"]]]]],[25,[[29,[[42,[[51,"1"],[51,"9"]]]]],[29,[[20,"DIGIT"],[33,"*"]]]]]]]]],[55,"\n"],[10,[[18,"Escaped"],[21,[[25,[[29,[[20,[[57,"BKSLASH"]]]]],[29,[[21,[[25,[[29,[[47,"b"]]]]],[25,[[29,[[47,"f"]]]]],[25,[[29,[[47,"n"]]]]],[25,[[29,[[47,"r"]]]]],[25,[[29,[[47,"t"]]]]],[25,[[29,[[47,"u"]]],[29,[[19,"hex"],[38,"4"]]]]],[55,"\n"],[25,[[29,[[20,[[57,"DQ"]]]]]]],[25,[[29,[[20,[[57,"BKSLASH"]]]]]]],[25,[[29,[[20,[[57,"SLASH"]]]]]]]]]]]]]]]]],[55,"\n"]]]

AST (Long-Form)

[PEGN Specification of AST Long-Form]

The human-friendly JSON long-form AST is used only for debugging and documentation and need never be parsed itself (despite it being JSON as well).

Here is a small bit of the resulting abstract syntax tree in JSON (long form):

["Grammar", [
  ["Meta", [
    ["Lang", "JSON"],
    ["LangExt", "RFC-8259"],
    ["MajorVer", "1"],
    ["MinorVer", "0"],
    ["PatchVer", "0"],
    ["Home", "gitlab.com/pegn/grammars/json"],
    ["EndLine", "\n"]
  ]],
  ["EndLine", "\n"],
  ["SchemaDef", [
    ["CheckId", "Grammar"],
    ["Expression", [
      ["Sequence", [
        ["Plain", [
          ["ClassId", [
            ["ResClassId", "ws"]
          ]],
          ["MinZero", "*"]
        ]],
...

Parent and Terminal Nodes

There are two meta-types of PEGN AST Nodes parents and terminals. Terminals contain nothing but a literal value. Parents contain nothing but other parent and terminal Nodes. This constraint prevents unnecessary complications.

Thinking Differently About Grammar Design

The parent and terminal limitations can present cognitive challenges when designing a PEGN grammar for those coming to PEGN from other methodologies. Ultimately, however, what appear to be challenges result in a more comprehensible grammar specification.

Consider the following example using a problem with the Copyright node when it was first added.

# humm ...
Copyright <-- '# Copyright ' (!EndLine any)+ EndLine
EndLine   <-- LF / CRLF / CR

Given the following sample data the error becomes apparent:

# Copyright 2020 Robert S Muhlestein (rwx@robs.io)

Notice the AST below does not contain a node containing any relevant text from the Copyright making it virtually useless.

["Copyright", [
  ["EndLine", "\n"]
]]

This problem occurs because both Copyright and EndLine are “significant” (SchemaDef) nodes and therefore the literal text is discarded from the AST when the EndLine definition triggers the parser of Copyright to become a parent instead of a terminal node. Since parents have no literal value we need more to capture the text. PEGN’s node tree model is deliberately minimal requiring us to improve our grammar by making it more explicit.

To fix this problem and preserve the literal text from Copyright we must define a significant terminal node type to contain it. Turns out we already have one, the Comment SchemaDef defined elsewhere in the grammar.

# better!
Copyright <-- '# Copyright ' Comment EndLine
Comment   <-- (!EndLine any)+
EndLine   <-- LF / CRLF / CR

Having corrected the problem we now see that after parsing the AST the relevant text of the Copyright is contained in the Comment terminal node.

["Copyright", [
  ["Comment", "2020 Robert S Muhlestein (rwx@robs.io)"],
  ["EndLine", "\n"]
]]

Another fix would be to have made EndLine insignificant (CheckDef), but that would break other requirements of this specific grammar.

No AST Node Attributes

Some rooted node tree data structure models allow for attributes. PEGN’s AST does not since attributes can more efficiently and precisely be indicated by adding an additional Parent or Terminal Node type.

Origins and History

PEGN was conceived and originally developed by Rob Muhlestein while creating grammars needed for the RWX KnowledgeNet specification including Ezmark, Datamark, BaseQL, and others. During Rob’s live-coding streams others jumped in to contribute providing valuable suggestions and merge requests. PEGN’s design has always been driven by practical needs making it useful for other projects. It is designed for creating easy, sustainable, readable grammars for any purpose. If you are planning anything involving any sort of grammar you might consider taking a look at PEGN.

Other Efforts

PEGN combines the best of PEG, ABNF, EBNF, and JSON. It’s closely based on the PEG example but with essential additions including ABNF specificity, limit syntax, generator hints, and a specific AST notation in JSON. Unlike other PEG approaches, PEGN grammars embrace any implementation language since they do not contain language-specific code such as the following:

Creating Documentation

Formal PEGN grammar specifications are documented in an accompanying README.md file that contains headings matching the definition identifiers which in turn may contain one or more Ezmark code blocks containing examples that double as input tests against other code blocks containing the expected AST in JSON long form (inspired by the CommonMark project). This documentation can then be easily combined with code generators using the Ezmark PEGN grammar parser built into the pegn utility (or any other tool capable of parsing Ezmark).

Linking Documentation with Definitions

The README.md file need only contain headings (of any depth) that specifically match the identifier from the formal grammar.pegn file. Headings must be surrounded by backticks for further distinction and so that they have better rendered formatting and do not trigger spelling errors. Nothing else is required to make the association. This allows for maximum flexibility when authoring the documentation including as much additional content unrelated to the specific definitions. It is, however, a good idea to group definition documentation into a single section. The suggested name is Grammar Definition Descriptions but this is entirely up to the author.

RWX KnowledgeNet Node

Each PEGN grammar repository conforms to the RWX KnowledgeNet definition and specification of a Knowledge Base with a single top-level Knowledge Node, which simply requires a README.md with certain YAML metadata header properties. As such most PEGN grammar specification repos will also include other relevant KnowledgeNet files such as those related to rendering (index.html, assets/, mygrammar-v1.0.0-alpha.pdf, etc.).

Wide adoption of any open source project requires a pristine legal pedigree demonstrating that use of the software and all related content under its licensed terms can be trusted to never put those using it at legal risk — especially legal entities other than individuals that are more frequently targeted for such litigation. Given the origins of PEGN outside of academia it seems prudent to provide a higher degree of attention to these legal considerations that academic publishing might have otherwise provided. The details of this section are designed to meet those needs with the hope that more will choose to use PEGN openly, collaborative, and privately.

MIME Type

Currently the official MIME type associated with the .pegn suffix related to PEGN grammars is text/x-pegn with the hope of eventually obtaining text/pegn from IANA.

Trademarks

Whether marked with ™ or not, PEGN, pegn, and the PEGN logo are trademarks of Robert S Muhlestein who maintains an exclusive right to authorize or control the use of these marks primarily to provide assurance to the PEGN community of consistency with respect to content of related tools and services. Use of PEGN trademarks is governed by the same guidelines applied to the Linux trademarks by the Linux Foundation. This enables the most permissive use without losing trademark control. Until a more formal trademark usage guideline document can be drafted and legally reviewed please use Linux Foundation guideline to assist when making decisions about use of the PEGN trademarks.

Copyright 2020 Robert S Muhlestein (rwx@robs.io)

Everything in this document and the rest of this repository falls under this copyright. All contributions to the project also fall under this copyright per the terms outlined in the Developer Certification of Origin which every contributor accepts by making any commit or merge request to the project git repository.

Licenses

PEGN is licensed under both of the following licenses:

Any content of this document or repository that falls under the legal definition of “software” is covered by the Apache-2 software license. All other content is covered under CC-BY-4. This is particularly important since the categorization of a meta-language such as PEGN’s formal grammar falls somewhere between the two and does not yet have much legal precedent. Most such meta-languages (PEG, ABNF, EBNF, BNF) are released through academic channels. Unlike other standards bodies no one will ever be required to pay for access to read any PEGN standard document.

Patents

Any and all patent rights associated with PEGN and anything in this repository are addressed under the Apache-2 license which contains explicit language regarding the granting of patent rights.

Contributing

Contributions are governed under the same DCO terms as those of the Linux and Gitlab projects. Any commit or merge request to this project Git repository legally constitutes acceptance of the terms of the Developer Certificate of Origin and the release of all rights by the contributor to the project and its owners.

IETF RFC Consideration

As the PEGN standard continues to receive practical adoption an IETF RFC may eventually be considered to further codify the standard in a way that provides further assuages potential legal concerns.