Title

ECS 120 Theory of Computation
Context-free grammars (CFG)
David Doty (adapted from slides authored by Julian Panetta)
University of California, Davis

Context-free grammars

  • Our second declarative model of computation is the context-free grammar (CFG).
  • It’s more powerful than regular expressions.
    • Any regular expression can be expressed as a CFG.
    • Not all CFGs can be expressed as regular expressions.
  • CFGs are useful to describe the syntax of programming languages.
    • They are very powerful for defining domain-specific languages (DSLs), simple interpreted languages, or structured file formats such as JSON and YAML.
    • Tools can automatically turn a CFG into a parser, which converts a sequence of tokens into a parse tree.
    • More complicated languages like C and C++ are unfortunately context sensitive and so cannot be fully specified by CFGs.

Context-free grammars

  • Simple example: \[\begin{align*} A &\to 0 A 1 \\ A &\to B \\ B &\to\ ! \end{align*}\]
  • A CFG has:
    • Productions or substitution rules in each line
      indicated by symbol, an arrow, and a replacement string.
    • A variable or non-terminal symbol appearing before each arrow
      that is to be replaced by the production.
      (\(A\) and \(B\) in this example)
    • Terminals (the alphabet over which it generates strings):
      symbols in the replacement string that are not variables.
      (\(0\), \(1\), and \(!\) in this example)
    • A start variable for kicking off string generation;
      the symbol at the upper-left corner, unless specified otherwise.
      (\(A\) in this example)

Why is this “context-free”?

  • The left-hand side of each rule is a single non-terminal symbol.
  • A context-sensitive grammar would allow the left-hand side to be a string of symbols as in: \[ A B \to A \string{xy},\] requiring information about the context in which the rule is applied.

Example CFG

\[\begin{align*} A &\to 0 A 1 \\ A &\to B \\ B &\to\ ! \end{align*}\]

  • Let’s use this grammar to generate a string.

    1. Start by writing the start variable \(A\).
    2. Find a variable in the current string and
      replace it with an applicable production rule.
    3. Repeat step 2 until no variables are left.
  • Example 1: \(A \fragment{\yields 0 A 1} \fragment{\yields 0 0 A 1 1 } \fragment{\yields 0 0 B 1 1 } \fragment{\yields 00!11 }\)

    The grammar generates \(\string{00!11}\) through this derivation.

  • Example 2: \(A \yields 0 A 1 \yields 0 B 1 \yields 0 ! 1\)

Parse tree for Example 1

0 A 1 0 8 1 0->1 0->8 2 A 0->2 3 0 7 1 2->3 2->7 4 A 2->4 5 B 4->5 6 ! 5->6

Portion of CFG for Python language

compound_stmt: if_stmt | while_stmt | function_def | ...
if_stmt:
    | 'if' named_expression ':' block elif_stmt 
    | 'if' named_expression ':' block [else_block] 
elif_stmt:
    | 'elif' named_expression ':' block elif_stmt 
    | 'elif' named_expression ':' block [else_block] 
else_block:
    | 'else' ':' block 
block:
    | NEWLINE INDENT statements DEDENT 
    | simple_stmts
...

https://docs.python.org/3/reference/grammar.html

Example CFG

\[\begin{align*} A &\to 0 A 11 \\ A &\to B \\ B &\to\ ! \end{align*}\]

Which of the following strings are generated by this grammar?

The set of strings generated is \(\{0^n ! 1^{2n} \mid n \geq 0\}\).

More examples and some shorthand notation

Let’s consider another CFG \(G\): \[\begin{align*} S &\to A B \\ A &\to 0 A \\ A &\to \emptystring \\ B &\to 1 B \\ B &\to \emptystring \end{align*}\]

We express this more concisely as: \[\begin{align*} S &\to A B \\ A &\to 0 A \or \emptystring \\ B &\to 1 B \or \emptystring \end{align*}\] where the \(\or\) symbol should be read as “or”.

Which of the following are generated by \(G\)?

The set of strings generated is matched by the regex \(0^* 1^*\).

More examples and some shorthand notation

Let’s consider another CFG \(G\): \[\begin{align*} S &\to A B \\ A &\to 0 A \\ A &\to \emptystring \\ B &\to 1 B \\ B &\to \emptystring \end{align*}\]

We express this more concisely as: \[\begin{align*} S &\to A B \\ A &\to 0 A \or \emptystring \\ B &\to 1 B \or \emptystring \end{align*}\] where the \(\or\) symbol should be read as “or”.

Parse tree for string 00111

2 0 4 0 8 1 10 1 12 1 0 S 1 A 0->1 7 B 0->7 1->2 3 A 1->3 7->8 9 B 7->9 3->4 5 A 3->5 9->10 11 B 9->11 11->12 13 B 11->13

