Lably
Skip to content

Parsing expression grammar

Type of grammar for describing formal languages

  • 27 min read
  • 7 chapters

Related on Lably

Through Computer science, String (computer science) and Context-free grammar

Unlike CFGs, PEGs cannot be ambiguous; a string has exactly one valid parse tree or none. It is conjectured that there exist context-free languages that cannot be recognized by a PEG, but this is not yet proven. PEGs are well suited to parsing computer languages (and artificial human languages such as Lojban) where multiple interpretation alternatives can be disambiguated locally, but are less likely to be useful for parsing natural languages where disambiguation may have to be global.

Definition

A parsing expression is a kind of pattern that each string may either match or not match. In case of a match, there is a unique prefix of the string (which may be the whole string, the empty string, or something in between) which has been consumed by the parsing expression; this prefix is what one would usually think of as having matched the expression. However, whether a string matches a parsing expression may (because of look-ahead predicates) depend on parts of it which come after the consumed part. A parsing expression language is a set of all strings that match some specific parsing expression.

A parsing expression grammar is a collection of named parsing expressions, which may reference each other. The effect of one such reference in a parsing expression is as if the whole referenced parsing expression was given in place of the reference. A parsing expression grammar also has a designated starting expression; a string matches the grammar if it matches its starting expression.

An element of a string matched is called a terminal symbol, or terminal for short. Likewise the names assigned to parsing expressions are called nonterminal symbols, or nonterminals for short. These terms would be descriptive for generative grammars, but in the case of parsing expression grammars they are merely terminology, kept mostly because of being near ubiquitous in discussions of parsing algorithms.

Syntax

Both abstract and concrete syntaxes of parsing expressions are seen in the literature, and in this article. The abstract syntax is essentially a mathematical formula and primarily used in theoretical contexts, whereas concrete syntax parsing expressions could be used directly to control a parser. The primary concrete syntax is that defined by Ford, although many tools have their own dialect of this. Other tools can be closer to using a programming-language native encoding of abstract syntax parsing expressions as their concrete syntax.

Atomic parsing expressions

The two main kinds of parsing expressions not containing another parsing expression are individual terminal symbols and nonterminal symbols. In concrete syntax, terminals are placed inside quotes (single or double), whereas identifiers not in quotes denote nonterminals:

<span id="mwUQ"></span> <span class="s2" id="mwUg">"terminal"</span> <span class="nc" id="mwUw">Nonterminal</span> <span class="s1" id="mwVA">'another terminal'</span>

In the abstract syntax there is no formalised distinction, instead each symbol is supposedly defined as either terminal or nonterminal, but a common convention is to use upper case for nonterminals and lower case for terminals.

The concrete syntax also has a number of forms for classes of terminals:

  • A . (period) is a parsing expression matching any single terminal.
  • Brackets around a list of characters [abcde] form a parsing expression matching one of the numerated characters. As in regular expressions, these classes may also include ranges [0-9A-Za-z] written as a hyphen with the range endpoints before and after it. (Unlike the case in regular expressions, bracket character classes do not have ^ for negation; that end can instead be had via not-predicates.)
  • Some dialects have further notation for predefined classes of characters, such as letters, digits, punctuation marks, or spaces; this is again similar to the situation in regular expressions.

In abstract syntax, such forms are usually formalised as nonterminals whose exact definition is elided for brevity; in Unicode, there are tens of thousands of characters that are letters. Conversely, theoretical discussions sometimes introduce atomic abstract syntax for concepts that can alternatively be expressed using composite parsing expressions. Examples of this include:

  • the empty string ε (as a parsing expression, it matches every string and consumes no characters),
  • end of input E (concrete syntax equivalent is !.), and
  • failure ⊥ (matches nothing).

In the concrete syntax, quoted and bracketed terminals have backslash escapes, so that "line feed or carriage return" may be written [\n\r]. The abstract syntax counterpart of a quoted terminal of length greater than one would be the sequence of those terminals; "bar" is the same as "b" "a" "r". The primary concrete syntax assigns no distinct meaning to terminals depending on whether they use single or double quotes, but some dialects treat one as case-sensitive and the other as case-insensitive.

