Skip to content

CS1729 Assignment 1

Joe Politz edited this page Oct 5, 2013 · 19 revisions

Compiling to JavaScript

Your first assignment has two parts:

  1. Compile a core Pyret to JavaScript,
  2. Implement a portion of the Pyret runtime in JavaScript.

Your grade will be based on the proportion of a selected test suite that you pass (as of October 5, it's not complete, but the test suite you need to pass for this assignment will be finalized by 11:59 pm October 7). Part of your grade for the whole semester will be based on keeping up-to-date with a growing set of test cases for the language, so it's in your best interest (and the best interest of a solid implementation) to be testing early and often beyond the suite that we pick for grading any particular assignment. We'll share and build up tests as the semester goes on to harden all the implementations.

Overview

Administrative Details

You should fork the pyret-lang repo on Github and do all your work on your own fork. If we have contributions from one another that we want to share, we'll incorporate them into the main Pyret repo and then distribute them from there.

Important: We are going to do our work on the nwo branch (the "New World Order" for Pyret). So when you fork and clone Pyret, before doing any work, you should run:

git checkout nwo

In order to allow us the freedom to add features that would interfere with the version of Pyret that's being used in courses, we're segregating our work to this branch. Joe will be responsible for merging into nwo any updates from the master branch, and updating the test suite accordingly. You'll be responsible for keeping your implementation up to date with these changes, which we'll try to keep to a minimum.

Source Layout

The Pyret repository has a directory called js/ that contains the files and support code you'll need for this assignment (and CS1729 in general):

  • runtime.js --- Your JavaScript implementation of Pyret's runtime. It corresponds to the file src/lang/runtime.rkt, and will hold your representation of Pyret values, and built-in functions and methods for accessing fields, updating objects, doing primitive arithmetic and string manipulation, etc. It is seeded with a naive representation of numbers, methods, and functions to show one simple approach to the problem.

  • pyret-to-js.arr --- Your Pyret implementation of a Pyret-to-JavaScript compiler. The main entry point is program-to-js(ast :: Program) -> String, which converts a parsed Pyret program to a string of JavaScript code. You will also be completing this implementation, which starts with a naive compilation for numbers, blocks, and application.

    You will need to refer to the definition of the Pyret AST in src/lang/racket-ffi/ast.arr. This is the complete Pyret surface grammar. For now, you only need to support a smaller subset of this (the post-desugaring subset of the language), which is documented below.

  • create-tests.arr --- A script that generates test files to run unit tests on your compiler. It outputs the tests into test-runner/tests.js as a JavaScript program that creates a TESTS datastructure. You can run this to re-generate your tests using

      raco pyret --no-checks create-tests.arr
    

    The js/tests/ directory contains Pyret files. The way test creation works is by taking these files, running them and capturing their output, and saving that output. Then, the TESTS datastructure on the client gets the JavaScript-compiled versions of the program, runs them in your runtime, and compares the output. This ensures that your implementation has exactly the same observable behavior as current Pyret.

    There is a special identifier bound in both your runtime and the evaluation of these tests, called test-print, which is simply a print statement that stores its output in a string that we can read off later. This lets us test values in the middle of computations as well as the final result. In addition to checking the output of test-print, we also call the built-in torepr (short for to-representation) on the final value of the program and compare that output. The provided runtime.js has a simple torepr implementation that is a fragment of the real implementation. All this means is that for programs that evaluate to simple values like true or 5, we don't need a verbose test-print statement added to test them; they'll print out a string representation of their value by default and we can test that.

    The test suite will grow over the course of the semester, from both your contributions and programs that we add as we go along (say, interpreters from CS173). This will ensure that all the implementations are able to handle the same Pyret that everyone is using for their assignments. For this assignment, however, we'll fix the test suite on Monday night to give everyone a concrete target.

  • test-runner/test-support.js --- This contains JavaScript support for running tests. This is where you can write JavaScript functions to use as predicates on the result of running compiled Pyret programs. Two are defined --- pyretEquals and isNumber --- you will probably want to write more predicates.

  • test-runner/test.html --- You shouldn't need to edit this file, it contains the test runner that uses tests.js and test-support.js to run your unit tests. You can open this file up in a browser to run the tests.

Core Pyret Expressions

Pyret goes through a desugaring phase before reaching core Pyret, which is the language you must compile to JavaScript. As a result, the set of forms you need to handle is much smaller than the entire data definition in ast.arr. You must compile these forms:

  • s_block(l :: Loc, stmts :: List<Expr>) --- A block of expressions as statements that get their own scope
  • s_user_block(l :: Loc, body :: Expr) --- Compiles identically to s_block, indicates that a user used the block: form (rather than an implicit block for a function body, etc.)
  • s_var(l :: Loc, name :: Bind, value :: Expr) --- Note that the desugaring process also checks well-formedness, so you can assume that only variables are used in s_assign expressions.
  • s_let(l :: Loc, name :: Bind, value :: Expr)
  • s_assign(l :: Loc, id :: String, value :: Expr)
  • s_if_else(l :: Loc, branches :: List<IfBranch>, _else :: Expr)
  • s_try(l :: Loc, body :: Expr, id :: Bind, _except :: Expr) --- Pyret's semantics for try/catch is modelled nicely by JavaScript's try/catch.
  • s_lam(l :: Loc, params :: List<String>, args :: List<Bind>, ann :: Ann, doc :: String, body :: Expr, check :: Expr) --- You don't need to worry about anything to do with types/annotations or check blocks. These are completely handled by desugaring and annotation checking before arriving at core Pyret.
  • s_method(l :: Loc, args :: List<Bind>, ann :: Ann, doc :: String, body :: Expr, check :: Expr)
  • s_extend(l :: Loc, super :: Expr, fields :: List<Member>) --- This is the expression form for obj.{field1: val1, ...}
  • s_obj(l :: Loc, fields :: List<Member>)
  • s_app(l :: Loc, _fun :: Expr, args :: List<Expr>)
  • s_id(l :: Loc, id :: String) --- You should think carefully about your representation of identifiers that come from your JavaScript implemented runtime.
  • s_num(l :: Loc, n :: Number)
  • s_bool(l :: Loc, b :: Bool)
  • s_str(l :: Loc, s :: String)
  • s_bracket(l :: Loc, obj :: Expr, field :: Expr) --- Important: You can assume for now that field is always an s_str. We'll get to hash tables and computed-string lookup later on.
  • s_colon_bracket(l :: Loc, obj :: Expr, field :: Expr) --- Important: You can assume for now that field is always an s_str. We'll get to hash tables and computed-string lookup later on.
  • s_get_bang(l :: Loc, obj :: Expr, field :: String) --- Corresponds to obj!x expressions
  • s_update(l :: Loc, super :: Expr, fields :: List<Member>) --- This is the expression form for obj!{field1: val1, ...}

Documentation for these forms is at http://cs.brown.edu/~joe/public/lang/Language_Constructs.html; it is not perfect, and you should ask questions about anything you don't understand. It's the documentation's job to be complete and clear, and we should strive to improve it and make it clear enough to all work from. For ground-truth reference, the implementation of compile.rkt is definitionally correct (since it is the current and only implementation of Pyret), but it's only one implementation and it certainly requires additional explanation.

Exceptions: You may avoid things that are noted as "deprecated" or "planned to be deprecated" in the documentation. This includes the o.[x] expression where x is a non-string, and object literals like {[expr]: val} where expr is a computed string.

Pyret Values

The file src/lang/runtime.rkt defines a number of Pyret's built-in helpers and data structures. The runtime, in particular, defines the following structs:

;; p-base: (SetOf Symbol) StringMap (Value ... -> Value) -> p-base
(struct p-base (brands dict app method) #:transparent)
(struct p-nothing p-base () #:transparent)
(struct p-object p-base () #:transparent)
;; p-num : p-base Number -> p-num
(struct p-num p-base (n) #:transparent)
;; p-bool : p-base Boolean -> p-bool
(struct p-bool p-base (b) #:transparent)
;; p-str : p-base String -> p-str
(struct p-str p-base (s) #:transparent)
;; p-fun : p-base (Loc -> Proc) -> p-fun
(struct p-fun p-base () #:transparent)
;; p-method: p-base Proc -> p-method
(struct p-method p-base () #:transparent)
;; p-mutable p-base Box (Listof (Value -> Value)) (Listof (Value -> Value))
(struct p-mutable p-base (b read-wrappers write-wrappers) #:transparent)
;; p-placeholder p-base Box (Listof (Value -> Value))
(struct p-placeholder p-base (b wrappers) #:mutable)

You need to define a JavaScript representation of each of these structures, and JavaScript equivalents of the functions that operate over them from runtime.rkt. Some examples of more or less one-to-one translations are in the provided runtime.js file. You do not have to replicate the same kind of representation as we have in the Racket implementation. Feel free to experiment, as we will be building up a large enough test suite over the time in the course to check correspondence of all our implementations.

Most of these values have relatively straightforward implementations in the Racket functions of runtime.rkt, which defines the built-in methods on numbers and strings, for example. Mutables and Placeholders are especially Pyret-specific; they have special documentation at http://cs.brown.edu/~joe/public/lang/s_mutables.html and http://cs.brown.edu/~joe/public/lang/s_placeholders.html.