|
| 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