Limits of
Computation
7 – A universal program (Self-interpreter) Bernhard Reus
1
So far…
• … we have learned the WHILE-language…
• …that we have chosen to represent our notion of computation (to write “effective procedures”).
• We learned how to represent programs-as- data…
•
…so now we can write interpreters.
2
• Eating your own tail?
We look at a special form THIS TIME of interpreter:
• self-interpreter
• WHILE-interpreter in WHILE
•
• a very important concept for computability theory (used later)
WHILE
and first an WH1LE-interpreter in WHILE
WHILE
http://www.strangedangers.com/content/item/158424.html
3
Compare to TMs
• Turing defined a “universal Turing machine” U
• that can take TM program description D and a word W as input on its tape
• and simulate the run of TM
D with given input W
• so U is aTM program which is an
interpreter for TM programs
a self-interpreter in TM
let’s use WHILE instead!
4
Use of self-interpreter?
• in practice:
“cheap” way to extend your programming
language with extra features (interpret them in smaller language)
• in computability theory:
we will explain this soon. Stay tuned!
5
First consider WH1LE
• …is like WHILE…
• …but programs can only have one variable. • simpler “memory management”
• Can we solve fewer problems in language WH1LE than in WHILE?
6
Interpret WH1LE in WHILE
• Since it is simpler, we first look at an
interpreter of WH1LE written in WHILE.
• Then we generalise to arbitrarily many variables and obtain a WHILE-interpreter in WHILE.
7
Tree Traversal of ASTs
(with intermediate
results)
NO RECURSION !
initialise tree and value stack to be empty push tree (to be traversed) on tree stack while tree stack not empty
pop a tree t from tree stack
if t is just an opcode o with arity n // a marker then pop n results r1,…,rn from value stack
r := o(r1,…,rn) // compute intermediate result
push r on value stack else // t proper tree
order of arguments important
if t’s opcode has n arguments
then push t’s opcode on tree stack // (as marker!)
push n subtrees of t on tree stack else // o is leaf
compute result and push on value stack
8
Recipe
read PD {
P := hd PD ;
D := hd tl PD;
B := hd tl P;
CSt := B;
DSt := nil;
(* input is a list [P,D] *)
(* P = [X,B,X] *)
(* D input data *)
(* B is program block *)
(* CSt is code stack *)
(* initially commands of B *)
(* DSt is computation stack for *)
(* intermediate results *)
(* D is initial value of variable *)
val := D;
state := [ CSt, DSt, val ];
(* wrap up state for STEP macro *)
while CSt { (* main loop for interpretation *)
state :=
CSt := hd state
}
val := hd tl tl state
}
write val
(* get command stack *)
(* get final value of variable *)
(* return value of the one variable *)
CSt is code stack (code in list format),
DSt is Stack of intermediate values, val contains value D of the one variable
9
.1.WH1LELF-INTER akeofreadetofrewrit
e form
at describ value stack odestack,fjustone hangeaccocharuleis s:
. . . . value to valnew. Below we will give th1o0se rules in diagrammatic form, showing
7.1. WH1LE CHAPTER 7. SELF-INTERPRETER CHAPTER 7. SE
.
d val
1 Step Macro
sakeofreadability, letuspresentthebehaviourofSTEPasasetofrewriterulesof
the form 1
ability, letuspresenttheb
that describe how the data structures of the traversal DSt, the value stack, CSt the
code stack, and val the “store” for execution that consists of just one variable,
f STEP as a s performs tree traversal based on CSt, DSt, and val.
[CSt,DSt,val] ) [CSt ,DSt ,val ]
change according to their current values. Diagrammatically, such a rule is depicted
as: ehowthedatastructuresofthetraversalSt,the
ne
e
w
n
hav
[CSt,DSt,val] ) [CSt ,DSt ,val ] new new new
i
ew
n
our
o
ew
val valnew anthe”forenonsistso
=) rdihentvalueracally,su
CSt
. ng t.o t
.
.
e “stor
DSt
.
. ircu.rre
Note that stacks grow upwards here, pushing and popping happens from the top.
Such a rule states that if the current code stack is CSt, the current value stack is
DSt, and the value of our one variable is val, then the STEP macro has to change ..
..
(or update) the code stack to CSt vanld the value stack to CSt and the variable new new
. xecutio
CStnew
.
. s.D.iag
. D that c
DStnew
.
. mm.ati
more clearly how the stacks (and the value of the variable) change according to
CSt DSt
CSt DSt
their top elements.
=) new new
. A set of those r.ules then describes, in a way, a big case-stat.ement that tells us . …. . what happens depe.nding on what values can be found on top of.the code stack and .
WH1LE-interpreter
7P
se h
h c
c a
value stack, respectively.
their top elements.
A set of those rules then describes, in a way, a big case-statement that tells u
what happens depending on what values can be found on top of the code stack an valu
N the code stac totheonevariabl used
e stack, respectively.
ow according to the interpreter of Fig. 7.2 the initial state sets
e body of the program B, the data stack to nil and the value of th to val, which in diagrammatic form is depicted as follows:
Initial set-up
state := [ CSt, DSt, val ];
B C1
C2 .
Cn
initial stack is the body of the program
B = [C1,C2,…,Cn] val
data stack (for intermediate results) initially empty
initial value of the one variable
he STEP macro needs to behave as prescribed by the following r
Tewriterulesi
diagrammatic form:
1 als
11
o following the pedagogical route taken by [1] 76
AST Leaves
(expressions without arguments)
12
s d
k e
n
Fig. 7.3: STEP macro for evaluating quote and var
We consider the leaves for ASTs, i.e. atoms of the form [quote,a] and variables
of the form [var, X ] where the number encoding the variable name does not mat- ter as it is always the same variable that will be used in a WH1LE-program. The
The evaluation of a quoted literal returns the literal as value which is therefore
diagrammatic form of the rules for AST leaves is depicted in in Fig. 7.3.
pushed on the data stack. The quote expression is popped from the code stack (as it
1
CHAPTER 7. SELF-INTERPRETER 7.1. WH LE
7.1.2.1 Nullary Expressions
Atoms
We consider the leaves for ASTs, i.e. atoms of the form [quote,a] and variables of the form [var, X ] where the number encoding the variable name does not mat-
ter as it is always the same variable that will be used in a WH1LE-program. The diagrammatic form of the rules for AST leaves is depicted in in Fig. 7.3.
a is any atom, eg nil [quote,a] val
=)
[var,X ] val . .
CSt DSt =)
a val
val val
. .
CSt DSt
.
CSt
.
. .
. . 1
CHAPTER 7. SELF-INTERPRETER
7.1. WH LE
.
DSt
.
….
.
CSt
.
.
DSt
.
7.1.2.1 Nullary Expressions
13
has been dealt with).
The evaluation of a variable (recall we have only one in language WH1LE returns
the [vqaulouteeo,af]this one variable anvdalthis is pushed onto the resulat stack. Thvealvariable expression is popped from the code stack.
Variable
. . . .
CSt DSt =) CSt DSt
7.1.2.2 C. ompound Ex. pressions . X is irrelevant in this case
.
Next, we need to handle single argument compound expressions hd and tl. The diagrammatic form of the rules for those is depicted in Fig. 7.4.
To evaluate an AST expression of the form [hd,E] we first need to evaluate E.
[var,X ] val
So a marker doHd is pushed onto the command stack, followed by the still to be
eveshinghappenstaueisproduced
=) keundontopofktwecanfinish
no at thonression.Weattargumenthas
val val
Once.a mar e computati
.
aluated expr .
r consumed
CSt
.
sion E.. Not
this stage.
DSt
rdoHdisfo .
of a hd exp
77
Fig. 7.3: STEP macro for evaluating quote and var
The evaluation of a quoted literal returns the literal as value which is therefore pushed on the data stack. The quote expression is popped from the code stack (as it has been dealt with).
The evaluation of a variable (recall we have only one in language WH1LE returns
on the.data s
CSt
the code stac .
lso know tha
ck as no val .
we know tha .
he evaluated
.
DSt
the value of this one variable and this is pushed onto the result stack. The variable
expression is popped from the code stack.
7.1.2.2 Compound Expressions
14
Next, we need to handle single argument compound expressions hd and tl. The
Compound Expressions
(unary and binary)
15
hd
(similarlyfortl) additional atoms
used as mnemonic markers: what needs to be done when this marker is popped off the stack
[hd,E ]
[hd,E] doHd
7.1. WH1LE 7.1. WH1LE
CHAPTER 7. SELF-INTERPRETER CHAPTER 7. SELF-INTERPRETER
E val E val
CSt DSt CSt
val val
. . =) . . . . =) . .
. . =). . . . . . ….
doHd
….
. . . .
CSt DSt CSt
DSt
. . . . …. . . . .
. . . .
DSt
doHd hv1.v2 i val 12
v1 val
doHd hv .v i val
v1 val . .
. 1 . 2
. . . .
…. . . . .
. . =) . .
CSt DSt CSt DSt
. . =) . .
CSt DSt CSt DSt
. . . . …. . . . .
. .
doHd nil val
. .
nil val
doHd nil val
nil val ….
. . . .
. . . .
. . . .
. . =) . .
CSt DSt CSt DSt
CS.t DS.t =) CS.t DS.t
….
. . . . …. . . . .
. .
.
E E
.
val val
val val
[tl,E ] 16 doTl [tl,E] doTl
. . =) . .
. . =) . .
. . =). . . . . .
. . . .
CSt DSt CSt
DSt
. . . .
CSt DSt CSt
DSt
. . . . …. . . . .
. . . .
cons
E F
F
E
Cd := cons C nil;
St := nil;
Auxiliary atoms
We now add new (encoded) atoms to
denote 6 more values in ID, d
doHd, doTl, doCons, doAsgn, doIf, doWhile
[[p]](d) for all p I-programs Proof. The overall structure
7.1. WH1LE
where STEP is the sequence o
Use: push on stack to indicate operation still to be do-ne
Proposition 4.1.1 There ex
CHAPTER 7. SELF-INTERPRETER
17
compute hd v and push the result on the data stack. Since we have now dealt with the hd expression we pop the marker off the code stack.
For an AST of the form [tl,E] we do exactly the samre aesafordhdPjusDt r;eplacing doHd by doTl and instead of the result of hd v we push the result of tl v on the
called I, in which programs interpreters will be used exte
4.1 A universal p
We first develop an interpret variable, and then modify thi
Let { :=, ;, while, var, q
of ID mentioned in Definition
4.1.1 Interpretation
been pushed on top of the data stack. So we pop this value v off the data stack,
is to prove correctness of the
P := hd PD;
data stack.
Next, we discuss the binary operator cons. The rules are shown in diagrammatic
7f.1ormA iSnelFf-igin.te7r.p5r.eter for WHILE -Programs with One Variable
C :=hd(tlP)
77
[cons,E,F ]
[cons,E,F ] doCons
while Cd do STEP;
ddoCons u
Input is a program in the ab through the first and only va
valval
ables: Cd, St, Vl. The first is . . h u.v⟨ iu.v ⟩
CSt
v .
=⇒ .
val
val
Vl := tl PD;
. . =⇒. .
Vl;
.. .. =) . . . .
vvaall =)
CSt DSt CSt DSt
CSt DSt
.. .. .. ..
CSt DSt
..
.. DSt .. DSt
CSt
.. DSt . . DSt
. .. . . . ..
Fig. 7.5 STEP macro for evaluating cons
Fig. 7.5: STEP macro for evaluating cons
To evaluate an AST expression of the form [ cons, E , F ] we first need to evaluate compute hd v and push the result on the data stack. Since we have now dealt with
expressions E and F . So a marker doCons is pushed onto the code stack, followed the hd expression we pop the marker off the code stack.
by the still to be evaluated expression trees E and F. Note that the order in which For an AST of the form [ tl, E ] we do exactly the same as for hd just replacing
18
we push both arguments is arbitrary in principle, but it is important that it is fixed doHd by doTl and instead of the result of hd v we push the result of tl v on the
such that the rule for doCons knows in which order to take values from the data data stack.
stack to build the resulting tree correctly. Nothing happens on the value stack as no
Next, we discuss the binary operator cons. The rules are shown in diagrammatic value is produced nor consumed at this stage. Once a marker doCons is found on
form in Fig. 7.5.
top of the command stack, we know that we can finish the computation of a cons
To evaluate an AST expression of the form [ cons, E , F ] we first need to evaluate
doCons
writ
e
. . . . . . . .
val
val
.
.
..
CSt .
. . CSt .
Commands
19
7.1. WH1LE
We have now dealt with the cons expression, hence we can pop the marker of tthe
code stack.
CHAPTER7. SELF-INTERPRETER
Assignment
X is irrelevant in this case
[:=,X,E ]
val E doAsgn
val
7.1.2.3 Commands
Finally, we have to consider how to execute the various commands: assiignmentt,,
conditional, and while loops. First we look at the assignment operator.. The rulles fforr that are depicted diagrammatically in Fig. 7.6.
. =) . .. . … . . . ..
CSt DSt CSt DSt
. . . .. . . . .. . . . ..
.
.
doAsgn v val v
. . . .. . . . .. . . . ..
CSt DSt =) CSt DSt
. . =) . .. . . . .. . . . ..
Fig. 7.6: STEP macro for evaluating assignment Fig. 7.6: STEP macro for evaluating assignment
To interpret an AST encoding an assignment of the form [ :=, X , E ] we first need To interpret an AST encoding an assignment of the form [ :=, X , E ] we first need
to evaluate the expression E. So we push first a marker doAsgn on the code stack to evaluate the expression E. 2S0o we push first a marker doAsgn on the code stack
followed by the still to be evaluated E. Nothing happens on the value stack as no followed by the still to be evaluated E. Nothing happens on the value stack as no
value is produced nor consumed at this stage. Once a marker doAsgn is found on top value is produced nor consumed at this stage. Once a marker doAsgn is found on top
of the command stack, we know that we can finish the interpretation of an assign- of the command stack, we know that we can finish the interpretation of an assign-
ment command. We also know that the value to be assigned has been pushed onto ment command. We also know that the value to be assigned has been pushed onto
the data stack. So we pop this value v off the data stack and update the “memory”, the data stack. So we pop this value v off the data stack and update the “memory”,
in this case the value for the one variable of the program. Since we have now dealt in this case the value for the one variable of the program. Since we have now dealt
block B onto the command stack. We also pop the top element, the used value of E, block BTTonto the command stack. We also pop the top element, the used value of E,
.
from the data stack. In case the top element of the data stack is nil, representing false, from the data stack. In case the top element of the data stack is nil, representing false,
we analogously pop marker and conditional and push the commands of the else- we analogously pop marker and conditional and push the commands of the else-
block B instead. Again we pop the top element off the data stack. It is important to block B Einstead. Again we pop the top element off the data stack. It is important to
notice that one does not push the blocks B and B , respectively, in their entirety as notice that one does not push the blocks B T and B E, respectively, in their entirety as
E
a list but rather pushes all commands of the corresponding list in the correct order. a list but rather pushes all commands of the corresponding list in the correct order.
This means that we push the last command first and the first command last, such
CSt CSt
. ..
TE
This means that we push the last command first and the first command last, such
that the first command of the block will now be executed first. The rules for the that the first command of the block will now be executed first. The rules for the
if
conditional are depicted in Fig. 7.7 conditional are depicted in Fig. 7.7
val val
E val E val
doIf
=) doIf .
=) . [if,E,BT,BE] .
DSt DSt
BT =[C1,C2,…,Cn] BT =[C1,C2,…,Cn]
Val val Val . val
[if,E,BT,BE] ..
[if,E,BT,BE] .. [if,E,BT,BE]
CSt CSt
doIf doIf
.. .. CSt . .. CSt . ..
D.St DSt
BT BT
C1 C1
C2 C2
..
Cn Cn
hu, vi hu, vi
. =) . .. =) . [if,E,BT,BE] .. . [if,E,BT,BE] . .
DSt DSt DSt DSt
CSt . CSt . .. . CSt .. CSt .
doIf nil doIf nil
..
BE BE
C1 C1
val C2 val C2
..
BE = [C1,C2,…,Cn] Analogously, if top element of DSt is nil, B is pushed on CSt
.
. =) .
.. =)C . [if,E,BT,BE] .. n .
[if,E,BT,BE] . Cn . DSt DSt
BE = [C1,C2,…,Cn]
E
val val
DSt DSt
CSt . CSt . .21 .
Fig. 7.7: STEP macro for evaluating if-then-else Fig. 7.7: STEP macro for evaluating if-then-else
81 81
7.1. WH1LE CHAPTER 7. SELF-INTERPRETER The final command, and the most complicate to execute, is the while loop
[while,E,B]. while
val E val doWhile
[while,E,B] CSt
doWhile
[while,E,B]
.
CSt
.
. =) . [while,E,B]
.
DSt
.
.
DSt
.
nil val .
DSt =) .
CSt
.
. DSt
CSt . .
val
22
B C1
B = [C1,C2,…,Cn] C2
The final command, and the most complicate to execute, is the while loop
[while,E,B].
val E val
val E [while,E,B]
doWhile
. =) . . [while,E,B] .
To interpret an AST representing a while loop [while,E,B], we first need to
the code stack, followed by a marker doWhile, followed by the expression E still
evaluate the guard expression E. Since we might need to evaluate the body of this
to be evaluated. Nothing happens on the value stack as no value is produced nor
loop for a yet unknown number of times we first push the entire while command on
consumed at this stage. Once a marker doWhile is found on top of the code stack
the code stack, followed by a marker doWhile, followed by the expression E still
to co w w h
we know that we have finished the evaluation of the guard expression and can decide on
val
[while,E,B] CSt
doWhile [while,E,B]
.
CSt
.
.=).. . . CSt[while,E,B] . . CSt .
DSt
DSt
doWhile DSt
DSt
. doWhile CSt nil . val doWhi val
. . le
nil
[while,E,B] .
val val
. .
DSt
. .
B = [C1,C2,…,Cn]
. DSt =) . CSt . CSt
.
DSt
.
=). . . . CSt
.
DSt
.
. C1
C2 doWhile .
[while,E,B] C h u.v i val .
B
B C1
C2 .
Cn
B = [C1,C2,…,Cn] keep all of this for repeated looping
h u.v i val
=) [while,E,B]
val
doWhile [while,E,B]
.
CSt
val . …
n
,E,B] .
. [while
DSt
.
=)CSt
. . .
. CS.t
CSt
. . . .
. Fig. 7.8: STEP macro for evaluating while
DSt
.
.
DSt
.
DSt
.
…
Fig. 7.8: STEP macro for evaluating while
To interpret an AST23representing a while loop [while,E,B], we first need to
evaluate the guard expression E. Since we might need to evaluate the body of this loop for a yet unknown number of times we first push the entire while command on
be evaluated. Nothing happens on the value stack as no value is produced nor
what to do next accordingly. We also know that the value of the guard expressi
nsumed at this stage. Once a marker doWhile is found on top of the code stack
has been pushed onto the data stack. We consider two cases:
e know that we have finished the evaluation of the guard expression and can decide hat to do next accordingly. We also know that the value of the guard expression
as been pushed onto the data stack. We consider two cases: 82
82
Changes to interpret
WHILE
24
Variable lookup
CHAPTER 7. SELF-INTERPRETER 7.2. A SELF-INTERPRETER FOR WHI X is now relevant
[var,X ]
[X1,v1] .
[X,v] =) .
[Xn,vn] [X1,v1]
[X2,v2]
. =)
v
E
doAsgn
X
[X1,v1] .
[X,v] .
[Xn,vn] [X1,v1]
[X2,v2] .
.
CSt
.
.
DSt
.
.
CSt
.
.
DSt
.
[:=X,E]
CSt DSt [Xn,vn] 25 CSt DSt [Xn,vn] CHAPTER 7. SELF-INTERPRETER 7.2. A SELF-INTERPRETER FOR WHILE
doAsgn
CHAP[vTaErR,X7]. SELF-INTER[PXRE,TvE]R7.2. ASELF-INTERPRETERFOvRWHILE[X ,v ]
1 [ 1 X 1 , v 1 ] [ X 1 , 1v 1 ] 1 …
LE
. v . [X2,v2] . . . . .
X
. . [X,v] . . [X,v]
[var,X] [X ,v ] v [X ,v ]
. .
. . 1.1 =). .1.1
[X,v] .
=)
C.St .DSt .. . . CSt . DSt
. . .. . .. .. . . .. . …
. . .
. . [X,v] . [.X,v] DSt CSt DSt
. . =) . .
. . . . [Xn,.vn] Assignment
CSt
. CSt . DSt .[Xn,vn] CSt . DSt . .
[Xn,vn] [Xn,vn]
[Xn,vn]
. . .
[Xn,vn] [X ,v ]
E
[:=X,E] . X .
[X1,v1] [X2,v2]
X is now relevant [X1,v1]
Fig. 7.10: Adapted rules for STEPn macro
[X ,v ]
1 [ X1 2 , v 2 ]
E
1 1 [X2,v2]
[X2,v2] . =)
[:=X,E] . .=) X . .
d o A s g n doAsgn
7.2.1 Store Manipulation Macros
.
[Xn,vn] [X ,v ]
CSt CSt
7.2.1.1 Lookup doAsggnn
DSt [Xn,vn] DSt [Xn,vn]
[X ,v[X] ,v ] 1111
CSt
CSt
DSt
DSt
[Xn,vn] [X ,v ]
.
The program lookup from Fig. 7[X.1,1v ]takes as input a list [X,St] w.here X is a un
. . Xv.
11 11
X v 2 [ X2 2 , v 2 ] .
number encoding a variable name and St is a list containing var[iXa,bv]le bindings, i
.. …=)….
[X,v] two-element lists of the form [X ,v ] where eachX is a number encoding a. varia
.. ….=)…..
CSt DSt i i . CSt i DSt . .
C.St . DSt . CSt . DSt .
. . [Xn,vn] . . [Xn,vn]
name and v 2 D is the current value of this variable. There is one exceptional ca
i .. . . [Xn,vn]
if X does not appear as variable in the list St, i.e. if one looks up a variable t
. . . . [[Xn,vn] Fig. 7.10: Adapted rules for STEPn macro
has not been initialised, then the result Res will be nil (as in this case it will
Fig. 7.10: Adapted rules for STEPn macro
have been assigned anything). The consequence is that uninitialised variables
implicitly looked up with value nil which is exactly what we need.
ary .e. ble se: hat not are
7.2.1 Store Manipulation Macros
26
7.2.1 Store Manipulation Macros
7T.2h.e1p.1rogLroamokluopokup from Fig. 7.11 takes as input a list [X,St] where X is a unary number encoding a variable name and St is a list containing variable bindings, i.e.
7.2.1.1 Lookup
two-element lists of the form [X ,v ] where eachX is a number encoding a variable
7.2. A SELF-INTERPRETER FOR WHILE CHAPTER 7. SELF-INTERPRETER
read PD { P:=hd PD D:=hd tl X:=hd P; Y:=hd tl B:=hd tl CSt := B;
; PD;
tl P; P;
(* input is a list [P, D] *) (* P = [pname[,X,B,Y] *)
(* D input data *)
(* X is input var name *)
(* Y is output var name *)
(* B is program code block *)
(* CSt is code stack *)
(* initially contains only B *)
(* DSt is data stack for *)
(* intermediate results *)
this part is as before but we now keep X and Y
DSt := nil;
bind := [ X, D ];
St := [ bind ];
state := [ CSt, DSt, St ];(* wrap state for STEP macro *)
while CSt { (* main loop for interpretation *)
(* initialise store *)
state :=
CSt := hd state
};
St := hd tl tl state;
arg := [ Y, St ];
Out :=
}
write Out
(* get command stack *)
(* get final store *)
(* wrap argument for lookup *)
(* lookup output variable value *)
(* return value of result variable *)
WHILE-interpreter
Cd is code stack (code in list format),
Vl contains value d of the one variable,
Fig. 7.9: A WHILE-interpreter in WHILE St is Stack of intermediate values
27
For an AST representing an assignment [:=,X,E] we again push the marker and the still to be evaluated expression E but we also need to remember which variable this assignment is for so we push the variable number X onto the command stack before we also push the marker. Once we then hit the doAsgn marker on the com- mand stack, we know the value of the assignment expression has been evaluated and resides on the top of the data stack.
We pop the marker doAsgn and the following variable number X off the com- mand stack, pop the result value v off the data stack, and update the element that corresponds to variable X in the list [[X1,v1],[X2,v2],…,[Xn,vn]] to contain v in the binding for X . This will be done in code using the auxiliary macro update described in the following Section 7.2.1.
Coding the macros
The code for the lookup and update, respectively, is based on simple list manip- ulation checking for the right binding using the atom encoding the variable name. Their code is briefly discussed in the following subsection which can be skipped if one is not interested in coding the self-interpreter.
Finally, the concrete code for the STEP macro is left as Exercise 1.
• The update and lookup macro are available
from Canvas as is the main interpreter loop.
• The STEP macro we might do partially at least
in the exercises
84
28
END
© 2008-21. Bernhard Reus, University of Sussex
Next time: Our first non-computable problem
29