class: center, middle # CS-3110: Formal Languages and Automata ## Pushdown Automata ### Chapter 4.4 --- # Recall: RPN * Last week we saw Reverse Polish Notation, e.g. `\(20\:0 - 4\:3 + *\)` * We also saw how one can do calculations with it, by pushing the numbers onto a stack and applying the operations * I briefly mentioned that a similar idea can be used for parsing them * Today we'll look at the automata-model that does this! --- class: medium # Recall: NFAs
* There are states and transitions * We read our word character for character and follow **all** possible transitions * The main limitation was that there was no memory (to count parenthesis, for example) * Let's add a stack! --- # Reminder: Stack * A stack is a sequential data structure that you can only access from one "end" * Visualization: The elements you store are listed from bottom to top * You can add something to the top (**push**) * Or you can retrieve and remove the top-most element (**pop**) --- class: medium # A Stack * In addition to just reading input symbols we now somehow need to interact with the stack * We'll do that with our transitions! * There are two operations: push and pop * Each transition therefore contains three parts - What to read from the input (if anything) - What to pop from the stack (if anything) - What to push onto the stack (if anything) --- class: medium # Transitions * `\(a, X/Y\)`: Read `a`, pop `X`, push `Y` (Note: We can **only** do this/follow this transition if the next symbol is `a` **and** the top of the stack is `X`) * `\(\varepsilon, X/Y\)`: Don't read anything, pop `X`, push `Y` (Only possible if the top of the stack is `X`) * `\(a, \varepsilon/Y\)`: Read `a`, don't pop anything, push `Y` (Only possible if the next input symbol is `a`) * `\(\varepsilon, \varepsilon/\varepsilon\)`: A "true" epsilon-transition (can be taken at any time) All other combinations are possible, too! --- # Pushdown Automata (PDAs) * Recall what our grammar rules looked like * The language of all even length palindromes: $$ S \rightarrow 1 S 1\\\\ S \rightarrow 0 S 0 \\\\ S \rightarrow \varepsilon $$ How would we turn this into an automaton? --- # Pushdown Automation: Example
--- class: small # Pushdown Automaton: Formal Definition A Pushdown Automaton is a 6-tuple: `\(M = (Q, \Sigma, \Lambda, q_0, \partial, F)\)` * `\(Q\)` is a finite set of states * `\(\Sigma\)` is the input alphabet * `\(\Lambda\)` is the **stack** alphabet (what we can push/pop) * `\(q_0 \in Q\)` is the start state * `\(F \subseteq Q\)` is the set of accepting (final) states * `\(\partial \subseteq ((Q \times (\Sigma \cup \{\varepsilon\}) \times \Lambda^\ast) \times (Q \times \Lambda^\ast))\)` is the transition relation (it has to be finite!) --- class: medium # Transition Relation Let's look at this in more detail: $$ \partial \subseteq ((Q \times (\Sigma \cup \{\varepsilon\}) \times \Lambda^\ast) \times (Q \times \Lambda^\ast))) $$ Each transition consists of two parts: * `\(Q \times (\Sigma \cup \{\varepsilon\}) \times \Lambda^\ast\)`: The current state, next input symbol (optional), and what to pop from the stack * `\(Q \times \Lambda^\ast\)`: The next state and what to push onto the stack Note: We can actually push and pop **multiple** symbols at the same time! --- # Another PDA
--- # Back to RPN * Now let's construct a PDA for our RPN expressions * First, note: Our stack alphabet can be *different* from the input alphabet $$ \Sigma = \\{0, 1, 2, \ldots, 9, +, -, /, *, ␣ \\}\\\\ \Lambda = \\{N\\} $$ * Let's start with recognizing numbers (without leading zeros) --- # Recognizing numbers
--- class: medium # RPNs * As we discussed, an operator needs two operands preceding it, and produces a result * Basically, when we evaluate we take two numbers, apply the operand and push the result * Parsing does the same, without applying any actual operation
--- # RPNs Putting it all together:
--- class: smallmath # Formal Definition $$ \begin{aligned} Q =\\{&\mathit{init}, n, n_0, \mathit{op}, f\\}\\\\ \Sigma = \\{&0, 1, 2, \ldots, 9, +, -, /, *, ␣ \\}\\\\ \Lambda = \\{&N\\}\\\\ q_0 =& \mathit{init}\\\\ F = \\{&f \\}\\\\ \partial = \\{&((\mathit{init}, (0, \varepsilon)), (n_0, N)), ((\mathit{init}, (1, \varepsilon)), (n, N)),\\\\ &((\mathit{init}, (2, \varepsilon)), (n, N)), ((\mathit{init}, (3, \varepsilon)), (n, N)),\\\\ & \\ldots, ((\mathit{init}, (9, \varepsilon)), (n, N)),\\\\ &((n, (0, N)), (n, N)), ((n, (1, N)), (n, N)),\\\\ &((n, (2, N)), (n, N)), ((n, (3, N)), (n, N)),\\\\ & \\ldots, ((n, (9, N)), (n, N)),\\\\ & ((\mathit{init}, (+, NN)), (\mathit{op}, N)), ((\mathit{init}, (-, NN)), (\mathit{op}, N)),\\\\ & \ldots\\\\ \\}& \end{aligned} $$ --- # Pushdown Automata: Power * The languages that can be recognized by pushdown automata (PDA) is exactly the set of context-free languages * Non-determinism is actually important here! A deterministic PDA can **not** recognize every context-free language * Constructing a PDA for a language given by a context-free grammar is straightforward * The other direction is not (in general) ... --- # Grammar to PDA * Given a grammar, how can we construct the corresponding PDA? * We just simulate our grammar derivation on the automaton! * And we can do that with just two states! --- class: small # Grammar to PDA * Generally, to apply a grammar rule we need to "have" the non-terminal symbol on its left hand side * We will store the current "contents" on the stack * When there is a non-terminal as the top-most element, our transition rules correspond to applying the grammar rules, pushing the right-hand side (in reverse order!) * When there is a terminal-symbol as the top-most element, the only allowed transition rule is to **read** the same symbol from our input * Note: We never talk about "states", all our transition rules stay within the same state! * We need one additional state to initialize the stack --- # Grammar to PDA example .left-column[ $$ S \rightarrow 1 S 1\\\\ S \rightarrow 0 S 0 \\\\ S \rightarrow \varepsilon $$ ] .right-column[
] --- class: mmedium # Other variations of PDAs * Our PDAs allow pushing and popping multiple symbols at once * This is convenient, but not strictly necessary: We could "force" the automaton to run through multiple states, and push/pop the symbols one by one * Our grammar-automaton needed two states, because we needed to initialize the stack. Some formalisms allow you to specify an initial stack state * Similarly, requiring an empty stack is convenient, but not strictly necessary. We could start by pushing a special symbol and only transition to an accepting state if we can pop that symbol --- class: medium # Deterministic PDAs * Last week we discussed how parsing is easier when we know which rule to apply next * Similarly, Pushdown Automata are easier to run/implement when they are deterministic * "Deterministic" in this case means: There is **at most** one transition rule we can apply (but there may be none!) * As mentioned, there are languages for which there is **no** deterministic pushdown automaton, but there is a non-deterministic PDA --- # Limitations? * Now we can store something/count! * What can we *not* do? Store multiple things that we can access *arbitrarily* (i.e. it's always a stack) * Let's try writing a grammar/construct a PDA for: $$ L = \\{a^n b^n c^n | n \in \mathbb{N}\\} $$ -- Oh no ... -- Looks like it's time for another pumping lemma (next time)