|
@@ -206,37 +206,42 @@ programs), so one cannot simply describe a language by listing all of
|
|
|
the programs in the language. Instead we write down a set of rules, a
|
|
|
\emph{grammar}, for building programs. We shall write our rules in a
|
|
|
variant of Backus-Naur Form (BNF)~\citep{Backus:1960aa,Knuth:1964aa}.
|
|
|
-As an example, we describe a small language, named $\itm{arith}$, of
|
|
|
+As an example, we describe a small language, named $R_0$, of
|
|
|
integers and arithmetic operations. The first rule says that any
|
|
|
integer is in the language:
|
|
|
\begin{equation}
|
|
|
-\itm{arith} ::= \Int \label{eq:arith-int}
|
|
|
+R_0 ::= \Int \label{eq:arith-int}
|
|
|
\end{equation}
|
|
|
Each rule has a left-hand-side and a right-hand-side. The way to read
|
|
|
a rule is that if you have all the program parts on the
|
|
|
right-hand-side, then you can create and AST node and categorize it
|
|
|
according to the left-hand-side. (We do not define $\Int$ because the
|
|
|
-reader already knows what an integer is.) A name such as $\itm{arith}$
|
|
|
+reader already knows what an integer is.) A name such as $R_0$
|
|
|
that is defined by the rules, is a \emph{non-terminal}.
|
|
|
+We make the
|
|
|
+simplifying design decision that all of the languages in this book
|
|
|
+only handle machine-representable integers (those representable with
|
|
|
+64-bits, i.e., the range $-2^{63}$ to $2^{63}$) which corresponds to
|
|
|
+the \texttt{fixnum} datatype in Racket.
|
|
|
|
|
|
-The second rule for the $\itm{arith}$ language is the \texttt{read}
|
|
|
+The second rule for the $R_0$ language is the \texttt{read}
|
|
|
operation that receives an input integer from the user of the program.
|
|
|
\begin{equation}
|
|
|
- \itm{arith} ::= (\key{read}) \label{eq:arith-read}
|
|
|
+ R_0 ::= (\key{read}) \label{eq:arith-read}
|
|
|
\end{equation}
|
|
|
|
|
|
-The third rule says that, given an $\itm{arith}$, you can build
|
|
|
+The third rule says that, given an $R_0$, you can build
|
|
|
another arith by negating it.
|
|
|
\begin{equation}
|
|
|
- \itm{arith} ::= (\key{-} \; \itm{arith}) \label{eq:arith-neg}
|
|
|
+ R_0 ::= (\key{-} \; R_0) \label{eq:arith-neg}
|
|
|
\end{equation}
|
|
|
-Symbols such as \key{-} that play an auxilliary role in the abstract
|
|
|
-syntax are called \emph{terminal} symbols.
|
|
|
+Symbols such as \key{-} in typewriter font are \emph{terminal} symbols
|
|
|
+and must appear literally in any program constructed with this rule.
|
|
|
|
|
|
-We can apply the rules to build ASTs in the $\itm{arith}$
|
|
|
+We can apply the rules to build ASTs in the $R_0$
|
|
|
language. For example, by rule \eqref{eq:arith-int}, \texttt{8} is an
|
|
|
-$\itm{arith}$, then by rule \eqref{eq:arith-neg}, the following AST is
|
|
|
-an $\itm{arith}$.
|
|
|
+$R_0$, then by rule \eqref{eq:arith-neg}, the following AST is
|
|
|
+an $R_0$.
|
|
|
\begin{center}
|
|
|
\begin{minipage}{0.25\textwidth}
|
|
|
\begin{lstlisting}
|
|
@@ -256,32 +261,42 @@ an $\itm{arith}$.
|
|
|
\end{minipage}
|
|
|
\end{center}
|
|
|
|
|
|
-The last rule for the $\itm{arith}$ language is for addition:
|
|
|
+The last rule for the $R_0$ language is for addition:
|
|
|
\begin{equation}
|
|
|
- \itm{arith} ::= (\key{+} \; \itm{arith} \; \itm{arith}) \label{eq:arith-add}
|
|
|
+ R_0 ::= (\key{+} \; R_0 \; R_0) \label{eq:arith-add}
|
|
|
\end{equation}
|
|
|
-Now we can see that the AST \eqref{eq:arith-prog} is in $\itm{arith}$.
|
|
|
-We know that \lstinline{(read)} is in $\itm{arith}$ by rule
|
|
|
+Now we can see that the AST \eqref{eq:arith-prog} is in $R_0$.
|
|
|
+We know that \lstinline{(read)} is in $R_0$ by rule
|
|
|
\eqref{eq:arith-read} and we have shown that \texttt{(- 8)} is in
|
|
|
-$\itm{arith}$, so we can apply rule \eqref{eq:arith-add} to show that
|
|
|
-\texttt{(+ (read) (- 8))} is in the $\itm{arith}$ language.
|
|
|
+$R_0$, so we can apply rule \eqref{eq:arith-add} to show that
|
|
|
+\texttt{(+ (read) (- 8))} is in the $R_0$ language.
|
|
|
|
|
|
If you have an AST for which the above four rules do not apply, then
|
|
|
-the AST is not in $\itm{arith}$. For example, the AST \texttt{(-
|
|
|
- (read) (+ 8))} is not in $\itm{arith}$ because there are no rules
|
|
|
+the AST is not in $R_0$. For example, the AST \texttt{(-
|
|
|
+ (read) (+ 8))} is not in $R_0$ because there are no rules
|
|
|
for \key{+} with only one argument, nor for \key{-} with two
|
|
|
arguments. Whenever we define a language with a grammar, we
|
|
|
implicitly mean for the language to be the smallest set of programs
|
|
|
that are justified by the rules. That is, the language only includes
|
|
|
those programs that the rules allow.
|
|
|
|
|
|
-It is common to have many rules with the same left-hand side, so the
|
|
|
-following vertical bar notation is used to gather several rules. We
|
|
|
-refer to each clause between a vertical bar as an ``alternative''.
|
|
|
+It is common to have many rules with the same left-hand side, so there
|
|
|
+is a vertical bar notation for gathering several rules, as shown in
|
|
|
+Figure~\ref{fig:r0-syntax}. Each clause between a vertical bar is
|
|
|
+called an ``alternative''.
|
|
|
+
|
|
|
+\begin{figure}[tbp]
|
|
|
+\fbox{
|
|
|
+\begin{minipage}{\textwidth}
|
|
|
\[
|
|
|
-\itm{arith} ::= \Int \mid ({\tt \key{read}}) \mid (\key{-} \; \itm{arith}) \mid
|
|
|
- (\key{+} \; \itm{arith} \; \itm{arith})
|
|
|
+R_0 ::= \Int \mid ({\tt \key{read}}) \mid (\key{-} \; R_0) \mid
|
|
|
+ (\key{+} \; R_0 \; R_0)
|
|
|
\]
|
|
|
+\end{minipage}
|
|
|
+}
|
|
|
+\caption{The syntax of the $R_0$ language.}
|
|
|
+\label{fig:r0-syntax}
|
|
|
+\end{figure}
|
|
|
|
|
|
\section{S-Expressions}
|
|
|
\label{sec:s-expr}
|
|
@@ -349,7 +364,7 @@ that may contain pattern-variables (preceded by a comma). The body
|
|
|
may contain any Racket code.
|
|
|
|
|
|
A \texttt{match} form may contain several clauses, as in the following
|
|
|
-function \texttt{leaf?} that recognizes when an $\itm{arith}$ node is
|
|
|
+function \texttt{leaf?} that recognizes when an $R_0$ node is
|
|
|
a leaf. The \texttt{match} proceeds through the clauses in order,
|
|
|
checking whether the pattern can match the input S-expression. The
|
|
|
body of the first clause that matches is executed. The output of
|
|
@@ -389,122 +404,16 @@ S-expression to see if it is a machine-representable integer.
|
|
|
\end{center}
|
|
|
|
|
|
|
|
|
-%% From this grammar, we have defined {\tt arith} by constraining its
|
|
|
-%% syntax. Effectively, we have defined {\tt arith} by first defining
|
|
|
-%% what a legal expression (or program) within the language is. To
|
|
|
-%% clarify further, we can think of {\tt arith} as a \textit{set} of
|
|
|
-%% expressions, where, under syntax constraints, \mbox{{\tt (+ 1 1)}} and
|
|
|
-%% {\tt -1} are inhabitants and {\tt (+ 3.2 3)} and {\tt (++ 2 2)} are
|
|
|
-%% not (see ~Figure\ref{fig:ast}).
|
|
|
-
|
|
|
-%% The relationship between a grammar and an AST is then similar to that
|
|
|
-%% of a set and an inhabitant. From this, every syntaxically valid
|
|
|
-%% expression, under the constraints of a grammar, can be represented by
|
|
|
-%% an abstract syntax tree. This is because {\tt arith} is essentially a
|
|
|
-%% specification of a Tree-like data-structure. In this case, tree nodes
|
|
|
-%% are the arithmetic operators {\tt +} and {\tt -}, and the leaves are
|
|
|
-%% integer constants. From this, we can represent any expression of {\tt
|
|
|
-%% arith} using a \textit{syntax expression} (s-exp).
|
|
|
-
|
|
|
-%% \begin{figure}[htbp]
|
|
|
-%% \centering
|
|
|
-%% \fbox{
|
|
|
-%% \begin{minipage}{0.85\textwidth}
|
|
|
-%% \[
|
|
|
-%% \begin{array}{lcl}
|
|
|
-%% exp &::=& sexp \mid (sexp*) \mid (unquote \; sexp) \\
|
|
|
-%% sexp &::=& Val \mid Var \mid (quote \; exp) \mid (quasiquote \; exp)
|
|
|
-%% \end{array}
|
|
|
-%% \]
|
|
|
-%% \end{minipage}
|
|
|
-%% }
|
|
|
-%% \caption{\textit{s-exp} syntax: $Val$ and $Var$ are shorthand for Value and Variable.}
|
|
|
-%% \label{fig:sexp-syntax}
|
|
|
-%% \end{figure}
|
|
|
-
|
|
|
-%% For our purposes, we will treat s-exps equivalent to \textit{possibly
|
|
|
-%% deeply-nested lists}. For the sake of brevity, the symbols $single$
|
|
|
-%% $quote$ ('), $backquote$ (`), and $comma$ (,) are reader sugar for
|
|
|
-%% {\tt quote}, {\tt quasiquote}, and {\tt unquote}. We provide several
|
|
|
-%% examples of s-exps and functions that return s-exps below. We use the
|
|
|
-%% {\tt >} symbol to represent interaction with a Racket REPL.
|
|
|
-%% \begin{verbatim}
|
|
|
-%% (define 1plus1 `(1 + 1))
|
|
|
-%% (define (1plusX x) `(1 + ,x))
|
|
|
-%% (define (XplusY x y) `(,x + ,y))
|
|
|
-
|
|
|
-%% > 1plus1
|
|
|
-%% '(1 + 1)
|
|
|
-%% > (1plusX 1)
|
|
|
-%% '(1 + 1)
|
|
|
-%% > (XplusY 1 1)
|
|
|
-%% '(1 + 1)
|
|
|
-%% > `,1plus1
|
|
|
-%% '(1 + 1)
|
|
|
-%% \end{verbatim}
|
|
|
-%% In any expression wrapped with {\tt quasiquote} ({\tt `}), sub-expressions
|
|
|
-%% wrapped with an {\tt unquote} expression are evaluated before the entire
|
|
|
-%% expression is returned wrapped in a {\tt quote} expression.
|
|
|
-
|
|
|
-% \marginpar{\scriptsize Introduce s-expressions, quote, and quasi-quote, and comma in
|
|
|
-% this section. Make sure to include examples of ASTs. The description
|
|
|
-% here of grammars is incomplete. It doesn't really say what grammars are or what they do, it
|
|
|
-% just shows an example. I would recommend reading my blog post: a crash course on
|
|
|
-% notation in PL theory, especially the sections on Definition by Rules
|
|
|
-% and Language Syntax and Grammars. -JGS}
|
|
|
-% \marginpar{\scriptsize The lambda calculus is more complex of an example that what we really
|
|
|
-% need at this point. I think we can make due with just integers and arithmetic. -JGS}
|
|
|
-% \marginpar{\scriptsize Regarding de-Bruijnizing as an example... that strikes me
|
|
|
-% as something that may be foreign to many readers. The examples in this
|
|
|
-% first chapter should try to be simple and hopefully connect with things
|
|
|
-% that the reader is already familiar with. -JGS}
|
|
|
-
|
|
|
-
|
|
|
-% \begin{enumerate}
|
|
|
-% \item Syntax transformation
|
|
|
-% \item Some Racket examples (factorial?)
|
|
|
-% \end{enumerate}
|
|
|
-
|
|
|
-%% For our purposes, our compiler will take a Scheme-like expression and
|
|
|
-%% transform it to X86\_64 Assembly. Along the way, we transform each
|
|
|
-%% input expression into a handful of \textit{intermediary languages}
|
|
|
-%% (IL). A key tool for transforming one language into another is
|
|
|
-%% \textit{pattern matching}.
|
|
|
-
|
|
|
-%% Racket provides a built-in pattern-matcher, {\tt match}, that we can
|
|
|
-%% use to perform operations on s-exps. As a preliminary example, we
|
|
|
-%% include a familiar definition of factorial, first without using match.
|
|
|
-%% \begin{verbatim}
|
|
|
-%% (define (! n)
|
|
|
-%% (if (zero? n) 1
|
|
|
-%% (* n (! (sub1 n)))))
|
|
|
-%% \end{verbatim}
|
|
|
-%% In this form of factorial, we are simply conditioning (viz. {\tt zero?})
|
|
|
-%% on the inputted natural number, {\tt n}. If we rewrite factorial using
|
|
|
-%% {\tt match}, we can match on the actual value of {\tt n}.
|
|
|
-%% \begin{verbatim}
|
|
|
-%% (define (! n)
|
|
|
-%% (match n
|
|
|
-%% (0 1)
|
|
|
-%% (n (* n (! (sub1 n))))))
|
|
|
-%% \end{verbatim}
|
|
|
-%% In this definition of factorial, the first {\tt match} line (viz. {\tt (0 1)})
|
|
|
-%% can be read as "if {\tt n} is 0, then return 1." The second line matches on an
|
|
|
-%% arbitrary variable, {\tt n}, and does not place any constraints on it. We could
|
|
|
-%% have also written this line as {\tt (else (* n (! (sub1 n))))}, where {\tt n}
|
|
|
-%% is scoped by {\tt match}. Of course, we can also use {\tt match} to pattern
|
|
|
-%% match on more complex expressions.
|
|
|
-
|
|
|
\section{Recursion}
|
|
|
\label{sec:recursion}
|
|
|
|
|
|
-Programs are inherently recursive in that an $\itm{arith}$ AST is made
|
|
|
-up of smaller $\itm{arith}$ ASTs. Thus, the natural way to process in
|
|
|
+Programs are inherently recursive in that an $R_0$ AST is made
|
|
|
+up of smaller $R_0$ ASTs. Thus, the natural way to process in
|
|
|
entire program is with a recursive function. As a first example of
|
|
|
such a function, we define \texttt{arith?} below, which takes an
|
|
|
arbitrary S-expression, {\tt sexp}, and determines whether or not {\tt
|
|
|
sexp} is in {\tt arith}. Note that each match clause corresponds to
|
|
|
-one grammar rule for $\itm{arith}$ and the body of each clause makes a
|
|
|
+one grammar rule for $R_0$ and the body of each clause makes a
|
|
|
recursive call for each child node. This pattern of recursive function
|
|
|
is so common that it has a name, \emph{structural recursion}. In
|
|
|
general, when a recursive function is defined using a sequence of
|
|
@@ -547,57 +456,8 @@ defined by structural recursion.
|
|
|
|
|
|
|
|
|
|
|
|
-
|
|
|
-%% Here, {\tt \#:when} puts constraints on the value of matched expressions.
|
|
|
-%% In this case, we make sure that every sub-expression in \textit{op} position
|
|
|
-%% is either {\tt +} or {\tt -}. Otherwise, we return an error, signaling a
|
|
|
-%% non-{\tt arith} expression. As we mentioned earlier, every expression
|
|
|
-%% wrapped in an {\tt unquote} is evaluated first. When used in a LHS {\tt match}
|
|
|
-%% sub-expression, these expressions evaluate to the actual value of the matched
|
|
|
-%% expression (i.e., {\tt arith-exp}). Thus, {\tt `(,e1 ,op ,e2)} and
|
|
|
-%% {\tt `(e1 op e2)} are not equivalent.
|
|
|
-
|
|
|
-
|
|
|
-% \begin{enumerate}
|
|
|
-% \item \textit{What is a base case?}
|
|
|
-% \item Using on a language (lambda calculus ->
|
|
|
-% \end{enumerate}
|
|
|
-%% Before getting into more complex {\tt match} examples, we first
|
|
|
-%% introduce the concept of \textit{structural recursion}, which is the
|
|
|
-%% general name for recurring over Tree-like or \textit{possibly
|
|
|
-%% deeply-nested list} structures. The key to performing structural
|
|
|
-%% recursion, which from now on we refer to simply as recursion, is to
|
|
|
-%% have some form of specification for the structure we are recurring
|
|
|
-%% on. Luckily, we are already familiar with one: a BNF or grammar.
|
|
|
-
|
|
|
-%% For example, let's take the grammar for $S_0$, which we include below.
|
|
|
-%% Writing a recursive program that takes an arbitrary expression of $S_0$
|
|
|
-%% should handle each expression in the grammar. An example program that
|
|
|
-%% we can write is an $interpreter$. To keep our interpreter simple, we
|
|
|
-%% ignore the {\tt read} operator.
|
|
|
-%% \begin{figure}[htbp]
|
|
|
-%% \centering
|
|
|
-%% \fbox{
|
|
|
-%% \begin{minipage}{0.85\textwidth}
|
|
|
-%% \[
|
|
|
-%% \begin{array}{lcl}
|
|
|
-%% \Op &::=& \key{+} \mid \key{-} \mid \key{*} \mid \key{read} \\
|
|
|
-%% \Exp &::=& \Int \mid (\Op \; \Exp^{*}) \mid \Var \mid \LET{\Var}{\Exp}{\Exp}
|
|
|
-%% \end{array}
|
|
|
-%% \]
|
|
|
-%% \end{minipage}
|
|
|
-%% }
|
|
|
-%% \caption{The syntax of the $S_0$ language. The abbreviation \Op{} is
|
|
|
-%% short for operator, \Exp{} is short for expression, \Int{} for integer,
|
|
|
-%% and \Var{} for variable.}
|
|
|
-%% %\label{fig:s0-syntax}
|
|
|
-%% \end{figure}
|
|
|
-%% \begin{verbatim}
|
|
|
-
|
|
|
-%% \end{verbatim}
|
|
|
-
|
|
|
\section{Interpreters}
|
|
|
-\label{sec:interp-arith}
|
|
|
+\label{sec:interp-R0}
|
|
|
|
|
|
The meaning, or semantics, of a program is typically defined in the
|
|
|
specification of the language. For example, the Scheme language is
|
|
@@ -606,49 +466,75 @@ defined in its reference manual~\citep{plt-tr}. In this book we use an
|
|
|
interpreter to define the meaning of each language that we consider,
|
|
|
following Reynold's advice in this
|
|
|
regard~\citep{reynolds72:_def_interp}. Here we will warm up by writing
|
|
|
-an interpreter for the $\itm{arith}$ language, which will also serve
|
|
|
-as a second example of structural recursion. The \texttt{interp-arith}
|
|
|
-function is defined in Figure~\ref{fig:interp-arith}. The body of the
|
|
|
+an interpreter for the $R_0$ language, which will also serve
|
|
|
+as a second example of structural recursion. The \texttt{interp-R0}
|
|
|
+function is defined in Figure~\ref{fig:interp-R0}. The body of the
|
|
|
function is a match on the input expression \texttt{e} and there is
|
|
|
-one clause per grammar rule for $\itm{arith}$. The clauses for
|
|
|
-internal AST nodes make recursive calls to \texttt{interp-arith} on
|
|
|
+one clause per grammar rule for $R_0$. The clauses for
|
|
|
+internal AST nodes make recursive calls to \texttt{interp-R0} on
|
|
|
each child node.
|
|
|
|
|
|
\begin{figure}[tbp]
|
|
|
\begin{lstlisting}
|
|
|
- (define (interp-arith e)
|
|
|
+ (define (interp-R0 e)
|
|
|
(match e
|
|
|
[(? fixnum?) e]
|
|
|
[`(read)
|
|
|
(define r (read))
|
|
|
(cond [(fixnum? r) r]
|
|
|
- [else (error 'interp-arith "expected an integer" r)])]
|
|
|
+ [else (error 'interp-R0 "expected an integer" r)])]
|
|
|
[`(- ,e)
|
|
|
- (fx- 0 (interp-arith e))]
|
|
|
+ (fx- 0 (interp-R0 e))]
|
|
|
[`(+ ,e1 ,e2)
|
|
|
- (fx+ (interp-arith e1) (interp-arith e2))]
|
|
|
+ (fx+ (interp-R0 e1) (interp-R0 e2))]
|
|
|
))
|
|
|
\end{lstlisting}
|
|
|
-\caption{Interpreter for the $\itm{arith}$ language.}
|
|
|
-\label{fig:interp-arith}
|
|
|
+\caption{Interpreter for the $R_0$ language.}
|
|
|
+\label{fig:interp-R0}
|
|
|
\end{figure}
|
|
|
|
|
|
-We make the simplifying design decision that the $\itm{arith}$
|
|
|
-language (and all of the languages in this book) only handle
|
|
|
-machine-representable integers, that is, the \texttt{fixnum} datatype
|
|
|
-in Racket. Thus, we implement the arithmetic operations using the
|
|
|
-appropriate fixnum operators.
|
|
|
+Let us consider the result of interpreting some example $R_0$
|
|
|
+programs. The following program simply adds two integers.
|
|
|
+\[
|
|
|
+\BINOP{+}{10}{32}
|
|
|
+\]
|
|
|
+The result is $42$, as you might expected.
|
|
|
+%
|
|
|
+The next example demonstrates that expressions may be nested within
|
|
|
+each other, in this case nesting several additions and negations.
|
|
|
+\[
|
|
|
+\BINOP{+}{10}{ \UNIOP{-}{ \BINOP{+}{12}{20} } }
|
|
|
+\]
|
|
|
+What is the result of the above program?
|
|
|
|
|
|
If we interpret the AST \eqref{eq:arith-prog} and give it the input
|
|
|
\texttt{50}
|
|
|
\begin{lstlisting}
|
|
|
- (interp-arith ast1.1)
|
|
|
+ (interp-R0 ast1.1)
|
|
|
\end{lstlisting}
|
|
|
-we get the answer to life, the universe, and everything
|
|
|
+we get the answer to life, the universe, and everything:
|
|
|
\begin{lstlisting}
|
|
|
42
|
|
|
\end{lstlisting}
|
|
|
|
|
|
+Moving on, the \key{read} operation prompts the user of the program
|
|
|
+for an integer. Given an input of $10$, the following program produces
|
|
|
+$42$.
|
|
|
+\[
|
|
|
+\BINOP{+}{(\key{read})}{32}
|
|
|
+\]
|
|
|
+We include the \key{read} operation in $R_1$ to demonstrate that order
|
|
|
+of evaluation can make a different.
|
|
|
+
|
|
|
+The behavior of the following program is somewhat subtle because
|
|
|
+Racket does not specify an evaluation order for arguments of an
|
|
|
+operator such as $-$.
|
|
|
+\[
|
|
|
+\BINOP{+}{\READ}{\UNIOP{-}{\READ}}
|
|
|
+\]
|
|
|
+Given the input $42$ then $10$, the above program can result in either
|
|
|
+$42$ or $-42$, depending on the whims of the Racket implementation.
|
|
|
+
|
|
|
The job of a compiler is to translate programs in one language into
|
|
|
programs in another language (typically but not always a language with
|
|
|
a lower level of abstraction) in such a way that each output program
|
|
@@ -677,8 +563,8 @@ is also be another example of structural recursion.
|
|
|
\section{Partial Evaluation}
|
|
|
\label{sec:partial-evaluation}
|
|
|
|
|
|
-In this section we consider a compiler that translates $\itm{arith}$
|
|
|
-programs into $\itm{arith}$ programs that are more efficient, that is,
|
|
|
+In this section we consider a compiler that translates $R_0$
|
|
|
+programs into $R_0$ programs that are more efficient, that is,
|
|
|
this compiler is an optimizer. Our optimizer will accomplish this by
|
|
|
trying to eagerly compute the parts of the program that do not depend
|
|
|
on any inputs. For example, given the following program
|
|
@@ -691,8 +577,8 @@ our compiler will translate it into the program
|
|
|
\end{lstlisting}
|
|
|
|
|
|
Figure~\ref{fig:pe-arith} gives the code for a simple partial
|
|
|
-evaluator for the $\itm{arith}$ language. The output of the partial
|
|
|
-evaluator is an $\itm{arith}$ program, which we build up using a
|
|
|
+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}
|
|
@@ -719,7 +605,7 @@ functions is the output of partially evaluating the children nodes.
|
|
|
[`(- ,e1) (pe-neg (pe-arith e1))]
|
|
|
[`(+ ,e1 ,e2) (pe-add (pe-arith e1) (pe-arith e2))]))
|
|
|
\end{lstlisting}
|
|
|
-\caption{A partial evaluator for the $\itm{arith}$ language.}
|
|
|
+\caption{A partial evaluator for the $R_0$ language.}
|
|
|
\label{fig:pe-arith}
|
|
|
\end{figure}
|
|
|
|
|
@@ -738,7 +624,7 @@ evaluator on several examples and tests the output program. The
|
|
|
\begin{lstlisting}
|
|
|
(define (test-pe pe p)
|
|
|
(assert "testing pe-arith"
|
|
|
- (equal? (interp-arith p) (interp-arith (pe-arith p)))))
|
|
|
+ (equal? (interp-R0 p) (interp-R0 (pe-arith p)))))
|
|
|
|
|
|
(test-pe `(+ (read) (- (+ 5 3))))
|
|
|
(test-pe `(+ 1 (+ (read) 1)))
|
|
@@ -775,75 +661,51 @@ e &::=& (\key{read}) \mid (\key{-} \;(\key{read})) \mid (\key{+} \; e \; e)\\
|
|
|
\label{ch:int-exp}
|
|
|
|
|
|
This chapter concerns the challenge of compiling a subset of Racket,
|
|
|
-which we name $S_0$, to x86-64 assembly code. The chapter begins with
|
|
|
-a description of the $S_0$ language (Section~\ref{sec:s0}) and then a
|
|
|
-description of x86-64 (Section~\ref{sec:x86-64}). The x86-64 assembly
|
|
|
-language is quite large, so we only discuss what is needed for
|
|
|
-compiling $S_0$. We will introduce more of x86-64 in later
|
|
|
-chapters. Once we have introduced $S_0$ and x86-64, we reflect on
|
|
|
-their differences and come up with a plan for a handful of steps that
|
|
|
-will take us from $S_0$ to x86-64 (Section~\ref{sec:plan-s0-x86}).
|
|
|
-The rest of the sections in this Chapter give detailed hints regarding
|
|
|
-what each step should do and how to organize your code
|
|
|
-(Sections~\ref{sec:uniquify-s0}, \ref{sec:flatten-s0},
|
|
|
-\ref{sec:select-s0} \ref{sec:assign-s0}, and \ref{sec:patch-s0}). We
|
|
|
-hope to give enough hints that the well-prepared reader can implement
|
|
|
-a compiler from $S_0$ to x86-64 while at the same time leaving room
|
|
|
-for some fun and creativity.
|
|
|
-
|
|
|
-\section{The $S_0$ Language}
|
|
|
+which we name $R_1$, to x86-64 assembly code~\citep{Matz:2013aa}. The
|
|
|
+chapter begins with a description of the $R_1$ language
|
|
|
+(Section~\ref{sec:s0}) and then a description of x86-64
|
|
|
+(Section~\ref{sec:x86-64}). The x86-64 assembly language is quite
|
|
|
+large, so we only discuss what is needed for compiling $R_1$. We will
|
|
|
+introduce more of x86-64 in later chapters. Once we have introduced
|
|
|
+$R_1$ and x86-64, we reflect on their differences and come up with a
|
|
|
+plan for a handful of steps that will take us from $R_1$ to x86-64
|
|
|
+(Section~\ref{sec:plan-s0-x86}). The rest of the sections in this
|
|
|
+Chapter give detailed hints regarding what each step should do and how
|
|
|
+to organize your code (Sections~\ref{sec:uniquify-s0} through
|
|
|
+\ref{sec:patch-s0}). We hope to give enough hints that the
|
|
|
+well-prepared reader can implement a compiler from $R_1$ to x86-64
|
|
|
+while at the same time leaving room for some fun and creativity.
|
|
|
+
|
|
|
+\section{The $R_1$ Language}
|
|
|
\label{sec:s0}
|
|
|
|
|
|
-The $S_0$ language includes integers, operations on integers
|
|
|
-(arithmetic and input), and variable definitions. The syntax of the
|
|
|
-$S_0$ language is defined by the grammar in
|
|
|
-Figure~\ref{fig:s0-syntax}. This language is rich enough to exhibit
|
|
|
-several compilation techniques but simple enough so that we can
|
|
|
-implement a compiler for it in two weeks of hard work. To give the
|
|
|
-reader a feeling for the scale of this first compiler, the instructor
|
|
|
-solution for the $S_0$ compiler consists of 6 recursive functions and
|
|
|
-a few small helper functions that together span 256 lines of code.
|
|
|
+The $R_1$ language extends the $R_0$ language
|
|
|
+(Figure~\ref{fig:r0-syntax}) with variable definitions. The syntax of
|
|
|
+the $R_1$ language is defined by the grammar in
|
|
|
+Figure~\ref{fig:r1-syntax}. This language is rich enough to exhibit
|
|
|
+several compilation techniques but simple enough so that the reader
|
|
|
+can implement a compiler for it in a couple weeks of part-time work.
|
|
|
+To give the reader a feeling for the scale of this first compiler, the
|
|
|
+instructor solution for the $R_1$ compiler consists of 6 recursive
|
|
|
+functions and a few small helper functions that together span 256
|
|
|
+lines of code.
|
|
|
|
|
|
\begin{figure}[btp]
|
|
|
\centering
|
|
|
\fbox{
|
|
|
-\begin{minipage}{0.85\textwidth}
|
|
|
+\begin{minipage}{\textwidth}
|
|
|
\[
|
|
|
-\begin{array}{lcl}
|
|
|
- \Op &::=& \key{+} \mid \key{-} \mid \key{*} \mid \key{read} \\
|
|
|
- \Exp &::=& \Int \mid (\Op \; \Exp^{*}) \mid \Var \mid \LET{\Var}{\Exp}{\Exp}
|
|
|
-\end{array}
|
|
|
+R_1 ::= \Int \mid ({\tt \key{read}}) \mid (\key{-} \; R_1) \mid
|
|
|
+ (\key{+} \; R_1 \; R_1) \mid \Var \mid \LET{\Var}{R_1}{R_1}
|
|
|
\]
|
|
|
\end{minipage}
|
|
|
}
|
|
|
-\caption{The syntax of the $S_0$ language. The abbreviation \Op{} is
|
|
|
- short for operator, \Exp{} is short for expression, \Int{} for integer,
|
|
|
- and \Var{} for variable.}
|
|
|
-\label{fig:s0-syntax}
|
|
|
+\caption{The syntax of the $R_1$ language.
|
|
|
+ The non-terminal \Var{} may be any Racket identifier.}
|
|
|
+\label{fig:r1-syntax}
|
|
|
\end{figure}
|
|
|
|
|
|
-The result of evaluating an expression is a value. For $S_0$, values
|
|
|
-are integers. To make it straightforward to map these integers onto
|
|
|
-x86-64 assembly~\citep{Matz:2013aa}, we restrict the integers to just
|
|
|
-those representable with 64-bits, the range $-2^{63}$ to $2^{63}$
|
|
|
-(``fixnums'' in Racket parlance).
|
|
|
-
|
|
|
-We start with some examples of $S_0$ programs, commenting on aspects
|
|
|
-of the language that will be relevant to compiling it. We start with
|
|
|
-one of the simplest $S_0$ programs; it adds two integers.
|
|
|
-\[
|
|
|
-\BINOP{+}{10}{32}
|
|
|
-\]
|
|
|
-The result is $42$, as you might expected.
|
|
|
-%
|
|
|
-The next example demonstrates that expressions may be nested within
|
|
|
-each other, in this case nesting several additions and negations.
|
|
|
-\[
|
|
|
-\BINOP{+}{10}{ \UNIOP{-}{ \BINOP{+}{12}{20} } }
|
|
|
-\]
|
|
|
-What is the result of the above program?
|
|
|
-
|
|
|
-The \key{let} construct defines a variable for used within it's body
|
|
|
+The \key{let} construct defines a variable for used within its body
|
|
|
and initializes the variable with the value of an expression. So the
|
|
|
following program initializes $x$ to $32$ and then evaluates the body
|
|
|
$\BINOP{+}{10}{x}$, producing $42$.
|
|
@@ -864,41 +726,65 @@ the same as your answer for this annotated version of the program.
|
|
|
\[
|
|
|
\LET{x_1}{32}{ \BINOP{+}{ \LET{x_2}{10}{x_2} }{ x_1 } }
|
|
|
\]
|
|
|
-
|
|
|
-Moving on, the \key{read} operation prompts the user of the program
|
|
|
-for an integer. Given an input of $10$, the following program produces
|
|
|
-$42$.
|
|
|
-\[
|
|
|
-\BINOP{+}{(\key{read})}{32}
|
|
|
-\]
|
|
|
-We include the \key{read} operation in $S_0$ to demonstrate that order
|
|
|
-of evaluation can make a different. Given the input $52$ then $10$,
|
|
|
-the following produces $42$ (and not $-42$).
|
|
|
-\[
|
|
|
-\LET{x}{\READ}{ \LET{y}{\READ}{ \BINOP{-}{x}{y} } }
|
|
|
-\]
|
|
|
The initializing expression is always evaluated before the body of the
|
|
|
-\key{let}, so in the above, the \key{read} for $x$ is performed before
|
|
|
-the \key{read} for $y$.
|
|
|
-%
|
|
|
-The behavior of the following program is somewhat subtle because
|
|
|
-Racket does not specify an evaluation order for arguments of an
|
|
|
-operator such as $-$.
|
|
|
+\key{let}, so in the following, the \key{read} for $x$ is performed
|
|
|
+before the \key{read} for $y$. Given the input $52$ then $10$, the
|
|
|
+following produces $42$ (and not $-42$).
|
|
|
\[
|
|
|
-\BINOP{-}{\READ}{\READ}
|
|
|
+\LET{x}{\READ}{ \LET{y}{\READ}{ \BINOP{-}{x}{y} } }
|
|
|
\]
|
|
|
-Given the input $42$ then $10$, the above program can result in either
|
|
|
-$42$ or $-42$, depending on the whims of the Racket implementation.
|
|
|
+
|
|
|
+Figure~\ref{fig:interp-R1} shows the interpreter for the $R_1$
|
|
|
+language. It extends the interpreter for $R_0$ with two new
|
|
|
+\key{match} clauses for variables and for \key{let}. For \key{let},
|
|
|
+we will need a way to communicate the initializing value of a variable
|
|
|
+to all the uses of a variable. To accomplish this, we maintain a
|
|
|
+mapping from variables to values, which is traditionally called an
|
|
|
+\emph{environment}. For simplicity, here we use an association list to
|
|
|
+represent the environment. The \key{interp-R1} function takes the
|
|
|
+current environment, \key{env}, as an extra parameter. When the
|
|
|
+interpreter encounters a variable, it finds the corresponding value
|
|
|
+using the \key{lookup} function (Appendix~\ref{appendix:utilities}).
|
|
|
+When the interpreter encounters a \key{let}, it evaluates the
|
|
|
+initializing expression, extends the environment with the result bound
|
|
|
+to the variable, then evaluates the body of the let.
|
|
|
+
|
|
|
+\begin{figure}[tbp]
|
|
|
+\begin{lstlisting}
|
|
|
+ (define (interp-R1 env e)
|
|
|
+ (match e
|
|
|
+ [(? symbol?) (lookup e env)]
|
|
|
+ [`(let ([,x ,e]) ,body)
|
|
|
+ (define v (interp-R1 env e))
|
|
|
+ (define new-env (cons (cons x v) env))
|
|
|
+ (interp-R1 new-env body)]
|
|
|
+ [(? fixnum?) e]
|
|
|
+ [`(read)
|
|
|
+ (define r (read))
|
|
|
+ (cond [(fixnum? r) r]
|
|
|
+ [else (error 'interp-R1 "expected an integer" r)])]
|
|
|
+ [`(- ,e)
|
|
|
+ (fx- 0 (interp-R1 env e))]
|
|
|
+ [`(+ ,e1 ,e2)
|
|
|
+ (fx+ (interp-R1 env e1) (interp-R1 env e2))]
|
|
|
+ ))
|
|
|
+\end{lstlisting}
|
|
|
+\caption{Interpreter for the $R_1$ language.}
|
|
|
+\label{fig:interp-R1}
|
|
|
+\end{figure}
|
|
|
+
|
|
|
+
|
|
|
|
|
|
The goal for this chapter is to implement a compiler that translates
|
|
|
-any program $P_1 \in S_0$ into a x86-64 assembly program $P_2$ such
|
|
|
+any program $P_1$ in $R_1$ into a x86-64 assembly program $P_2$ such
|
|
|
that the assembly program exhibits the same behavior on an x86
|
|
|
-computer as the $S_0$ program running in a Racket implementation.
|
|
|
+computer as the $R_1$ program running in a Racket implementation.
|
|
|
+That is, they both output the same integer $n$.
|
|
|
\[
|
|
|
\begin{tikzpicture}[baseline=(current bounding box.center)]
|
|
|
- \node (p1) at (0, 0) {$P_1 \in S_0$};
|
|
|
- \node (p2) at (4, 0) {$P_2 \in \text{x86-64}$};
|
|
|
- \node (o) at (4, -2) {$n \in \mathbb{Z}$};
|
|
|
+ \node (p1) at (0, 0) {$P_1$};
|
|
|
+ \node (p2) at (4, 0) {$P_2$};
|
|
|
+ \node (o) at (4, -2) {$n$};
|
|
|
|
|
|
\path[->] (p1) edge [above] node {\footnotesize compile} (p2);
|
|
|
\path[->] (p1) edge [left] node {\footnotesize run in Racket} (o);
|
|
@@ -906,7 +792,7 @@ computer as the $S_0$ program running in a Racket implementation.
|
|
|
\end{tikzpicture}
|
|
|
\]
|
|
|
In the next section we introduce enough of the x86-64 assembly
|
|
|
-language to compile $S_0$.
|
|
|
+language to compile $R_1$.
|
|
|
|
|
|
\section{The x86-64 Assembly Language}
|
|
|
\label{sec:x86-64}
|
|
@@ -973,6 +859,7 @@ specified by the label, which we shall use to implement
|
|
|
\label{fig:x86-a}
|
|
|
\end{figure}
|
|
|
|
|
|
+
|
|
|
\begin{wrapfigure}{r}{2.25in}
|
|
|
\begin{lstlisting}
|
|
|
.globl _main
|
|
@@ -1116,33 +1003,33 @@ communicated from one step of the compiler to the next.
|
|
|
\label{fig:x86-ast-a}
|
|
|
\end{figure}
|
|
|
|
|
|
-\section{Planning the trip from $S_0$ to x86-64}
|
|
|
+\section{Planning the trip from $R_1$ to x86-64}
|
|
|
\label{sec:plan-s0-x86}
|
|
|
|
|
|
To compile one language to another it helps to focus on the
|
|
|
differences between the two languages. It is these differences that
|
|
|
the compiler will need to bridge. What are the differences between
|
|
|
-$S_0$ and x86-64 assembly? Here we list some of the most important the
|
|
|
+$R_1$ and x86-64 assembly? Here we list some of the most important the
|
|
|
differences.
|
|
|
|
|
|
\begin{enumerate}
|
|
|
\item x86-64 arithmetic instructions typically take two arguments and
|
|
|
- update the second argument in place. In contrast, $S_0$ arithmetic
|
|
|
+ update the second argument in place. In contrast, $R_1$ arithmetic
|
|
|
operations only read their arguments and produce a new value.
|
|
|
|
|
|
-\item An argument to an $S_0$ operator can be any expression, whereas
|
|
|
+\item An argument to an $R_1$ operator can be any expression, whereas
|
|
|
x86-64 instructions restrict their arguments to integers, registers,
|
|
|
and memory locations.
|
|
|
|
|
|
-\item An $S_0$ program can have any number of variables whereas x86-64
|
|
|
+\item An $R_1$ program can have any number of variables whereas x86-64
|
|
|
has only 16 registers.
|
|
|
|
|
|
-\item Variables in $S_0$ can overshadow other variables with the same
|
|
|
+\item Variables in $R_1$ can overshadow other variables with the same
|
|
|
name. The registers and memory locations of x86-64 all have unique
|
|
|
names.
|
|
|
\end{enumerate}
|
|
|
|
|
|
-We ease the challenge of compiling from $S_0$ to x86 by breaking down
|
|
|
+We ease the challenge of compiling from $R_1$ to x86 by breaking down
|
|
|
the problem into several steps, dealing with the above differences one
|
|
|
at a time. The main question then becomes: in what order do we tackle
|
|
|
these differences? This is often one of the most challenging questions
|
|
@@ -1179,8 +1066,8 @@ ordering.
|
|
|
}
|
|
|
\end{tikzpicture}
|
|
|
\]
|
|
|
-We further simplify the translation from $S_0$ to x86 by identifying
|
|
|
-an intermediate language named $C_0$, roughly half-way between $S_0$
|
|
|
+We further simplify the translation from $R_1$ to x86 by identifying
|
|
|
+an intermediate language named $C_0$, roughly half-way between $R_1$
|
|
|
and x86, to provide a rest stop along the way. We name the language
|
|
|
$C_0$ because it is vaguely similar to the $C$
|
|
|
language~\citep{Kernighan:1988nx}. The differences \#4 and \#1,
|
|
@@ -1189,7 +1076,7 @@ steps, \key{uniquify} and \key{flatten}, which bring us to
|
|
|
$C_0$.
|
|
|
\[
|
|
|
\begin{tikzpicture}[baseline=(current bounding box.center)]
|
|
|
-\foreach \i/\p in {S_0/1,S_0/2,C_0/3}
|
|
|
+\foreach \i/\p in {R_1/1,R_1/2,C_0/3}
|
|
|
{
|
|
|
\node (\p) at (\p*3,0) {\large $\i$};
|
|
|
}
|
|
@@ -1205,9 +1092,9 @@ AST into an output AST. We refer to such a function as a \emph{pass}
|
|
|
because it makes a pass over the AST.
|
|
|
|
|
|
The syntax for $C_0$ is defined in Figure~\ref{fig:c0-syntax}. The
|
|
|
-$C_0$ language supports the same operators as $S_0$ but the arguments
|
|
|
+$C_0$ language supports the same operators as $R_1$ but the arguments
|
|
|
of operators are now restricted to just variables and integers. The
|
|
|
-\key{let} construct of $S_0$ is replaced by an assignment statement
|
|
|
+\key{let} construct of $R_1$ is replaced by an assignment statement
|
|
|
and there is a \key{return} construct to specify the return value of
|
|
|
the program. A program consists of a sequence of statements that
|
|
|
include at least one \key{return} statement.
|
|
@@ -1328,9 +1215,9 @@ implement the clauses for variables and for the \key{let} construct.
|
|
|
|
|
|
\begin{exercise}
|
|
|
\normalfont % I don't like the italics for exercises. -Jeremy
|
|
|
-Test your \key{uniquify} pass by creating three example $S_0$ programs
|
|
|
+Test your \key{uniquify} pass by creating three example $R_1$ programs
|
|
|
and checking whether the output programs produce the same result as
|
|
|
-the input programs. The $S_0$ programs should be designed to test the
|
|
|
+the input programs. The $R_1$ programs should be designed to test the
|
|
|
most interesting parts of the \key{uniquify} pass, that is, the
|
|
|
programs should include \key{let} constructs, variables, and variables
|
|
|
that overshadow eachother. The three programs should be in a
|
|
@@ -1350,7 +1237,7 @@ your \key{uniquify} pass on the example programs.
|
|
|
\section{Flatten Expressions}
|
|
|
\label{sec:flatten-s0}
|
|
|
|
|
|
-The \key{flatten} pass will transform $S_0$ programs into $C_0$
|
|
|
+The \key{flatten} pass will transform $R_1$ programs into $C_0$
|
|
|
programs. In particular, the purpose of the \key{flatten} pass is to
|
|
|
get rid of nested expressions, such as the $\UNIOP{-}{10}$ in the
|
|
|
following program.
|
|
@@ -1450,7 +1337,7 @@ procedure.
|
|
|
|
|
|
As discussed in Section~\ref{sec:plan-s0-x86}, the
|
|
|
\key{assign-homes} pass places all of the variables on the stack.
|
|
|
-Consider again the example $S_0$ program $\BINOP{+}{52}{ \UNIOP{-}{10} }$,
|
|
|
+Consider again the example $R_1$ program $\BINOP{+}{52}{ \UNIOP{-}{10} }$,
|
|
|
which after \key{select-instructions} looks like the following.
|
|
|
\[
|
|
|
\begin{array}{l}
|
|
@@ -1515,7 +1402,7 @@ argument must be a register.
|
|
|
\section{Print x86-64}
|
|
|
\label{sec:print-x86}
|
|
|
|
|
|
-The last step of the compiler from $S_0$ to x86-64 is to convert the
|
|
|
+The last step of the compiler from $R_1$ to x86-64 is to convert the
|
|
|
x86-64 AST (defined in Figure~\ref{fig:x86-ast-a}) to the string
|
|
|
representation (defined in Figure~\ref{fig:x86-a}). The Racket
|
|
|
\key{format} and \key{string-append} functions are useful in this
|
|
@@ -1538,7 +1425,7 @@ and then store in the $\itm{info}$ field of the \key{program}.
|
|
|
%% having interpreters for all the intermediate languages. Indeed, the
|
|
|
%% file \key{interp.rkt} in the supplemental code provides interpreters
|
|
|
%% for all the intermediate languages described in this book, starting
|
|
|
-%% with interpreters for $S_0$, $C_0$, and x86 (in abstract syntax).
|
|
|
+%% with interpreters for $R_1$, $C_0$, and x86 (in abstract syntax).
|
|
|
|
|
|
%% The file \key{run-tests.rkt} automates the process of running the
|
|
|
%% interpreters on the output programs of each pass and checking their
|
|
@@ -2051,7 +1938,7 @@ shown in Figure~\ref{fig:reg-alloc-passes}.
|
|
|
\chapter{Booleans, Type Checking, and Control Flow}
|
|
|
\label{ch:bool-types}
|
|
|
|
|
|
-\section{The $S_1$ Language}
|
|
|
+\section{The $R_2$ Language}
|
|
|
|
|
|
\begin{figure}[htbp]
|
|
|
\centering
|
|
@@ -2066,12 +1953,12 @@ shown in Figure~\ref{fig:reg-alloc-passes}.
|
|
|
\]
|
|
|
\end{minipage}
|
|
|
}
|
|
|
-\caption{The $S_1$ language, an extension of $S_0$
|
|
|
+\caption{The $R_2$ language, an extension of $R_1$
|
|
|
(Figure~\ref{fig:s0-syntax}).}
|
|
|
-\label{fig:s1-syntax}
|
|
|
+\label{fig:s2-syntax}
|
|
|
\end{figure}
|
|
|
|
|
|
-\section{Type Checking $S_1$ Programs}
|
|
|
+\section{Type Checking $R_2$ Programs}
|
|
|
|
|
|
\marginpar{\scriptsize Type checking is a difficult thing to cover, I think, without having 522 as a prerequisite for this course. -- Cam}
|
|
|
% T ::= Integer | Boolean
|
|
@@ -2137,7 +2024,7 @@ Overall, the statement $\Gamma \vdash e : T$ is an example of what is
|
|
|
called a \emph{judgment}. In particular, this judgment says, ``In
|
|
|
environment $\Gamma$, expression $e$ has type $T$.''
|
|
|
Figure~\ref{fig:S1-type-system} shows the type checking rules for
|
|
|
-$S_1$.
|
|
|
+$R_2$.
|
|
|
|
|
|
\begin{figure}
|
|
|
\begin{gather*}
|
|
@@ -2162,7 +2049,7 @@ $S_1$.
|
|
|
\Gamma \vdash e_3 : T}
|
|
|
{\Gamma \vdash \IF{e_1}{e_2}{e_3} : T}
|
|
|
\end{gather*}
|
|
|
-\caption{Type System for $S_1$.}
|
|
|
+\caption{Type System for $R_2$.}
|
|
|
\label{fig:S1-type-system}
|
|
|
\end{figure}
|
|
|
|
|
@@ -2253,7 +2140,7 @@ $S_1$.
|
|
|
|
|
|
We provide several interpreters in the \key{interp.rkt} file. The
|
|
|
\key{interp-scheme} function takes an AST in one of the Racket-like
|
|
|
-languages considered in this book ($S_0, S_1, \ldots$) and interprets
|
|
|
+languages considered in this book ($R_1, R_2, \ldots$) and interprets
|
|
|
the program, returning the result value. The \key{interp-C} function
|
|
|
interprets an AST for a program in one of the C-like languages ($C_0,
|
|
|
C_1, \ldots$), and the \key{interp-x86} function interprets an AST for
|
|
@@ -2271,6 +2158,8 @@ Boolean \key{bool} is false.
|
|
|
(define (assert msg bool) ...)
|
|
|
\end{lstlisting}
|
|
|
|
|
|
+The \key{lookup} function ...
|
|
|
+
|
|
|
The \key{interp-tests} function takes a compiler name (a string) a
|
|
|
description of the passes a test family name (a string), and a list of
|
|
|
test numbers, and runs the compiler passes and the interpreters to
|