|
| 1 | +#lang scribble/manual |
| 2 | +@title[#:tag "Assignment 7" #:style 'unnumbered]{Assignment 7: Symbols, interning, and gensym} |
| 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, Nov 12, 11:59PM} |
| 11 | + |
| 12 | +@(define repo "FIXME") |
| 13 | + |
| 14 | +The goal of this assignment is to (1) implement symbols and the |
| 15 | +@racket[eq?] primitive operation, (2) to implement symbol interning by |
| 16 | +program transformation. |
| 17 | + |
| 18 | +Assignment repository: |
| 19 | +@centered{@link[repo repo]} |
| 20 | + |
| 21 | +You are given a repository with a starter compiler similar to the |
| 22 | +@seclink["Loot"]{Loot} language we studied in class. |
| 23 | + |
| 24 | +The given code also implements all the ``plus'' features we've |
| 25 | +developed in past assignments. |
| 26 | + |
| 27 | +@section[#:tag-prefix "a7-" #:style 'unnumbered]{Symbols} |
| 28 | + |
| 29 | +Your first task is to implement symbols for the Loot+ language. |
| 30 | +You've used symbols extensively throughout the semester, so their use |
| 31 | +should be familiar to you. A symbol evaluates to itself: |
| 32 | + |
| 33 | +@ex[ |
| 34 | +'foo |
| 35 | +] |
| 36 | + |
| 37 | +Your first task is to implement a symbol data type. The given code |
| 38 | +includes syntax checking for programs that may contain symbols and |
| 39 | +run-time support for printing symbols. The compiler has been stubbed |
| 40 | +for compiling symbols. You will need to implement |
| 41 | +@racket[compile-symbol] in @tt{compile.rkt}. |
| 42 | + |
| 43 | +A symbol can be represented much like a string: as a continuous |
| 44 | +sequence of characters in memory, along with a length field. The type |
| 45 | +tag is different, since strings and symbols should be disjoint data |
| 46 | +types. |
| 47 | + |
| 48 | +Once you implement @racket[compile-symbol], you should be able to |
| 49 | +write programs that contain symbols. |
| 50 | + |
| 51 | +@section[#:tag-prefix "a7-" #:style 'unnumbered]{Pointer equality} |
| 52 | + |
| 53 | +Your next task is to implement the @racket[eq?] primitive operation, |
| 54 | +which compares two values for pointer equality. Immediate values |
| 55 | +(characters, integers, booleans, empty list, etc.) should be |
| 56 | +pointer-equal to values that are ``the same.'' So for example: |
| 57 | + |
| 58 | +@ex[ |
| 59 | +(eq? '() '()) |
| 60 | +(eq? 5 5) |
| 61 | +(eq? #\a #\a) |
| 62 | +(eq? #\t #\t) |
| 63 | +] |
| 64 | + |
| 65 | +On the other hand, values that are allocated in memory such as boxes, |
| 66 | +pairs, procedures, etc., are only @racket[eq?] to each other if they |
| 67 | +are allocated to the same location in memory. So for example, the |
| 68 | +following could all produce @racket[#f]: |
| 69 | + |
| 70 | +@ex[ |
| 71 | +(eq? (λ (x) x) (λ (x) x)) |
| 72 | +(eq? (cons 1 2) (cons 1 2)) |
| 73 | +(eq? (box 1) (box 1)) |
| 74 | +] |
| 75 | + |
| 76 | +However these must be produce @racket[#t]: |
| 77 | + |
| 78 | +@ex[ |
| 79 | +(let ((x (λ (x) x))) |
| 80 | + (eq? x x)) |
| 81 | +(let ((x (cons 1 2))) |
| 82 | + (eq? x x)) |
| 83 | +(let ((x (box 1))) |
| 84 | + (eq? x x)) |
| 85 | +] |
| 86 | + |
| 87 | +Applying @racket[eq?] to any two values from disjoint data types |
| 88 | +should produce @racket[#f]: |
| 89 | + |
| 90 | +@ex[ |
| 91 | +(eq? 0 #f) |
| 92 | +(eq? #\a "a") |
| 93 | +(eq? '() #t) |
| 94 | +(eq? 'fred "fred") |
| 95 | +] |
| 96 | + |
| 97 | +The given compiler is stubbed for the @racket[eq?] primitive. You |
| 98 | +must implement @racket[compile-eq?]. |
| 99 | + |
| 100 | +@section[#:tag-prefix "a7-" #:style 'unnumbered]{Interning symbols} |
| 101 | + |
| 102 | +One thing you may notice at this point is that because symbols are |
| 103 | +allocated in memory, the behavior @racket[eq?] with your compiler |
| 104 | +differs from Racket's behavior. |
| 105 | + |
| 106 | +In Racket, two symbols which are written the same way in a given |
| 107 | +program are @racket[eq?] to each other. |
| 108 | + |
| 109 | +@ex[ |
| 110 | +(eq? 'x 'x) |
| 111 | +] |
| 112 | + |
| 113 | +But your compiler will (probably) produce @racket[#f]. |
| 114 | + |
| 115 | +The problem is that Racket ``interns'' symbols, meaning that all |
| 116 | +occurrences of a symbol are allocated to the same memory location. |
| 117 | +(Languages like Java also do this with string literals.) |
| 118 | + |
| 119 | +Extend your compiler so that @racket[eq?] behaves correctly on |
| 120 | +symbols. Note, you should @emph{not change the way @racket[eq?] |
| 121 | +works}, rather you should change how symbols are handled by the |
| 122 | +compiler. |
| 123 | + |
| 124 | +The most effective way to implement symbol interning is to apply a |
| 125 | +program transformation to the given program to compile. This |
| 126 | +transformation should replace multiple occurrences of the same symbol |
| 127 | +with a variable that is bound to that symbol, and that symbol should |
| 128 | +be allocated exactly once. |
| 129 | + |
| 130 | +So for example, |
| 131 | + |
| 132 | +@racketblock[ |
| 133 | +(eq? 'fred 'fred) |
| 134 | +] |
| 135 | + |
| 136 | +could be transformed to: |
| 137 | + |
| 138 | +@racket[ |
| 139 | +(let ((x 'fred)) |
| 140 | + (eq? x x)) |
| 141 | +] |
| 142 | + |
| 143 | +The latter should result in @racket[#t] since the @racket['fred] |
| 144 | +symbol is allocated exactly once. |
| 145 | + |
| 146 | +The compiler uses a @racket[intern-symbols] function, which does |
| 147 | +nothing in the given code, but should be re-defined to perform the |
| 148 | +symbol interning program transformation. Note: you probably want to |
| 149 | +define a few helper functions to make @racket[intern-symbols] work. |
| 150 | + |
| 151 | +@section[#:tag-prefix "a7-" #:style 'unnumbered]{Generating symbols} |
| 152 | + |
| 153 | +Finally, implement the @racket[gensym] primitive, which generates a |
| 154 | +symbol distinct from all other symbols. |
| 155 | + |
| 156 | +To keep things simple, you should implement the nullary version of |
| 157 | +@racket[gensym], i.e. it should take zero arguments and produce a new |
| 158 | +symbol. |
| 159 | + |
| 160 | +The following program should always produce @racket[#f]: |
| 161 | + |
| 162 | +@ex[ |
| 163 | +(eq? (gensym) (gensym)) |
| 164 | +] |
| 165 | + |
| 166 | +But the following should always produce @racket[#t]: |
| 167 | + |
| 168 | + |
| 169 | +@ex[ |
| 170 | +(let ((x (gensym))) |
| 171 | + (eq? x x)) |
| 172 | +] |
| 173 | + |
| 174 | +Note: Racket's @racket[gensym] will generate a new name for a symbol, |
| 175 | +usually something like @racket['g123456], where each successive call |
| 176 | +to @racket[gensym] will produce @racket['g123457], @racket['g123458], |
| 177 | +@racket['g123459], etc. Yours does not have to do this (although it's |
| 178 | +fine if it does). All that matters is that @racket[gensym] produces a |
| 179 | +symbol that is not @racket[eq?] to any other symbol but itself. |
| 180 | + |
| 181 | +@section[#:tag-prefix "a7-" #:style 'unnumbered]{Bonus} |
| 182 | + |
| 183 | +Should you find yourself having completed the assignment with time to |
| 184 | +spare, you could try implementing @racket[compile-tail-apply], which |
| 185 | +compiles uses of @racket[apply] that appear in tail position. It is |
| 186 | +currently defined to use the non-tail-call code generator, which means |
| 187 | +@racket[apply] does not make a proper tail call. |
| 188 | + |
| 189 | +Keep in mind that this language, the subexpression of @racket[apply] |
| 190 | +are arbitrary expressions: @racket[(apply _e0 _e1)] and that |
| 191 | +@racket[_e0] may evaluate to a closure, i.e. a function with a saved |
| 192 | +environment. Moreover, the function may have been defined to have |
| 193 | +variable arity. All of these issues will conspire to make tail calls |
| 194 | +with @racket[apply] tricky to get right. |
| 195 | + |
| 196 | +This isn't worth any credit, but you might learn something. |
| 197 | + |
| 198 | +@section[#:tag-prefix "a7-" #:style 'unnumbered]{Testing} |
| 199 | + |
| 200 | +You can test your code in several ways: |
| 201 | + |
| 202 | +@itemlist[ |
| 203 | + |
| 204 | + @item{Using the command line @tt{raco test .} from |
| 205 | + the directory containing the repository to test everything.} |
| 206 | + |
| 207 | + @item{Using the command line @tt{raco test <file>} to |
| 208 | + test only @tt{<file>}.} |
| 209 | + |
| 210 | + @item{Pushing to github. You can |
| 211 | + see test reports at: |
| 212 | + @centered{@link["https://travis-ci.com/cmsc430/"]{ |
| 213 | + https://travis-ci.com/cmsc430/}} |
| 214 | + |
| 215 | + (You will need to be signed in in order see results for your private repo.)}] |
| 216 | + |
| 217 | +Note that only a small number of tests are given to you, so you should |
| 218 | +write additional test cases. |
| 219 | + |
| 220 | +@bold{There is separate a repository for tests!} When you push your |
| 221 | +code, Travis will automatically run your code against the tests. If |
| 222 | +you would like to run the tests locally, clone the following |
| 223 | +repository into the directory that contains your compiler and run |
| 224 | +@tt{raco test .} to test everything: |
| 225 | + |
| 226 | +@centered{@tt{https://github.com/cmsc430/assign07-test.git}} |
| 227 | + |
| 228 | +This repository will evolve as the week goes on, but any time there's |
| 229 | +a significant update it will be announced on Piazza. |
| 230 | + |
| 231 | +@section[#:tag-prefix "a7-" #:style 'unnumbered]{Submitting} |
| 232 | + |
| 233 | +Pushing your local repository to github ``submits'' your work. We |
| 234 | +will grade the latest submission that occurs before the deadline. |
| 235 | + |
0 commit comments