|
@@ -532,7 +532,7 @@ node it is the child of). If a node has no children, it is a
|
|
|
%% S-expression.
|
|
|
%% \begin{lstlisting}
|
|
|
%% (define ast1.4 `(- 8))
|
|
|
-%% (define ast1.1 `(+ (read) ,ast1.4))
|
|
|
+%% (define ast1_1 `(+ (read) ,ast1.4))
|
|
|
%% \end{lstlisting}
|
|
|
%% In general, the Racket expression that follows the comma (splice)
|
|
|
%% can be any expression that produces an S-expression.
|
|
@@ -571,7 +571,7 @@ operator has zero children:
|
|
|
\end{lstlisting}
|
|
|
whereas the addition operator has two children:
|
|
|
\begin{lstlisting}
|
|
|
-(define ast1.1 (Prim '+ (list rd neg-eight)))
|
|
|
+(define ast1_1 (Prim '+ (list rd neg-eight)))
|
|
|
\end{lstlisting}
|
|
|
|
|
|
We have made a design choice regarding the \code{Prim} structure.
|
|
@@ -906,7 +906,7 @@ Consider the following example. \index{subject}{match} \index{subject}{pattern m
|
|
|
\begin{minipage}{0.5\textwidth}
|
|
|
{\if\edition\racketEd\color{olive}
|
|
|
\begin{lstlisting}
|
|
|
-(match ast1.1
|
|
|
+(match ast1_1
|
|
|
[(Prim op (list child1 child2))
|
|
|
(print op)])
|
|
|
\end{lstlisting}
|
|
@@ -997,6 +997,8 @@ def leaf(arith):
|
|
|
return False
|
|
|
case BinOp(e1, Add(), e2):
|
|
|
return False
|
|
|
+ case _:
|
|
|
+ return False
|
|
|
|
|
|
print(leaf(Call(Name('input_int'), [])))
|
|
|
print(leaf(UnaryOp(USub(), eight)))
|
|
@@ -1028,6 +1030,11 @@ print(leaf(Constant(8)))
|
|
|
|
|
|
|
|
|
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
|
|
|
True
|
|
|
False
|
|
@@ -1056,11 +1063,12 @@ of your choice (e.g. \code{e1} and \code{e2}).
|
|
|
\label{sec:recursion}
|
|
|
\index{subject}{recursive function}
|
|
|
|
|
|
-Programs are inherently recursive. For example, an \LangInt{} expression is
|
|
|
-often made of smaller expressions. Thus, the natural way to process an
|
|
|
-entire program is with a recursive function. As a first example of
|
|
|
-such a recursive function, we define \texttt{exp} below, which takes
|
|
|
-an arbitrary value and determines whether or not it is an \LangInt{}
|
|
|
+Programs are inherently recursive. For example, an \LangInt{}
|
|
|
+expression is often made of smaller expressions. Thus, the natural way
|
|
|
+to process an entire program is with a recursive function. As a first
|
|
|
+example of such a recursive function, we define the function
|
|
|
+\code{exp} in Figure~\ref{fig:exp-predicate}, which takes an
|
|
|
+arbitrary value and determines whether or not it is an \LangInt{}
|
|
|
expression.
|
|
|
%
|
|
|
We say that a function is defined by \emph{structural recursion} when
|
|
@@ -1070,12 +1078,16 @@ makes a recursive call on each
|
|
|
child node.\footnote{This principle of structuring code according to
|
|
|
the data definition is advocated in the book \emph{How to Design
|
|
|
Programs} \url{https://htdp.org/2020-8-1/Book/index.html}.}.
|
|
|
-Below we also define a second function, named \code{Rint}, that
|
|
|
-determines whether an AST is an \LangInt{} program. In general we can
|
|
|
+\python{We define a second function, named \code{stmt}, that recognizes
|
|
|
+whether a value is a \LangInt{} statement.}
|
|
|
+\python{Finally, }
|
|
|
+Figure~\ref{fig:exp-predicate} \racket{also} defines \code{Rint}, which
|
|
|
+determines whether an AST is a program in \LangInt{}. In general we can
|
|
|
expect to write one recursive function to handle each non-terminal in
|
|
|
a grammar.\index{subject}{structural recursion}
|
|
|
-%
|
|
|
-\begin{center}
|
|
|
+
|
|
|
+\begin{figure}[tp]
|
|
|
+{\if\edition\racketEd\color{olive}
|
|
|
\begin{minipage}{0.7\textwidth}
|
|
|
\begin{lstlisting}
|
|
|
(define (exp ast)
|
|
@@ -1092,7 +1104,7 @@ a grammar.\index{subject}{structural recursion}
|
|
|
[(Program '() e) (exp e)]
|
|
|
[else #f]))
|
|
|
|
|
|
-(Rint (Program '() ast1.1)
|
|
|
+(Rint (Program '() ast1_1)
|
|
|
(Rint (Program '()
|
|
|
(Prim '- (list (Prim 'read '())
|
|
|
(Prim '+ (list (Num 8)))))))
|
|
@@ -1118,7 +1130,83 @@ a grammar.\index{subject}{structural recursion}
|
|
|
#f
|
|
|
\end{lstlisting}
|
|
|
\end{minipage}
|
|
|
-\end{center}
|
|
|
+\fi}
|
|
|
+{\if\edition\pythonEd\color{purple}
|
|
|
+\begin{minipage}{0.7\textwidth}
|
|
|
+\begin{lstlisting}
|
|
|
+def exp(e):
|
|
|
+ match e:
|
|
|
+ case Constant(n):
|
|
|
+ return True
|
|
|
+ case Call(Name('input_int'), []):
|
|
|
+ return True
|
|
|
+ case UnaryOp(USub(), e1):
|
|
|
+ return exp(e1)
|
|
|
+ case BinOp(e1, Add(), e2):
|
|
|
+ return exp(e1) and exp(e2)
|
|
|
+ case _:
|
|
|
+ return False
|
|
|
+
|
|
|
+def stmt(s):
|
|
|
+ match s:
|
|
|
+ case Call(Name('print'), [e]):
|
|
|
+ return exp(e)
|
|
|
+ case Expr(e):
|
|
|
+ return exp(e)
|
|
|
+ case _:
|
|
|
+ return False
|
|
|
+
|
|
|
+def Rint(p):
|
|
|
+ match p:
|
|
|
+ case Module(body):
|
|
|
+ return all([stmt(s) for s in body])
|
|
|
+ case _:
|
|
|
+ return False
|
|
|
+
|
|
|
+print(Rint(Module([Expr(ast1_1)])))
|
|
|
+print(Rint(Module([Expr(BinOp(read, Sub(),
|
|
|
+ UnaryOp(Add(), Constant(8))))])))
|
|
|
+\end{lstlisting}
|
|
|
+\end{minipage}
|
|
|
+\vrule
|
|
|
+\begin{minipage}{0.25\textwidth}
|
|
|
+\begin{lstlisting}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ True
|
|
|
+ False
|
|
|
+\end{lstlisting}
|
|
|
+\end{minipage}
|
|
|
+\fi}
|
|
|
+
|
|
|
+\caption{Example of recursive functions for \LangInt{}. These functions
|
|
|
+ recognize whether an AST is in \LangInt{}.}
|
|
|
+\label{fig:exp-predicate}
|
|
|
+\end{figure}
|
|
|
|
|
|
|
|
|
%% You may be tempted to merge the two functions into one, like this:
|
|
@@ -1147,71 +1235,131 @@ a grammar.\index{subject}{structural recursion}
|
|
|
|
|
|
|
|
|
\section{Interpreters}
|
|
|
-\label{sec:interp-Rint}
|
|
|
+\label{sec:interp_Rint}
|
|
|
\index{subject}{interpreter}
|
|
|
|
|
|
-In general, the intended behavior of a program is defined by the
|
|
|
-specification of the language. For example, the Scheme language is
|
|
|
-defined in the report by \cite{SPERBER:2009aa}. The Racket language is
|
|
|
-defined in its reference manual~\citep{plt-tr}. In this book we use
|
|
|
-interpreters to specify each language that we consider. An interpreter
|
|
|
-that is designated as the definition of a language is called a
|
|
|
+The behavior of a program is defined by the specification of the
|
|
|
+programming language.
|
|
|
+%
|
|
|
+\racket{For example, the Scheme language is defined in the report by
|
|
|
+ \cite{SPERBER:2009aa}. The Racket language is defined in its
|
|
|
+ reference manual~\citep{plt-tr}.}
|
|
|
+%
|
|
|
+\python{For example, the Python language is defined in the Python
|
|
|
+ Language Reference~\citep{PSF21:python_ref} and the CPython interpreter~\citep{PSF21:cpython}.}
|
|
|
+%
|
|
|
+In this book we use interpreters
|
|
|
+to specify each language that we consider. An interpreter that is
|
|
|
+designated as the definition of a language is called a
|
|
|
\emph{definitional interpreter}~\citep{reynolds72:_def_interp}.
|
|
|
-\index{subject}{definitional interpreter} We warm up by creating a definitional
|
|
|
-interpreter for the \LangInt{} language, which serves as a second example
|
|
|
-of structural recursion. The \texttt{interp-Rint} function is defined in
|
|
|
-Figure~\ref{fig:interp-Rint}. The body of the function is a match on the
|
|
|
-input program followed by a call to the \lstinline{interp-exp} helper
|
|
|
-function, which in turn has one match clause per grammar rule for
|
|
|
-\LangInt{} expressions.
|
|
|
+\index{subject}{definitional interpreter} We warm up by creating a
|
|
|
+definitional interpreter for the \LangInt{} language, which serves as
|
|
|
+a second example of structural recursion. The \texttt{interp\_Rint}
|
|
|
+function is defined in Figure~\ref{fig:interp_Rint}. The body of the
|
|
|
+function is a match on the input program followed by a call to the
|
|
|
+\lstinline{interp_exp} helper function, which in turn has one match
|
|
|
+clause per grammar rule for \LangInt{} expressions.
|
|
|
|
|
|
\begin{figure}[tp]
|
|
|
+{\if\edition\racketEd\color{olive}
|
|
|
\begin{lstlisting}
|
|
|
-(define (interp-exp e)
|
|
|
+(define (interp_exp e)
|
|
|
(match e
|
|
|
[(Int n) n]
|
|
|
[(Prim 'read '())
|
|
|
(define r (read))
|
|
|
(cond [(fixnum? r) r]
|
|
|
- [else (error 'interp-exp "read expected an integer" r)])]
|
|
|
+ [else (error 'interp_exp "read expected an integer" r)])]
|
|
|
[(Prim '- (list e))
|
|
|
- (define v (interp-exp e))
|
|
|
+ (define v (interp_exp e))
|
|
|
(fx- 0 v)]
|
|
|
[(Prim '+ (list e1 e2))
|
|
|
- (define v1 (interp-exp e1))
|
|
|
- (define v2 (interp-exp e2))
|
|
|
+ (define v1 (interp_exp e1))
|
|
|
+ (define v2 (interp_exp e2))
|
|
|
(fx+ v1 v2)]))
|
|
|
|
|
|
-(define (interp-Rint p)
|
|
|
+(define (interp_Rint p)
|
|
|
(match p
|
|
|
- [(Program '() e) (interp-exp e)]))
|
|
|
+ [(Program '() e) (interp_exp e)]))
|
|
|
+\end{lstlisting}
|
|
|
+\fi}
|
|
|
+{\if\edition\pythonEd\color{purple}
|
|
|
+\begin{lstlisting}
|
|
|
+def interp_exp(e):
|
|
|
+ match e:
|
|
|
+ case BinOp(left, Add(), right):
|
|
|
+ l = interp_exp(left)
|
|
|
+ r = interp_exp(right)
|
|
|
+ return l + r
|
|
|
+ case UnaryOp(USub(), v):
|
|
|
+ return - interp_exp(v)
|
|
|
+ case Constant(value):
|
|
|
+ return value
|
|
|
+ case Call(Name('input_int'), []):
|
|
|
+ return int(input())
|
|
|
+
|
|
|
+def interp_stmt(s):
|
|
|
+ match s:
|
|
|
+ case Expr(Call(Name('print'), [arg])):
|
|
|
+ print(interp_exp(arg))
|
|
|
+ case Expr(value):
|
|
|
+ interp_exp(value)
|
|
|
+
|
|
|
+def interp_Pint(p):
|
|
|
+ match p:
|
|
|
+ case Module(body):
|
|
|
+ for s in body:
|
|
|
+ interp_stmt(s)
|
|
|
\end{lstlisting}
|
|
|
+\fi}
|
|
|
\caption{Interpreter for the \LangInt{} language.}
|
|
|
-\label{fig:interp-Rint}
|
|
|
+\label{fig:interp_Rint}
|
|
|
\end{figure}
|
|
|
|
|
|
Let us consider the result of interpreting a few \LangInt{} programs. The
|
|
|
following program adds two integers.
|
|
|
+{\if\edition\racketEd\color{olive}
|
|
|
\begin{lstlisting}
|
|
|
(+ 10 32)
|
|
|
\end{lstlisting}
|
|
|
+\fi}
|
|
|
+{\if\edition\pythonEd\color{purple}
|
|
|
+\begin{lstlisting}
|
|
|
+print(10 + 32)
|
|
|
+\end{lstlisting}
|
|
|
+\fi}
|
|
|
The result is \key{42}, the answer to life, the universe, and
|
|
|
everything: \code{42}!\footnote{\emph{The Hitchhiker's Guide to the
|
|
|
Galaxy} by Douglas Adams.}.
|
|
|
%
|
|
|
We wrote the above program in concrete syntax whereas the parsed
|
|
|
abstract syntax is:
|
|
|
+{\if\edition\racketEd\color{olive}
|
|
|
\begin{lstlisting}
|
|
|
(Program '() (Prim '+ (list (Int 10) (Int 32))))
|
|
|
\end{lstlisting}
|
|
|
-
|
|
|
+\fi}
|
|
|
+{\if\edition\pythonEd\color{purple}
|
|
|
+\begin{lstlisting}
|
|
|
+Module([Expr(Call(Name('print'), [BinOp(Constant(10), Add(), Constant(32))]))])
|
|
|
+\end{lstlisting}
|
|
|
+\fi}
|
|
|
The next example demonstrates that expressions may be nested within
|
|
|
each other, in this case nesting several additions and negations.
|
|
|
+{\if\edition\racketEd\color{olive}
|
|
|
\begin{lstlisting}
|
|
|
(+ 10 (- (+ 12 20)))
|
|
|
\end{lstlisting}
|
|
|
+\fi}
|
|
|
+{\if\edition\pythonEd\color{purple}
|
|
|
+\begin{lstlisting}
|
|
|
+print(10 + -(12 + 20))
|
|
|
+\end{lstlisting}
|
|
|
+\fi}
|
|
|
+
|
|
|
What is the result of the above program?
|
|
|
|
|
|
+{\if\edition\racketEd\color{olive}
|
|
|
As mentioned previously, the \LangInt{} language does not support
|
|
|
arbitrarily-large integers, but only $63$-bit integers, so we
|
|
|
interpret the arithmetic operations of \LangInt{} using fixnum arithmetic
|
|
@@ -1241,26 +1389,36 @@ it is required to report that an error occurred. To signal an error,
|
|
|
exit with a return code of \code{255}. The interpreters in chapters
|
|
|
\ref{ch:Rdyn} and \ref{ch:Rgrad} use
|
|
|
\code{trapped-error}.
|
|
|
+\fi}
|
|
|
+
|
|
|
+% TODO: how to deal with too-large integers in the Python interpreter?
|
|
|
|
|
|
%% This convention applies to the languages defined in this
|
|
|
%% book, as a way to simplify the student's task of implementing them,
|
|
|
%% but this convention is not applicable to all programming languages.
|
|
|
%%
|
|
|
|
|
|
-Moving on to the last feature of the \LangInt{} 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
|
|
|
+Moving on to the last feature of the \LangInt{} language, the
|
|
|
+\READOP{} operation prompts the user of the program for an integer.
|
|
|
+Recall that program \eqref{eq:arith-prog} requests an integer input
|
|
|
+and then subtracts \code{8}. So if we run
|
|
|
+{\if\edition\racketEd\color{olive}
|
|
|
+\begin{lstlisting}
|
|
|
+(interp_Rint (Program '() ast1_1))
|
|
|
+\end{lstlisting}
|
|
|
+\fi}
|
|
|
+{\if\edition\pythonEd\color{purple}
|
|
|
\begin{lstlisting}
|
|
|
-(interp-Rint (Program '() ast1.1))
|
|
|
+interp_Pint(Module([Expr(Call(Name('print'), [ast1_1]))]))
|
|
|
\end{lstlisting}
|
|
|
-and if the input is \code{50}, the result is \code{42}.
|
|
|
+\fi}
|
|
|
+\noindent and if the input is \code{50}, the result is \code{42}.
|
|
|
|
|
|
-We include the \key{read} operation in \LangInt{} so a clever student
|
|
|
+We include the \READOP{} operation in \LangInt{} so a clever student
|
|
|
cannot implement a compiler for \LangInt{} 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 the
|
|
|
-first instance of this course.)
|
|
|
+code to produce the output.\footnote{Yes, a clever student did this in the
|
|
|
+first instance of this course!}
|
|
|
|
|
|
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
|
|
@@ -1356,7 +1514,7 @@ Appendix~\ref{appendix:utilities}.\\
|
|
|
\begin{lstlisting}
|
|
|
(define (test-pe p)
|
|
|
(assert "testing pe-Rint"
|
|
|
- (equal? (interp-Rint p) (interp-Rint (pe-Rint p)))))
|
|
|
+ (equal? (interp_Rint p) (interp_Rint (pe-Rint p)))))
|
|
|
|
|
|
(test-pe (parse-program `(program () (+ 10 (- (+ 5 3))))))
|
|
|
(test-pe (parse-program `(program () (+ 1 (+ 3 1)))))
|
|
@@ -9744,7 +9902,7 @@ Next consider the match case for \code{vector-ref}. The
|
|
|
\code{check-tag} auxiliary function (Figure~\ref{fig:interp-Rdyn-aux})
|
|
|
is used to ensure that the first argument is a vector and the second
|
|
|
is an integer. If they are not, a \code{trapped-error} is raised.
|
|
|
-Recall from Section~\ref{sec:interp-Rint} that when a definition
|
|
|
+Recall from Section~\ref{sec:interp_Rint} that when a definition
|
|
|
interpreter raises a \code{trapped-error} error, the compiled code
|
|
|
must also signal an error by exiting with return code \code{255}. A
|
|
|
\code{trapped-error} is also raised if the index is not less than
|
|
@@ -13750,7 +13908,7 @@ for the compilation of \LangPoly{}.
|
|
|
\index{subject}{interpreter}
|
|
|
|
|
|
We provide interpreters for each of the source languages \LangInt{},
|
|
|
-\LangVar{}, $\ldots$ in the files \code{interp-Rint.rkt},
|
|
|
+\LangVar{}, $\ldots$ in the files \code{interp\_Rint.rkt},
|
|
|
\code{interp-Rvar.rkt}, etc. The interpreters for the intermediate
|
|
|
languages \LangCVar{} and \LangCIf{} are in \code{interp-Cvar.rkt} and
|
|
|
\code{interp-C1.rkt}. The interpreters for \LangCVec{}, \LangCFun{}, pseudo-x86,
|