Skip to content

Commit fff918f

Browse files
committed
Assignment 6.
1 parent 4c23d2e commit fff918f

2 files changed

Lines changed: 380 additions & 0 deletions

File tree

www/assignments.scrbl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@
88
@include-section{assignments/3.scrbl}
99
@include-section{assignments/4.scrbl}
1010
@include-section{assignments/5.scrbl}
11+
@include-section{assignments/6.scrbl}

www/assignments/6.scrbl

Lines changed: 379 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,379 @@
1+
#lang scribble/manual
2+
@title[#:tag "Assignment 6" #:style 'unnumbered]{Assignment 6: Apply, arity checking, and variable arity functions}
3+
4+
@(require (for-label (except-in racket ...)))
5+
@(require "../notes/fraud-plus/semantics.rkt")
6+
@(require redex/pict)
7+
8+
@(require "../notes/ev.rkt")
9+
10+
@bold{Due: Tues, Oct 22, 11:59PM}
11+
12+
@(define repo "https://classroom.github.com/a/dLXAGLLp")
13+
14+
The goal of this assignment is to (1) implement arity checking in a
15+
language with functions, (2) to implement the @racket[apply]
16+
operation, a fundamental operation that allows functions to be applied
17+
to lists of arguments, and (3) to implement variable arity functions,
18+
i.e. functions that take any number of arguments.
19+
20+
Assignment repository:
21+
@centered{@link[repo repo]}
22+
23+
You are given a repository with a starter compiler similar to the
24+
@seclink["Iniquity"]{Iniquity} language we studied in class.
25+
26+
This started code is slightly different from past code in that:
27+
28+
@itemlist[
29+
30+
@item{It implements all of the ``plus'' features we've covered in
31+
previous assignments. It is worth studying this code to compare with
32+
the solutions you came up with. Try to formulate a technical critique
33+
of both solutions.}
34+
35+
@item{Parsing has been eliminated. Instead the code uses
36+
@racket[read] to read in an S-Expression and syntax checking is done
37+
at the level of S-Expressions. Hopefully you've noticed that as the
38+
langauge gets larger and more complicated, so too does the parsing.
39+
Writing the parsing code is both tedious and easy to get wrong. Why
40+
is this? Our data representation of @tt{Expr}s is a subset of
41+
@tt{S-Expression}s (the stuff you can write with @racket[quote]).
42+
We're trying to process an unstructured stream of tokens into a
43+
structured, nested datum, but also at the same time verify that datum
44+
follows the rules of @tt{Expr}ness.
45+
46+
This design can be simplified by first processing tokens into
47+
@tt{S-Expression}s, @emph{then} checking whether this
48+
@tt{S-Expression} is in the @tt{Expr} subset. Parsing the whole
49+
@tt{S-Expression} set is actually easier than parsing the @tt{Expr}
50+
subset. And we can use an existing tool for doing it: @racket[read].
51+
52+
This was in fact our first approach to parsing. We have returned from
53+
this detour so that hopefully you can appreciate the value in it's
54+
approach. Concrete syntax and parsing are orthogonal to the semantics
55+
of a language, which is the real focus of a compiler.}
56+
57+
]
58+
59+
@section[#:tag-prefix "a6-" #:style 'unnumbered]{Overview}
60+
61+
Of all the assignments so far, this one asks you to write the least
62+
amount of code, and yet, is probably the most difficult.
63+
64+
65+
You are free to tackle this parts in any order you see fit, @emph{but}
66+
I recommend approaching them in the order described here so that you
67+
can peicemeal work up to a full solution. Tackling everything at once
68+
will make things harder.
69+
70+
71+
@section[#:tag-prefix "a6-" #:style 'unnumbered]{Apply yourself}
72+
73+
The @racket[apply] operation gives programmers the ability to apply a
74+
function to a list as though the elements of that list are the
75+
arguments of the function. In other words, it does the following:
76+
@racket[(apply f ls)] @math{=} @racket[(f v1 ...)], where @racket[ls]
77+
is @racket[(list v1 ...)].
78+
79+
Your task is to implement (a slightly simplified version) of the
80+
@racket[apply] operation, which takes the name of a function (this
81+
assignment is based on @seclink["Iniquity"]{Iniquity}, which only has
82+
second-class functions) and an argument that is expected to evaluate
83+
to a list. The given function is then called with the elements of the
84+
list as its arguments.
85+
86+
For example, this calls the @racket[f] function, which expects two
87+
arguments and adds them together, with a list containing @racket[1]
88+
and @racket[2]. The result is equivalent to @racket[(f 1 2)],
89+
i.e. @racket[3]:
90+
91+
@ex[
92+
(begin
93+
(define (f x y) (+ x y))
94+
(apply f (cons 1 (cons 2 '()))))
95+
]
96+
97+
Note that the list argument to @racket[apply] is an arbitrary
98+
expression, which when evaluated, should produce a list. We could
99+
have written the example as follows to emphasize that the compiler
100+
cannot simply examine the sub-expression to determine the arguments to
101+
the function being called:
102+
103+
@ex[
104+
(begin
105+
(define (f x y) (+ x y))
106+
(define (g z) (cons z (cons 2 '())))
107+
(apply f (g 1)))
108+
]
109+
110+
This addition adds the following to @tt{Expr}s:
111+
112+
113+
@#reader scribble/comment-reader
114+
(racketblock
115+
;; type Expr =
116+
;; ...
117+
;; | `(apply ,Variable ,Expr)
118+
)
119+
120+
The @tt{syntax.rkt} file contains code for checking the syntax of Iniquity+.
121+
122+
The @tt{interp.rkt} file contains a reference implementation of
123+
@racket[apply].
124+
125+
The @tt{compile.rkt} file contains the compile for Iniquity+, and has
126+
stubbed out a function for compile @racket[apply] expressions:
127+
128+
@#reader scribble/comment-reader
129+
(racketblock
130+
;; Variable Expr CEnv -> Asm
131+
(define (compile-apply f e c) '())
132+
)
133+
134+
Your job is to correctly implement this function.
135+
136+
The key idea here is that evaluating @racket[e] should produce a list.
137+
That list is going to be represented as a linked list of pair pointers
138+
in the heap that eventually terminates in the empty list. But in
139+
order to call a function, we need to have arguments placed
140+
appropriately on the stack.
141+
142+
@margin-note{The compiler will need to emit code to check the
143+
assumption that the argumgent is a list and signal an error if it is
144+
violated, but you might first try to get this working on examples that
145+
are correct uses of @racket[apply] before thinking about the error
146+
cases.}
147+
148+
Until now, the compiler has always known how many arguments are given:
149+
you just look at the syntax of the call expression. But here we don't
150+
know statically how many elements of the list there are.
151+
152+
So, the compiler will need to emit code that traverses the list and
153+
places the element at the appropriate position on the stack. After
154+
this, the function can be called as usual.
155+
156+
Since the list can be arbitrarily large, the emitted ``copy list
157+
elements to stack'' code will need to be a loop. Here's a sketch of
158+
how the loop operates:
159+
160+
@itemlist[
161+
162+
@item{it has a register containing the list (either
163+
empty or a pointer to a pair).}
164+
165+
@item{it has a register containing a pointer to the
166+
next spot on the stack to put an argument.}
167+
168+
@item{if the list is empty, the loop is done.}
169+
170+
@item{if the list is a pair, copy the @racket[car] to the stack, set
171+
the register holding the list to @racket[cdr], and advance the
172+
register holding the pointer to the stack.}
173+
174+
]
175+
176+
Once you have this working for ``good examples,'' revisit the code to
177+
interleave type checking to validate that the subexpression produced a
178+
list.
179+
180+
181+
@section[#:tag-prefix "a6-" #:style 'unnumbered]{Arity-check yourself, before you wreck yourself}
182+
183+
When we started looking at functions and function applications, we
184+
wrote an interpreter that did arity checking, i.e. just before making
185+
a function call, it confirmed that the function definition had as many
186+
parameters as the call had arguments.
187+
188+
The compiler, however, does no such checking. This means that
189+
arguments will silently get dropped when too many are supplied and
190+
(much worse!) parameters will be bound to junk values when too few are
191+
supplied; the latter has the very unfortunate effect of possibly
192+
leaking local variable's values to expressions out of the scope of
193+
those variables. (This has important security ramifications.)
194+
195+
The challenge here is that the arity needs to be checked at run-time
196+
(at least it will be with the addition of first-class functions,
197+
or... @racket[apply]). But at run-time, we don't have access to the
198+
syntax of the function definition or the call. So in order to check
199+
the arity of a call, we must emit code to do the checking and to
200+
compute the relevant information for carrying out the check.
201+
202+
Here is the idea: when compiling a function definition, the arity of
203+
the function is clear from the number of parameters of the definition.
204+
If the caller of a function can communicate the number of arguments to
205+
the function, then the function can just check that this number
206+
matches the expected number.
207+
208+
When compiling a call, the number of arguments is obvious, so the call
209+
should communicate this number to the function (which then checks it).
210+
211+
How should this number be communicated? A simple solution is to pass
212+
the number as though it were the first argument of the function.
213+
214+
Hence, a function of @math{n} arguments will be compiled as a function
215+
of @math{n+1} arguments. A call with @math{m} arguments will be
216+
compiled as a call with @math{m+1} arguments, where the value of the
217+
first argument is @math{m}. The emitted code for a function should
218+
now check that the value of the first argument is equal to @math{n}
219+
and signal an error when it is not.
220+
221+
You will need to modify @racket[compile-call] and
222+
@racket[compile-define] to implement this arity checking protocol.
223+
You will also need to update @racket[compile-apply], but first try to
224+
get things working for normal calls.
225+
226+
227+
@section[#:tag-prefix "a6-" #:style 'unnumbered]{Apply with arity checks}
228+
229+
What happens now with your previously good examples of using
230+
@racket[apply]? Why is everything coming up @racket['err] now?
231+
232+
The problem is that once you implement the arity checking mechanism
233+
for function definitions, this checking will also happen when you call
234+
the function via @racket[apply], but your @racket[apply] code is not
235+
communicating the number of arguments, so the call is likely to fail
236+
the check. (Pop quiz: can you make an example that ``works,''
237+
i.e. calls a function via @racket[apply] but avoids the arity check
238+
error?)
239+
240+
In order to get @racket[apply] working again, you'll need to have it
241+
follow the protocol for calling the function with the number of
242+
arguments as the first argument.
243+
244+
But how many arguments are there? Well, there are as many arguments
245+
as there are elements in the list. Update @racket[compile-apply] so
246+
the emitted code computes this quantity and passes it in the
247+
appropriate spot on the stack. After doing this, all your earlier
248+
examples should work again, it should catch arity errors where the
249+
function expects a different number of arguments from the number of
250+
elements in the list.
251+
252+
@section[#:tag-prefix "a6-" #:style 'unnumbered]{Variable arity functions}
253+
254+
So far, the arity of every function is some fixed natural number.
255+
However, it's quite useful to have functions that can take any number
256+
of arguments.
257+
258+
This is possible in Racket with the use of variable arity functions.
259+
260+
For example:
261+
262+
@ex[
263+
(begin
264+
(define (f . xs) xs)
265+
(f 1 2 3))
266+
]
267+
268+
Note the use of ``@tt{.}'' in the formal parameters of the function.
269+
This syntax is indicating that @racket[f] can be applied to any number
270+
of arguments (including 0). The arguments will be bundled together in
271+
a list, which is bound to the @racket[xs] variable. So this
272+
application produces the list @racket['(1 2 3)].
273+
274+
We can also write a function like this:
275+
276+
@ex[
277+
(begin
278+
(define (f x y . zs) zs)
279+
(f 1 2 3))
280+
]
281+
282+
This function must be applied to at least 2 arguments. The first two
283+
arguments are bound to @racket[x] and @racket[y]. Any additional
284+
arguments are bundled in a list and bound to @racket[zs]. So this
285+
expression produces a list of one element: @racket[3].
286+
287+
To accomodate this, the syntax of formal parameters in function
288+
definitions is updated from:
289+
290+
@#reader scribble/comment-reader
291+
(racketblock
292+
;; type Formals = (Listof Variable)
293+
)
294+
295+
To:
296+
@#reader scribble/comment-reader
297+
(racketblock
298+
;; type Formals =
299+
;; | '()
300+
;; | Variable
301+
;; | (Cons Variable Formals)
302+
)
303+
304+
Meaning the formals ``list'' can be an improper list and when it is,
305+
the final variable is the one that binds to a list containing all
306+
remaining arguments.
307+
308+
To implement variable arity functions, you'll need to update
309+
@racket[compile-define] to handle this form of function defintion.
310+
The code includes a stubbed case for matching variable arity function
311+
definitions; you just need to write the code.
312+
313+
The idea is inverse to that of @racket[apply]: an arbitrary number of
314+
arguments are passed on the stack and the function must convert the
315+
appropriate number of stack arguments to a list.
316+
317+
You'll need to implement this part of the assignment after adding the
318+
arity checking protocol. This is because the function caller needs to
319+
pass in the count of the number of arguments. Without this
320+
information, the function won't know how many arguments to put in a
321+
list.
322+
323+
If the function requires at least @math{n} arguments and is called
324+
with @math{m} arguments, then @math{m-n} arguments should be placed in
325+
a list and the list should be passed in as the @math{n+1}th argument.
326+
(If the function is called with less than @math{n} arguments, then it
327+
is an arity error.) So like @racket[apply], you'll need a loop, but
328+
instead of copy from a list to a stack, you'll need to copy from the
329+
stack to a list.
330+
331+
@section[#:tag-prefix "a6-" #:style 'unnumbered]{Bonus}
332+
333+
Should you find yourself having completed the assignment with time to
334+
spare, you could try adding proper tail calls to the compiler. You
335+
will have to make it work with arity checking and calling
336+
@racket[apply] in tail position should make a tail call to the
337+
function.
338+
339+
This isn't worth any credit, but you might learn something.
340+
341+
342+
@section[#:tag-prefix "a6-" #:style 'unnumbered]{Testing}
343+
344+
You can test your code in several ways:
345+
346+
@itemlist[
347+
348+
@item{Using the command line @tt{raco test .} from
349+
the directory containing the repository to test everything.}
350+
351+
@item{Using the command line @tt{raco test <file>} to
352+
test only @tt{<file>}.}
353+
354+
@item{Pushing to github. You can
355+
see test reports at:
356+
@centered{@link["https://travis-ci.com/cmsc430/"]{
357+
https://travis-ci.com/cmsc430/}}
358+
359+
(You will need to be signed in in order see results for your private repo.)}]
360+
361+
Note that only a small number of tests are given to you, so you should
362+
write additional test cases.
363+
364+
@bold{There is separate a repository for tests!} When you push your
365+
code, Travis will automatically run your code against the tests. If
366+
you would like to run the tests locally, clone the following
367+
repository into the directory that contains your compiler and run
368+
@tt{raco test .} to test everything:
369+
370+
@centered{@tt{https://github.com/cmsc430/assign06-test.git}}
371+
372+
This repository will evolve as the week goes on, but any time there's
373+
a significant update it will be announced on Piazza.
374+
375+
@section[#:tag-prefix "a6-" #:style 'unnumbered]{Submitting}
376+
377+
Pushing your local repository to github ``submits'' your work. We
378+
will grade the latest submission that occurs before the deadline.
379+

0 commit comments

Comments
 (0)