Here’s what the concrete syntax of the base language looks like, and how we’ll represent it as abstract syntax in JavaScript:
Concrete Syntax | JS AST | |
---|---|---|
e ::= |
primValue
e1
if e1 then e2 else e3
x
let x = e1 in e2
fun x -> e
ef ea
|
new Lit( primValue)
+ , - ,
* , / , % , = , != ,
< , > , && ,
|| Note: the = and
!= operators in our language have the same semantics as
JavaScript’s === and !== operators,
respectively.
new BinOp( op, e1,
e2)
new If( e1, e2,
e3)
new Var( x) new Let( new Var( x) , e1,
e2)
new Fun( new Var( x) , e) new Call( ef, ea)
|
x ::= | ||
primValue ::= |
|
Lit
,
BinOp
, If
, etc.) are declared in this
file.
As in the calculator exercise, we have already written a interp
that takes an AST of an expression and returns the value that
it produces:
v ::= |
primValue
new Closure( new Fun( …) , env)
|
We have included some unit tests for your prototype below. These will run automatically every time you refresh this page. Please note that these tests are not meant to be comprehensive, they’re just a few examples to give you a better understanding of the semantics of the language. (We expect you will still need more information on the semantics of the language, and may want to know the motivation behind some of our design decisions. Feel free to ask us in class or on Piazza.)
We strongly recommend that you add your own unit tests — feel free to share
them with other students on Piazza, too! Adding tests is easy: all you have to do is edit
asst5-tests.js
, the format is pretty
self-explanatory. (The tests for Assignment 6 are in
asst6-tests.js
.)
Turn in your interp.js (and classes.js, if needed) through Canvas.
Next, we’ll make several extensions to the language:
(1, 2, 3 + 4)
should evaluate
to (1, 2, 7)
.
Cons (1, Cons (2, Nil))
uses the
data constructors Cons
and Nil
to represent the list
[1; 2]
.
6
, and
-1
. In the case of a match failure, let and
fun should throw an exception.
Here’s how your language will grow to accommodate these extensions:
Concrete Syntax | JS AST | |
---|---|---|
e ::= |
…
let rec x = e1
in e2
( e1,
…, en)
C e
let p = e1 in e2
match e with p1 -> e1 | … | pn -> en
[ e | p <- elist
, epred]
delay e
force e
|
…
new LetRec( x, e1,
e2)
new Tuple([ e1,
…, en])
new Datum( C, e)
new Let( p, e1, e2)
new ListComp( e, p, elist
, epred) new Delay( e) new Force( e) |
p ::= |
=== to the pattern.
_
( p1,
…, pn)
|
new Lit( primValue)
new Wildcard()
new Var( x) new Tuple([ p1,
…, pn])
new Datum( C, p)
|
C ::= | ||
v ::= |
…
new Tuple([ v1,
…, vn])
new Datum( C, v)
|
What we’ve got at this point is a prototype of a functional language that is very similar to ML. So go ahead and give yourself a good pat on the back, you deserve it! This kind of naive implementation style, where you always favor simplicity over performance, is very useful to researchers and language designers because (i) it enables you to get something working quickly, and with minimal effort — something to think with! — and (ii) the simplicity of the prototype makes it easy for you to experiment with changes and extensions to the semantics of the language.
If you’re interested in pushing this project further, here are a few things you might like to try:
ref
, !
, and :=
expressions.