|
@@ -799,41 +799,40 @@ It produces an error:
|
|
fx+: result is not a fixnum
|
|
fx+: result is not a fixnum
|
|
\end{lstlisting}
|
|
\end{lstlisting}
|
|
We establish the convention that if running the definitional
|
|
We establish the convention that if running the definitional
|
|
-interpreter on a program produces an error, then the meaning of the
|
|
|
|
-program is \emph{unspecified}. That means the compiler for the
|
|
|
|
-language is under no obligation for such a program; it may or may not
|
|
|
|
|
|
+interpreter on a program produces an error, then the meaning of that
|
|
|
|
+program is \emph{unspecified}. That means a compiler for the language
|
|
|
|
+is under no obligations regarding that program; it may or may not
|
|
produce an executable, and if it does, that executable can do
|
|
produce an executable, and if it does, that executable can do
|
|
anything. This convention applies to the languages defined in this
|
|
anything. This convention applies to the languages defined in this
|
|
book, as a way to simplify the student's task of implementing them,
|
|
book, as a way to simplify the student's task of implementing them,
|
|
-but this convention does not generally hold for real programming
|
|
|
|
-languages.
|
|
|
|
|
|
+but this convention is not applicable to all programming languages.
|
|
|
|
|
|
-Moving on to the next feature of the $R_0$ language, the \key{read}
|
|
|
|
-operation prompts the user of the program for an integer. If we
|
|
|
|
-interpret the AST \eqref{eq:arith-prog} and give it the input
|
|
|
|
-\texttt{50}
|
|
|
|
|
|
+Moving on to the last feature of the $R_0$ language, the \key{read}
|
|
|
|
+operation prompts the user of the program for an integer. Recall that
|
|
|
|
+program \eqref{eq:arith-prog} performs a \key{read} and then subtracts
|
|
|
|
+\code{8}. So if we run
|
|
\begin{lstlisting}
|
|
\begin{lstlisting}
|
|
(interp-R0 ast1.1)
|
|
(interp-R0 ast1.1)
|
|
\end{lstlisting}
|
|
\end{lstlisting}
|
|
-we get the answer to life, the universe, and everything:
|
|
|
|
-\begin{lstlisting}
|
|
|
|
- 42
|
|
|
|
-\end{lstlisting}
|
|
|
|
|
|
+and the input the integer \code{50} we get the answer to life, the
|
|
|
|
+universe, and everything: \code{42}.
|
|
|
|
+
|
|
We include the \key{read} operation in $R_0$ so a clever student
|
|
We include the \key{read} operation in $R_0$ so a clever student
|
|
-cannot implement a compiler for $R_0$ simply by running the
|
|
|
|
-interpreter at compilation time to obtain the output and then
|
|
|
|
-generating the trivial code to return the output. (A clever student
|
|
|
|
-did this in a previous version of the course.)
|
|
|
|
|
|
+cannot implement a compiler for $R_0$ that simply runs the interpreter
|
|
|
|
+during compilation to obtain the output and then generates the trivial
|
|
|
|
+code to produce the output. (Yes, a clever student did this in a
|
|
|
|
+previous version of the course.)
|
|
|
|
|
|
The job of a compiler is to translate a program in one language into a
|
|
The job of a compiler is to translate a program in one language into a
|
|
program in another language so that the output program behaves the
|
|
program in another language so that the output program behaves the
|
|
-same way as the input program. This idea is depicted in the following
|
|
|
|
-diagram. Suppose we have two languages, $\mathcal{L}_1$ and
|
|
|
|
-$\mathcal{L}_2$, and an interpreter for each language. Suppose that
|
|
|
|
-the compiler translates program $P_1$ in language $\mathcal{L}_1$ into
|
|
|
|
-program $P_2$ in language $\mathcal{L}_2$. Then interpreting $P_1$
|
|
|
|
-and $P_2$ on their respective interpreters with input $i$ should yield
|
|
|
|
-the same output $o$.
|
|
|
|
|
|
+same way as the input program does according to its definitional
|
|
|
|
+interpreter. This idea is depicted in the following diagram. Suppose
|
|
|
|
+we have two languages, $\mathcal{L}_1$ and $\mathcal{L}_2$, and an
|
|
|
|
+interpreter for each language. Suppose that the compiler translates
|
|
|
|
+program $P_1$ in language $\mathcal{L}_1$ into program $P_2$ in
|
|
|
|
+language $\mathcal{L}_2$. Then interpreting $P_1$ and $P_2$ on their
|
|
|
|
+respective interpreters with input $i$ should yield the same output
|
|
|
|
+$o$.
|
|
\begin{equation} \label{eq:compile-correct}
|
|
\begin{equation} \label{eq:compile-correct}
|
|
\begin{tikzpicture}[baseline=(current bounding box.center)]
|
|
\begin{tikzpicture}[baseline=(current bounding box.center)]
|
|
\node (p1) at (0, 0) {$P_1$};
|
|
\node (p1) at (0, 0) {$P_1$};
|
|
@@ -845,15 +844,14 @@ the same output $o$.
|
|
\path[->] (p1) edge [left] node {interp-$\mathcal{L}_1$($i$)} (o);
|
|
\path[->] (p1) edge [left] node {interp-$\mathcal{L}_1$($i$)} (o);
|
|
\end{tikzpicture}
|
|
\end{tikzpicture}
|
|
\end{equation}
|
|
\end{equation}
|
|
-In the next section we see our first example of a compiler, which is
|
|
|
|
-another example of structural recursion.
|
|
|
|
|
|
+In the next section we see our first example of a compiler.
|
|
|
|
|
|
|
|
|
|
\section{Example Compiler: a Partial Evaluator}
|
|
\section{Example Compiler: a Partial Evaluator}
|
|
\label{sec:partial-evaluation}
|
|
\label{sec:partial-evaluation}
|
|
|
|
|
|
In this section we consider a compiler that translates $R_0$
|
|
In this section we consider a compiler that translates $R_0$
|
|
-programs into $R_0$ programs that are more efficient, that is,
|
|
|
|
|
|
+programs into $R_0$ programs that may be more efficient, that is,
|
|
this compiler is an optimizer. Our optimizer will accomplish this by
|
|
this compiler is an optimizer. Our optimizer will accomplish this by
|
|
trying to eagerly compute the parts of the program that do not depend
|
|
trying to eagerly compute the parts of the program that do not depend
|
|
on any inputs. For example, given the following program
|
|
on any inputs. For example, given the following program
|
|
@@ -867,14 +865,12 @@ our compiler will translate it into the program
|
|
|
|
|
|
Figure~\ref{fig:pe-arith} gives the code for a simple partial
|
|
Figure~\ref{fig:pe-arith} gives the code for a simple partial
|
|
evaluator for the $R_0$ language. The output of the partial evaluator
|
|
evaluator for the $R_0$ language. The output of the partial evaluator
|
|
-is an $R_0$ program, which we build up using a combination of
|
|
|
|
-quasiquotes and commas. (Though no quasiquote is necessary for
|
|
|
|
-integers.) In Figure~\ref{fig:pe-arith}, the normal structural
|
|
|
|
-recursion is captured in the main \texttt{pe-arith} function whereas
|
|
|
|
-the code for partially evaluating negation and addition is factored
|
|
|
|
-into two separate helper functions: \texttt{pe-neg} and
|
|
|
|
-\texttt{pe-add}. The input to these helper functions is the output of
|
|
|
|
-partially evaluating the children nodes.
|
|
|
|
|
|
+is an $R_0$ program. In Figure~\ref{fig:pe-arith}, the normal
|
|
|
|
+structural recursion is captured in the main \texttt{pe-arith}
|
|
|
|
+function whereas the code for partially evaluating negation and
|
|
|
|
+addition is factored into two separate helper functions:
|
|
|
|
+\texttt{pe-neg} and \texttt{pe-add}. The input to these helper
|
|
|
|
+functions is the output of partially evaluating the children nodes.
|
|
|
|
|
|
\begin{figure}[tbp]
|
|
\begin{figure}[tbp]
|
|
\begin{lstlisting}
|
|
\begin{lstlisting}
|