Composite parsing expressions

Given any existing parsing expressions e, e1, and e2, a new parsing expression can be constructed using the following operators:

  • Sequence: e1 e2
  • Ordered choice: e1 / e2
  • Zero-or-more: e*
  • One-or-more: e+
  • Optional: e?
  • And-predicate: &e
  • Not-predicate: !e
  • Group: (e)

Operator priorities are as follows, based on Table 1 in:

Grammars

In the concrete syntax, a parsing expression grammar is simply a sequence of nonterminal definitions, each of which has the form

<span id="mwwg"></span> <span class="nc" id="mwww">Identifier</span> <span class="nc" id="mwxA">LEFTARROW</span> <span class="nc" id="mwxQ">Expression</span>

The Identifier is the nonterminal being defined, and the Expression is the parsing expression it is defined as referencing. The LEFTARROW varies a bit between dialects, but is generally some left-pointing arrow or assignment symbol, such as <-, ←, :=, or =. One way to understand it is precisely as making an assignment or definition of the nonterminal. Another way to understand it is as a contrast to the right-pointing arrow → used in the rules of a context-free grammar; with parsing expressions the flow of information goes from expression to nonterminal, not nonterminal to expression.

As a mathematical object, a parsing expression grammar is a tuple (N,Σ,P,eS), where N is the set of nonterminal symbols, Σ is the set of terminal symbols, P is a function from N to the set of parsing expressions on N∪Σ, and eS is the starting parsing expression. Some concrete syntax dialects give the starting expression explicitly, but the primary concrete syntax instead has the implicit rule that the first nonterminal defined is the starting expression.

It is worth noticing that the primary dialect of concrete syntax parsing expression grammars does not have an explicit definition terminator or separator between definitions, although it is customary to begin a new definition on a new line; the LEFTARROW of the next definition is sufficient for finding the boundary, if one adds the constraint that a nonterminal in an Expression must not be followed by a LEFTARROW. However, some dialects may allow an explicit terminator, or outright require it.

Example

This is a PEG that recognizes mathematical formulas that apply the basic five operations to non-negative integers.

<span id="mw6g"></span><span class="nc" id="mw6w">Expr</span> <span class="o" id="mw7A">←</span> <span class="nc" id="mw7Q">Sum</span> <span class="nc" id="mw7g">Sum</span> <span class="o" id="mw7w">←</span> <span class="nc" id="mw8A">Product</span> <span class="p" id="mw8Q">((</span><span class="s1" id="mw8g">'+'</span> <span class="o" id="mw8w">/</span> <span class="s1" id="mw9A">'-'</span><span class="p" id="mw9Q">)</span> <span class="nc" id="mw9g">Product</span><span class="p" id="mw9w">)</span><span class="o" id="mw-A">*</span> <span class="nc" id="mw-Q">Product</span> <span class="o" id="mw-g">←</span> <span class="nc" id="mw-w">Power</span> <span class="p" id="mw_A">((</span><span class="s1" id="mw_Q">'*'</span> <span class="o" id="mw_g">/</span> <span class="s1" id="mw_w">'/'</span><span class="p" id="mwAQA">)</span> <span class="nc" id="mwAQE">Power</span><span class="p" id="mwAQI">)</span><span class="o" id="mwAQM">*</span> <span class="nc" id="mwAQQ">Power</span> <span class="o" id="mwAQU">←</span> <span class="nc" id="mwAQY">Value</span> <span class="p" id="mwAQc">(</span><span class="s1" id="mwAQg">'^'</span> <span class="nc" id="mwAQk">Power</span><span class="p" id="mwAQo">)</span><span class="o" id="mwAQs">?</span> <span class="nc" id="mwAQw">Value</span> <span class="o" id="mwAQ0">←</span> <span class="p" id="mwAQ4">[</span><span class="s" id="mwAQ8">0-9</span><span class="p" id="mwARA">]</span><span class="o" id="mwARE">+</span> <span class="o" id="mwARI">/</span> <span class="s1" id="mwARM">'('</span> <span class="nc" id="mwARQ">Expr</span> <span class="s1" id="mwARU">')'</span>

