|
| 1 | +#lang scribble/manual |
| 2 | +@title[#:tag "Assignment 4" #:style 'unnumbered]{Assignment 4: Let there be Variables, Characters} |
| 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: Thurs, Sept 26, 11:59PM} |
| 11 | + |
| 12 | +@(define repo "https://classroom.github.com/a/HuBPd1o9") |
| 13 | + |
| 14 | +The goal of this assignment is to extend a compiler with binding forms |
| 15 | +and a character data type. |
| 16 | + |
| 17 | +Assignment repository: |
| 18 | +@centered{@link[repo repo]} |
| 19 | + |
| 20 | +You are given a repository with a starter compiler similar to the |
| 21 | +@seclink["Fraud"]{Fraud} language we studied in class. You are tasked |
| 22 | +with: |
| 23 | + |
| 24 | +@itemlist[ |
| 25 | + |
| 26 | +@item{incorporating the Con+ features you added in |
| 27 | +@seclink["Assignment 3"]{Assignment 3},} |
| 28 | + |
| 29 | +@item{extending the language to include a character data type,} |
| 30 | + |
| 31 | +@item{extending the @racket[let]-binding form of the language to bind |
| 32 | +any number of variables, and} |
| 33 | + |
| 34 | +@item{updating the parser to work for Fraud+.} |
| 35 | + |
| 36 | +] |
| 37 | + |
| 38 | +@section[#:tag-prefix "a4-" #:style 'unnumbered]{From Con+ to Fraud+} |
| 39 | + |
| 40 | +Implement the @racket[abs] and unary @racket[-] operations and the |
| 41 | +@racket[cond] form from @seclink["Assignment 3"]{Con+}. You can start |
| 42 | +from your previous code, but you will need to update it to work for |
| 43 | +Fraud+. |
| 44 | + |
| 45 | +In particular, functions should signal an error when applied to the |
| 46 | +wrong type of argument and your @racket[cond] form should work with |
| 47 | +@emph{arbitrary} question expressions. In other words, @racket[cond] |
| 48 | +should work like @racket[if] in @seclink["Dupe"]{Dupe}. |
| 49 | + |
| 50 | +The formal semantics of @racket[cond] are defined as: |
| 51 | + |
| 52 | +@(define ((rewrite s) lws) |
| 53 | + (define lhs (list-ref lws 2)) |
| 54 | + (define rhs (list-ref lws 3)) |
| 55 | + (list "" lhs (string-append " " (symbol->string s) " ") rhs "")) |
| 56 | + |
| 57 | +@(require (only-in racket add-between)) |
| 58 | +@(define-syntax-rule (show-judgment name i j) |
| 59 | + (with-unquote-rewriter |
| 60 | + (lambda (lw) |
| 61 | + (build-lw (lw-e lw) (lw-line lw) (lw-line-span lw) (lw-column lw) (lw-column-span lw))) |
| 62 | + (with-compound-rewriters (['+ (rewrite '+)] |
| 63 | + ['- (rewrite '–)] |
| 64 | + ['= (rewrite '=)] |
| 65 | + ['!= (rewrite '≠)]) |
| 66 | + (apply centered |
| 67 | + (add-between |
| 68 | + (build-list (- j i) |
| 69 | + (λ (n) (begin (judgment-form-cases (list (+ n i))) |
| 70 | + (render-judgment-form name)))) |
| 71 | + (hspace 4)))))) |
| 72 | + |
| 73 | +@(show-judgment 𝑭-𝒆𝒏𝒗 0 1) |
| 74 | +@(show-judgment 𝑭-𝒆𝒏𝒗 1 2) |
| 75 | +@(show-judgment 𝑭-𝒆𝒏𝒗 2 3) |
| 76 | + |
| 77 | + |
| 78 | +The following files have already been updated for you: |
| 79 | +@itemlist[ |
| 80 | +@item{@tt{ast.rkt}} |
| 81 | +@item{@tt{syntax.rkt}} |
| 82 | +@item{@tt{interp.rkt}} |
| 83 | +] |
| 84 | + |
| 85 | +You will need to modify @tt{compile.rkt} to correctly implement these |
| 86 | +features. |
| 87 | + |
| 88 | + |
| 89 | +@section[#:tag-prefix "a4-" #:style 'unnumbered]{Adding a Bit of Character} |
| 90 | + |
| 91 | +Racket has a Character data type for representing single letters. A |
| 92 | +Racket character can represent any of the 1,114,112 Unicode |
| 93 | +@link["http://unicode.org/glossary/#code_point"]{code points}. |
| 94 | + |
| 95 | +The way a character is most often written is an octothorp, followed by |
| 96 | +a backslash, followed by the character itself. So for example the |
| 97 | +character @tt{a} is written @racket[#\a]. The character @tt{λ} is |
| 98 | +written @racket[#\λ]. The character @tt{文} is written @racket[#\文]. |
| 99 | + |
| 100 | +A character can be converted to an integer and @emph{vice versa}: |
| 101 | + |
| 102 | +@ex[ |
| 103 | +(char->integer #\a) |
| 104 | +(char->integer #\λ) |
| 105 | +(char->integer #\文) |
| 106 | +(integer->char 97) |
| 107 | +(integer->char 955) |
| 108 | +(integer->char 25991) |
| 109 | +] |
| 110 | + |
| 111 | +However, integers in the range of valid code points are acceptable to |
| 112 | +@racket[integer->char] and using any other integer will produce an |
| 113 | +error: |
| 114 | + |
| 115 | +@ex[ |
| 116 | +(eval:error (integer->char -1)) |
| 117 | +(eval:error (integer->char 55296)) |
| 118 | +] |
| 119 | + |
| 120 | +There are a few other ways to write characters (see the Racket |
| 121 | +@link["https://docs.racket-lang.org/reference/reader.html#%28part._parse-character%29"]{Reference} |
| 122 | +for the details), but you don't have to worry much about this since |
| 123 | +the lexer takes care of reading characters in all their different |
| 124 | +forms and the run-time system given to you takes care of printing |
| 125 | +them. |
| 126 | + |
| 127 | +Your job is extend the compiler to handle the compilation of |
| 128 | +characters and implement the operations @racket[integer->char], |
| 129 | +@racket[char->integer], and @racket[char?]. The operations should |
| 130 | +work as in Racket and should signal an error (i.e. @racket['err]) |
| 131 | +whenever Racket produces an error. While you're at it, implement |
| 132 | +the predicates @racket[integer?] and @racket[boolean?], too. |
| 133 | + |
| 134 | +The following files have already been updated for you: |
| 135 | + |
| 136 | +@itemlist[ |
| 137 | + |
| 138 | +@item{@tt{ast.rkt}} |
| 139 | + |
| 140 | +@item{@tt{syntax.rkt}} |
| 141 | + |
| 142 | +@item{@tt{interp.rkt}} |
| 143 | + |
| 144 | +@item{@tt{main.c}} |
| 145 | + |
| 146 | +] |
| 147 | + |
| 148 | +You will need to modify @tt{compile.rkt} to correctly implement these |
| 149 | +features. Note that you must use the same representation of |
| 150 | +characters as used in the run-time system and should not change |
| 151 | +@tt{main.c}. |
| 152 | + |
| 153 | +@section[#:tag-prefix "a4-" #:style 'unnumbererd]{Generalizing Let} |
| 154 | + |
| 155 | +The Fraud language has a let form that binds a single variable in the |
| 156 | +scope of some expression. This is a restriction of the more general |
| 157 | +form of @racket[let] that binds any number of expressions. So for |
| 158 | +example, |
| 159 | + |
| 160 | +@racketblock[ |
| 161 | +(let ((x 1) (y 2) (z 3)) |
| 162 | + _e) |
| 163 | +] |
| 164 | + |
| 165 | +simultaneously binds @racket[x], @racket[y], and @racket[z] in the |
| 166 | +scope of @racket[_e]. |
| 167 | + |
| 168 | +The syntax of a @racket[let] expression allows any number of binders |
| 169 | +to occur, so @racket[(let () _e)] is valid syntax and is equivalent to |
| 170 | +@racket[_e]. |
| 171 | + |
| 172 | +The binding of each variable is only in scope in the body, @bold{not} |
| 173 | +in the right-hand-sides of any of the @racket[let]. |
| 174 | + |
| 175 | +For example, @racketblock[(let ((x 1) (y x)) 0)] is a syntax error |
| 176 | +because the occurrence of @racket[x] is not bound. |
| 177 | + |
| 178 | +The following files have already been updated for you: |
| 179 | + |
| 180 | +@itemlist[ |
| 181 | + |
| 182 | +@item{@tt{ast.rkt}} |
| 183 | + |
| 184 | +@item{@tt{interp.rkt}} |
| 185 | + |
| 186 | +] |
| 187 | + |
| 188 | +Update @tt{syntax.rkt} to define two functions: |
| 189 | + |
| 190 | +@itemize[ |
| 191 | + |
| 192 | +@item{@code[#:lang "racket"]{expr? ; Any -> Boolean}, which consumes |
| 193 | +anything and determines if it is a well-formed expression, i.e. it |
| 194 | +must be an instance of an @tt{Expr} @emph{and} each @racket[let] |
| 195 | +expression must bind a distinct set of variables.} |
| 196 | + |
| 197 | +@item{@code[#:lang "racket"]{closed? ; Expr -> Boolean}, which consumes |
| 198 | +an @tt{Expr} and determines if it is closed, i.e. every variable |
| 199 | +occurrence is bound.} |
| 200 | + |
| 201 | +] |
| 202 | + |
| 203 | +Update @tt{compile.rkt} to correctly compile the generalized form of |
| 204 | +@racket[let]. The compiler may assume the input is a closed |
| 205 | +expression. |
| 206 | + |
| 207 | +@section[#:tag-prefix "a4-" #:style 'unnumbered]{Extending your Parser} |
| 208 | + |
| 209 | + |
| 210 | +Extend your Con+ parser for the Fraud+ language based on the following |
| 211 | +grammar: |
| 212 | + |
| 213 | +@verbatim{ |
| 214 | +<expr> ::= integer |
| 215 | + | character |
| 216 | + | variable |
| 217 | + | ( <compound> ) |
| 218 | + | [ <compound> ] |
| 219 | + |
| 220 | +<compound> ::= <prim> <expr> |
| 221 | + | if <expr> <expr> <expr> |
| 222 | + | cond <clause>* <else> |
| 223 | + | let <bindings> <expr> |
| 224 | + |
| 225 | +<prim> ::= add1 | sub1 | abs | - | zero? | integer->char | char->integer |
| 226 | + | char? | integer? | boolean? |
| 227 | + |
| 228 | +<clause> ::= ( <expr> <expr> ) |
| 229 | + | [ <expr> <expr> ] |
| 230 | + |
| 231 | +<else> ::= ( else <expr> ) |
| 232 | + | [ else <expr> ] |
| 233 | + |
| 234 | +<bindings> ::= ( <binding>* ) |
| 235 | + | [ <binding>* ] |
| 236 | + |
| 237 | +<binding> ::= ( variable <expr> ) |
| 238 | + | [ variable <expr> ] |
| 239 | +} |
| 240 | + |
| 241 | +There is a lexer given to you in @tt{lex.rkt}, which provides two |
| 242 | +functions: @racket[lex-string] and @racket[lex-port], which consume a |
| 243 | +string or an input port, respectively, and produce a list of tokens, |
| 244 | +which are defined as follows: |
| 245 | + |
| 246 | +@margin-note{Note that the @tt{Token} type has changed slightly from |
| 247 | +@secref{Assignment 3}: @racket['add1] is now @racket['(prim add1)], |
| 248 | +@racket['cond] is now @racket['(keyword cond)], etc.} |
| 249 | + |
| 250 | +@#reader scribble/comment-reader |
| 251 | +(racketblock |
| 252 | +;; type Token = |
| 253 | +;; | Integer |
| 254 | +;; | Char |
| 255 | +;; | Boolean |
| 256 | +;; | `(variable ,Variable) |
| 257 | +;; | `(keyword ,Keyword) |
| 258 | +;; | `(prim ,Prim) |
| 259 | +;; | 'lparen ;; ( |
| 260 | +;; | 'rparen ;; ) |
| 261 | +;; | 'lsquare ;; [ |
| 262 | +;; | 'rsquare ;; ] |
| 263 | +;; | 'eof ;; end of file |
| 264 | + |
| 265 | +;; type Variable = Symbol (other than 'let, 'cond, etc.) |
| 266 | + |
| 267 | +;; type Keyword = |
| 268 | +;; | 'let |
| 269 | +;; | 'cond |
| 270 | +;; | 'else |
| 271 | +;; | 'if |
| 272 | + |
| 273 | +;; type Prim = |
| 274 | +;; | 'add1 |
| 275 | +;; | 'sub1 |
| 276 | +;; | 'zero? |
| 277 | +;; | 'abs |
| 278 | +;; | '- |
| 279 | +;; | 'integer->char |
| 280 | +;; | 'char->integer |
| 281 | +;; | 'char? |
| 282 | +;; | 'boolean? |
| 283 | +;; | 'integer? |
| 284 | +) |
| 285 | + |
| 286 | +The lexer will take care of reading the @tt{#lang racket} header and |
| 287 | +remove any whitespace. |
| 288 | + |
| 289 | +You must complete the code in @tt{parse.rkt} to implement the parser |
| 290 | +which constructs an s-expression representing a valid Fraud+ |
| 291 | +expression, if possible, from a list of tokens. The @racket[parse] |
| 292 | +function should have the following signature and must be provided by |
| 293 | +the module: |
| 294 | + |
| 295 | +@#reader scribble/comment-reader |
| 296 | +(racketblock |
| 297 | +;; parse : [Listof Token] -> Expr |
| 298 | +) |
| 299 | + |
| 300 | +As an example, @racket[parse] should produce @racket['(add1 (sub1 7))] |
| 301 | +if given |
| 302 | + |
| 303 | +@racketblock['(lparen (prim add1) lparen (prim sub1) 7 rparen rparen eof)] |
| 304 | + |
| 305 | + |
| 306 | +You should not need to make any changes to @tt{lex.rkt}. |
| 307 | + |
| 308 | +The given @tt{interp-file.rkt} and @tt{compile-file.rkt} code no |
| 309 | +longer use @racket[read], but instead use the parser. This means you |
| 310 | +neither will work until the parser is complete. |
| 311 | + |
| 312 | + |
| 313 | +@bold{The code you are given includes two(!) implementations of the |
| 314 | +Con+ parser.} One implementation follows the imperative approach; the |
| 315 | +other follows the functional approach. |
| 316 | + |
| 317 | +You may extend either, or you may throw out the given code and start |
| 318 | +from the code you wrote previously. |
| 319 | + |
| 320 | + |
| 321 | +@section[#:tag-prefix "a4-" #:style 'unnumbered]{Testing} |
| 322 | + |
| 323 | +You can test your code in several ways: |
| 324 | + |
| 325 | +@itemlist[ |
| 326 | + |
| 327 | + @item{Using the command line @tt{raco test .} from |
| 328 | + the directory containing the repository to test everything.} |
| 329 | + |
| 330 | + @item{Using the command line @tt{raco test <file>} to |
| 331 | + test only @tt{<file>}.} |
| 332 | + |
| 333 | + @item{Pushing to github. You can |
| 334 | + see test reports at: |
| 335 | + @centered{@link["https://travis-ci.com/cmsc430/"]{ |
| 336 | + https://travis-ci.com/cmsc430/}} |
| 337 | + |
| 338 | + (You will need to be signed in in order see results for your private repo.)}] |
| 339 | + |
| 340 | +Note that only a small number of tests are given to you, so you should |
| 341 | +write additional test cases. |
| 342 | + |
| 343 | +@bold{We have removed @tt{random.rkt}} and instead provide a |
| 344 | +@tt{random-exprs.rkt} module which provides @racket[exprs], a list |
| 345 | +of 500 closed expressions. It is used in the |
| 346 | +@tt{test/compile-rand.rkt} file to randomly test compiler correctness. |
| 347 | +This should help speed up the testing process since the random |
| 348 | +generation is slow. |
| 349 | + |
| 350 | +@section[#:tag-prefix "a4-" #:style 'unnumbered]{Submitting} |
| 351 | + |
| 352 | +Pushing your local repository to github ``submits'' your work. We |
| 353 | +will grade the latest submission that occurs before the deadline. |
| 354 | + |
0 commit comments