diff --git a/README.md b/README.md
index 3e2c1bac4f7e33d6178e33d3d46996a8d9339b68..fb82cb996c4e654b93c9c12f94a8da863e4b9f9a 100644
--- a/README.md
+++ b/README.md
@@ -33,7 +33,7 @@ The grade is based on a midterm (30%) as well as team project work (70%). Please
 | :--  | :--  | :-- | :--        | :--   | :--    | :--                  | :--                          | :--                          |
 |      | .23... | Fri | 14.03.2025 | 13:15 | ELA 2  | Lecture 6  | [Name Analysis](https://mediaspace.epfl.ch/media/06-01%2C+Name+Analysis/0_1b9t1hz8) [(PDF)](info/lectures/lec06-name-analysis.pdf), [Type Systems as Inductive Relations](https://mediaspace.epfl.ch/media/07-01%2C+Introduction+to+Types+and+Inductive+Relations/0_3hxblocu) [(PDF)](info/lectures/lec06-inductive.pdf) . [Operational Semantics](https://mediaspace.epfl.ch/media/07-02%2C+Operational+Semantics/0_3ru05nbo) [(PDF)](info/lectures/lec06-operational.pdf) |
 |      | .23... | Fri | 14.03.2025 | 15:15 | ELA 2  | Lab 3 | Parser lab |
-| 5    | ..3... | Wed | 19.03.2025 | 13:15 | BC 01  | Exercises 3 | LL(1) Grammars |
+| 5    | ..3... | Wed | 19.03.2025 | 13:15 | BC 01  | Exercises 3 | [LL(1) Grammars](info/exercises/ex-03.pdf) [(solutions)](info/exercises/ex-03-sol.pdf) |
 |      | ..3... | Fri | 21.03.2025 | 13:15 | ELA 2  | Lecture 7 | Type Checking |
 |      | ..34.. | Fri | 21.03.2025 | 15:15 | ELA 2  | Lab 4 | Typer lab release |
 | 6    | ..34.. | Wed | 26.03.2025 | 13:15 | BC 01  | Exercises 4 | Parsing. Type checking |
diff --git a/info/exercises/ex-02-sol.pdf b/info/exercises/ex-02-sol.pdf
index 47bb3a8cece402275ad716d843f80b826cb19d13..d40f8164741e89eb6aedcbea0eff3928e1311bbd 100644
Binary files a/info/exercises/ex-02-sol.pdf and b/info/exercises/ex-02-sol.pdf differ
diff --git a/info/exercises/ex-02.pdf b/info/exercises/ex-02.pdf
index 4af57628d82238c937075f44b6b6cfac0eb42b4b..8ca9194bcadba07fa29c83cb0b9df97a2039c656 100644
Binary files a/info/exercises/ex-02.pdf and b/info/exercises/ex-02.pdf differ
diff --git a/info/exercises/ex-03-sol.pdf b/info/exercises/ex-03-sol.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..6b54106a485de679167f1f6f084f4180b158270b
Binary files /dev/null and b/info/exercises/ex-03-sol.pdf differ
diff --git a/info/exercises/ex-03.pdf b/info/exercises/ex-03.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..840f896161f7c97fa1de069582c065432d99d06a
Binary files /dev/null and b/info/exercises/ex-03.pdf differ
diff --git a/info/exercises/src/ex-03/ex/compute.tex b/info/exercises/src/ex-03/ex/compute.tex
new file mode 100644
index 0000000000000000000000000000000000000000..e7792fd2ce6bfe72241192bbc3935588cdeefe16
--- /dev/null
+++ b/info/exercises/src/ex-03/ex/compute.tex
@@ -0,0 +1,229 @@
+
+% Compiler Design 3.9
+\begin{exercise}{}
+  Compute \(\nullable\), \(\first\), and \(\follow\) for the non-terminals \(A\)
+  and \(B\) in the following grammar:
+  %
+  \begin{align*}
+    A &::= BAa \\
+    A &::=  \\
+    B &::= bBc \\
+    B &::= AA
+  \end{align*}
+
+  Remember to extend the language with an extra start production for the
+  computation of \(\follow\).
+
+  \begin{solution}
+    \begin{enumerate}
+      \item \(\nullable\): we get the constraints
+      \begin{gather*}
+        \nullable(A) = \nullable(BAa) \lor \nullable(\epsilon) \\
+        \nullable(B) = \nullable(bBc) \lor \nullable(AA)
+      \end{gather*}
+
+      We can solve these to get \(\nullable(A) = \nullable(B) = true\).
+
+      \item \(\first\): we get the constraints (given that both \(A\) and \(B\)
+      are nullable):
+      \begin{align*}
+        \first(A) &= \first(BAa) \cup \first(\epsilon) \\
+                  &= \first(B) \cup \first(A) \cup \emptyset \\
+                  &= \first(B) \cup \first(A) \\
+        \first(B) &= \first(bBc) \cup \first(AA) \\
+                  &= \{b\} \cup \first(A) \cup \first(A) \cup \emptyset \\
+                  &= \{b\} \cup \first(A)
+      \end{align*}
+
+      Starting from \(\first(A) = \first(B) = \emptyset\), we iteratively
+      compute the fixpoint to get \(\first(A) = \first(B) = \{a, b\}\).
+
+      \item \(\follow\): we add a production \(A' ::= A~\mathbf{EOF}\), and get
+      the constraints (in order of productions):
+      \begin{gather*}
+        \{\mathbf{EOF}\} \subseteq \follow(A) \\
+        \\
+        \first(A) \subseteq \follow(B) \\
+        \{a\} \subseteq \follow(A) \\
+        \\
+        \{c\} \subseteq \follow(B) \\
+        \\
+        \first(A) \subseteq \follow(A) \\
+        \follow(B) \subseteq \follow(A)
+      \end{gather*}
+
+      Substituting the computed \(\first\) sets, and computing a fixpoint, we
+      get \(\follow(A) = \{a, b, c,\mathbf{EOF}\}\) and \(\follow(B) = \{a, b,
+      c\}\).
+    \end{enumerate}
+  \end{solution}
+\end{exercise}
+
+% Compiler design 3.11
+\begin{exercise}{}
+  
+  Given the following grammar for arithmetic expressions:
+
+  \begin{align*}
+    S &::= Exp~\mathbf{EOF} \\
+    Exp &::= Exp_2~ Exp_* \\
+    Exp_* &::= +~ Exp_2~ Exp_* \\
+    Exp_* &::= -~ Exp_2~ Exp_* \\
+    Exp_* &::= \\
+    Exp_2 &::= Exp_3~ Exp_{2*} \\
+    Exp_{2*} &::= *~ Exp_3~ Exp_{2*} \\
+    Exp_{2*} &::= /~ Exp_3~ Exp_{2*} \\
+    Exp_{2*} &::= \\
+    Exp_3 &::= \mathbf{num} \\
+    Exp_3 &::= (Exp) \\
+  \end{align*}
+
+  \begin{enumerate}
+    \item Compute \(\nullable\), \(\first\), \(\follow\) for each of the
+    non-terminals in the grammar.
+    \item Check if the grammar is LL(1). If not, modify the grammar to make it
+    so.
+    \item Build the LL(1) parsing table for the grammar.
+    \item Using your parsing table, parse or attempt to parse (till error) the
+    following strings, assuming that \(\mathbf{num}\) matches any natural
+    number:
+    \begin{enumerate}
+      \item \((3 + 4) * 5 ~\mathbf{EOF}\)
+      \item \(2 + + ~\mathbf{EOF}\)
+      \item \(2 ~\mathbf{EOF}\)
+      \item \(2 * 3 + 4 ~\mathbf{EOF}\)
+      \item \(2 + 3 * 4 ~\mathbf{EOF}\)
+    \end{enumerate}
+  \end{enumerate}
+
+  \begin{solution}
+    \begin{enumerate}
+      \item We can compute the \(\nullable\), \(\first\), and \(\follow\) sets as:
+
+        \begin{enumerate}
+          \item \(\nullable\): 
+          %
+            \begin{align*}
+              \nullable(Exp) &= false \\
+              \nullable(Exp_*) &= true \\
+              \nullable(Exp_2) &= false \\
+              \nullable(Exp_{2*}) &= true \\
+              \nullable(Exp_3) &= false
+            \end{align*}
+
+          \item \(\first\): we have constraints:
+            %
+            \begin{align*}
+              \first(Exp) &= \first(Exp_2) \\
+              \first(Exp_*) &= \{+\} \cup \{-\} \cup \emptyset \\
+              \first(Exp_2) &= \first(Exp_3) \\
+              \first(Exp_{2*}) &= \{*\} \cup \{/\} \cup \emptyset \\
+              \first(Exp_3) &= \{\mathbf{num}\} \cup \{(\}
+            \end{align*}
+            %
+            which can be solved to get:
+            %
+            \begin{align*}
+              \first(Exp) &= \{\mathbf{num}, (\} \\
+              \first(Exp_*) &= \{+, -\} \\
+              \first(Exp_2) &= \{\mathbf{num}, (\} \\
+              \first(Exp_{2*}) &= \{*, /\} \\
+              \first(Exp_3) &= \{\mathbf{num}, (\}
+            \end{align*}
+          \item \(\follow\): we have constraints (for each rule, except
+          empty/terminal rules):
+          \begin{multicols}{2}
+            \allowdisplaybreaks
+            \begin{align*}
+              \{\mathbf{EOF}\} &\subseteq \follow(Exp) \\
+              &\\
+              \first(Exp_*) &\subseteq \follow(Exp_2) \\
+              \follow(Exp) &\subseteq \follow(Exp_2) \\
+              \follow(Exp) &\subseteq \follow(Exp_*) \\
+              &\\
+              \first(Exp_*) &\subseteq \follow(Exp_2) \\
+              \follow(Exp_*) &\subseteq \follow(Exp_2) \\
+              &\\
+              \first(Exp_*) &\subseteq \follow(Exp_2) \\
+              \follow(Exp_*) &\subseteq \follow(Exp_2) \\
+              &\\
+              \first(Exp_{2*}) &\subseteq \follow(Exp_3) \\
+              \follow(Exp_2) &\subseteq \follow(Exp_3) \\
+              \follow(Exp_2) &\subseteq \follow(Exp_{2*}) \\
+              &\\
+              \first(Exp_{2*}) &\subseteq \follow(Exp_3) \\
+              \follow(Exp_{2*}) &\subseteq \follow(Exp_3) \\
+              &\\
+              \first(Exp_{2*}) &\subseteq \follow(Exp_3) \\
+              \follow(Exp_{2*}) &\subseteq \follow(Exp_3) \\
+              &\\
+              \{)\} &\subseteq \follow(Exp) \\
+            \end{align*}
+          \end{multicols}
+
+          The fixpoint can again be computed to get:
+          \begin{align*}
+            \follow(S) &= \{\} \\
+            \follow(Exp) &= \{), \mathbf{EOF}\} \\
+            \follow(Exp_*) &= \{), \mathbf{EOF}\} \\
+            \follow(Exp_2) &= \{+, -, ), \mathbf{EOF}\} \\
+            \follow(Exp_{2*}) &= \{+, -, ), \mathbf{EOF}\} \\
+            \follow(Exp_3) &= \{+, -, *, /, ), \mathbf{EOF}\}
+          \end{align*}
+
+        \end{enumerate}
+      \item The grammar is LL(1), there are no conflicts. Demonstrated by the
+      parsing table below.
+      \item LL(1) parsing table:
+        \begin{center}
+          \begin{tabular}{c|c|c|c|c|c|c|c|c}
+            & \(\mathbf{num}\) & \(+\) & \(-\) & \(*\) & \(/\) & \((\) & \()\) & \(\mathbf{EOF}\) \\
+            \hline
+            \(S\) & 1 & & & & & 1 & &\\
+            \(Exp\) & 1 & & & & & 1 & &\\
+            \(Exp_*\) & & 1 & 2 & & & & 3 & 3 \\
+            \(Exp_2\) & 1 & & & & & 1 & & \\
+            \(Exp_{2*}\) & & 3 & 3 & 1 & 2 & & 3 & 3 \\
+            \(Exp_3\) & 1 & & & & & 2 & & \\
+          \end{tabular}
+        \end{center}
+      \item Parsing the strings:
+      \begin{enumerate}
+        \item \((3 + 4) * 5 ~\mathbf{EOF}\) \checkmark
+        \item \(2 + + ~\mathbf{EOF}\) --- fails on the second \(+\). The
+        corresponding error cell in the parsing table is \((Exp_2, +)\).
+        \item \(2 ~\mathbf{EOF}\) \checkmark
+        \item \(2 * 3 + 4 ~\mathbf{EOF}\) \checkmark
+        \item \(2 + 3 * 4 ~\mathbf{EOF}\) fails on the \(*\). Error at \((Exp_*, *)\).
+      \end{enumerate}
+
+      Example step-by-step LL(1) parsing state for \(2 * 3 + 4\):
+      \begin{center}
+        \begin{tabular}{c c}
+          Lookahead token & Stack \\
+          \hline
+          \(2\) & \(S\) \\
+          \(2\) & \(Exp ~ \mathbf{EOF}\) \\
+          \(2\) & \(Exp_2 ~ Exp_* ~ \mathbf{EOF}\) \\
+          \(2\) & \(Exp_3 ~ Exp_{2*} ~ Exp_* ~ \mathbf{EOF}\) \\
+          \(2\) & \(\mathbf{num} ~ Exp_{2*} ~ Exp_* ~ \mathbf{EOF}\) \\
+          \(*\) & \(Exp_{2*} ~ Exp_* ~ \mathbf{EOF}\) \\
+          \(*\) & \(* ~Exp_3 ~ Exp_{2*} ~ Exp_* ~ \mathbf{EOF}\) \\
+          \(3\) & \(Exp_3 ~ Exp_{2*} ~ Exp_* ~ \mathbf{EOF}\) \\
+          \(3\) & \(\mathbf{num} ~ Exp_{2*} ~ Exp_* ~ \mathbf{EOF}\) \\
+          \(+\) & \(Exp_{2*} ~ Exp_* ~ \mathbf{EOF}\) \\
+          \(+\) & \(Exp_* ~ \mathbf{EOF}\) \\
+          \(+\) & \(+ ~Exp_2 ~Exp_* ~ \mathbf{EOF}\) \\
+          \(4\) & \(Exp_2 ~Exp_* ~ \mathbf{EOF}\) \\
+          \(4\) & \(Exp_3 ~Exp_{2*} ~Exp_* ~ \mathbf{EOF}\) \\
+          \(4\) & \(\mathbf{num} ~Exp_{2*} ~Exp_* ~ \mathbf{EOF}\) \\
+          \(\mathbf{EOF}\) & \(Exp_{2*} ~Exp_* ~ \mathbf{EOF}\) \\
+          \(\mathbf{EOF}\) & \(Exp_* ~ \mathbf{EOF}\) \\
+          \(\mathbf{EOF}\) & \(\mathbf{EOF}\) \\
+        \end{tabular}
+      \end{center}
+    \end{enumerate}
+  \end{solution}
+
+\end{exercise}
+
diff --git a/info/exercises/src/ex-03/ex/prefix.tex b/info/exercises/src/ex-03/ex/prefix.tex
new file mode 100644
index 0000000000000000000000000000000000000000..e9a3bae18696161d09eac39ae26028404c43b39b
--- /dev/null
+++ b/info/exercises/src/ex-03/ex/prefix.tex
@@ -0,0 +1,67 @@
+
+\begin{exercise}{}
+
+  If \(L\) is a regular language, then the set of prefixes of words in \(L\) is
+  also a regular language. Given this fact, from a regular expression for \(L\),
+  we should be able to obtain a regular expression for the set of all prefixes
+  of words in \(L\) as well.
+  
+  We want to do this with a function \(\prefixes\) that is recursive over the
+  structure of the regular expression for \(L\), i.e. of the form:
+  %
+  \begin{align*}
+    \prefixes(\epsilon) &= \epsilon \\
+    \prefixes(a) &= a \mid \epsilon \\
+    \prefixes(r \mid s) &= \prefixes(r) \mid \prefixes(s) \\
+    \prefixes(r \cdot s) &= \ldots \\
+    \prefixes(r^*) &= \ldots \\
+    \prefixes(r^+) &= \ldots
+  \end{align*}
+
+  \begin{enumerate}
+    \item Complete the definition of \(\prefixes\) above by filling in the
+    missing cases.
+    \item Use this definition to find:
+    \begin{enumerate}
+      \item \(\prefixes(ab^*c)\)
+      \item \(\prefixes((a \mid bc)^*)\)
+    \end{enumerate}
+  \end{enumerate}
+
+  \begin{solution}
+    The computation for \(\prefixes(\cdot)\) is similar to the computation of
+    \(\first(\cdot)\) for grammars.
+
+    \begin{enumerate}
+      \item The missing cases:
+      \begin{enumerate}
+        \item \(\prefixes(r \cdot s) = \prefixes(r) \mid r \cdot \prefixes(s)\).
+        Either we have read \(r\) partially, or we have read all of \(r\), and a
+        part of \(s\).
+        \item \(\prefixes(r^*) = r*\cdot\prefixes(r)\). We can
+        consider \(r^* = \epsilon \mid r \mid rr \mid \ldots\), and apply the
+        rules for union and concatenation. Intuitively, if the word has \(n \ge
+        0\) instances of \(r\), we can read \(m < n\) instances of \(r\), and
+        then a prefix of the next instance of \(r\).
+        \item \(\prefixes(r^+) = r^* \cdot \prefixes(r)\). Same as
+        previous. Why does the empty case still appear?
+      \end{enumerate}
+      \item The prefix computations are:
+      \begin{enumerate}
+        \item \(\prefixes(ab^*c) = \epsilon \mid a \mid ab^*(b \mid c \mid \epsilon)\). Computation:
+        \begin{align*}
+          \prefixes(ab^*c) &= \prefixes(a) \mid a\cdot\prefixes(b^*c) & [\text{concatenation}]\\
+                           &= (a \mid \epsilon) \mid a\cdot\prefixes(b^*c) &[a]\\
+                           &= (a \mid \epsilon) \mid a\cdot(\prefixes(b^*) \mid b^*\prefixes(c)) &[\text{concatenation}]\\
+                           &= (a \mid \epsilon) \mid a\cdot(\prefixes(b^*) \mid b^*(c \mid \epsilon)) &[c]\\
+                           &= (a \mid \epsilon) \mid a\cdot(b^*\prefixes(b) \mid b^*(c \mid \epsilon)) &[\text{star}]\\
+                           &= (a \mid \epsilon) \mid a\cdot(b^*(b \mid \epsilon) \mid b^*(c \mid \epsilon)) &[b]\\
+                           &= (a \mid \epsilon) \mid a\cdot(b^*(b \mid c \mid \epsilon)) &[\text{rewrite}]\\
+                           &= \epsilon \mid a \mid a\cdot(b^*(b \mid c \mid \epsilon)) & [\text{rewrite}]\\
+        \end{align*}
+        \item \(\prefixes((a \mid bc)^*) = (a \mid bc)^*(\epsilon \mid a \mid b \mid bc)\).
+      \end{enumerate}
+    \end{enumerate}
+  \end{solution}
+  
+\end{exercise}
diff --git a/info/exercises/src/ex-03/ex/table.tex b/info/exercises/src/ex-03/ex/table.tex
new file mode 100644
index 0000000000000000000000000000000000000000..1e3768b1df53b1d347de1a770aa219f2d4153ed5
--- /dev/null
+++ b/info/exercises/src/ex-03/ex/table.tex
@@ -0,0 +1,166 @@
+
+% this language is not LL 1 actually, I think
+
+% \begin{exercise}{}
+%   Consider the following grammar of \(\mathbf{if}-\mathbf{then}-\mathbf{else}\) expressions with assignments:
+%   %
+%   \begin{align*}
+%     stmt &::= \mathbf{if} ~id = id~ \mathbf{then} ~stmt ~optStmt \\
+%          &::= \{ stmt^* \} \\
+%          &::= id = id; \\
+%     optStmt &::= \epsilon \mid \mathbf{else} ~stmt \\
+%   \end{align*}
+  
+%   \begin{enumerate}
+%     \item Show that the grammar is ambiguous.
+%     \item Is the grammar LL(1)?
+%   \end{enumerate}
+
+% \end{exercise}
+
+
+\begin{exercise}{}
+  Argue that the following grammar is \emph{not} LL(1). Produce an equivalent
+  LL(1) grammar.
+
+  \begin{equation*}
+    E ::= \mathbf{num} + E \mid \mathbf{num} - E 
+  \end{equation*}
+
+  \begin{solution}
+    The language is clearly not LL(1), as on seeing a token \(\mathbf{num}\), we
+    cannot decide whether to continue parsing it as \(\mathbf{num} + E\) or
+    \(\mathbf{num} - E\). 
+
+    The notable problem is the common prefix between the two rules. We can
+    separate this out by introducing a new non-terminal \(T\). This is a
+    transformation known as \emph{left factorization}.
+
+    \begin{align*}
+      E &::= \mathbf{num} ~T \\
+      T &::= + E \mid - E
+    \end{align*}
+
+    % without changing the terms or the overall "structure" of the grammar, we
+    % have logically partitioned it to fit within our parsing schema.
+
+  \end{solution}
+\end{exercise}
+
+\begin{exercise}{}
+  Consider the following grammar:
+
+  \begin{equation*}
+    S ::= S(S) \mid S[S] \mid () \mid [\;]
+  \end{equation*}
+    
+  Check whether the same transformation as the previous case can be applied to
+  produce an LL(1) grammar. If not, argue why, and suggest a different
+  transformation.
+
+  \begin{solution}
+    Applying left factorization to the grammar, we get:
+    \begin{align*}
+      S &::= S ~T \mid S ~T \mid () \mid [\;] \\
+      T &::= (S) \mid [S]
+    \end{align*}
+
+    This is not LL(1), as on reading a token ``\((\)'', we cannot decide whether
+    this is the final parentheses (base case) in the expression, or whether
+    there is a \(T\) following it.
+
+    The problem is that this version of the grammar is left-recursive. A
+    recursive-descent parser for this grammar would loop forever on the first
+    rule. This is caused by the fact that our parsers are top-down, left to
+    right. We can fix this by \emph{moving} the recursion to the right. This is
+    generally called \emph{left recursion elimination}.
+
+    Transformed grammar steps (explanation below):
+
+    Left recursion elimination (not LL(1) yet! \(\first(S') = \{(, [\;\}\)):
+    \begin{align*}
+      S &::= S' \mid ()S' \mid [\;]S' \\
+      S' &::= (S)S' \mid [S]S'
+    \end{align*}
+
+    Inline \(S'\) once in \(S ::= S'\):
+    \begin{align*}
+      S &::= (S)S' \mid [S]S' \mid ()S' \mid [\;]S' \\
+      S' &::= (S)S' \mid [S]S' \mid \epsilon
+    \end{align*}
+
+    Finally, left factorize \(S\) to get an LL(1) grammar:
+    \begin{align*}
+      S &::= (T_1 \mid [T_2 \\
+      T_1 &::= S)S' \mid ~)S' \\
+      T_2 &::= S]S' \mid ~]S' \\
+      S' &::= (S)S' \mid [S]S' \mid \epsilon
+    \end{align*}
+
+    To eliminate left-recursion in general, consider a non-terminal \(A ::=
+    A\alpha \mid \beta\), where \(\beta\) does not start with \(A\) (not
+    left-recursive). We can remove the left recursion by introducing a new
+    non-terminal, \(A'\), such that:
+    \begin{align*}
+      A &::= A' \mid \beta A' \\
+      A' &::= \alpha A' \mid \epsilon
+    \end{align*}
+    i.e., for the left-recursive rule \(A\alpha\), we instead attempt to parse
+    an \(\alpha\) followed by the rest. In exchange, the base case \(\beta\) now
+    expects an \(A'\) to follow it.
+    %
+    Note that \(\beta\) can be empty as well.
+    
+    Intuitively, we are shifting the direction in which we look for instances of
+    \(A\). Consider a partial derivation starting from \(\beta \alpha \alpha
+    \alpha\). The original version of the grammar would complete the parsing as:
+    \begin{center}
+      \begin{forest}
+        [\(A\)
+          [\(A\)
+            [\(A\)
+              [\(A\)
+                [\(\beta\)]
+              ]
+              [\(\alpha\)]
+            ]
+            [\(\alpha\)]
+          ]
+          [\(\alpha\)]
+        ]     
+      \end{forest}
+    \end{center}
+    but with the new grammar, we parse it as:
+    \begin{center}
+      \begin{forest}
+        [\(A\)
+          [\(\beta\)]
+          [\(A'\)
+            [\(\alpha\)]
+            [\(A'\)
+              [\(\alpha\)]
+              [\(A'\)
+                [\(\alpha\)]
+                [\(A'\)
+                  [\(\epsilon\)]
+                ]
+              ]
+            ]
+          ]
+        ]
+      \end{forest}
+    \end{center}
+
+    There are two main pitfalls to remember with left-recursion elimination:
+    \begin{enumerate}
+      \item it may need to be applied several times till the grammar is
+      unchanged, as the first transformation may introduce new (indirect)
+      recursive rules (check \(A ::= AA\alpha \mid \epsilon\)).
+      \item it may require \emph{inlining} some non-terminals, when the left
+      recursion is \emph{indirect}. For example, consider \(A ::= B\alpha, B ::=
+      A\beta\), where there is no immediate reduction to do, but inlining \(B\),
+      we get \(A ::= A\beta\alpha\), where the elimination can be applied.
+    \end{enumerate}
+  \end{solution}
+
+\end{exercise}
diff --git a/info/exercises/src/ex-03/main.tex b/info/exercises/src/ex-03/main.tex
new file mode 100644
index 0000000000000000000000000000000000000000..863bfb5fb306c6a168db62c1b4f92343f4d0c355
--- /dev/null
+++ b/info/exercises/src/ex-03/main.tex
@@ -0,0 +1,32 @@
+\documentclass[a4paper]{article}
+
+\input{../macro}
+
+\ifdefined\ANSWERS
+  \if\ANSWERS1
+    \printanswers
+  \fi
+\fi
+
+\DeclareMathOperator{\prefixes}{prefixes}
+\DeclareMathOperator{\first}{first}
+\DeclareMathOperator{\nullable}{nullable}
+\DeclareMathOperator{\follow}{follow}
+
+\title{CS 320 \\ Computer Language Processing\\Exercises: Week 4}
+\author{}
+\date{March 19, 2025}
+
+\begin{document}
+\maketitle
+
+% prefixes of regular expressions
+\input{ex/prefix}
+
+% compute nullable follow first for CFGs
+\input{ex/compute}
+
+% build ll1 parsing table, parse or attempt to parse some strings
+\input{ex/table}
+
+\end{document}
diff --git a/info/exercises/src/macro.tex b/info/exercises/src/macro.tex
index 24c8c53448f543e26423b12ebb18bd8f4d585103..716f8d6e652bf59ef226fa50656b185a67e2397a 100644
--- a/info/exercises/src/macro.tex
+++ b/info/exercises/src/macro.tex
@@ -6,6 +6,7 @@
 \usepackage{xspace}
 \usepackage[colorlinks]{hyperref}
 \usepackage{tabularx}
+\usepackage{multicol}
 
 % for drawing
 \usepackage{tikz}