In the above example, the terminal symbols are characters of text, represented by characters in single quotes, such as '(' and ')'. The range [0-9] is a shortcut for the ten characters from '0' to '9'. (This range syntax is the same as the syntax used by regular expressions.) The nonterminal symbols are the ones that expand to other rules: Value, Power, Product, Sum, and Expr. Note that rules Sum and Product don't lead to desired left-associativity of these operations (they don't deal with associativity at all, and it has to be handled in post-processing step after parsing), and the Power rule (by referring to itself on the right) results in desired right-associativity of exponent. Also note that a rule like Sum ← Sum ('+' / '-') Product / Product (with intention to achieve left-associativity) would cause infinite recursion, so it cannot be used in practice even though it can be expressed in the grammar.

Semantics

The fundamental difference between context-free grammars and parsing expression grammars is that the PEG's choice operator is ordered. If the first alternative succeeds, the second alternative is ignored. Thus ordered choice is not commutative, unlike unordered choice as in context-free grammars. Ordered choice is analogous to soft cut operators available in some logic programming languages.

The consequence is that if a CFG is transliterated directly to a PEG, any ambiguity in the former is resolved by deterministically picking one parse tree from the possible parses. By carefully choosing the order in which the grammar alternatives are specified, a programmer has a great deal of control over which parse tree is selected.

Parsing expression grammars also add the and- and not- syntactic predicates. Because they can use an arbitrarily complex sub-expression to "look ahead" into the input string without actually consuming it, they provide a powerful syntactic lookahead and disambiguation facility, in particular when reordering the alternatives cannot specify the exact parse tree desired.

Operational interpretation of parsing expressions

Each nonterminal in a parsing expression grammar essentially represents a parsing function in a recursive descent parser, and the corresponding parsing expression represents the "code" comprising the function. Each parsing function conceptually takes an input string as its argument, and yields one of the following results:

  • success, in which the function may optionally move forward or consume one or more characters of the input string supplied to it, or
  • failure, in which case no input is consumed.

An atomic parsing expression consisting of a single terminal (i.e. literal) succeeds if the first character of the input string matches that terminal, and in that case consumes the input character; otherwise the expression yields a failure result. An atomic parsing expression consisting of the empty string always trivially succeeds without consuming any input.

An atomic parsing expression consisting of a nonterminal A represents a recursive call to the nonterminal-function A. A nonterminal may succeed without actually consuming any input, and this is considered an outcome distinct from failure.

The sequence operator e1 e2 first invokes e1, and if e1 succeeds, subsequently invokes e2 on the remainder of the input string left unconsumed by e1, and returns the result. If either e1 or e2 fails, then the sequence expression e1 e2 fails (consuming no input).

The choice operator e1 / e2 first invokes e1, and if e1 succeeds, returns its result immediately. Otherwise, if e1 fails, then the choice operator backtracks to the original input position at which it invoked e1, but then calls e2 instead, returning e2's result.

The zero-or-more, one-or-more, and optional operators consume zero or more, one or more, or zero or one consecutive repetitions of their sub-expression e, respectively. Unlike in context-free grammars and regular expressions, however, these operators always behave greedily, consuming as much input as possible and never backtracking. (Regular expression matchers may start by matching greedily, but will then backtrack and try shorter matches if they fail to match.) For example, the expression a* will always consume as many a's as are consecutively available in the input string, and the expression (a* a) will always fail because the first part (a*) will never leave any a's for the second part to match.

The and-predicate expression &e invokes the sub-expression e, and then succeeds if e succeeds and fails if e fails, but in either case never consumes any input.

The not-predicate expression !e succeeds if e fails and fails if e succeeds, again consuming no input in either case.

More examples

The following recursive rule matches standard C-style if/then/else statements in such a way that the optional "else" clause always binds to the innermost "if", because of the implicit prioritization of the '/' operator. (In a context-free grammar, this construct yields the classic dangling else ambiguity.)