More examples

Let’s generate all strings of properly nested parentheses.
For example, \(()\) and \((())()\) are valid, but \((()\) and \()(\) are not.

  • We can produce a valid string of parentheses by:
    • Surrounding an existing valid string with a matching pair of parentheses.
    • Concatenating two existing valid strings.
  • We can express this as a CFG: \[ S \to \fragment{( S )} \fragment{ \or S S} \fragment{ \or \emptystring} \hspace{15em} \]

Parse tree for string \((()(()))\)

1 ( 4 ( 7 ) 9 ( 11 ( 14 ) 15 ) 16 ) 0 S 0->1 0->16 2 S 0->2 3 S 2->3 8 S 2->8 3->4 3->7 5 S 3->5 8->9 8->15 10 S 8->10 10->11 10->14 12 S 10->12

Formal definition of CFGs (syntax)

A context-free grammar (CFG) is a 4-tuple \(G = (\Gamma, \Sigma, S, \rho)\) where:

  1. \(\Gamma\) is a finite set of variables (non-terminal symbols).
  2. \(\Sigma\) is a finite alphabet, disjoint from \(\Gamma\) of terminals.
  3. \(S \in \Gamma\) is the start variable.
  4. \(\rho \subseteq \fragment{\Gamma \times} \fragment{(\Gamma \cup \Sigma)^*}\) is a finite set of rules.
  • By default, if no start variable is specified, the left-hand-side variable from the first rule is used as the start variable.
  • Variables are usually upper-case symbols but can be more readable names like \(\cfgvar{EXPR}\) and \(\cfgvar{TERM}\).

Formal definition of computation by CFGs (semantics)

First some terminology:

  • If \(u, v, w \in {(\Gamma \cup \Sigma)}^*\) are strings of variables and terminals,
    and \(A \to w\) is a rule in the grammar,
    then we say that: \[ u A v \yields u w v \quad \quad (u A v \; \; \textbf{yields} \; u w v) \]
  • If there is sequence of strings \(u_1, u_2, \ldots, u_k\) for \(k \ge 0\) such that: \[ u \yields u_1 \yields u_2 \yields \ldots \yields u_k \yields v \] then we say that \(u\) derives \(v\), writing \(u \derives v\).

A grammar \(G\) with start variable \(S\) accepts or generates/produces \(w \in \Sigma^*\) if \(S \derives w\).

The language decided by grammar \(G\) is \(L(G) = \setbuild{w \in \Sigma^*}{S \derives w}\).

A language \(A\) is called context-free or CFG-decidable if there is a CFG \(G\) s.t. \(L(G) = A\).

Some strategies for designing CFGs

  • CFGs naturally support the operations of union, concatenation, and “calling” another CFG as a subroutine.
  • Design a CFG for the (non-DFA-decidable) language \(\setbuild{0^n 1^n}{n \ge 0} \cup \setbuild{1^n 0^n}{n \ge 0}\)
    • Suppose we have a grammar with start symbol \(A\) deciding language \(L_A = \setbuild{0^n 1^n}{n \ge 0}\), and similarly for \(L_B = \setbuild{1^n 0^n}{n \ge 0}\). Then start with the rule \(S \to\ A\ |\ B\).
    • For \(L_A = \setbuild{0^n 1^n}{n \ge 0}\), how to define this language recursively?
      • base case: \(x \in L_A\) if \(x = \emptystring\)
      • recursive case: \(x \in L_A\) if \(x = 0 y 1\), where \(y \in L_A\) (note \(|y| < |x|\)).
    • This leads to the rules \(A \to 0 A 1 \or \emptystring\) to decide \(L_A\). Similarly, CFG for \(L_B\) is \(B \to 1 B 0 \or \emptystring\).
  • Putting it all together, we have: \[ \begin{array}{rcl} S &\to& A \or B \\ A &\to& 0 A 1 \or \emptystring \\ B &\to& 1 B 0 \or \emptystring \end{array} \]

DFA-to-CFG construction and RRGs

  • Any DFA can easily be converted into a CFG!

  • Consider the DFA \(M = (Q, \Sigma, \delta, s, F)\)

    • Create a variable \(R_i\) for each state \(q_i \in Q\).
    • For each transition \(\delta(q_i, a) = q_j\) in the DFA, add the rule: \[ R_i \to a R_j \]
    • For each accept state \(q_i \in F\), add the rule: \[ R_i \to \emptystring \]
    • Make \(R_0\) (corresponding to start state \(s\)) the start variable.
  • The resulting CFG decides (generates) exactly the same language as the DFA decides.

  • This is an example of a right-regular grammar (RRG):

    A right-regular grammar (RRG) is a CFG where each rule is of the form: \(X \to a Y\) or of the form \(X \to \emptystring\).