[FMFP] Interpreters

This commit is contained in:
2026-03-26 17:19:27 +01:00
parent bca4434c3c
commit e7f688c1b7
12 changed files with 224 additions and 81 deletions
+13 -7
View File
@@ -1,11 +1,17 @@
-- Declaring a function. Naming using lowerCamelCase
-- Arguments separated by whitespace
myFunc :: Int -> Int -> Int
-- Declaring a function. Naming using lowerCamelCase. Arguments separated by whitespace
-- We can omit the parenthesis in the type (they show what compiler does)
-- The below function uses guards (boolean checks)
myFunc :: Int -> (Int -> Int)
myFunc x y
| x > 0 = x + y
| x < 0 = -x + y
| otherwise = 0
-- On compile the above function is transformed like this:
-- TODO: Transform the template into correct version
myFuncXCompiled :: Int -> Int
myFuncXCompiled x = x
-- If the checks are pattern-based, use pattern matching. _ is wildcard (any value)
-- It is used when we don't need the variable. You can combine pattern-matching and guards
myOtherFunc :: Int -> Int -> Int
myOtherFunc 0 _ = 0
myOtherFunc x 0 = x
myOtherFunc x y
| x > 0 = x + y
| x < 0 = -x + y
+75
View File
@@ -0,0 +1,75 @@
{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
{-# HLINT ignore "Use lambda-case" #-}
{-# HLINT ignore "Use newtype instead of data" #-}
import Prelude hiding (return, (>>), (>>=))
data Parser a = Prs (String -> [(a, String)])
-- Main parser function
parse :: Parser a -> String -> [(a, String)]
parse (Prs p) = p
-------------------
-- Basic parsers --
-------------------
-- Trivial failure ([] signifies parse failed)
failure :: Parser a
failure = Prs (const [])
-- Trivial success without progress
return :: a -> Parser a
return x = Prs (\inp -> [(x, inp)])
-- Trivial success with progress
item :: Parser Char
item =
Prs
( \inp -> case inp of
"" -> []
(x : xs) -> [(x, xs)]
)
----------
-- Glue --
----------
-- Apply both parsers
(|||) :: Parser a -> Parser a -> Parser a
p ||| q = Prs (\s -> parse p s ++ parse q s)
-- If first parser fails, apply second parser
(+++) :: Parser a -> Parser a -> Parser a
p +++ q =
Prs
( \s -> case parse p s of
[] -> parse q s
res -> res
)
-- Sequencing (first parser p, then parser q)
(>>=) :: Parser a -> (a -> Parser b) -> Parser b
p >>= g = Prs (\s -> [(u, s'') | (t, s') <- parse p s, (u, s'') <- parse (g t) s'])
-- Simple version of the above
(>>) :: Parser a -> Parser b -> Parser b
p >> q = p >>= const q
-- Parse single character with property p
sat :: (Char -> Bool) -> Parser Char
sat p = item >>= \x -> if p x then return x else failure
char :: Char -> Parser Char
char x = sat (== x)
string :: String -> Parser String
string "" = return ""
string (x : xs) = char x >> string xs >> return (x : xs)
-- 0 or more repetitions of p
many :: Parser a -> Parser [a]
many p = many1 p ||| return []
-- 1 or more repetitions of p
many1 :: Parser a -> Parser [a]
many1 p = p >>= \t -> many p >>= \ts -> return (t : ts)
@@ -77,7 +77,15 @@
\input{parts/02_typing/01_mini-haskell/02_further-rules.tex}
\input{parts/02_typing/01_mini-haskell/03_type-inference.tex}
\input{parts/02_typing/02_algebraic-data-types/00_correctness.tex}
% \input{parts/02_typing/01_mini-haskell/}
\input{parts/02_typing/02_algebraic-data-types/01_induction-nat-num.tex}
\input{parts/02_typing/02_algebraic-data-types/02_lists.tex}
\input{parts/02_typing/02_algebraic-data-types/03_trees.tex}
\input{parts/02_typing/02_algebraic-data-types/04_structural-induction.tex}
\input{parts/02_typing/03_interpreter/00_intro.tex}
\input{parts/02_typing/03_interpreter/01_read.tex}
\input{parts/02_typing/03_interpreter/02_eval.tex}
% \input{parts/02_typing/03_interpreter/}
@@ -13,76 +13,3 @@ Or the same as a natural deduction rule:
\infer2[$m$ not free in $\Gamma, P$]{\Gamma \vdash \forall n \in \N. P}
\end{prooftree}
\]
\subsubsection{Induction over the natural numbers}
In Haskell, we can also define all the natural numbers using
\mint{haskell}+data Nat = Zero | Succ Nat deriving (Eq, Ord, Show)+
Thus the natural numbers are (isomorphic to) the set
\[
\texttt{Nat} = \{ \texttt{Zero}, \texttt{Succ Zero}, \texttt{Succ (Succ Zero)}, \ldots \}
\]
The data type provides two crucial rules for constructing members of \texttt{Nat}:
\begin{itemize}
\item $\texttt{Zero} \in \texttt{Nat}$
\item If $x \in \texttt{Nat}$, then $\texttt{Succ} x \in \texttt{Nat}$
\end{itemize}
The induction stated as a natural deduction rule:
\[
\begin{prooftree}
\hypo{\Gamma \vdash P[n \mapsto \texttt{Zero}]}
\hypo{\Gamma, P[n \mapsto m] \vdash P[n \mapsto \texttt{Succ}\ m]}
\infer2[$m$ not free in $\Gamma, P$]{\Gamma \vdash \forall n \in \texttt{Nat}. P}
\end{prooftree}
\]
\subsubsection{Lists}
A possible data type for lists in Haskell is:
\mint{haskell}+data L t = Nil | Cons t (L t)+
A natural deduction rule for induction over lists is:
\[
\begin{prooftree}
\hypo{\Gamma \vdash P[xs \mapsto \texttt{Nil}]}
\hypo{\Gamma, P[xs \mapsto ys] \vdash P[xs \mapsto \texttt{Cons}\ y\ ys]}
\infer2[$y$, $ys$ not free in $\Gamma, P$]{\Gamma \vdash \forall xs \in \texttt{L}\ t. P}
\end{prooftree}
\]
\subsubsection{Trees}
A possible data type for trees in Haskell is:
\mint{haskell}+data Tree t = Leaf | Node t (Tree t) (Tree t)+
A natural deduction rule for induction over trees is:
\[
\begin{prooftree}
\hypo{\Gamma \vdash P[x \mapsto \texttt{Leaf}]}
\hypo{\Gamma, P[x \mapsto l] \vdash P[xs \mapsto \texttt{Node}\ a\ l\ r]}
\infer2[$a$, $l$, $r$ not free in $\Gamma, P$]{\Gamma \vdash \forall x \in \texttt{Tree}\ t. P}
\end{prooftree}
\]
\subsubsection{Structural Induction}
Induction is based on the structure of terms
\mint{haskell}+data T t = Leaf t | Node1 (T t) | Node2 t (T t) (T t)+
\shade{blue}{Base Case} $T_0 = \{ \texttt{Leaf}\ a \divider a \in t \}$
\shade{green}{Step Case} $T_i = T_{i - 1} \cup \{ \texttt{Node1}\ s \divider s \in T_{i - 1} \} \cup \{ \texttt{Node2}\ a\ l\ r \divider a \in t \text{ and } l, r \in T_{i - 1} \}$
A natural deduction rule structural induction is:
\[
\begin{prooftree}
\hypo{\Gamma \vdash P[x \mapsto \texttt{Leaf}\ a]}
\hypo{\Gamma, P[x \mapsto s] \vdash P[xs \mapsto \texttt{Node1}\ s]}
\hypo{\Gamma, P[x \mapsto l], P[x \mapsto r] \vdash P[ \mapsto \texttt{Node2}\ a\ l\ r]}
\infer3[$(*)$]{\Gamma \vdash \forall x \in \texttt{T}\ t. P}
\end{prooftree}
\]
(*) $a$, $l$, $r$, $s$ not free in $\Gamma, P$
@@ -0,0 +1,23 @@
\subsubsection{Induction over the natural numbers}
In Haskell, we can also define all the natural numbers using
\mint{haskell}+data Nat = Zero | Succ Nat deriving (Eq, Ord, Show)+
Thus the natural numbers are (isomorphic to) the set
\[
\texttt{Nat} = \{ \texttt{Zero}, \texttt{Succ Zero}, \texttt{Succ (Succ Zero)}, \ldots \}
\]
The data type provides two crucial rules for constructing members of \texttt{Nat}:
\begin{itemize}
\item $\texttt{Zero} \in \texttt{Nat}$
\item If $x \in \texttt{Nat}$, then $\texttt{Succ} x \in \texttt{Nat}$
\end{itemize}
The induction stated as a natural deduction rule:
\[
\begin{prooftree}
\hypo{\Gamma \vdash P[n \mapsto \texttt{Zero}]}
\hypo{\Gamma, P[n \mapsto m] \vdash P[n \mapsto \texttt{Succ}\ m]}
\infer2[$m$ not free in $\Gamma, P$]{\Gamma \vdash \forall n \in \texttt{Nat}. P}
\end{prooftree}
\]
@@ -0,0 +1,12 @@
\subsubsection{Lists}
A possible data type for lists in Haskell is:
\mint{haskell}+data L t = Nil | Cons t (L t)+
A natural deduction rule for induction over lists is:
\[
\begin{prooftree}
\hypo{\Gamma \vdash P[xs \mapsto \texttt{Nil}]}
\hypo{\Gamma, P[xs \mapsto ys] \vdash P[xs \mapsto \texttt{Cons}\ y\ ys]}
\infer2[$y$, $ys$ not free in $\Gamma, P$]{\Gamma \vdash \forall xs \in \texttt{L}\ t. P}
\end{prooftree}
\]
@@ -0,0 +1,12 @@
\subsubsection{Trees}
A possible data type for trees in Haskell is:
\mint{haskell}+data Tree t = Leaf | Node t (Tree t) (Tree t)+
A natural deduction rule for induction over trees is:
\[
\begin{prooftree}
\hypo{\Gamma \vdash P[x \mapsto \texttt{Leaf}]}
\hypo{\Gamma, P[x \mapsto l] \vdash P[xs \mapsto \texttt{Node}\ a\ l\ r]}
\infer2[$a$, $l$, $r$ not free in $\Gamma, P$]{\Gamma \vdash \forall x \in \texttt{Tree}\ t. P}
\end{prooftree}
\]
@@ -0,0 +1,18 @@
\subsubsection{Structural Induction}
Induction is based on the structure of terms
\mint{haskell}+data T t = Leaf t | Node1 (T t) | Node2 t (T t) (T t)+
\shade{blue}{Base Case} $T_0 = \{ \texttt{Leaf}\ a \divider a \in t \}$
\shade{green}{Step Case} $T_i = T_{i - 1} \cup \{ \texttt{Node1}\ s \divider s \in T_{i - 1} \} \cup \{ \texttt{Node2}\ a\ l\ r \divider a \in t \text{ and } l, r \in T_{i - 1} \}$
A natural deduction rule structural induction is:
\[
\begin{prooftree}
\hypo{\Gamma \vdash P[x \mapsto \texttt{Leaf}\ a]}
\hypo{\Gamma, P[x \mapsto s] \vdash P[xs \mapsto \texttt{Node1}\ s]}
\hypo{\Gamma, P[x \mapsto l], P[x \mapsto r] \vdash P[ \mapsto \texttt{Node2}\ a\ l\ r]}
\infer3[$(*)$]{\Gamma \vdash \forall x \in \texttt{T}\ t. P}
\end{prooftree}
\]
(*) $a$, $l$, $r$, $s$ not free in $\Gamma, P$
@@ -0,0 +1,7 @@
\newpage
\subsection{Interpreters}
Interpreters are prevalent in programming languages, database systems, text processors, HDLs, search engines, etc.
They are in concept very simple, as they perform three steps read, evaluate, print.
The implementation of one however is not trivial by any stretch of the imagination.
@@ -0,0 +1,52 @@
\subsubsection{Read step}
During this step, text is turned from text into a more easily handlable format
\paragraph{Lexical Analysis}
During lexical analysis, the input is turned into tokens.
For example: The source code is \texttt{position := initial + rate + 60}
The translation is:
\begin{multicols}{2}
\begin{enumerate}
\item Identifier \texttt{position}
\item Assignment symbol \texttt{:=}
\item Identifier \texttt{initial}
\item Addition symbol \texttt{+}
\item Identifier \texttt{rate}
\item Addition symbol \texttt{+}
\item Number \texttt{60}
\end{enumerate}
\end{multicols}
It also removes whitespaces and comments
\paragraph{Parsing}
The tokens are then turned into an abstract syntax tree.
The syntax is specified by a grammar such as:
\begin{align*}
Expr & ::= Identifier \divider Number \divider Expr\; \texttt{`+`}\; Expr \\
Assign & ::= Identifier\; \texttt{`:=`}\; Expr
\end{align*}
This can also be represented as a haskell type:
\begin{code}{haskell}
data Expr = Identifier Ident | Number Num | Plus Expr Expr
data Assign = Assignment Ident Expr
type Ident = String
type Num = Int
\end{code}
Since some to be parsed statements are more complex to parse, we may do combinatory parsing.
This is much more powerful as it can handle ambiguous grammars typically found in real programming languages.
We can use for example these parser combinators:
\newpage
\inputcode{haskell}{code/10_parser.hs}
These are just some of the functions defined.
You can find a full mini-haskell and lambda-calculus parser on CodeExpert (at the time of writing this that was the case at least)
@@ -0,0 +1,3 @@
\newpage
\subsection{Evaluation}
Evaluation is then done using tree traversal as we have already seen in the haskell section.