<span id="mwAZM"></span><span class="nc" id="mwAZQ">S</span> <span class="o" id="mwAZU">←</span> <span class="s1" id="mwAZY">'if'</span> <span class="nc" id="mwAZc">C</span> <span class="s1" id="mwAZg">'then'</span> <span class="nc" id="mwAZk">S</span> <span class="s1" id="mwAZo">'else'</span> <span class="nc" id="mwAZs">S</span> <span class="o" id="mwAZw">/</span> <span class="s1" id="mwAZ0">'if'</span> <span class="nc" id="mwAZ4">C</span> <span class="s1" id="mwAZ8">'then'</span> <span class="nc" id="mwAaA">S</span>

The following recursive rule matches Pascal-style nested comment syntax, (* which can (* nest *) like this *). Recall that . matches any single character.

<span id="mwAac"></span><span class="nc" id="mwAag">C</span> <span class="o" id="mwAak">←</span> <span class="nc" id="mwAao">Begin</span> <span class="nc" id="mwAas">N</span><span class="o" id="mwAaw">*</span> <span class="nc" id="mwAa0">End</span> <span class="nc" id="mwAa4">Begin</span> <span class="o" id="mwAa8">←</span> <span class="s1" id="mwAbA">'(*'</span> <span class="nc" id="mwAbE">End</span> <span class="o" id="mwAbI">←</span> <span class="s1" id="mwAbM">'*)'</span> <span class="nc" id="mwAbQ">N</span> <span class="o" id="mwAbU">←</span> <span class="nc" id="mwAbY">C</span> <span class="o" id="mwAbc">/</span> <span class="p" id="mwAbg">(</span><span class="o" id="mwAbk">!</span><span class="nc" id="mwAbo">Begin</span> <span class="o" id="mwAbs">!</span><span class="nc" id="mwAbw">End</span> <span class="k" id="mwAb0">.</span><span class="p" id="mwAb4">)</span>

The parsing expression foo &(bar) matches and consumes the text "foo" but only if it is followed by the text "bar". The parsing expression foo !(bar) matches the text "foo" but only if it is not followed by the text "bar". The expression !(a+ b) a matches a single "a" but only if it is not part of an arbitrarily long sequence of a's followed by a b.

The parsing expression ('a'/'b')* matches and consumes an arbitrary-length sequence of a's and b's. The production rule S ← ('a' S 'b')? matches the simple context-free language {anbn:n≥0}.

The following parsing expression grammar describes the classic non-context-free language {anbncn:n≥0}:

<span id="mwAfM"></span><span class="nc" id="mwAfQ">S</span> <span class="o" id="mwAfU">←</span> <span class="o" id="mwAfY">&</span><span class="p" id="mwAfc">(</span><span class="nc" id="mwAfg">A</span> <span class="o" id="mwAfk">!</span><span class="p" id="mwAfo">(</span><span class="s1" id="mwAfs">'a'</span><span class="o" id="mwAfw">/</span><span class="s1" id="mwAf0">'b'</span><span class="p" id="mwAf4">))</span> <span class="s1" id="mwAf8">'a'</span><span class="o" id="mwAgA">*</span> <span class="nc" id="mwAgE">B</span> <span class="o" id="mwAgI">!</span><span class="k" id="mwAgM">.</span> <span class="nc" id="mwAgQ">A</span> <span class="o" id="mwAgU">←</span> <span class="p" id="mwAgY">(</span><span class="s1" id="mwAgc">'a'</span> <span class="nc" id="mwAgg">A</span> <span class="s1" id="mwAgk">'b'</span><span class="p" id="mwAgo">)</span><span class="o" id="mwAgs">?</span> <span class="nc" id="mwAgw">B</span> <span class="o" id="mwAg0">←</span> <span class="p" id="mwAg4">(</span><span class="s1" id="mwAg8">'b'</span> <span class="nc" id="mwAhA">B</span> <span class="s1" id="mwAhE">'c'</span><span class="p" id="mwAhI">)</span><span class="o" id="mwAhM">?</span>

Sign in to keep reading

6 more chapters are free with a Lably account.

More in Computing and communication