CS 3520/6520: Programming Languages
Ideas behind the design and implementation of programming languages. Syntactic description; scope and lifetime of variables; runtime stack organization; parsing and abstract syntax; semantic issues; type systems; programming paradigms; interpreters and compilers.
Goal and Objectives
This course cover concepts of programming languages generally and functional languages in particular, including functions, continuations, syntactic sugar, and type systems. These concepts are explained primarily though a sequence of interpreters and type checkers.
At the end of this course, students will be able to:
- employ a recipe for functional programming: data, examples, template (including natural recursions), implementation, and tests
- use and implement lexical scope, closures, records, objects, and classes
- encode language constructs using constructs that are more primitive, especially lambda-calculus terms
- represent continuations and state explicitly, instead of relying on host-language implementations
- use and implement different evaluation orders: eager and lazy
- explain type soundness and implement sound type checkers for functional and object-oriented languages that include subtyping and parametric polymorphism
Materials for this course:
Programming Languages: Application and Interpretation, Second EditionLinks to an external site., Shriram Krishnamurthi (PDFLinks to an external site.)
DrRacket: https://download.racket-lang.org
Syllabus
CTIHB 109 MW 11:50-1:10 (livestreamed and recorded via Zoom)
Instructors
Co-Instructors:
Ben Greenman <blg@cs.utah.edu> MEB 3252
Matthew Flatt <mflatt@cs.utah.edu> MEB 3256
<help-cs3520@lists.utah.edu>
Teaching Assistant:
Alec Mills <help-cs3520@lists.utah.edu>
Required Materials
Materials for this course:
Communication
The easiest way to contact your instructor directly is to use the Inbox, located in the far left Canvas menu.
You can also contact your instructor in the following ways:
Asking for Help
When asking a question in a place where it makes sense to show your code (i.e., in a direct message to the instructor or a TA):
- Do include the full source of your program as an attachment.
- Do point the instructor/TA to a particular part of the program (e.g., by line number) where you have a question or where you think there’s a problem.
- Don’t send just a fragment of your code. The instructor/TA probably needs more context. Also, If you don’t understand why a fragment of code has problems, then maybe you’re looking at the wrong fragment.
- Don’t include just a screen shot of the program text. Sometimes, the instructor/TA needs to try running your program to see what happens, and screenshots don’t compile. A screen shot is useful sometimes, but in that case, include both program text and a screen image.
- Don’t copy and paste the text into your mail message, because mail transport mangles text. Provide a program as an attachment.
Evaluation
Your performance in this course will be evaluated by:
- Homework assignments (55%)
- Two midterm exams (15% each)
- Final programming assignment (10%)
- Video-lecture quizzes (5%)
Course Policies
Submitting Assignments
All assignments must be submitted via the “Handin” button in DrRacket. Install the uu-cs3520
package and restart DrRacket to get the “Handin” button. See DrRacket and the Handin Button for more information. You can check your handin status and get feedback from the Handin status server.
Each set of lecture videos has an associated quiz on Canvas. Quizzes are due before the corresponding class meeting.
Late Assignments
Late quizzes are not accepted. Homework submissions will be accepted up to 48 hours after the deadline. For each student, up to two late homework submissons (each up to 48 hours late) will be accepted without penalty. After a student’s first two late submissions, a late submission within 24 hours of the deadline will be penalized 25%. A submission more than 24 hours late but less than 48 hours late will be penalized 50%.
Large Language Models
The use of large language models (LLMs) such as ChatGPT is permitted on homework assignments. An LLM’s contribution to your homework submission must be acknowledged in a comment. Students should take care to use LLMs in a way that fosters their own learning — perhaps, for example, relying on ChatGPT as a way of understanding existing code rather than having it change code or write new code for you. The course staff will not provide technical or financial support to help students use LLMs. Do not rely on LLMs to answer course-related questions (grading, due dates, etc.); ask the course staff instead. The use of LLMs, program interpreters, or remote resources is not permitted during in-class midterm exams.
Some general disclaimers: LLMs may output text that is grossly incorrect, subtly incorrect, or otherwise misleading. LLM companies may track the queries you submit. LLMs may have, in the aggregate, a large cost to the environment that companies do not (or, cannot) fully account for.
Grading
Grading for this course
Each quiz associated with a set of lecture videos is worth a number of points corresponding to the number of questions on the quiz, where each correct answer is worth 1 point. The intent is that you take the quiz after/during watching the videos. At the end of the semester, any cumulative quiz grade of 60% or more is rounded up to 100%. Consequently, you should not be unduly concerned about missed quiz questions, even though there are no retakes, as long as you are following the videos and answering most questions correctly.
Homework is graded on the following scale: check+ = 100% for perfect or nearly perfect work, check = 80% for somewhat flawed but acceptable work, check- = 50% for seriously flawed work, 0 = 0% for missing or completely unacceptable work, and check++ = 110% for perfect work plus extra credit (when specifically offered). All homework assignments are weighted equally.
Exams are graded on a scale of 0 to 100 points.
A final assignment will be similar to a homework assignment, but larger, and the handin process will include a 1-page report. The final assignment serves much the same role as a final exam.
Accommodations
Disclaimer
Accommodations will be considered on an individual basis and may require documentation.
Please contact your instructor and/or teaching assistant as soon as possible (preferably shortly before the semester begins) to request accommodations of any kind.
Extreme personal circumstances
Please contact your instructor as soon as possible if an extreme personal circumstance
(hospitalization, death of a close relative, natural disaster, etc.) is interfering with your ability to
complete your work.
Religious Practice
To request an accommodation for religious practices, contact your instructor at the beginning of the semester.
Active Duty Military
If you are student on active duty with the military and experience issues that prevent you from participating in the course because of deployment or service responsibilities, contact your instructor as soon as possible to discuss appropriate accommodations.
Disability Access
All written information in this course can be made available in an alternative format with prior notification to the Center for Disability Services (CDS). CDS will work with you and the instructor to make arrangements for accommodations. Prior notice is appreciated. To read the full accommodations policy for the University of Utah, please see Section Q of the Instruction & Evaluation regulations.
If you will need accommodations in this class, contact:
Center for Disability Services
801-581-5020
disability.utah.edu
162 Union Building
200 S. Central Campus Dr.
Salt Lake City, UT 84112
Changes to the Syllabus
This syllabus is not a contract. It is meant to serve as an outline and guide for your course. Please note that your instructor may modify it to accommodate the needs of your class.
You will be notified of any changes to the Syllabus.
Lectures
Homework Assignments
My website does not have
Shplait language syntax highlighting, so the following code snippets are not syntax highlighted. As shplait is a descendant of
Rhombus that is built on
Racket, I am using Racket syntax highlighting as backup.
HW0
Difficulty: ★☆☆☆
Part 0 — Create Handin Account
After installing the uu-cs3520
package as described in DrRacket and the Handin Button, select the Manage CS 3520 Handin… menu item from DrRacket’s File menu. Change to the New User panel, and pick a username and password. (Use your real name and real Utah student ID, so that we can connect your homework submissions with you.)
You will have to install uu-cs3520
for DrRacket on each different filesystem that you use to get a Handin button. However, after creating a handin account once from any machine, you can use DrRacket’s Handin button on any other machine.
Use #lang shplait
at the start of your program, so that it’s implemented in the Plait language.
Part 1 — Implement to_the_3rd
Define the function to_the_3rd
, which takes a number and raises it to the 3rd power.
Your program should include check
forms to check to_the_3rd
on a few inputs.
Example use: to_the_3rd(17)
should produce 4913
.
Part 2 — Implement to_the_42nd
Define the function to_the_42nd
(in the same program), which takes a number and raises it to the 42nd power.
It’s probably a good idea to use functions like to_the_3rd
to build up to to_the_42nd
.
Your program should include check
forms to check to_the_42nd
on a few inputs.
Example use: to_the_42nd(17)
should produce 4773695331839566234818968439734627784374274207965089
.
Part 3 — Implement plural
Define plural
, which takes a string and returns a string. If the given string ends in “y”, the result should be the same as the input but with the “y” replaced by “ies”. Otherwise, the result should be the same as the given string with “s” added to the end.
Your program should include check
forms to check plural
on a few inputs.
Example uses: plural("baby")
should produce "babies"
, while plural("fish")
should produce "fishs"
.
See the documentation for Shplait to find functions that you need to work with strings.
Part 4 — Implement electricity-usage
Use the following type definition (add it to your program):
type Light
| bulb(watts :: Int,
technology :: Symbol)
| candle(inches :: Int)
Implement the function electricity_usage
, which takes a Light
and produces the number of watthours of electricity that the light uses in 24 hours. Your function will need to use match
.
As always, include relevant check
forms.
Example uses: electricity_usage(bulb(100, #'halogen))
should produce 2400
, while electricity_usage(candle(10))
should produce 0
.
Part 5 — Implement use-for-one-hour
Implement the function use_for_one_hour
, which takes a Light
and produces another Light
that represents the given light source after it is used for another hour. Assume that a candle burns one inch per hour (unless it is already gone), and assume that a light bulb is the same after one hour of use.
Example uses: use_for_one_hour(bulb(100, #'halogen))
should produce bulb(100, #'halogen)
, while use_for_one_hour(candle(10))
should produce candle(9)
.
Part 6 — Handin
Click the Handin button in DrRacket to submit your homework. The Handin button submits whatever is in DrRacket’s definitions area, so click Handin after you have defined to_the_3rd
, to_the_42nd
, plural
, electricity_usage
, and use_for_one_hour
, all in the same program.
The handin process will check that you have defined the right names. It will also run some tests on your programs. If tests fail, then your handin will not be accepted—but you can override that by clicking Submit even if server reports problems. That checkbox won’t override very basic problems, however, such as not using #lang shplait
or submitting a program that doesn’t compile.
You can double-check that handin was successful by visiting the Handin status server. Log in with the account that you created for handin. You can check whether a submission has been uploaded, you can check the time of the upload, and you can download your submission. Solutions and grades for your assignments will appear later in that same place.
Solution
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
| #lang shplait
// Part 1
fun to_the_3rd(n :: Int):
n * n * n
check: to_the_3rd(0)
~is 0
check: to_the_3rd(2)
~is 8
check: to_the_3rd(17)
~is 4913
check: to_the_3rd(-4)
~is -64
// Part 2
fun to_the_15th(n :: Int): // (n^3)^3 * n^3 * n^3 = n^15
to_the_3rd(to_the_3rd(n)) * to_the_3rd(n) * to_the_3rd(n)
fun to_the_27th(n :: Int): // ((n^3)^3)^3 = n^27
to_the_3rd(
to_the_3rd(
to_the_3rd(n)
)
)
fun to_the_42nd(n :: Int): // n^27 * n^15 = n^42
to_the_27th(n) * to_the_15th(n)
check: to_the_15th(0)
~is 0
check: to_the_15th(2)
~is 32768
check: to_the_15th(17)
~is 2862423051509815793
check: to_the_15th(-3)
~is -14348907
check: to_the_27th(2)
~is 134217728
check: to_the_27th(7)
~is 65712362363534280139543
check: to_the_27th(-6)
~is -1023490369077469249536
check: to_the_42nd(0)
~is 0
check: to_the_42nd(1)
~is 1
check: to_the_42nd(17)
~is 4773695331839566234818968439734627784374274207965089
check: to_the_42nd(2)
~is 4398046511104
check: to_the_42nd(-5)
~is 227373675443232059478759765625
// Part 3
fun plural(str :: String):
cond
| string_length(str) == 0: "s" // I thought this should be empty string, but the submission test fails.
| string_get(str, string_length(str)-1) == #{#\y}: substring(str, 0, string_length(str)-1) +& "ies"
| ~else: str +& "s"
check: plural("baby")
~is "babies"
check: plural("fish")
~is "fishs"
check: plural("")
~is "s"
// Part 4
type Light
| bulb(watts :: Int,
technology :: Symbol)
| candle(inches :: Int)
fun electricity_usage(l :: Light):
match l:
| bulb(w, t): w * 24
| candle(i): 0
check: electricity_usage(bulb(100, #'halogen))
~is 2400
check: electricity_usage(bulb(0, #'free_energy))
~is 0
check: electricity_usage(bulb(-30, #'somehow_this_bulb_produces_energy))
~is -720
check: electricity_usage(candle(10))
~is 0
// Part 5
fun use_for_one_hour(l :: Light):
match l:
| bulb(w,t): l
| candle(i):
if i > 0
| candle(i-1)
| candle(0)
check: use_for_one_hour(bulb(100, #'halogen))
~is bulb(100, #'halogen)
check: use_for_one_hour(candle(10))
~is candle(9)
check: use_for_one_hour(candle(0))
~is candle(0)
|
HW1
Difficulty: ★★☆☆
The following Tree
datatype implements a binary tree with a number in each node and leaf:
type Tree
| leaf(val :: Int)
| node(val :: Int,
left :: Tree,
right :: Tree)
Part 1 — Sum
Implement a sum
function that takes a tree and returns the sum of the numbers in the tree.
Example:
sum(node(5, leaf(6), leaf(7)))
should produce 18
.
Part 2 — Negate
Implement the function negate
, which takes a tree and returns a tree that has the same shape, but with all the numbers negated.
Example:
negate(node(5, leaf(6), leaf(7)))
should produce node(-5, leaf(-6), leaf(-7))
.
Part 3 — Contains
Implement the function contains
, which takes a tree and a number and returns #true
if the number is in the tree, #false
otherwise.
Example:
contains(node(5, leaf(6), leaf(7)), 6)
should produce #true
.
Part 4 — Big Leaves?
Implement the function has_big_leaves
, which takes a tree and returns #true
if every leaf is bigger than the sum of numbers in the path of nodes from the root that reaches the leaf.
Examples:
has_big_leaves(node(5, leaf(6), leaf(7)))
should produce #true
, while has_big_leaves(node(5, node(2, leaf(8), leaf(6)), leaf(7)))
should produce #false
(since 6 is smaller than 5 plus 2).
The has_big_leaves
function should be a thin wrapper on another function, perhaps has_bigger_leaves
, that accumulates a sum of node values.
A tree that is just a leaf has no nodes between the leaf and the root, which means that the path from the root corresponds to an empty sequence of numbers. Conventionally, the sum of an empty sequence of numbers is defined to be 0
, because it’s convenient and consistent; that’s the intent here, too.
Part 5 — Positive Trees
Implement the function positive_trees
, which takes a list of trees and returns #true
if every member of the list is a positive tree, where a positive tree is one whose numbers sum to a positive value.
Hint 1: This function takes a list, not a tree, so don’t try to use the template for a tree function.
Hint 2: Use your sum
function as a helper.
Hint 3: positive_trees([])
should produce #true
, because there’s no tree in the empty list whose numbers sum to 0
or less.
More examples:
check: positive_trees(cons(leaf(6), []))
~is #true
check: positive_trees(cons(leaf(-6), []))
~is #false
check: positive_trees(cons(node(1, leaf(6), leaf(-6)), []))
~is #true
check: positive_trees(cons(node(1, leaf(6), leaf(-6)),
(cons(node(0, leaf(0), leaf(1)), []))))
~is #true
check: positive_trees(cons(node(-1, leaf(6), leaf(-6)),
cons(node(0, leaf(0), leaf(1)), [])))
~is #false
Part 6 — Optional challenge: Flatten
CS 3520 and CS 6520 students are welcome to complete the exercise, but it does not count for extra credit.
Implement the function flatten
, which takes a tree and returns a list that has all numbers in the tree’s nodes and leaves. The numbers should be ordered to match an inorder traversal of the tree, and a number that appears multiple times in the tree should appear the same number of times in the list.
Your function should run in time proportional to the size of the tree, which rules out making a list of the tree numbers using append
on recursive calls. You may find it helpful to recur on a right subtree before a left subtree.
Hint: Does the function take a list or a tree? Which template should you use?
Submit
Submissions in this class are directly supported by the programming environment, DrRacket. Submit your work using the handin button in DrRacket.
Solution
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
| #lang shplait
type Tree
| leaf(val :: Int)
| node(val :: Int,
left :: Tree,
right :: Tree)
// Part 1
fun sum(t :: Tree):
match t
| leaf(v): v
| node(v, l, r): v + sum(l) + sum(r)
check: sum(node(5, leaf(6), leaf(7)))
~is 18
check: sum(leaf(-10))
~is -10
check: sum(node(5, leaf(2), node(4, leaf(6), leaf(8))))
~is 5+2+4+6+8
// Part 2
fun negate(t :: Tree):
match t
| leaf(v): leaf(-v)
| node(v, l, r): node(-v, negate(l), negate(r))
check: negate(node(5, leaf(6), leaf(7)))
~is node(-5, leaf(-6), leaf(-7))
check: negate(leaf(-10))
~is leaf(10)
check: negate(node(5, leaf(2), node(4, leaf(6), leaf(8))))
~is node(-5, leaf(-2), node(-4, leaf(-6), leaf(-8)))
// Part 3
fun contains(t :: Tree, n :: Int):
match t
| leaf(v): v == n
| node(v, l, r): v == n || contains(l, n) || contains(r, n)
check: contains(node(5, leaf(6), leaf(7)), 6)
~is #true
check: contains(node(5, leaf(2), node(4, leaf(6), leaf(8))), 8)
~is #true
check: contains(node(5, leaf(2), node(4, leaf(6), leaf(8))), 3)
~is #false
check: contains(leaf(1), 2)
~is #false
// Part 4
fun has_bigger_leaves(t :: Tree, total :: Int):
match t
| leaf(v): v > total
| node(v, l, r): has_bigger_leaves(l, total + v) && has_bigger_leaves(r, total + v)
fun has_big_leaves(t :: Tree):
has_bigger_leaves(t, 0)
check: has_bigger_leaves(leaf(10), 9)
~is #true
check: has_bigger_leaves(leaf(10), 11)
~is #false
check: has_bigger_leaves(leaf(10), 10)
~is #false
check: has_bigger_leaves(node(5, node(2, leaf(8), leaf(6)), leaf(7)), 0)
~is #false
check: has_bigger_leaves(node(5, leaf(6), leaf(7)), 0)
~is #true
check: has_big_leaves(node(5, leaf(6), leaf(7)))
~is #true
check: has_big_leaves(node(5, node(2, leaf(8), leaf(6)), leaf(7)))
~is #false
check: has_big_leaves(leaf(5))
~is #true
// Part 5
fun positive_trees(ts :: Listof(Tree)):
match ts
| []: #true
| cons(t, rest_t): sum(t) > 0 && positive_trees(rest_t)
check: positive_trees(cons(leaf(6),
[]))
~is #true
check: positive_trees(cons(leaf(-6),
[]))
~is #false
check: positive_trees(cons(node(1, leaf(6), leaf(-6)),
[]))
~is #true
check: positive_trees(cons(node(1, leaf(6), leaf(-6)),
(cons(node(0, leaf(0), leaf(1)),
[]))))
~is #true
check: positive_trees(cons(node(-1, leaf(6), leaf(-6)),
cons(node(0, leaf(0), leaf(1)),
[])))
~is #false
// Part 6: Optional
fun flatten(t :: Tree):
match t
| node(v, l, r): append(append(flatten(l), cons(v, [])), flatten(r))
| leaf(v): cons(v, [])
check: flatten(leaf(0))
~is [0]
check: flatten(node(5, node(2, leaf(8), leaf(6)), leaf(7)))
~is [8, 2, 6, 5, 7]
check: flatten(node(6, node(4, node(2, leaf(1), leaf(3)), leaf(5)), node(8, leaf(7), node(10, leaf(9), leaf(11)))))
~is [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
|
HW2
Difficulty: ★★★☆
Part 1 — Maximum
Start with the interpreter with functions, and add a max
operator that takes two numbers and returns the larger of them. The syntax for max
should be similar to a function call.
Since you must change the Exp
datatype, and since different people may change it in different ways, you must update the parse
function, which accepts a syntax object and produces an Exp
value.
Examples:
check: interp(parse('max(1, 2)'),
[])
~is 2
check: interp(parse('max(4+5, 2+3)'),
[])
~is 9
Part 2 — Functions that Accept Multiple Arguments
Extend the interpreter to support multiple or zero arguments to a function, and multiple or zero arguments in a function call.
For example,
fun area(w, h): w * h
defines a function that takes two arguments, while
fun five(): 5
defines a function that takes zero arguments. Similarly,
area(3, 4)
calls the function area
with two arguments, while
five()
calls the function five
with zero arguments.
At run-time, a new error is now possible: function application with the wrong number of arguments. Your interp
function should detect the mismatch and report an error that includes the words “wrong arity”.
To support functions with multiple arguments, you’ll have to change fd
and appE
and all tests that use them. When you update the parse
function, note that match
supports ...
in a pattern to indicate zero or more repetitions of the preceding pattern, and so do templates, and syntax_to_list
recognizes []
-shaped syntax. So '$sym($arg, ...)'
matches any number of arguments, and syntax_to_list('[$arg, ...]')
converts the arguments into a list of syntax objects.
Beware of putting the multi-argument application pattern too early in parse
, since that pattern is likely to match other forms. In addition, you’ll need to update the parse_fundef
function that takes one quoted fun
form and produces a FunDef
value.
Preserve the starting behavior that an argument expression is always evaluated before it is passed to a function. That is, only numbers should be substituted into a function body for a function call.
Examples:
check: interp(parse('f(1, 2)'),
[parse_fundef('fun f(x, y): x + y')])
~is 3
check: interp(parse('f() + f()'),
[parse_fundef('fun f(): 5')])
~is 10
check: interp(parse('f(2, 3)'),
[parse_fundef('fun f(x, y): y + x + x')])
~is 7
check: interp(parse('f(1)'),
[parse_fundef('fun f(x, y): x+y')])
~raises "wrong arity"
Remember that Shplait provides map
, which takes a function and a list, and applies the function to each element in the list, returning a list of results. For example, if ars
is a list of syntax objects to parse, map(parse, ars)
produces a list of Exp
s by parsing each S-expression.
But also remember that map
doesn’t work for everything. Sometimes, when you have a list to process (or maybe two lists in parallel), then you need to write a new function using the template for lists.
If you get stuck here, then see HW 2 Hint (but you should try this part before you look at the hint).
Part 3 — Function Argument Checking (CS 6520)
This exercise is required only for CS 6520 students. CS 3520 students are welcome to complete the exercise, but it does not count for extra credit.
A function is ill-defined if two of its argument names are the same. To prevent this problem, update your parse_fundef
function to detect this problem and report a “bad syntax” error.
For example, parse_fundef('fun f(x, x): x')
must report a “bad syntax” error, while parse_fundef('fun f(x, y): x')
should produce a FunDef
value.
Solution
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
| #lang shplait
type Exp
| intE(n :: Int)
| idE(s :: Symbol)
| plusE(l :: Exp,
r :: Exp)
| multE(l :: Exp,
r :: Exp)
| appE(s :: Symbol,
args :: Listof(Exp))
| maxE(l :: Exp,
r :: Exp)
type FunDef
| fd(name :: Symbol,
args :: Listof(Symbol),
body :: Exp)
// An EXP is either
// - 'NUMBER'
// - 'SYMBOL'
// - 'EXP + EXP'
// - 'EXP * EXP'
// - 'SYMBOL(EXP)'
// - '(EXP)'
fun parse(s :: Syntax) :: Exp:
cond
| syntax_is_integer(s):
intE(syntax_to_integer(s))
| syntax_is_symbol(s):
idE(syntax_to_symbol(s))
| ~else:
match s
| '$left + $right':
plusE(parse(left),
parse(right))
| '$left * $right':
multE(parse(left),
parse(right))
| 'max($left, $right)':
maxE(parse(left),
parse(right))
| '$sym($arg, ...)':
appE(syntax_to_symbol(sym),
map(parse, syntax_to_list('[$arg, ...]')))
| '($e)':
parse(e)
| ~else:
error(#'parse, "invalid input: " +& s)
fun parse_fundef(s :: Syntax) :: FunDef:
match s
| 'fun $name($arg, ...): $body':
fd(syntax_to_symbol(name),
check_unique(map(syntax_to_symbol,
syntax_to_list('[$arg, ...]'))),
parse(body))
| ~else:
error(#'parse, "invalid input: " +& s)
module test:
check: parse('2')
~is intE(2)
check: parse('2 + 1')
~is plusE(intE(2), intE (1))
check: parse('3 * 4')
~is multE(intE(3), intE(4))
check: parse('3 * 4 + 8')
~is plusE(multE(intE(3), intE(4)),
intE(8))
check: parse('max(1, 9)')
~is maxE(intE(1), intE(9))
check: parse('double(9)')
~is appE(#'double, [intE(9)])
check: parse('1 + double(9)')
~is plusE(intE(1),
appE(#'double, [intE(9)]))
check: parse('3 * (4 + 8)')
~is multE(intE(3),
plusE(intE(4), intE(8)))
check: parse('1 2')
~raises "invalid input"
check: parse_fundef('fun double(x): x + x')
~is fd(#'double, [#'x], plusE(idE(#'x), idE(#'x)))
check: parse_fundef('fun f: 1')
~raises "invalid input"
def double_def = parse_fundef('fun double(x): x + x')
def quadruple_def = parse_fundef('fun quadruple(x): double(double(x))')
// check_unique ----------------------------------------
fun check_unique(syms :: Listof(Symbol)):
match syms
| []: []
| cons(sym, rst_syms):
if member(sym, rst_syms)
| error(#'parse, "bad syntax, duplicate argument: " +& sym)
| cons(sym, check_unique(rst_syms))
module test:
check: check_unique([])
~is []
check: check_unique([#'a, #'b, #'c])
~is [#'a, #'b, #'c]
check: check_unique([#'a, #'b, #'b])
~raises "bad syntax"
check: check_unique([#'a, #'b, #'a])
~raises "bad syntax"
// interp ----------------------------------------
fun interp(a :: Exp, defs :: Listof(FunDef)) :: Int:
match a
| intE(n): n
| idE(s): error(#'interp, "free variable: " +& s)
| plusE(l, r): interp(l, defs) + interp(r, defs)
| multE(l, r): interp(l, defs) * interp(r, defs)
| appE(s, args):
block:
def a_def = get_fundef(s, defs)
if length(fd.args(a_def)) == length(args)
| interp(substs(map(fun (arg): intE(interp(arg, defs)),
args),
fd.args(a_def),
fd.body(a_def)),
defs)
| error(#'interp, "wrong arity")
| maxE(l, r): max(interp(l, defs), interp(r, defs))
module test:
check: interp(parse('2'), [])
~is 2
check: interp(parse('x'), [])
~raises "free variable"
check: interp(parse('2 + 1'), [])
~is 3
check: interp(parse('2 * 1'), [])
~is 2
check: interp(parse('(2 * 3) + (5 + 8)'), [])
~is 19
check: interp(parse('max(2 * 3, 4 * 2)'), [])
~is 8
check: interp(parse('double(8)'),
[double_def])
~is 16
check: interp(parse('quadruple(8)'),
[double_def, quadruple_def])
~is 32
check: interp(parse('quadruple(8, 9)'),
[double_def, quadruple_def])
~raises "wrong arity"
// get_fundef ----------------------------------------
fun get_fundef(s :: Symbol, defs :: Listof(FunDef)) :: FunDef:
match defs
| []: error(#'get_fundef, "undefined function: " +& s)
| cons(a_def, rst_defs):
if s == fd.name(a_def)
| a_def
| get_fundef(s, rst_defs)
module test:
check: get_fundef(#'double, [double_def])
~is double_def
check: get_fundef(#'double, [double_def, quadruple_def])
~is double_def
check: get_fundef(#'double, [quadruple_def, double_def])
~is double_def
check: get_fundef(#'quadruple, [quadruple_def, double_def])
~is quadruple_def
check: get_fundef(#'double, [])
~raises "undefined function"
// subst ----------------------------------------
fun subst(what :: Exp, for :: Symbol, in :: Exp):
match in
| intE(n): in
| idE(s): (if for == s | what | in)
| plusE(l, r): plusE(subst(what, for, l),
subst(what, for, r))
| multE(l, r): multE(subst(what, for, l),
subst(what, for, r))
| appE(s, args): appE(s, map(fun (arg):
subst(what, for, arg),
args))
| maxE(l, r): maxE(subst(what, for, l),
subst(what, for, r))
module test:
check: subst(parse('8'), #'x, parse('9'))
~is parse('9')
check: subst(parse('8'), #'x, parse('x'))
~is parse('8')
check: subst(parse('8'), #'x, parse('y'))
~is idE(#'y)
check: subst(parse('8'), #'x, parse('x + y'))
~is plusE(parse('8'), idE(#'y))
check: subst(parse('8'), #'x, parse('y * x'))
~is multE(idE(#'y), parse('8'))
check: subst(parse('8'), #'x, parse('double(x)'))
~is parse('double(8)')
check: subst(parse('8'), #'x, parse('max(x, y)'))
~is parse('max(8, y)')
// subst ----------------------------------------
fun substs(whats :: Listof(Exp), fors :: Listof(Symbol), in :: Exp):
match whats
| []: in
| cons(what, rst_whats):
subst(what,
first(fors),
substs(rst_whats,
rest(fors),
in))
module test:
check: substs([], [], intE(0))
~is intE(0)
check: substs([parse('8'), parse('9')], [#'x, #'y], parse('x + y'))
~is parse('8 + 9')
|
HW3
Difficulty: ★★☆☆
Part 1 — Booleans
Start with the interpreter with function values, and extend the implementation to support boolean literals, an equality test, and a conditional form:
<Exp> = ....
| #true
| #false
| <Exp> == <Exp>
| if <Exp> | <Exp> | <Exp>
The ==
operator should only work on number values, and if
should only work when the value of the first subexpression is a boolean. Report a “not a number” error if a subexpression of ==
produces a non-number value, and report a “not a boolean” error when the first subexpression of if
does not produce a boolean. The if
form should evaluate its second subexpression only when the first subexpression’s value is true, and it should evaluate its third subexpression only when the first subexpression’s value is false. The precedence of ==
should be lower than +
, and if
should have the lowest precedence like let
.
Note that you not only need to extend Exp
with new kinds of expressions, you will also need to add booleans to Value
.
For example,
#true
should produce a true value, while
if #true
| 1 + 2
| 5
should produce 3, and
if 1
| 2
| 3
should report a “not a boolean” error.
As usual, update parse to support the extended language.
More examples:
check: interp(parse('1 + 2 == 3'),
mt_env)
~is interp(parse('#true'),
mt_env)
check: interp(parse('if 2 == 1 + 1
| 7
| 8'),
mt_env)
~is interp(parse('7'),
mt_env)
check: interp(parse('if #false
| 1 + (fun (x): x)
| 9'),
mt_env)
~is interp(parse('9'),
mt_env)
check: interp(parse('if #true
| 10
| 1 + (fun (x): x)'),
mt_env)
~is interp(parse('10'),
mt_env)
check: interp(parse('if 1 | 2 | 3'),
mt_env)
~raises "not a boolean"
Part 2 — Hiding Variables
Add an unlet
form (lowest precedence) that hides the nearest visible binding (if any) of a specified variable, but lets other bindings through. For example,
let x = 1:
unlet x:
x
should raise a “free variable” exception, but
let x = 1:
let x = 2:
unlet x:
x
should produce 1
. If there’s no visible binding of a variable around an unlet
of the variable, the unlet
does not hide anything (but it’s not an error).
As before, you must update the parse
function.
Examples:
check: interp(parse('let x = 1:
unlet x:
x'),
mt_env)
~raises "free variable"
check: interp(parse('let x = 1:
x + (unlet x:
1)'),
mt_env)
~is interp(parse('2'), mt_env)
check: interp(parse('let x = 1:
let x = 2:
x + (unlet x:
x)'),
mt_env)
~is interp(parse('3'), mt_env)
check: interp(parse('let x = 1:
let x = 2:
let z = 3:
x + (unlet x:
x + z)'),
mt_env)
~is interp(parse('6'), mt_env)
check: interp(parse('let f = (fun (z):
let z = 8:
unlet z:
z):
f(2)'),
mt_env)
~is interp(parse('2'), mt_env)
Part 3 — Thunks
A thunk is like a function of zero arguments, whose purpose is to delay a computation. Extend your interpreter with a delay
form that creates a thunk, and a force
form that causes a thunk’s expression to be evaluated:
<Exp> = ....
| delay: <Exp>
| force(<Exp>)
A thunk is a new kind of value, like a number, function, or boolean. For parsing, delay
has lowest precedence. The force
form has a precedence like function calls, but it should be preferred by the parser over a function-call form, so it can be matched just before a function-call form in parse
.
Examples:
delay: 1 + (fun (x): x)
produces a thunk value without complaining that a function is not a number, while
force(delay: 1 + (fun (x): x))
triggers a “not a number” error. As another example,
let ok = (delay: 1 + 2):
let bad = (delay: 1 + #false):
force(ok)
produces 3, while
let ok = (delay: 1 + 2):
let bad = (delay: 1 + #false):
force(bad)
triggers a “not a number” error.
More examples:
check: interp(parse('force(1)'),
mt_env)
~raises "not a thunk"
check: interp(parse('force(if 8 == 8 | (delay: 7) | (delay: 9))'),
mt_env)
~is interp(parse('7'),
mt_env)
check: interp(parse('let d = (let y = 8:
delay: y + 7):
let y = 9:
force(d)'),
mt_env)
~is interp(parse('15'),
mt_env)
Note: In some languages, delay
creates a promise. A promise is like a thunk, but when a promise is force
d multiple times, the result from the first time is remembered and returned all the other times, so that the expression in a promise is evaluated at most once. We’re not concerned with that facet of delay
and force
, for now.
Solution
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
| #lang shplait
type Value
| intV(n :: Int)
| closV(arg :: Symbol,
body :: Exp,
env :: Env)
| boolV(v :: Boolean)
// We need to remember what was in the environment when thunk was defined.
// This is so that later defined variables will not override free variables inside thunk
// So each thunk will have it's own saved snapshot of environment
| thunkV(v :: Exp, env :: Env)
type Exp
| boolE(v :: Boolean)
| compE(l :: Exp,
r :: Exp)
| intE(n :: Int)
| idE(s :: Symbol)
| plusE(l :: Exp,
r :: Exp)
| multE(l :: Exp,
r :: Exp)
| letE(n :: Symbol,
rhs :: Exp,
body :: Exp)
| unletE(n :: Symbol,
body :: Exp)
| funE(n :: Symbol,
body :: Exp)
| appE(fn :: Exp,
arg :: Exp)
| ifE(condition :: Exp,
trueE :: Exp,
falseE :: Exp)
| delayE(body :: Exp)
| forceE(body :: Exp)
type Binding
| bind(name :: Symbol,
val :: Value)
type Env = Listof(Binding)
def mt_env = []
def extend_env = cons
fun remove_env(name :: Symbol, env :: Env):: Env:
match env
| []: []
| cons(b, b_rest): if bind.name(b) == name:
| b_rest
| cons(b, remove_env(name, b_rest))
// parse ----------------------------------------
fun parse(s :: Syntax) :: Exp:
cond
| syntax_is_integer(s):
intE(syntax_to_integer(s))
| s == '#true':
boolE(#true)
| s == '#false':
boolE(#false)
| syntax_is_symbol(s):
idE(syntax_to_symbol(s))
| ~else:
match s
| 'delay: $body':
delayE(parse(body))
| 'force($body)':
forceE(parse(body))
| 'let $name = $rhs:
$body':
letE(syntax_to_symbol(name),
parse(rhs),
parse(body))
| 'unlet $name:
$body':
unletE(syntax_to_symbol(name),
parse(body))
| 'if $condition | $left | $right':
ifE(parse(condition), parse(left), parse(right))
| '$left == $right':
compE(parse(left),
parse(right))
| '$left + $right':
plusE(parse(left),
parse(right))
| '$left * $right':
multE(parse(left),
parse(right))
| 'fun ($id): $body':
funE(syntax_to_symbol(id),
parse(body))
| '$fn($arg)':
appE(parse(fn),
parse(arg))
| '($e)':
parse(e)
| ~else:
error(#'parse, "invalid input: " +& s)
module test:
check: parse('2')
~is intE(2)
check: parse('x')
~is idE(#'x)
check: parse('2 + 1')
~is plusE(intE(2), intE (1))
check: parse('3 * 4')
~is multE(intE(3), intE(4))
check: parse('3 * 4 + 8')
~is plusE(multE(intE(3), intE(4)),
intE(8))
check: parse('fun (x): 9')
~is funE(#'x, intE(9))
check: parse('double(9)')
~is appE(idE(#'double), intE(9))
check: parse('1 + double(9)')
~is plusE(intE(1),
appE(idE(#'double), intE(9)))
check: parse('3 * (4 + 8)')
~is multE(intE(3),
plusE(intE(4), intE(8)))
check: parse('let x = 1 + 2:
y')
~is letE(#'x,
plusE(intE(1), intE(2)),
idE(#'y))
check: parse('1 2')
~raises "invalid input"
check: parse('if 2 == 1 + 1
| 7
| 8')
~is ifE(compE(intE(2),plusE(intE(1),intE(1))), intE(7), intE(8))
// interp ----------------------------------------
fun interp(a :: Exp, env :: Env) :: Value:
match a
| intE(n): intV(n)
| boolE(v): boolV(v)
| compE(l, r): bool_equal(interp(l, env), interp(r, env))
| idE(s): lookup(s, env)
| plusE(l, r): num_plus(interp(l, env), interp(r, env))
| multE(l, r): num_mult(interp(l, env), interp(r, env))
| letE(n, rhs, body):
def rhs_v = interp(rhs, env)
interp(body,
extend_env(bind(n, rhs_v),
env))
| unletE(n, body):
interp(body,
remove_env(n, env))
| funE(n, body): closV(n, body, env)
| ifE(condition, l, r):
match interp(condition, env)
| boolV(v): if v:
| interp(l, env)
| interp(r, env)
| ~else: error(#'interp, "not a boolean")
| appE(fn, arg):
match interp(fn, env)
| closV(n, body, c_env):
interp(body,
extend_env(bind(n, interp(arg, env)),
c_env))
| ~else: error(#'interp, "not a function")
| forceE(body):
match interp(body, env)
| thunkV(e, thunk_env): interp(e, thunk_env)
| ~else: error(#'interp, "not a thunk")
| delayE(body):
thunkV(body, env)
module test:
check: interp(parse('2'), mt_env)
~is intV(2)
check: interp(parse('x'), mt_env)
~raises "free variable"
check: interp(parse('x'),
extend_env(bind(#'x, intV(9)), mt_env))
~is intV(9)
check: interp(parse('2 + 1'), mt_env)
~is intV(3)
check: interp(parse('2 * 1'), mt_env)
~is intV(2)
check: interp(parse('(2 * 3) + (5 + 8)'), mt_env)
~is intV(19)
check: interp(parse('fun (x): x + x'),
mt_env)
~is closV(#'x, plusE(idE(#'x), idE(#'x)), mt_env)
check: interp(parse('let x = 5: x + x'),
mt_env)
~is intV(10)
check: interp(parse('let x = 5:
let x = x + 1:
x + x'),
mt_env)
~is intV(12)
check: interp(parse('let x = 5:
let y = 6:
x'),
mt_env)
~is intV(5)
check: interp(parse('(fun (x): x + x)(8)'),
mt_env)
~is intV(16)
check: interp(parse('1(2)'), mt_env)
~raises "not a function"
check: interp(parse('1 + (fun (x): x)'), mt_env)
~raises "not a number"
check: interp(parse('let bad = (fun (x): x + y):
let y = 5:
bad(2)'),
mt_env)
~raises "free variable"
#//
time:
interp(parse('let x2 = (fun (n): n + n):
let x4 = (fun (n): x2(x2(n))):
let x16 = (fun (n): x4(x4(n))):
let x256 = (fun (n): x16(x16(n))):
let x65536 = (fun (n): x256(x256(n))):
x65536(1)'),
mt_env)
// num_plus and num_mult ----------------------------------------
fun num_op(op :: (Int, Int) -> Int, l :: Value, r :: Value) :: Value:
cond
| l is_a intV && r is_a intV:
intV(op(intV.n(l), intV.n(r)))
| ~else:
error(#'interp, "not a number")
fun num_plus(l :: Value, r :: Value) :: Value:
num_op(fun (a, b): a+b, l, r)
fun num_mult(l :: Value, r :: Value) :: Value:
num_op(fun (a, b): a*b, l, r)
fun bool_op(op :: (Int, Int) -> Boolean, l :: Value, r :: Value) :: Value:
cond
| l is_a intV && r is_a intV:
boolV(op(intV.n(l), intV.n(r)))
| ~else:
error(#'interp, "not a number")
fun bool_equal(l :: Value, r :: Value) :: Value:
bool_op(fun (a, b): a == b, l, r)
module test:
check: num_plus(intV(1), intV(2))
~is intV(3)
check: num_mult(intV(3), intV(2))
~is intV(6)
// lookup ----------------------------------------
fun lookup(n :: Symbol, env :: Env) :: Value:
match env
| []: error(#'lookup, "free variable")
| cons(b, rst_env):
cond
| n == bind.name(b):
bind.val(b)
| ~else:
lookup(n, rst_env)
module test:
check: lookup(#'x, mt_env)
~raises "free variable"
check: lookup(#'x, extend_env(bind(#'x, intV(8)), mt_env))
~is intV(8)
check: lookup(#'x, extend_env(bind(#'x, intV(9)),
extend_env(bind(#'x, intV(8)),
mt_env)))
~is intV(9)
check: lookup(#'y, extend_env(bind(#'x, intV(9)),
extend_env(bind(#'y, intV(8)),
mt_env)))
~is intV(8)
check: interp(parse('x + 3'),
extend_env(bind(#'x, intV(2)),
mt_env))
~is intV(5)
// Part 1
module test:
check: interp(parse('1 + 2 == 3'),
mt_env)
~is interp(parse('#true'),
mt_env)
check: interp(parse('if 2 == 1 + 1
| 7
| 8'),
mt_env)
~is interp(parse('7'),
mt_env)
check: interp(parse('if #false
| 1 + (fun (x): x)
| 9'),
mt_env)
~is interp(parse('9'),
mt_env)
check: interp(parse('if #true
| 10
| 1 + (fun (x): x)'),
mt_env)
~is interp(parse('10'),
mt_env)
check: interp(parse('if 1 | 2 | 3'),
mt_env)
~raises "not a boolean"
check: interp(parse('if #true == 5 | 6 | 7'),
mt_env)
~raises "not a number"
check: interp(parse('if (5 == 6) == 5 | 6 | 7'),
mt_env)
~raises "not a number"
// Part 2
module test:
check: interp(parse('let x = 1:
unlet x:
x'),
mt_env)
~raises "free variable"
check: interp(parse('let x = 1:
x + (unlet x:
1)'),
mt_env)
~is interp(parse('2'), mt_env)
check: interp(parse('let x = 1:
let x = 2:
x + (unlet x:
x)'),
mt_env)
~is interp(parse('3'), mt_env)
check: interp(parse('let x = 1:
let x = 2:
let z = 3:
x + (unlet x:
x + z)'),
mt_env)
~is interp(parse('6'), mt_env)
check: interp(parse('let f = (fun (z):
let z = 8:
unlet z:
z):
f(2)'),
mt_env)
~is interp(parse('2'), mt_env)
check: interp(parse('unlet x:
x'), mt_env)
~raises "free variable"
// Part 3
module test:
check: interp(parse('force(1)'), mt_env)
~raises "not a thunk"
check: interp(parse('force(if 8 == 8 | (delay: 7) | (delay: 9))'), mt_env)
~is interp(parse('7'), mt_env)
check: interp(parse('let d = (let y = 8:
delay: y + 7):
let y = 9:
force(d)'),
mt_env)
~is interp(parse('15'),
mt_env)
// Got some of these from discord
check: interp(parse('let add = (fun (y): delay: x+y):
let x = 5:
force(add(5) + 10)'), mt_env)
~raises "not a number"
check: interp(parse('let d = (let y = 1:
delay: x + y + 2):
let x = 3:
force(d)'), mt_env)
~raises "free variable"
check: interp(parse('delay: 1 + (fun (x): x)'), mt_env)
~is thunkV(plusE(intE(1), funE(#'x, idE(#'x))), mt_env)
check: interp(parse('let add = (fun (y): delay: x+y):
let x = 5:
force(add(5) + 10)'), mt_env)
~raises "not a number"
check: interp(parse('let x = (if 0 == 1 | 1 | 2):
3'), mt_env)
~is interp(parse('3'), mt_env)
check: interp(parse('let x = (if 0 | 1 | 2):
3'), mt_env)
~raises "not a boolean"
|
HW4
Difficulty: ★★★★
Part 1 — Improving Assignment
Start with store_reslet.rhm
. In the starting program, the representation of the store grows every time that a box’s content is modified with set_box
. Change the implementation of set_box
so that the old value of the box is dropped (i.e., replaced with the new value) instead of merely hidden by the outside-in search order of fetch
.
Example:
check: interp(parse('let b = box(1):
begin:
set_box(b, 2)
unbox(b)'),
mt_env,
mt_store)
~is res(intV(2),
override_store(cell(1, intV(2)),
mt_store))
Part 2 — Sequences
Generalize begin
to allow one or more sub-expressions, instead of exactly two sub-expressions.
<Exp> = ....
| begin:
<Exp>
<Exp>
...
Example:
check: interp(parse('let b = box(1):
begin:
set_box(b, 2 + unbox(b))
set_box(b, 3 + unbox(b))
set_box(b, 4 + unbox(b))
unbox(b)'),
mt_env,
mt_store)
~is res(intV(10),
override_store(cell(1, intV(10)),
mt_store))
A note on pattern matching: To match a begin
form with one or more expressions, each on its own line, you can use this Shplait pattern:
'begin:
$exp0
$exp
...'
After matching that pattern, exp0
stands for one line/group, and exp
is a sequence of additional lines/groups. Although the exp
repetition is created from separate lines, the repetition doesn’t have to be used in a template as different lines. You can use syntax_to_list('[$exp0, $exp, ...]')
to put the matches together in a list syntax object and convert it to a list of syntax objects.
A tip on dealing with nonempty lists: As you know, a list has two cases: []
and cons(<item>, <list>)
. A nonempty_list
has two different cases: it is always a cons pair and its rest may or may not be empty. A match
cannot easily follow the shape of nonempty lists (it needs a case for []
and a nested match), so we recommend cond
instead, which can check for the first nonempty-list case with the test is_empty(rest(nonempty_list))
.
Part 3 — Records
Extend the interpreter to support the construction of records with named fields, to support field selection from a record (as in record.rhm
):
<Exp> = ....
| {<Sym> : <Exp>, ...}
| <Exp> . <Sym>
Adding records means that the language now has four kinds of values: numbers, functions, boxes, and records. At run-time, an error may occur because a record is misused as a number or function, a number or function is supplied to .
, or a record supplied to .
does not have the named field, and so on. Your error message for the last case should include the words “no such field”, but beyond that constraint, you can make up your own error messages.
Expressions within a {}
form should be evaluated when the {}
form itself is evaluated, and in the order that the expressions appear in the {}
form. For example,
let b = box(0):
let r = { a: unbox(b) }:
begin:
set_box(b, 1)
r.a
should produce 0
, not 1
, because unbox(b)
is evaluated when the {}
expression is evaluated, not when the .
expression is evaluated.
Note that you will not be able to use map
to interp
field values, since a store must be carried from one field’s evaluation to the next. Instead, interp
ing the field value will be more like interp
ing a sequence of expressions for begin
.
You can assume (without checking) that all field names in a {}
form are distinct.
For homework purposes, we don’t want to nail down the representation of a record value, because there are many choices. The examples below therefore use interp_expr
, which you must define as a wrapper on interp
that takes just an Exp
and produces just a syntax object: a syntax number if interp
produces any number, the syntax object 'function'
if interp
produces a closure, the syntax object 'box'
if interp
produces a box, or the syntax object 'record'
if interp
produces a record value.
Examples:
check: interp_expr(parse('1 + 4'))
~is '5'
check: interp_expr(parse('{ a: 10, b: 1 + 2 }'))
~is 'record'
check: interp_expr(parse('{ a: 10, b: 1 + 2 }.b'))
~is '1'
check: interp_expr(parse('{ a: 10 }.b'))
~raises "no such field"
check: interp_expr(parse('{ r : { z: 0 } }.r'))
~is 'record'
check: interp_expr(parse('{ r : { z: 0 } }.r.z'))
~is '0'
check: interp_expr(parse('let b = box(0):
let r = { a: unbox(b) }:
begin:
set_box(b, 1)
r.a'))
~is '0'
This exercise is required for CS 6520 students, but it counts as extra credit for CS 3520 students.
Add an assignment form that modifies the value of a record field imperatively (as opposed to functional update):
<Exp> = ....
| <Exp> . <Sym> := <Exp>
Evaluation of a {}
expression allocates a location for each of its fields. A .
expression without :=
accesses from the record produced by the sub-expression the value in the location of the field named by the identifier. A .
with :=
form changes the value in the location for a field; the value of the sub-expression after :=
determines the field’s new value, and that value is also the result of the assignment expression.
Note: Making record fields mutable has the same effect as forcing every field of a record to be a Moe box, where the box contains the proper value of the field. Internal to the interpreter implementation, you could use Moe boxes in your implementation of mutable records, or you could use addresses more directly. You should not use Shplait boxes at all.
Examples:
check: interp_expr(parse('let r = { x: 1 }:
r.x'))
~is '1'
check: interp_expr(parse('let r = { x: 1 }:
begin:
r.x := 5
r.x'))
~is '5'
check: interp_expr(parse('let r = { x: 1 }:
let get_r = (fun (d): r):
begin:
(get_r(0)).x := 6
(get_r(0)).x'))
~is '6'
check: interp_expr(parse('let g = (fun (r): r.a:
let s = (fun (r): fun (v): r.b := v:
let r1 = { a: 0, b: 2 }:
let r2 = { a: 3, b: 4 }:
r1.b + (begin:
s(r1)(g(r2))
(begin:
s(r2)(g(r1))
r1.b) + r2.b)'))
~is '5'
Solution
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
| #lang shplait
type Location = Int
type Value
| intV(n :: Int)
| closV(arg :: Symbol,
body :: Exp,
env :: Env)
| boxV(l :: Location)
// took the second approach:
// Records are symbols to locations of store
| recV(ns :: Listof(Symbol),
vs :: Listof(Location))
type Exp
| intE(n :: Int)
| idE(s :: Symbol)
| plusE(l :: Exp,
r :: Exp)
| multE(l :: Exp,
r :: Exp)
| letE(n :: Symbol,
rhs :: Exp,
body :: Exp)
| funE(n :: Symbol,
body :: Exp)
| appE(fn :: Exp,
arg :: Exp)
| boxE(arg :: Exp)
| unboxE(arg :: Exp)
| setboxE(bx :: Exp,
val :: Exp)
| beginE(es :: Listof(Exp))
| recordE(s :: Listof(Symbol),
args :: Listof(Exp))
| getE(rec :: Exp,
n :: Symbol)
| setE(rec :: Exp,
n :: Symbol,
val :: Exp)
type Binding
| bind(name :: Symbol,
val :: Value)
type Env = Listof(Binding)
def mt_env = []
def extend_env = cons
type Storage
| cell(location :: Location,
val :: Value)
type Store = Listof(Storage)
def mt_store = []
def override_store = cons
type Result
| res(v :: Value,
s :: Store)
// parse ----------------------------------------
fun parse(s :: Syntax) :: Exp:
cond
| syntax_is_integer(s):
intE(syntax_to_integer(s))
| syntax_is_symbol(s):
idE(syntax_to_symbol(s))
| ~else:
match s
| '{ $name: $field_exp, ...}':
recordE(map(syntax_to_symbol, syntax_to_list('[$name, ...]')),
map(parse, syntax_to_list('[$field_exp, ...]')))
| 'let $name = $rhs:
$body':
letE(syntax_to_symbol(name),
parse(rhs),
parse(body))
| '$left + $right':
plusE(parse(left),
parse(right))
| '$left * $right':
multE(parse(left),
parse(right))
// Putting this before record.exp as this should match first
| '$rec . $name := $exp':
if syntax_is_symbol(name)
| setE(parse(rec), syntax_to_symbol(name), parse(exp))
| error(#'parse, "key must be symbol, got " +& exp +& " . " +& name)
| '$exp . $name':
if syntax_is_symbol(name)
| getE(parse(exp), syntax_to_symbol(name))
| error(#'parse, "key must be symbol, got " +& exp +& " . " +& name)
| 'fun ($id): $body':
funE(syntax_to_symbol(id),
parse(body))
| 'box($val_arg)':
boxE(parse(val_arg))
| 'unbox($bx_arg)':
unboxE(parse(bx_arg))
| 'set_box($bx_arg, $val_arg)':
setboxE(parse(bx_arg), parse(val_arg))
| '$fn($arg)':
appE(parse(fn),
parse(arg))
| 'begin:
$exp0
$exp
...':
beginE(map(parse, syntax_to_list('[$exp0, $exp, ...]')))
| '($e)':
parse(e)
| ~else:
error(#'parse, "invalid input: " +& s)
module test:
check: parse('2')
~is intE(2)
check: parse('x')
~is idE(#'x)
check: parse('2 + 1')
~is plusE(intE(2), intE (1))
check: parse('3 * 4')
~is multE(intE(3), intE(4))
check: parse('3 * 4 + 8')
~is plusE(multE(intE(3), intE(4)),
intE(8))
check: parse('fun (x): 9')
~is funE(#'x, intE(9))
check: parse('double(9)')
~is appE(idE(#'double), intE(9))
check: parse('1 + double(9)')
~is plusE(intE(1),
appE(idE(#'double), intE(9)))
check: parse('3 * (4 + 8)')
~is multE(intE(3),
plusE(intE(4), intE(8)))
check: parse('let x = 1 + 2:
y')
~is letE(#'x,
plusE(intE(1), intE(2)),
idE(#'y))
check: parse('box(0)')
~is boxE(intE(0))
check: parse('unbox(b)')
~is unboxE(idE(#'b))
check: parse('set_box(b, 0)')
~is setboxE(idE(#'b), intE(0))
check: parse('begin:
1
2')
~is beginE([intE(1), intE(2)])
check: parse('{ x: 2, y: 3 }')
~is recordE([#'x, #'y],
[intE(2), intE(3)])
check: parse('(1 + 2).a')
~is getE(plusE(intE(1), intE(2)), #'a)
check: parse('{ x: 2, y: 3 }')
~is recordE([#'x, #'y],
[intE(2), intE(3)])
check: parse('(1 + 2).a')
~is getE(plusE(intE(1), intE(2)), #'a)
check: parse('1 2')
~raises "invalid input"
// reslet form ----------------------------------------
macro 'reslet ($v_id, $sto_id) = $call:
$body':
'match $call
| res($v_id, $sto_id):
$body'
fun update_box(loc:: Location, val:: Value, store:: Store):: Store:
match store
| []: error(#'update_box, "location not found")
| cons(store, rest_store): match store
| cell(l, v): if l == loc
| cons(cell(l, val), rest_store)
| cons(store, update_box(loc, val, rest_store))
// This is a type that contains
// 1. Whats the last location that is added/updates
// 2. Current store
type LocationStore
| locstore(loc :: Location, sto :: Store)
// For a list of exps, create a new location in store for each exps and put the value in it. O(n)
// returns the list of store after each location added.
fun create_exp_store(es :: Listof(Exp), env :: Env, sto :: Store) :: Listof(LocationStore):
match es
| []: []
| cons(e, e_rst):
reslet (v_v, sto_v) = interp(e, env, sto):
block:
def l = new_loc(sto_v)
def new_sto = override_store(cell(l, v_v),
sto_v)
cons(locstore(l, new_sto), create_exp_store(e_rst, env, new_sto))
// interprets the record (O(n))
fun interp_rec(ns :: Listof(Symbol), es :: Listof(Exp), env :: Env, sto :: Store) :: Result:
if is_empty(ns)
| res(recV([], []), sto)
| block:
def locationStores = create_exp_store(es, env, sto)
def locs = map(fun (x): locstore.loc(x), locationStores)
def latest_sto = locstore.sto(last_of(locationStores))
res(recV(ns, locs), latest_sto)
// gets the last element of array (O(n))
fun last_of(items :: Listof(?a)):: ?a:
match items
| []: error(#'last_of, "list is empty")
| cons(i, i_rst): if is_empty(i_rst)
| i
| last_of(i_rst)
// interp ----------------------------------------
fun interp(a :: Exp, env :: Env, sto :: Store) :: Result:
match a
| intE(n): res(intV(n), sto)
| idE(s): res(lookup(s, env), sto)
| plusE(l, r): reslet (v_l, sto_l) = interp(l, env, sto):
reslet (v_r, sto_r) = interp(r, env, sto_l):
res(num_plus(v_l, v_r), sto_r)
| multE(l, r): reslet (v_l, sto_l) = interp(l, env, sto):
reslet (v_r, sto_r) = interp(r, env, sto_l):
res(num_mult(v_l, v_r), sto_r)
| letE(n, rhs, body):
reslet (v_rhs, sto_rhs) = interp(rhs, env, sto):
interp(body,
extend_env(bind(n, v_rhs),
env),
sto_rhs)
| funE(n, body): res(closV(n, body, env), sto)
| appE(fn, arg):
reslet (v_fn, sto_fn) = interp(fn, env, sto):
match v_fn
| closV(n, body, c_env):
reslet (v_arg, sto_arg) = interp(arg, env, sto_fn):
interp(body,
extend_env(bind(n, v_arg),
c_env),
sto_arg)
| ~else: error(#'interp, "not a function")
| boxE(a):
reslet (v, sto_v) = interp(a, env, sto):
block:
def l = new_loc(sto_v)
res(boxV(l),
override_store(cell(l, v),
sto_v))
| unboxE(a):
reslet (v, sto_v) = interp(a, env, sto):
match v
| boxV(l): res(fetch(l, sto_v),
sto_v)
| ~else: error(#'interp, "not a box")
| setboxE(bx, val):
reslet (b_b, sto_b) = interp(bx, env, sto):
reslet (v_v, sto_v) = interp(val, env, sto_b):
match b_b
| boxV(l):
res(v_v, update_box(l, v_v, sto_v))
| ~else: error(#'interp, "not a box")
| beginE(exps):
if length(exps) == 0
| error(#'interp, "begin expressions cannot be empty")
| reslet (v_e, sto_e) = interp(first(exps), env, sto):
if is_empty(rest(exps))
| res(v_e, sto_e)
| interp(beginE(rest(exps)), env, sto_e)
| recordE(ns, as):
interp_rec(ns, as, env, sto)
| getE(a, n):
reslet (v_v, sto_v) = interp(a, env, sto):
match v_v
| recV(ns, vs): res(fetch(find(n, ns, vs), sto_v),
sto_v)
| ~else: error(#'interp, "not a record")
| setE(rec, name, exp):
reslet (rec_v, sto_rec) = interp(rec, env, sto):
reslet (v_v, sto_v) = interp(exp, env, sto_rec):
res(v_v, mutateRecord(name,
v_v,
rec_v,
sto_v))
// Update the location with new value(v) in store from the name of record.
fun mutateRecord(name :: Symbol, v :: Value, rec :: Value, sto :: Store):: Store:
match sto
| []: error(#'mutateRecord, "record is empty: " +& name)
| cons(c1, c_rst): match c1
| cell(li, vali): if li == find(name, recV.ns(rec), recV.vs(rec))
| cons(cell(li, v), c_rst)
| cons(cell(li, vali), mutateRecord(name, v, rec, c_rst))
// find & update ----------------------------------------
// Takes a name and two parallel lists, returning an item from the
// second list where the name matches the item from the first list.
// I updated this to get any second type of parallel list
fun find(n :: Symbol,
ns :: Listof(Symbol),
vs :: Listof(?a)) :: ?a:
match ns
| []: error(#'interp, "no such field: " +& n)
| cons(ns_n, ns_rst): if n == ns_n
| first(vs)
| find(n, ns_rst, rest(vs))
module test:
check: find(#'a, [#'a, #'b], [intV(1), intV(2)])
~is intV(1)
check: find(#'b, [#'a, #'b], [intV(1), intV(2)])
~is intV(2)
check: find(#'a, [], [])
~raises "no such field"
module test:
check: interp(parse('2'), mt_env, mt_store)
~is res(intV(2), mt_store)
check: interp(parse('x'), mt_env, mt_store)
~raises "free variable"
check: interp(parse('x'),
extend_env(bind(#'x, intV(9)), mt_env),
mt_store)
~is res(intV(9), mt_store)
check: interp(parse('2 + 1'), mt_env, mt_store)
~is res(intV(3), mt_store)
check: interp(parse('2 * 1'), mt_env, mt_store)
~is res(intV(2), mt_store)
check: interp(parse('(2 * 3) + (5 + 8)'), mt_env, mt_store)
~is res(intV(19), mt_store)
check: interp(parse('fun (x): x + x'),
mt_env,
mt_store)
~is res(closV(#'x, plusE(idE(#'x), idE(#'x)), mt_env),
mt_store)
check: interp(parse('let x = 5: x + x'),
mt_env,
mt_store)
~is res(intV(10), mt_store)
check: interp(parse('let x = 5:
let x = x + 1:
x + x'),
mt_env,
mt_store)
~is res(intV(12), mt_store)
check: interp(parse('let x = 5:
let y = 6:
x'),
mt_env,
mt_store)
~is res(intV(5), mt_store)
check: interp(parse('(fun (x): x + x)(8)'),
mt_env,
mt_store)
~is res(intV(16), mt_store)
check: interp(parse('box(5)'),
mt_env,
mt_store)
~is res(boxV(1),
override_store(cell(1, intV(5)),
mt_store))
check: interp(parse('unbox(box(5))'),
mt_env,
mt_store)
~is res(intV(5),
override_store(cell(1, intV(5)),
mt_store))
check: interp(parse('set_box(box(5), 6)'),
mt_env,
mt_store)
~is res(intV(6),
override_store(cell(1, intV(6)),
mt_store))
check: interp(parse('begin:
1
2'),
mt_env,
mt_store)
~is res(intV(2), mt_store)
check: interp(parse('let b = box(5):
begin:
set_box(b, 6)
unbox(b)'),
mt_env,
mt_store)
~is res(intV(6),
override_store(cell(1, intV(6)),
mt_store))
check: interp(parse('1(2)'), mt_env, mt_store)
~raises "not a function"
check: interp(parse('1 + (fun (x): x)'), mt_env, mt_store)
~raises "not a number"
check: interp(parse('let bad = (fun (x): x + y):
let y = 5:
bad(2)'),
mt_env,
mt_store)
~raises "free variable"
check: interp(parse('unbox(2)'), mt_env, mt_store)
~raises "not a box"
check: interp(parse('set_box(2, 3)'), mt_env, mt_store)
~raises "not a box"
// num_plus and num_mult ----------------------------------------
fun num_op(op :: (Int, Int) -> Int, l :: Value, r :: Value) :: Value:
cond
| l is_a intV && r is_a intV:
intV(op(intV.n(l), intV.n(r)))
| ~else:
error(#'interp, "not a number")
fun num_plus(l :: Value, r :: Value) :: Value:
num_op(fun (a, b): a+b, l, r)
fun num_mult(l :: Value, r :: Value) :: Value:
num_op(fun (a, b): a*b, l, r)
module test:
check: num_plus(intV(1), intV(2))
~is intV(3)
check: num_mult(intV(3), intV(2))
~is intV(6)
// lookup ----------------------------------------
fun lookup(n :: Symbol, env :: Env) :: Value:
match env
| []: error(#'lookup, "free variable: " +& n)
| cons(b, rst_env):
cond
| n == bind.name(b):
bind.val(b)
| ~else:
lookup(n, rst_env)
module test:
check: lookup(#'x, mt_env)
~raises "free variable"
check: lookup(#'x, extend_env(bind(#'x, intV(8)), mt_env))
~is intV(8)
check: lookup(#'x, extend_env(bind(#'x, intV(9)),
extend_env(bind(#'x, intV(8)),
mt_env)))
~is intV(9)
check: lookup(#'y, extend_env(bind(#'x, intV(9)),
extend_env(bind(#'y, intV(8)),
mt_env)))
~is intV(8)
// store operations ----------------------------------------
fun new_loc(sto :: Store) :: Location:
1 + max_address(sto)
fun max_address(sto :: Store) :: Location:
match sto
| []: 0
| cons(c, rst_sto): max(cell.location(c),
max_address(rst_sto))
fun fetch(l :: Location, sto :: Store) :: Value:
match sto
| []: error(#'interp, "unallocated location")
| cons(c, rst_sto): if l == cell.location(c)
| cell.val(c)
| fetch(l, rst_sto)
module test:
check: new_loc(mt_store)
~is 1
check: max_address(mt_store)
~is 0
check: max_address(override_store(cell(2, intV(9)),
mt_store))
~is 2
check: fetch(2, override_store(cell(2, intV(9)),
mt_store))
~is intV(9)
check: fetch(2, override_store(cell(2, intV(10)),
override_store(cell(2, intV(9)),
mt_store)))
~is intV(10)
check: fetch(3, override_store(cell(2, intV(10)),
override_store(cell(3, intV(9)),
mt_store)))
~is intV(9)
check: fetch(2, mt_store)
~raises "unallocated location"
// Part 1
module test:
check: update_box(2, intV(1),
override_store(cell(1, intV(10)), mt_store))
~raises "location not found"
check: interp(parse('let b1 = box(1):
let b2 = box(2):
let b3 = box(3):
begin:
begin:
set_box(b3, 1)
set_box(b2, 3)
set_box(b1, 2)'),
mt_env, mt_store)
~is res(intV(2),
override_store(cell(3, intV(1)),
override_store(cell(2, intV(3)),
override_store(cell(1, intV(2)),
mt_store))))
check: interp(parse('let b = box(1):
begin:
set_box(b, 2)
unbox(b)'),
mt_env,
mt_store)
~is res(intV(2),
override_store(cell(1, intV(2)),
mt_store))
// Part 2
module test:
// This syntax 'abc:' is not even allowed in shplait.
// So passing a PARSED expression type to interp
check: interp(beginE([]), mt_env, mt_store)
~raises "begin expressions cannot be empty"
check: parse('begin:
1
2
3
4
5')
~is beginE([intE(1), intE(2), intE(3), intE(4), intE(5)])
check: interp(parse('let b = box(1):
begin:
set_box(b, 2 + unbox(b))
set_box(b, 3 + unbox(b))
set_box(b, 4 + unbox(b))
unbox(b)'),
mt_env,
mt_store)
~is res(intV(10),
override_store(cell(1, intV(10)),
mt_store))
check: interp(parse('5 . x'), mt_env, mt_store)
~raises "not a record"
check: last_of([1, 2, 3, 4])
~is 4
check: last_of([])
~raises "list is empty"
// Part 3
fun interp_expr(e :: Exp):: Syntax:
reslet (v, sto) = interp(e, mt_env, mt_store):
match v
| intV(val): integer_to_syntax(val)
| closV(arg, body, env): 'function'
| boxV(l): 'box'
| recV(ns, as): 'record'
module test:
check: interp_expr(parse('1 + 4'))
~is '5'
check: interp_expr(parse('{ a: 10 }.10'))
~raises "key must be symbol"
check: interp_expr(parse('{ a: 10, b: 1 + 2 }'))
~is 'record'
check: interp_expr(parse('{ a: 10, b: 1 + 0 }.b'))
~is '1'
check: interp_expr(parse('{ a: 10 }.b'))
~raises "no such field"
check: interp_expr(parse('{ r : { z: 0 } }.r'))
~is 'record'
check: interp_expr(parse('{ r : { z: 0 } }.r.z'))
~is '0'
check: interp_expr(parse('let b = box(0):
let r = { a: unbox(b) }:
begin:
set_box(b, 1)
r.a'))
~is '0'
check: interp_expr(parse('(fun (x): x + 2)'))
~is 'function'
check: interp_expr(parse('box(1)'))
~is 'box'
// Part 4
module test:
check: interp_expr(parse('let r = { x: 1 }:
r.x'))
~is '1'
check: interp_expr(parse('let r = { x: 1 }:
begin:
r.x := 5
r.x'))
~is '5'
check: interp_expr(parse('let r = { x: 1 }:
let get_r = (fun (d): r):
begin:
(get_r(0)).x := 6
(get_r(0)).x'))
~is '6'
check: interp_expr(parse('let g = (fun (r): r.a):
let s = (fun (r): fun (v): r.b := v):
let r1 = { a: 0, b: 2 }:
let r2 = { a: 3, b: 4 }:
r1.b + (begin:
s(r1)(g(r2))
(begin:
s(r2)(g(r1))
r1.b) + r2.b)'))
~is '5'
check: interp_expr(parse('{ x:10, y:20 }.z := 30'))
~raises "no such field"
check: interp_expr(parse('{ x:10 }.5 := 5'))
~raises "key must be symbol"
check: interp_expr(parse('{}'))
~is 'record'
check: interp_expr(parse('{}.a := 5'))
~raises "record is empty"
|
HW5
Difficulty: ★★☆☆
Start with lambda_if0.rhm, which doesn’t already include recursive binding and also doesn’t include *
for multiplication.
Part 1 — Syntactic Sugar for Recursive Bindings
Extend the parse
function so that it supports a letrec
form for recursive function bindings. The parsing precedence for letrec
should be lowest.
<Exp> = ...
| letrec <Symbol> = <Exp>:
<Exp>
You must not change the interp
function at all.
The Encoding Recursion slides spell out how to extend the parser to make letrec
work, especially at the end of part 4. You may find the following definition useful:
def mk_rec_fun:
'(fun (body_proc):
let fX = (fun (fX):
let f = (fun (x):
fX(fX)(x)):
body_proc(f)):
fX(fX))'
The above definition makes sense only if you can keep track of different languages and how they interact. The mk_rec_fun
definition above is a Shplait definition. The value of mk_rec_fun
is a representation of the concrete syntax of a Moe expression. If you pass mk_rec_fun
to parse
, you get a Shplait value that is an interp
retable representation of a Moe expression.
Example:
check: interp(parse('letrec f = (fun (n):
if n == 0
| 0
| f(n + -1) + -1):
f(10)'),
mt_env)
~is intV(-10)
Note on testing: Normally, when you modify parse
, you should add an example/test to illustrate and check the addition. This exercise, however, is one of the rare cases where a parse
example/test doesn’t really help, because the expected value for even a simple input is unwieldy, and a test can do little more than repeat the implementation. So, you are excused from adding a parse
test in this case. An interp
test like the one above is more useful.
Part 2 — Implementing a Two-Argument Function in Moe
Define the Shplait constant plus
as a representation of the concrete syntax of a Moe expression such that
interp(parse('($plus)(n)(m)'), mt_env)
produces the same value as
interp(parse('n + m'), mt_env)
for any Moe numbers n
and m
.
In other words, you add a Shplait definition:
def plus = 'fun ....'
to the interpreter program, replacing the ....
with something that creates the desired Moe function.
You should not change the interp
or parse
function for this part.
The suggested shape of plus
above uses parentheses around the function. Those parentheses are not required, but they avoid errors if you try using $plus
in a template and omit parentheses around the use. We will only try your plus
in a context with parentheses around it, though, like in '($plus)(n)(m)'
.
Part 3 — Implementing a Recursive Function in Moe
Define the Shplait constant times
such that
interp(parse('($times)(n)(m)'), mt_env)
produces the same value as
intV(n * m)
for any non-negative integers n
and m
.
You should not change the interp
or parse
function for this part.
Solution
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
| #lang shplait
type Value
| intV(n :: Int)
| closV(arg :: Symbol,
body :: Exp,
env :: Env)
type Exp
| intE(n :: Int)
| idE(s :: Symbol)
| plusE(l :: Exp,
r :: Exp)
| funE(n :: Symbol,
body :: Exp)
| appE(fn :: Exp,
arg :: Exp)
| if0E(tst :: Exp,
thn :: Exp,
els :: Exp)
type Binding
| bind(name :: Symbol,
val :: Value)
type Env = Listof(Binding)
def mt_env = []
def extend_env = cons
// parse ----------------------------------------
fun parse(s :: Syntax) :: Exp:
cond
| syntax_is_integer(s):
intE(syntax_to_integer(s))
| syntax_is_symbol(s):
idE(syntax_to_symbol(s))
| ~else:
match s
// For being the lowest precedence, putting it in top
| 'letrec $name = $rhs:
$body':
// No need to convolute the environment with variables
// So letting mk_rec_fun here (also suggested not to edit anything except parse())
let mk_rec_fun = '(fun (body_proc):
let fX = (fun (fX):
let f = (fun (x):
fX(fX)(x)):
body_proc(f)):
fX(fX))':
parse('let $(name) = $(mk_rec_fun)(fun ($(name)):
$(rhs)):
$(body)')
| 'if $tst == 0
| $thn
| $els':
if0E(parse(tst), parse(thn), parse(els))
| 'let $name = $rhs:
$body':
appE(funE(syntax_to_symbol(name),
parse(body)),
parse(rhs))
| '$left + $right':
plusE(parse(left),
parse(right))
| 'fun ($id): $body':
funE(syntax_to_symbol(id),
parse(body))
| '$fn($arg)':
appE(parse(fn),
parse(arg))
| '($e)':
parse(e)
| ~else:
error(#'parse, "invalid input: " +& s)
module test:
check: parse('2')
~is intE(2)
check: parse('x')
~is idE(#'x)
check: parse('2 + 1')
~is plusE(intE(2), intE (1))
check: parse('fun (x): 9')
~is funE(#'x, intE(9))
check: parse('double(9)')
~is appE(idE(#'double), intE(9))
check: parse('1 + double(9)')
~is plusE(intE(1),
appE(idE(#'double), intE(9)))
check: parse('3 + (4 + 8)')
~is plusE(intE(3),
plusE(intE(4), intE(8)))
check: parse('let x = 1 + 2:
y')
~is appE(funE(#'x,
idE(#'y)),
plusE(intE(1), intE(2)))
check: parse('if 1 == 0 | 2 | 3')
~is if0E(intE(1), intE(2), intE(3))
check: parse('1 2')
~raises "invalid input"
// interp ----------------------------------------
fun interp(a :: Exp, env :: Env) :: Value:
match a
| intE(n): intV(n)
| idE(s): lookup(s, env)
| plusE(l, r): num_plus(interp(l, env), interp(r, env))
| funE(n, body): closV(n, body, env)
| appE(fn, arg):
match interp(fn, env)
| closV(n, body, c_env):
interp(body,
extend_env(bind(n, interp(arg, env)),
c_env))
| ~else: error(#'interp, "not a function")
| if0E(tst, thn, els):
interp(if num_is_zero(interp(tst, env))
| thn
| els,
env)
module test:
check: interp(parse('2'), mt_env)
~is intV(2)
check: interp(parse('x'), mt_env)
~raises "free variable"
check: interp(parse('x'),
extend_env(bind(#'x, intV(9)), mt_env))
~is intV(9)
check: interp(parse('2 + 1'), mt_env)
~is intV(3)
check: interp(parse('(2 + 3) + (5 + 8)'), mt_env)
~is intV(18)
check: interp(parse('fun (x): x + x'),
mt_env)
~is closV(#'x, plusE(idE(#'x), idE(#'x)), mt_env)
check: interp(parse('let x = 5: x + x'),
mt_env)
~is intV(10)
check: interp(parse('let x = 5:
let x = x + 1:
x + x'),
mt_env)
~is intV(12)
check: interp(parse('let x = 5:
let y = 6:
x'),
mt_env)
~is intV(5)
check: interp(parse('(fun (x): x + x)(8)'),
mt_env)
~is intV(16)
check: interp(parse('if 0 == 0 | 2 | 3'),
mt_env)
~is intV(2)
check: interp(parse('if 1 == 0 | 2 | 3'),
mt_env)
~is intV(3)
check: interp(parse('1(2)'), mt_env)
~raises "not a function"
check: interp(parse('1 + (fun (x): x)'), mt_env)
~raises "not a number"
check: interp(parse('let bad = (fun (x): x + y):
let y = 5:
bad(2)'),
mt_env)
~raises "free variable"
check: interp(parse('if (fun (x): x) == 0 | 2 | 3'),
mt_env)
~raises "not a number"
// num_plus ----------------------------------------
fun num_op(op :: (Int, Int) -> Int, l :: Value, r :: Value) :: Value:
cond
| l is_a intV && r is_a intV:
intV(op(intV.n(l), intV.n(r)))
| ~else:
error(#'interp, "not a number")
fun num_plus(l :: Value, r :: Value) :: Value:
num_op(fun (a, b): a+b, l, r)
fun num_is_zero(v :: Value) :: Boolean:
match v
| intV(n): n == 0
| ~else: error(#'interp, "not a number")
module test:
check: num_plus(intV(1), intV(2))
~is intV(3)
check: num_is_zero(intV(0))
~is #true
check: num_is_zero(intV(1))
~is #false
// lookup ----------------------------------------
fun lookup(n :: Symbol, env :: Env) :: Value:
match env
| []: error(#'lookup, "free variable: " +& n)
| cons(b, rst_env):
cond
| n == bind.name(b):
bind.val(b)
| ~else:
lookup(n, rst_env)
module test:
check: lookup(#'x, mt_env)
~raises "free variable"
check: lookup(#'x, extend_env(bind(#'x, intV(8)), mt_env))
~is intV(8)
check: lookup(#'x, extend_env(bind(#'x, intV(9)),
extend_env(bind(#'x, intV(8)),
mt_env)))
~is intV(9)
check: lookup(#'y, extend_env(bind(#'x, intV(9)),
extend_env(bind(#'y, intV(8)),
mt_env)))
~is intV(8)
// Part 1
module test:
check: interp(parse('letrec f = (fun (n):
if n == 0
| 0
| f(n + -1) + -1):
f(10)'),
mt_env)
~is intV(-10)
check: interp(parse('letrec sum = (fun (n):
if n == 0
| 0
| n + sum(n + -1)):
sum(10)'),
mt_env)
~is intV(55)
check: interp(parse('letrec fac = (fun (n):
if n == 0
| 1
| ($times)(n)(fac(n + -1))):
fac(5)'),
mt_env)
~is intV(120)
// Part 2
def plus = '(fun (x):
fun (y):
x + y)'
module test:
check: interp(parse('($plus)(5)(10)'), mt_env)
~is interp(parse('5 + 10'), mt_env)
check: interp(parse('($plus(0)(0))'), mt_env)
~is intV(0)
// Part 3
def times = 'letrec multiply = (fun (n):
fun (m):
if m == 0
| 0
| n + multiply(n)(m + -1)):
multiply'
module test:
check: interp(parse('($times)(10)(20)'), mt_env)
~is intV(10 * 20)
check: interp(parse('($times)(0)(20)'), mt_env)
~is intV(0)
check: interp(parse('($times)(20)(0)'), mt_env)
~is intV(0)
check: interp(parse('($times)(0)(0)'), mt_env)
~is intV(0)
|
HW6
Difficulty: ★★☆☆
Part 1 — Conditional and Lazy Pairs
Implement an interpreter with lazy evaluation and the following grammar:
<Exp> = <Number>
| <Symbol>
| <Exp> + <Exp>
| <Exp> * <Exp>
| fun (<Symbol>): <Exp>
| <Exp>(<Exp>)
| let <Symbol> = <Exp>: <Exp>
| (<Exp>)
| if <Exp> == 0 | <Exp> | <Exp>
| pair(<Exp>, <Exp>)
| fst(<Exp>)
| snd(<Exp>)
That is, a language with single-argument functions and application, an if-zero conditional, and pair, fst, and snd operations. (The language does not include recursive bindings or records.) Unlike cons, the pair operation does not require its second argument to be a list (and we do not have an empty-list value, anyway). The if form has weakest precedence. The pair, fst, and snd forms have a precedence like function calls, but should be preferred by the parser over a function-call form, so they can be matched just before a function-call form in parse.
Implement your interpreter with the eager Shplait language, not a lazy language.
Evaluation of the interpreted langauge must be lazy. In particular, if a function never uses the value of an argument, then the argument expression should not be evaluated. Similarly, if the first or second part of a pair is never needed, then the first or second expression should not be evaluated. Even if the first or second part of a particular pair is used multiple times, the expression to compute the first or second part should be evaluated only once.
From the starting code, expand the parse function to support the new forms: if, pair, fst, and snd. Also, as in HW 4, provide an interp_expr function; the interp_expr wrapper for interp should take an expression and return either a number syntax object, ‘function’ for a function result, or ‘pair’ for a pair result. (Meanwhile, the interp function should never return the syntax object ‘pair’, just like the starting interp function never returns the syntax object ‘function’.) Note that pair results must be distinct from function results, so you will need to modify interp and not just use encodings via parse.
check: interp_expr(parse('10'))
~is '10'
check: interp_expr(parse('10 + 17'))
~is '27'
check: interp_expr(parse('10 * 7'))
~is '70'
check: interp_expr(parse('(fun (x): x + 12)(1 + 17)'))
~is '30'
check: interp_expr(parse('let x = 0:
let f = (fun (y): x + y):
f(1) + (let x = 3:
f(2))'))
~is '3'
check: interp_expr(parse('if 0 == 0 | 1 | 2'))
~is '1'
check: interp_expr(parse('if 1 == 0 | 1 | 2'))
~is '2'
check: interp_expr(parse('pair(1, 2)'))
~is 'pair'
check: interp_expr(parse('fst(pair(1, 2))'))
~is '1'
check: interp_expr(parse('snd(pair(1, 2))'))
~is '2'
check: interp_expr(parse('let p = pair(1, 2):
fst(p) + snd(p)'))
~is '3'
check: interp_expr(parse('let f = (fun (x):
pair(x, x)):
fst(f(3)) + snd(f(4))'))
~is '7'
// Lazy evaluation:
check: interp_expr(parse('(fun (x): 0)(1 + (fun (y): y))'))
~is '0'
check: interp_expr(parse('let x = (1 + (fun (y): y)):
0'))
~is '0'
check: interp_expr(parse('fst(pair(3,
1 + (fun (y): y)))'))
~is '3'
check: interp_expr(parse('snd(pair(1 + (fun (y): y),
4))'))
~is '4'
check: interp_expr(parse('fst(pair(5,
// Infinite loop:
(fun (x): x(x))(fun (x): x(x))))'))
~is '5'
check: interp_expr(
parse(
// Use call-by-name mkrec, which
// is simpler than call-by-value:
'let mkrec = (fun (body_proc):
let fX = (fun (fX):
body_proc(fX(fX))):
fX(fX)):
let fib = mkrec(fun (fib):
// Fib:
fun (n):
if n == 0
| 1
| if (n + -1) == 0
| 1
| fib(n + -1) + fib(n + -2)):
// Call fib on 4:
fib(4)'
)
)
~is '5'
check: interp_expr(
parse(
// Use call-by-name mkrec, which
// is simpler than call-by-value:
'let mkrec = (fun (body_proc):
let fX = (fun (fX):
body_proc(fX(fX))):
fX(fX)):
let nats_from = mkrec(fun (nats_from):
// nats-from:
fun (n):
pair(n, nats_from(n + 1))):
let list_ref = mkrec(fun (list_ref):
// list_ref:
fun (n):
fun (l):
if n == 0
| fst(l)
| list_ref(n + -1)(snd(l))):
// Call list-ref on infinite list:
list_ref(4)(nats_from(2))'
)
)
~is '6'
Part 2 — Stress Test
There’s no new implementation for this part. Just make sure that your interpreter is able to run the following two tests within a few seconds. If it takes tens of seconds or even more than a minute, then your interpreter is not lazy enough—probably because it re-interps the expression for a pair’s first or second part every time the first or second part is accessed, which turns the linear-time program into a quadratic-time program.
The handin server will not run this test, but we will check your implementation against this test when grading. It must produce the right answer within a few seconds.
check: interp_expr(
parse(
'let mkrec = (fun (body_proc):
let fX = (fun (fX):
body_proc(fX(fX))):
fX(fX)):
let nats_to = mkrec(fun (nats_to):
fun (n):
if n == 0
| pair(0, 0)
| let l = nats_to(n + -1):
pair(fst(l) + 1,
l)):
let sum = mkrec(fun (sum):
fun (n):
fun (l):
if n == 0
| 0
| fst(l) + sum(n + -1)(snd(l))):
sum(10000)(nats_to(10000))'
)
)
~is '50005000'
check: interp_expr(
parse(
'let mkrec = (fun (body_proc):
let fX = (fun (fX):
body_proc(fX(fX))):
fX(fX)):
let nats_to = mkrec(fun (nats_to):
fun (n):
if n == 0
| pair(0, 0)
| let l = nats_to(n + -1):
pair(l,
snd(l) + 1)):
let sum = mkrec(fun (sum):
fun (n):
fun (l):
if n == 0
| 0
| snd(l) + sum(n + -1)(fst(l))):
sum(10000)(nats_to(10000))'
)
)
~is '50005000'
Make sure your implementation handles these tests efficiently.
Solution
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
| #lang shplait
type Value
| intV(n :: Int)
| closV(arg :: Symbol,
body :: Exp,
env :: Env)
| pairV(p1 :: Thunk,
p2 :: Thunk)
type Thunk
| delay(arg :: Exp,
env :: Env,
done :: Boxof(Optionof(Value)))
type Exp
| intE(n :: Int)
| idE(s :: Symbol)
| plusE(l :: Exp,
r :: Exp)
| multE(l :: Exp,
r :: Exp)
| funE(n :: Symbol,
body :: Exp)
| appE(fn :: Exp,
arg :: Exp)
| if0E(e :: Exp,
true :: Exp,
false :: Exp)
| pairE(e1 :: Exp,
e2 :: Exp)
| fstE(p :: Exp)
| sndE(p :: Exp)
type Binding
| bind(name :: Symbol,
val :: Thunk)
type Env = Listof(Binding)
def mt_env = []
def extend_env = cons
// parse ----------------------------------------
fun parse(s :: Syntax) :: Exp:
cond
| syntax_is_integer(s):
intE(syntax_to_integer(s))
| syntax_is_symbol(s):
idE(syntax_to_symbol(s))
| ~else:
match s
| 'if $exp == 0 | $true | $false':
if0E(parse(exp), parse(true), parse(false))
| 'let $name = $rhs:
$body':
appE(funE(syntax_to_symbol(name),
parse(body)),
parse(rhs))
| '$left + $right':
plusE(parse(left),
parse(right))
| '$left * $right':
multE(parse(left),
parse(right))
| 'fun ($id): $body':
funE(syntax_to_symbol(id),
parse(body))
| 'pair($e1, $e2)':
pairE(parse(e1), parse(e2))
| 'fst($p)':
fstE(parse(p))
| 'snd($p)':
sndE(parse(p))
| '$fn($arg)':
appE(parse(fn),
parse(arg))
| '($e)':
parse(e)
| ~else:
error(#'parse, "invalid input: " +& s)
module test:
check: parse('2')
~is intE(2)
check: parse('x')
~is idE(#'x)
check: parse('2 + 1')
~is plusE(intE(2), intE (1))
check: parse('3 * 4')
~is multE(intE(3), intE(4))
check: parse('3 * 4 + 8')
~is plusE(multE(intE(3), intE(4)),
intE(8))
check: parse('fun (x): 9')
~is funE(#'x, intE(9))
check: parse('double(9)')
~is appE(idE(#'double), intE(9))
check: parse('1 + double(9)')
~is plusE(intE(1),
appE(idE(#'double), intE(9)))
check: parse('3 * (4 + 8)')
~is multE(intE(3),
plusE(intE(4), intE(8)))
check: parse('let x = 1 + 2:
y')
~is appE(funE(#'x,
idE(#'y)),
plusE(intE(1), intE(2)))
check: parse('1 2')
~raises "invalid input"
// interp ----------------------------------------
fun interp(a :: Exp, env :: Env) :: Value:
match a
| intE(n): intV(n)
| idE(s): force(lookup(s, env))
| plusE(l, r): num_plus(interp(l, env), interp(r, env))
| multE(l, r): num_mult(interp(l, env), interp(r, env))
| funE(n, body): closV(n, body, env)
| if0E(e, t, f): if interp(e, env) == intV(0)
| interp(t, env)
| interp(f, env)
| pairE(p1, p2): pairV(delay(p1, env, box(none())), delay(p2, env, box(none())))
| fstE(p): match interp(p, env)
| pairV(p1, p2): force(p1)
| ~else: error(#'interp, "not a pair")
| sndE(p): match interp(p, env)
| pairV(p1, p2): force(p2)
| ~else: error(#'interp, "not a pair")
| appE(fn, arg):
match interp(fn, env)
| closV(n, body, c_env):
interp(body,
extend_env(bind(n, delay(arg, env, box(none()))),
c_env))
| ~else: error(#'interp, "not a function")
module test:
check: interp(parse('2'), mt_env)
~is intV(2)
check: interp(parse('x'), mt_env)
~raises "free variable"
check: interp(parse('x'),
extend_env(bind(#'x, delay(intE(9), mt_env, box(none()))),
mt_env))
~is intV(9)
check: interp(parse('2 + 1'), mt_env)
~is intV(3)
check: interp(parse('2 * 1'), mt_env)
~is intV(2)
check: interp(parse('(2 * 3) + (5 + 8)'), mt_env)
~is intV(19)
check: interp(parse('fun (x): x + x'),
mt_env)
~is closV(#'x, plusE(idE(#'x), idE(#'x)), mt_env)
check: interp(parse('let x = 5: x + x'),
mt_env)
~is intV(10)
check: interp(parse('let x = 5:
let x = x + 1:
x + x'),
mt_env)
~is intV(12)
check: interp(parse('let x = 5:
let y = 6:
x'),
mt_env)
~is intV(5)
check: interp(parse('(fun (x): x + x)(8)'),
mt_env)
~is intV(16)
check: interp(parse('1(2)'), mt_env)
~raises "not a function"
check: interp(parse('1 + (fun (x): x)'), mt_env)
~raises "not a number"
check: interp(parse('let bad = (fun (x): x + y):
let y = 5:
bad(2)'),
mt_env)
~raises "free variable"
time:
interp(parse('let x2 = (fun (n): n + n):
let x4 = (fun (n): x2(x2(n))):
let x16 = (fun (n): x4(x4(n))):
let x256 = (fun (n): x16(x16(n))):
let x65536 = (fun (n): x256(x256(n))):
x65536(1)'),
mt_env)
// force ----------------------------------------
fun force(t :: Thunk) :: Value:
match t
| delay(a, e, done):
match unbox(done)
| none():
let v = interp(a, e):
set_box(done, some(v))
v
| some(v):
v
module test:
check: force(delay(intE(8), mt_env, box(none())))
~is intV(8)
check: block:
def d = delay(intE(8), mt_env, box(none()))
begin:
force(d)
force(d)
~is intV(8)
check: force(delay(intE(8), mt_env, box(some(intV(9)))))
~is intV(9)
check: force(delay(idE(#'x),
extend_env(bind(#'x, delay(intE(9), mt_env, box(none()))),
mt_env),
box(none())))
~is intV(9)
// num_plus and num_mult ----------------------------------------
fun num_op(op :: (Int, Int) -> Int, l :: Value, r :: Value) :: Value:
cond
| l is_a intV && r is_a intV:
intV(op(intV.n(l), intV.n(r)))
| ~else:
error(#'interp, "not a number")
fun num_plus(l :: Value, r :: Value) :: Value:
num_op(fun (a, b): a+b, l, r)
fun num_mult(l :: Value, r :: Value) :: Value:
num_op(fun (a, b): a*b, l, r)
module test:
check: num_plus(intV(1), intV(2))
~is intV(3)
check: num_mult(intV(3), intV(2))
~is intV(6)
// lookup ----------------------------------------
fun lookup(n :: Symbol, env :: Env) :: Thunk:
match env
| []: error(#'lookup, "free variable: " +& n)
| cons(b, rst_env):
cond
| n == bind.name(b):
bind.val(b)
| ~else:
lookup(n, rst_env)
module test:
check: lookup(#'x, mt_env)
~raises "free variable"
check: lookup(#'x, extend_env(bind(#'x, delay(intE(8), mt_env, box(none()))),
mt_env))
~is delay(intE(8), mt_env, box(none()))
check: lookup(#'x, extend_env(bind(#'x, delay(intE(9), mt_env, box(none()))),
extend_env(bind(#'x, delay(intE(8), mt_env, box(none()))),
mt_env)))
~is delay(intE(9), mt_env, box(none()))
check: lookup(#'y, extend_env(bind(#'x, delay(intE(9), mt_env, box(none()))),
extend_env(bind(#'y, delay(intE(8), mt_env, box(none()))),
mt_env)))
~is delay(intE(8), mt_env, box(none()))
// Part 1
fun interp_expr(e :: Exp):: Syntax:
match interp(e, mt_env)
| intV(val): integer_to_syntax(val)
| closV(arg, body, env): 'function'
| pairV(p1, p2): 'pair'
module test:
check: interp_expr(parse('10'))
~is '10'
check: interp_expr(parse('10 + 17'))
~is '27'
check: interp_expr(parse('10 * 7'))
~is '70'
check: interp_expr(parse('(fun (x): x + 12)(1 + 17)'))
~is '30'
check: interp_expr(parse('let x = 0:
let f = (fun (y): x + y):
f(1) + (let x = 3:
f(2))'))
~is '3'
check: interp_expr(parse('if 0 == 0 | 1 | 2'))
~is '1'
check: interp_expr(parse('if 1 == 0 | 1 | 2'))
~is '2'
check: interp_expr(parse('pair(1, 2)'))
~is 'pair'
check: interp_expr(parse('fst(pair(1, 2))'))
~is '1'
check: interp_expr(parse('snd(pair(1, 2))'))
~is '2'
check: interp_expr(parse('let p = pair(1, 2):
fst(p) + snd(p)'))
~is '3'
check: interp_expr(parse('let f = (fun (x):
pair(x, x)):
fst(f(3)) + snd(f(4))'))
~is '7'
// Lazy evaluation:
check: interp_expr(parse('(fun (x): 0)(1 + (fun (y): y))'))
~is '0'
check: interp_expr(parse('let x = (1 + (fun (y): y)):
0'))
~is '0'
check: interp_expr(parse('fst(pair(3,
1 + (fun (y): y)))'))
~is '3'
check: interp_expr(parse('snd(pair(1 + (fun (y): y),
4))'))
~is '4'
check: interp_expr(parse('fst(pair(5,
// Infinite loop:
(fun (x): x(x))(fun (x): x(x))))'))
~is '5'
check: interp_expr(
parse(
// Use call-by-name mkrec, which
// is simpler than call-by-value:
'let mkrec = (fun (body_proc):
let fX = (fun (fX):
body_proc(fX(fX))):
fX(fX)):
let fib = mkrec(fun (fib):
// Fib:
fun (n):
if n == 0
| 1
| if (n + -1) == 0
| 1
| fib(n + -1) + fib(n + -2)):
// Call fib on 4:
fib(4)'
)
)
~is '5'
check: interp_expr(
parse(
// Use call-by-name mkrec, which
// is simpler than call-by-value:
'let mkrec = (fun (body_proc):
let fX = (fun (fX):
body_proc(fX(fX))):
fX(fX)):
let nats_from = mkrec(fun (nats_from):
// nats-from:
fun (n):
pair(n, nats_from(n + 1))):
let list_ref = mkrec(fun (list_ref):
// list_ref:
fun (n):
fun (l):
if n == 0
| fst(l)
| list_ref(n + -1)(snd(l))):
// Call list-ref on infinite list:
list_ref(4)(nats_from(2))'
)
)
~is '6'
// to cover all code
check: interp_expr(
parse(
'fun (x): pair(x,x)'))
~is 'function'
check: interp_expr(
parse(
'fst(5)'))
~raises "not a pair"
check: interp_expr(
parse(
'snd(5)'))
~raises "not a pair"
// Part 2
module test:
time:
check: interp_expr(
parse(
'let mkrec = (fun (body_proc):
let fX = (fun (fX):
body_proc(fX(fX))):
fX(fX)):
let nats_to = mkrec(fun (nats_to):
fun (n):
if n == 0
| pair(0, 0)
| let l = nats_to(n + -1):
pair(fst(l) + 1,
l)):
let sum = mkrec(fun (sum):
fun (n):
fun (l):
if n == 0
| 0
| fst(l) + sum(n + -1)(snd(l))):
sum(10000)(nats_to(10000))'
)
)
~is '50005000'
time:
check: interp_expr(
parse(
'let mkrec = (fun (body_proc):
let fX = (fun (fX):
body_proc(fX(fX))):
fX(fX)):
let nats_to = mkrec(fun (nats_to):
fun (n):
if n == 0
| pair(0, 0)
| let l = nats_to(n + -1):
pair(l,
snd(l) + 1)):
let sum = mkrec(fun (sum):
fun (n):
fun (l):
if n == 0
| 0
| snd(l) + sum(n + -1)(fst(l))):
sum(10000)(nats_to(10000))'
)
)
~is '50005000'
|
HW7
Difficulty: ★★★☆
Start with let_cc2.rhm
, which is like let_cc.rhm, but it changes the representation of function expressions, application expressions, and closure values to support a list of arguments—which is the boring work behind part 2 below. The parse function in let_cc2.rhm still matches only single-argument functions and applications, so you’ll have to change that when you’re ready to work on part 2.
Part 1 — More Arithmetic Operators
Add two new arithmetic operators: neg
and avg
. The neg
form takes a single number and returns its negation; the avg
form takes three numbers (evaluated left to right) and returns their average. Also, add if
constrained to == 0
as usual.
<Exp> = <Number>
| <Symbol>
| <Exp> + <Exp>
| <Exp> * <Exp>
| fun (<Symbol>): <Exp>
| <Exp>(<Exp>)
| let <Symbol> = <Exp>: <Exp>
| (<Exp>)
| neg(<Exp>)
| avg(<Exp>, <Exp>, <Exp>)
| if <Exp> == 0 | <Exp> | <Exp>
The if
form has the weakest precedence. The neg
and avg
forms have a precedence like function calls, but should be preferred by the parser over a function-call form in parse
.
Implement neg
and avg
as core forms without relying on the interpreter’s existing implementation of addition and multiplication (e.g., don’t generate a doPlusK
continuation while interpreting them).
As in previous homeworks, provide interp_expr
, which takes an expression, interprets it with an empty environment, and produces either a number syntax object or 'function'
for a closure or continuation result.
Examples:
check: interp_expr(parse('neg(2)'))
~is '-2'
check: interp_expr(parse('avg(0, 6, 6)'))
~is '4'
check: interp_expr(parse('let_cc k: neg(k(3))'))
~is '3'
check: interp_expr(parse('let_cc k: avg(0, k(3), 0)'))
~is '3'
check: interp_expr(parse('let_cc k: avg(k(2), k(3), 0)'))
~is '2'
check: interp_expr(parse('if 1 == 0 | 2 | 3'))
~is '3'
check: interp_expr(parse('if 0 == 0 | 2 | 3'))
~is '2'
check: interp_expr(parse('let_cc k: if k(9) == 0 | 2 | 3'))
~is '9'
The use of let_cc
in these examples ensures that your continuation-passing style rules are not broken. The functions interp
and continue
should only call each other via tail calls.
Part 2 — Functions that Accept Multiple Arguments, Again
Change your interpreter to support functions with multiple or zero arguments and corresponding function calls:
<Exp> = <Number>
| <Symbol>
| <Exp> + <Exp>
| <Exp> * <Exp>
| fun (<Symbol>, ...): <Exp>
| <Exp>(<Exp>, ...)
| let <Symbol> = <Exp>: <Exp>
| (<Exp>)
| neg(<Exp>)
| avg(<Exp>, <Exp>, <Exp>)
| if <Exp> == 0 | <Exp> | <Exp>
Assume that each argument is distinct for a fun expression (i.e., your parser does not need to check). If the wrong number of arguments are provided, then a clear error message should be reported. Implement support for multiple arguments as part of the interpreter, not as sugar.
When a Moe program calls a continuation value (instead of a closure), the continuation value should still always take a single argument, and the interpreter should report a clear error if zero or multiple arguments are provided to a continuation.
If you’d like a hint, see HW 7 part 2 Hint. (Working without the hint makes this assignment ★★★★ instead of ★★★☆.)
check: interp_expr(parse('(fun(x, y): y + neg(x))(10, 12)'))
~is '2'
check: interp_expr(parse('fun (): 12'))
~is 'function'
check: interp_expr(parse('fun (x): fun (): x'))
~is 'function'
check: interp_expr(parse('(fun (x): fun (): x)(13)()'))
~is '13'
check: interp_expr(parse('let_cc esc: (fun (x, y): x)(1, esc(3))'))
~is '3'
check: interp_expr(parse('let f = (fun (x, y): fun (z): z + y):
(let_cc esc: f(1, let_cc k: esc(k)))(10)'))
~is '20'
check: interp_expr(parse('(fun (x, y): 1)(1, 2, 3)'))
// error because function is given too many arguments
~raises ""
check: interp_expr(parse('let_cc esc: esc()'))
// error because continuation is given 0 arguments
~raises ""
check: interp_expr(parse('let_cc esc: esc(1, 2)'))
// error because continuation is given 2 arguments
~raises ""
This part is extra credit for CS 3520 and CS 6520 students.
Extend your interpreter to support a try
form for exception handling:
<Exp> = ...
| try:
<Exp>
~catch: <Exp>
When raising an exception, instead of searching the continuation for a tryK
continuation, arrange for the interpreter to keep track of the current handler so that exceptions are raised in constant time, regardless of how long the continuation is.
Solution
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
| #lang shplait
type Value
| intV(n :: Int)
| closV(arg :: Listof(Symbol),
body :: Exp,
env :: Env)
| contV(k :: Cont)
| errorV(message :: String)
type Exp
| intE(n :: Int)
| idE(s :: Symbol)
| plusE(l :: Exp,
r :: Exp)
| multE(l :: Exp,
r :: Exp)
| divE(l :: Exp,
r :: Exp)
| funE(n :: Listof(Symbol),
body :: Exp)
| appE(fn :: Exp,
arg :: Listof(Exp))
| letccE(n :: Symbol,
body :: Exp)
| negE(e :: Exp)
| avgE(e1 :: Exp,
e2 :: Exp,
e3 :: Exp)
| if0E(e :: Exp,
true :: Exp,
false :: Exp)
| tryE(body :: Exp,
handle :: Exp)
type Binding
| bind(name :: Symbol,
val :: Value)
type Env = Listof(Binding)
def mt_env = []
def extend_env = cons
type Cont
| doneK()
| ifK(true :: Exp,
false :: Exp,
e :: Env,
k :: Cont)
| negK(e :: Env,
k :: Cont)
// Everytime we do cumulative sum
| avg3K(e2 :: Exp,
e3 :: Exp,
env :: Env,
k :: Cont)
| avg2K(v1 :: Value,
e3 :: Exp,
env :: Env,
k :: Cont)
| avg1K(e3 :: Exp,
env :: Env,
k :: Cont)
| avg0K(v12 :: Value,
env :: Env,
k :: Cont)
// And finally divide by 3
| doAvgK(k :: Cont)
| plusSecondK(r :: Exp,
e :: Env,
k :: Cont)
| doPlusK(v :: Value,
k :: Cont)
// To make Moe independent of Shplaits div-by-0 exception,
// I implemented division in moe with div-by-0 exception
| divSecondK(r :: Exp,
e :: Env,
k :: Cont)
| doDivK(v :: Value,
k :: Cont)
| multSecondK(r :: Exp,
e :: Env,
k :: Cont)
| doMultK(v :: Value,
k :: Cont)
// First I have tried not to use hint, and was successful
// Later I looked at the hint and found it to be much better solution
// | appArgFirstK(args :: Listof(Exp),
// env :: Env,
// k :: Cont)
// | appArgK(fn :: Value,
// fn_env :: Env,
// env :: Env,
// ns :: Listof(Symbol),
// nextArgs :: Listof(Exp),
// k :: Cont)
// | doAppK(fn_env :: Env,
// k :: Cont)
// Using hint below
| appArgsK(as :: Listof(Exp),
env :: Env,
k :: Cont)
| appK(f :: Value,
vs :: Listof(Value),
as :: Listof(Exp),
env :: Env,
k :: Cont)
// parse ----------------------------------------
fun parse(s :: Syntax) :: Exp:
cond
| syntax_is_integer(s):
intE(syntax_to_integer(s))
| syntax_is_symbol(s):
idE(syntax_to_symbol(s))
| ~else:
match s
| 'if $e == 0 | $true | $false':
if0E(parse(e),
parse(true),
parse(false))
| 'let_cc $name:
$body':
letccE(syntax_to_symbol(name),
parse(body))
| 'let $name = $rhs:
$body':
appE(funE([syntax_to_symbol(name)],
parse(body)),
[parse(rhs)])
| '$left + $right':
plusE(parse(left),
parse(right))
| '$left / $right':
divE(parse(left),
parse(right))
| '$left * $right':
multE(parse(left),
parse(right))
| 'fun ($ids, ...): $body':
funE(map(syntax_to_symbol, syntax_to_list('[$ids, ...]')),
parse(body))
| 'neg($e)':
negE(parse(e))
| 'avg($e1, $e2, $e3)':
avgE(parse(e1),
parse(e2),
parse(e3))
| '$fn($args, ...)':
appE(parse(fn),
map(parse, syntax_to_list('[$args, ...]')))
| '($e)':
parse(e)
| 'try:
$body
~catch: $handle':
tryE(parse(body), parse(handle))
| ~else:
error(#'parse, "invalid input: " +& s)
module test:
check: parse('2')
~is intE(2)
check: parse('x')
~is idE(#'x)
check: parse('2 + 1')
~is plusE(intE(2), intE (1))
check: parse('3 * 4')
~is multE(intE(3), intE(4))
check: parse('3 / 4')
~is divE(intE(3), intE(4))
check: parse('3 * 4 + 8')
~is plusE(multE(intE(3), intE(4)),
intE(8))
check: parse('3 / 4 + 8')
~is plusE(divE(intE(3), intE(4)),
intE(8))
check: parse('fun (x): 9')
~is funE([#'x], intE(9))
check: parse('double(9)')
~is appE(idE(#'double), [intE(9)])
check: parse('1 + double(9)')
~is plusE(intE(1),
appE(idE(#'double), [intE(9)]))
check: parse('3 * (4 + 8)')
~is multE(intE(3),
plusE(intE(4), intE(8)))
check: parse('let x = 1 + 2:
y')
~is appE(funE([#'x],
idE(#'y)),
[plusE(intE(1), intE(2))])
check: parse('let_cc k: 0')
~is letccE(#'k, intE(0))
check: parse('1 2')
~raises "invalid input"
check: parse('fun (x, y, z): x + y + z')
~is funE([#'x, #'y, #'z], plusE(plusE(idE(#'x), idE(#'y)), idE(#'z)))
check: parse('try:
0
~catch: 8')
~is tryE(intE(0), intE(8))
check: parse('try:
neg(10)
~catch: 5')
~is tryE(negE(intE(10)), intE(5))
// Like env, we are keeping track of the stack of handlers for each try cases
type Handler
| handlerE(e :: Exp, env :: Env, k :: Cont)
type HandlerStack = Listof(Handler)
def mt_handler = []
def extend_handler = cons
// Whenever we escape from exception, we continue the continuation before the try-catch
// with the catch value. If there is no try-catch wrapped, we simply return the errorV
fun escape(hs :: HandlerStack, e_v :: Value) :: Value:
match hs
| []: e_v
| cons(h, h_rest): match h
| handlerE(e, h_env, h_k): interp(e, h_env, h_k, h_rest)
// interp ----------------------------------------
fun interp(a :: Exp, env :: Env, k :: Cont, hs :: HandlerStack) :: Value:
match a
| if0E(e, true, false): interp(e, env,
ifK(true, false, env, k), hs)
| negE(e): interp(e, env,
negK(env, k), hs)
| avgE(e1, e2, e3): interp(e1, env,
avg3K(e2, e3, env, k), hs)
| intE(n): continue(k, intV(n), hs)
| idE(s): lookup(s, env, k, hs)
| plusE(l, r): interp(l, env,
plusSecondK(r, env, k), hs)
| multE(l, r): interp(l, env,
multSecondK(r, env, k), hs)
| divE(l, r): interp(l, env,
divSecondK(r, env, k), hs)
| funE(ns, body): continue(k, closV(ns, body, env), hs)
// | appE(fn, args): interp(fn, env,
// appArgFirstK(args, env, k), hs)
| appE(fn, args): interp(fn, env, appArgsK(args, env, k), hs)
| letccE(n, body):
interp(body,
extend_env(bind(n, contV(k)),
env),
k, hs)
| tryE(body, handler):
interp(body, env, k, extend_handler(handlerE(handler, env, k), hs))
fun continue(k :: Cont, v :: Value, hs :: HandlerStack) :: Value:
match k
| doneK(): v
| ifK(true, false, env, next_k):
match v
| intV(val):
if val == 0
| interp(true, env, next_k, hs)
| interp(false, env, next_k, hs)
| ~else: escape(hs, errorV("not a number"))
| negK(env, next_k): num_mult(v, intV(-1), next_k, hs)
| avg3K(e2, e3, env, next_k):
interp(e2, env,
avg2K(v, e3, env, next_k), hs)
| avg2K(v1, e3, env, next_k):
num_plus(v1, v, avg1K(e3, env, next_k), hs)
| avg1K(e3, env, next_k):
interp(e3, env,
avg0K(v, env, next_k), hs)
| avg0K(v12, env, next_k):
num_plus(v12, v, doAvgK(next_k), hs)
| doAvgK(next_k): num_div(v, intV(3), next_k, hs)
| plusSecondK(r, env, next_k):
interp(r, env,
doPlusK(v, next_k), hs)
| doPlusK(v_l, next_k):
num_plus(v_l, v, next_k, hs)
| multSecondK(r, env, next_k):
interp(r, env,
doMultK(v, next_k), hs)
| divSecondK(r, env, next_k):
interp(r, env,
doDivK(v, next_k), hs)
| doDivK(v_l, next_k):
num_div(v_l, v, next_k, hs)
| doMultK(v_l, next_k):
num_mult(v_l, v, next_k, hs)
// | appArgFirstK(args, env, next_k):
// match args
// | []: match v
// | closV(ns, body, c_env): if length(ns) == length(args)
// | continue(doAppK(env, next_k), v)
// | error(#'interp, "function argument mismatch")
// | contV(k): error(#'interp, "continuation must have exactly 1 argument")
// | ~else: error(#'interp, "not a function or continuation")
// | cons(arg, rest_args):
// match v
// | closV(ns, body, c_env): interp(arg, env,
// appArgK(v, c_env, env, ns, rest_args, next_k))
// | contV(k_v): interp(arg, env,
// appArgK(v, env, env, [], [], next_k))
// | appArgK(fn, fn_env, env, ns, next_args, next_k):
// match next_args
// | []: match fn
// | closV(c_ns, body, c_env): continue(doAppK(extend_env(bind(first(ns), v)), next_k), fn)
// | contV(k_v): continue(doAppK
// | cons(arg, rest_args): interp(arg, env,
// appArgK(fn, extend_env(bind(first(ns), v), fn_env),
// env, rest(ns), rest_args, next_k))
// | doAppK(fn_env, next_k):
// match v:
// | closV(ns, body, env): interp(body, fn_env, next_k)
// | contV(k): continue(k, v)
// | ~else: error(#'interp, "not a function or continuation")
| appArgsK(as, env, next_k):
match as
| []: match v
| closV(ns, body, c_env): if length(ns) == 0
| interp(body, c_env, next_k, hs)
| escape(hs, errorV("wrong number of arguments"))
| contV(k_v): escape(hs, errorV("continuations must have exactly 1 value"))
| ~else: escape(hs, errorV("not a function"))
| cons(a, ra): interp(a, env,
appK(v, [], ra, env, next_k), hs)
| appK(f, vs, as, env, next_k):
match as
| []: match f
| closV(ns, body, c_env): let vs = reverse(cons(v, vs)):
if length(ns) == length(vs)
| interp(body,
append(map2(fun (x, y): bind(x, y), ns, vs), c_env),
next_k, hs)
| escape(hs, errorV("wrong number of arguments"))
| contV(k_v): if length(vs) == 0
| continue(k_v, v, hs)
| escape(hs, errorV("continuations must have exactly 1 value"))
| ~else: escape(hs, errorV("not a function"))
| cons(a, ra): interp(a, env, appK(f, cons(v, vs), ra, env, next_k), hs)
check: map2(fun (x, y): bind(x, y), [#'a, #'b, #'c], reverse(cons(intV(3), [intV(2), intV(1)])))
~is [bind(#'a, intV(1)), bind(#'b, intV(2)), bind(#'c, intV(3))]
module test:
check: interp(parse('2'), mt_env, doneK(), mt_handler)
~is intV(2)
check: interp(parse('x'), mt_env, doneK(), mt_handler)
~is errorV("free variable")
check: interp(parse('x'),
extend_env(bind(#'x, intV(9)), mt_env),
doneK(), mt_handler)
~is intV(9)
check: interp(parse('2 + 1'), mt_env, doneK(), mt_handler)
~is intV(3)
check: interp(parse('2 * 1'), mt_env, doneK(), mt_handler)
~is intV(2)
check: interp(parse('10 / 2'), mt_env, doneK(), mt_handler)
~is intV(5)
check: interp(parse('(2 * 3) + (5 + 8)'), mt_env, doneK(), mt_handler)
~is intV(19)
check: interp(parse('(15 / 5) + (5 + 8)'), mt_env, doneK(), mt_handler)
~is intV(16)
check: interp(parse('1 / 0'), mt_env, doneK(), mt_handler)
~is errorV("div by zero")
check: interp(parse('fun (x): x + x'),
mt_env,
doneK(), mt_handler)
~is closV([#'x], plusE(idE(#'x), idE(#'x)), mt_env)
check: interp(parse('5 + 10'), mt_env, doneK(), mt_handler)
~is intV(15)
check: interp(appE(funE([#'x], plusE(intE(1), idE(#'x))), [intE(5)]),
mt_env,
doneK(), mt_handler)
~is intV(6)
check: interp(parse('let x = 5:
let x = x + 1:
x + x'),
mt_env,
doneK(), mt_handler)
~is intV(12)
check: interp(parse('let x = 5:
let y = 6:
x'),
mt_env,
doneK(), mt_handler)
~is intV(5)
check: interp(parse('(fun (x): x + x)(8)'),
mt_env,
doneK(), mt_handler)
~is intV(16)
check: interp(parse('1(2)'), mt_env, doneK(), mt_handler)
~is errorV("not a function")
check: interp(parse('1 + (fun (x): x)'), mt_env, doneK(), mt_handler)
~is errorV("not a number")
check: interp(parse('let bad = (fun (x): x + y):
let y = 5:
bad(2)'),
mt_env,
doneK(), mt_handler)
~is errorV("free variable")
check: interp(parse('let_cc k: k(0)'), mt_env, doneK(), mt_handler)
~is intV(0)
check: interp(parse('let_cc k: 1 + k(0)'),
mt_env,
doneK(), mt_handler)
~is intV(0)
check: interp(parse('let f = (let_cc k: k):
f(fun (x): 10)'),
mt_env,
doneK(), mt_handler)
~is intV(10)
// Eager:
check: interp(parse('(fun (x): 0)(1(2))'), mt_env, doneK(), mt_handler)
~is errorV("not a function")
check: continue(doneK(), intV(5), mt_handler)
~is intV(5)
check: continue(plusSecondK(intE(6), mt_env, doneK()), intV(5), mt_handler)
~is intV(11)
check: continue(doPlusK(intV(7), doneK()), intV(5), mt_handler)
~is intV(12)
check: continue(multSecondK(intE(6), mt_env, doneK()), intV(5), mt_handler)
~is intV(30)
check: continue(doMultK(intV(7), doneK()), intV(5), mt_handler)
~is intV(35)
// num_plus and num_mult ----------------------------------------
fun num_op(op :: (Int, Int) -> Int, l :: Value, r :: Value, k :: Cont, hs :: HandlerStack) :: Value:
cond
| l is_a intV && r is_a intV:
continue(k, intV(op(intV.n(l), intV.n(r))), hs)
| ~else:
escape(hs, errorV("not a number"))
fun num_plus(l :: Value, r :: Value, k :: Cont, hs :: HandlerStack) :: Value:
num_op(fun (a, b): a+b, l, r, k, hs)
fun num_mult(l :: Value, r :: Value, k :: Cont, hs :: HandlerStack) :: Value:
num_op(fun (a, b): a*b, l, r, k, hs)
fun num_div(l :: Value, r :: Value, k :: Cont, hs :: HandlerStack) :: Value:
if intV.n(r) == 0
| escape(hs, errorV("div by zero"))
| num_op(fun (a, b): a / b, l, r, k, hs)
module test:
check: num_plus(intV(1), intV(2), doneK(), mt_handler)
~is intV(3)
check: num_mult(intV(3), intV(2), doneK(), mt_handler)
~is intV(6)
check: num_div(intV(6), intV(2), doneK(), mt_handler)
~is intV(3)
check: num_div(intV(5), intV(0), doneK(), mt_handler)
~is errorV("div by zero")
// lookup ----------------------------------------
fun lookup(n :: Symbol, env :: Env, k :: Cont, hs :: HandlerStack) :: Value:
match env
| []: escape(hs, errorV("free variable"))
| cons(b, rst_env):
cond
| n == bind.name(b):
continue(k, bind.val(b), hs)
| ~else:
lookup(n, rst_env, k, hs)
module test:
check: lookup(#'x, mt_env, doneK(), mt_handler)
~is errorV("free variable")
check: lookup(#'x, extend_env(bind(#'x, intV(8)), mt_env), doneK(), mt_handler)
~is intV(8)
check: lookup(#'x, extend_env(bind(#'x, intV(9)),
extend_env(bind(#'x, intV(8)),
mt_env)), doneK(), mt_handler)
~is intV(9)
check: lookup(#'y, extend_env(bind(#'x, intV(9)),
extend_env(bind(#'y, intV(8)),
mt_env)), doneK(), mt_handler)
~is intV(8)
// Part 1
fun interp_expr(e :: Exp) :: Syntax:
match interp(e, mt_env, doneK(), mt_handler)
| intV(v): integer_to_syntax(v)
| closV(arg, body, env): 'function'
| contV(k): 'function'
| errorV(message): 'error'
module test:
check: interp_expr(parse('neg(2)'))
~is '-2'
check: interp_expr(parse('avg(0, 6, 6)'))
~is '4'
check: interp_expr(parse('let_cc k: neg(k(3))'))
~is '3'
check: interp_expr(parse('let_cc k: avg(0, k(3), 0)'))
~is '3'
check: interp_expr(parse('let_cc k: avg(k(2), k(3), 0)'))
~is '2'
check: interp_expr(parse('if 1 == 0 | 2 | 3'))
~is '3'
check: interp_expr(parse('if 0 == 0 | 2 | 3'))
~is '2'
check: interp_expr(parse('let_cc k: if k(9) == 0 | 2 | 3'))
~is '9'
// Additional checks to cover
check: interp_expr(parse('if (fun (x): x) == 0 | 2 | 3'))
~is 'error'
check: interp_expr(parse('fun (x): x'))
~is 'function'
check: interp_expr(parse('let_cc foo: foo'))
~is 'function'
// Part 2
module test:
check: interp_expr(parse('fun(x, y): x + neg(y)'))
~is 'function'
check: interp_expr(parse('(fun(x, y): y + neg(x))(10, 12)'))
~is '2'
check: interp_expr(parse('fun (): 12'))
~is 'function'
check: interp_expr(parse('fun (x): fun (): x'))
~is 'function'
check: interp_expr(parse('(fun (x): fun (): x)(13)()'))
~is '13'
check: interp_expr(parse('let_cc esc: (fun (x, y): x)(1, esc(3))'))
~is '3'
check: interp_expr(parse('let f = (fun (x, y): fun (z): z + y):
(let_cc esc: f(1, let_cc k: esc(k)))(10)'))
~is '20'
check: interp(parse('(fun (x, y): 1)(1, 2, 3)'), mt_env, doneK(), mt_handler)
// error because function is given too many arguments,
// but the specific error message is not specified
~is errorV("wrong number of arguments")
check: interp(parse('let_cc esc: esc()'), mt_env, doneK(), mt_handler)
// error because continuation is given 0 arguments
~is errorV("continuations must have exactly 1 value")
check: interp(parse('let_cc esc: esc(1, 2)'), mt_env, doneK(), mt_handler)
// error because continuation is given 2 arguments
~is errorV("continuations must have exactly 1 value")
// To cover the code
check: interp(parse('(fun (x): 5)()'), mt_env, doneK(), mt_handler)
~is errorV("wrong number of arguments")
check: interp(parse('5()'), mt_env, doneK(), mt_handler)
~is errorV("not a function")
// Part 3 (bonus: O(1) try-catch)
module test:
check: parse('try:
neg(fun (x): x)
~catch: 10')
~is tryE(negE(funE([#'x], idE(#'x))),
intE(10))
check: interp(parse('x'), mt_env, doneK(), mt_handler)
~is errorV("free variable")
check: interp(parse('1 + x'), mt_env, doneK(), mt_handler)
~is errorV("free variable")
check: interp(parse('1 + (fun (x): x)'), mt_env, doneK(), mt_handler)
~is errorV("not a number")
check: interp(parse('(1 + (fun (x): x))(3)'), mt_env, doneK(), mt_handler)
~is errorV("not a number")
check: interp(parse('try:
0
~catch: 8'),
mt_env,
doneK(), mt_handler)
~is intV(0)
check: interp(parse('try:
0(0)
~catch: 8'),
mt_env,
doneK(), mt_handler)
~is intV(8)
check: interp(parse('try: neg((fun (x): x))
~catch: 10'), mt_env, doneK(), mt_handler)
~is intV(10)
check: interp_expr(parse('try:
avg(fun (x): x, 2, 3)
~catch: 1'))
~is '1'
check: interp_expr(parse('try:
avg(1, fun (x): x, 3)
~catch: 1'))
~is '1'
check: interp_expr(parse('try:
avg(1, 2, fun (x): x)
~catch: 1'))
~is '1'
check: interp_expr(parse('try:
2 * (fun(x): x)
~catch: 5
+ 5'))
~is '10'
check: interp_expr(parse('try:
neg(5 + fun(x): x)
~catch: 10'))
~is '10'
check: interp_expr(parse('try:
(fun (x): 20) + 10
~catch: 15'))
~is '15'
// testing nested trys
check: interp_expr(parse('try:
5 + try:
10 + (fun (x): 20)
~catch: 1
~catch: 2'))
~is '6'
check: interp_expr(parse('5 + try:
(fun (x): x + 1)(try:
20 + x
~catch: 30)
~catch: 10'))
~is '36'
|
HW8
Difficulty: ★★★☆
Your solution will start with 5.rhm. However, you should think of an addition to 5.rhm as being a translation of an addition to lambda_k.rhm. So, it’s a good idea to first implement part 1 in lambda_k.rhm, and then translate to 5.rhm.
Part 1 — Boxes
Add box
, unbox
, and is_box
:
<Exp> = ....
| box(<Exp>)
| unbox(<Exp>)
| is_box(<Exp>)
The is_box
form returns 0
if the result of its argument expression is a box, 1
if it’s not a box. Even so, your interpreter can assume that the argument to unbox
is always a box (which is consistent with other forms in the initial interpreter, such as the way that +
assumes that its arguments are numbers).
Test coverage: To achieve test coverage, you are allowed to simply comment out the error cases in move
and update
, since triggering those requires extreme measures. If you need a program that runs out of memory, you can use the Moe program let f = (fun (f): 1 + f(f)): f(f)
.
Examples:
reset()
N check: interpx(compile(parse('unbox(unbox(box(box(3))))'),
mt_env),
empty_env,
init_k())
~is 3
reset()
N check: interpx(compile(parse('is_box(3)'),
mt_env),
empty_env,
init_k())
~is 1
reset()
N check: interpx(compile(parse('is_box(box(3))'),
mt_env),
empty_env,
init_k())
~is 0
reset()
N check: interpx(compile(parse('is_box(fun (x): box(x))'),
mt_env),
empty_env,
init_k())
~is 1
N check: interpx(compile(
parse(
'let mkrec = (fun (body_proc):
(fun (fX):
fX(fX))(fun (fX):
body_proc(fun (x):
fX(fX)(x)))):
let chain = mkrec(fun (chain):
fun (n):
if n == 0
| 1
| box(chain(n + -1))):
let unchain = mkrec(fun (unchain):
fun (n):
fun (b):
if n == 0
| b
| unchain(n + -1)(unbox(b))):
unchain(13)(chain(13))'
),
mt_env),
empty_env,
init_k())
~is 1
CS 3520 and CS 6520 students can complete this exercise for extra credit, but it is not required.
Add set_box
:
<Exp> = ....
| set_box(<Exp>, <Exp>)
You can make set_box
return whatever you like, and your interpreter can assume that the first argument to set_box
is always a box.
reset()
N check: interpx(compile(parse('let b = box(3):
let dummy = set_box(b, 4):
unbox(b)'),
mt_env),
empty_env,
init_k())
~is 4
reset()
N check: interpx(
compile(
parse(
'let mkrec = (fun (body_proc):
(fun (fX):
fX(fX))(fun (fX):
body_proc(fun (x):
fX(fX)(x)))):
let chain = mkrec(fun (chain):
fun (n):
if n == 0
| 1
| box(chain(n + -1))):
let bx = box(chain(13)):
let unchain = mkrec(
fun (unchain):
fun (n):
if n == 0
| unbox(bx)
| let dummy = set_box(bx, unbox(unbox(bx))):
unchain(n + -1)):
unchain(13)'
),
mt_env),
empty_env,
init_k())
~is 1
Solution
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
| #lang shplait
// Garbage collection for run-time memory
type Exp
| intE(n :: Int)
| idE(s :: Symbol)
| plusE(l :: Exp,
r :: Exp)
| multE(l :: Exp,
r :: Exp)
| funE(n :: Symbol,
body :: Exp)
| appE(fn :: Exp,
arg :: Exp)
| if0E(tst :: Exp,
thn :: Exp,
els :: Exp)
| boxE(e :: Exp)
| unboxE(e :: Exp)
| isBoxE(e :: Exp)
| setBoxE(b :: Exp,
val :: Exp)
/*
type ExpD
8 | intD(n :: Int)
9 | atD(n :: Int)
10 | plusD(l :: ExpD,
r :: ExpD)
11 | multD(l :: ExpD,
r :: ExpD)
12 | funD(body :: ExpD)
13 | appD(fn :: ExpD,
arg :: ExpD)
14 | if0D(tst :: ExpD,
thn :: ExpD,
els :: ExpD)
18 | boxD(e :: ExpD)
19 | unboxD(e :: ExpD)
20 | isBoxD(e :: ExpD)
*/
def doneK = 0
def plusSecondK = 1
def doPlusK = 2
def multSecondK = 3
def doMultK = 4
def appArgK = 5
def doAppK = 6
def doIf0K = 7
def intD = 8
def atD = 9
def plusD = 10
def multD = 11
def funD = 12
def appD = 13
def if0D = 14
def intV = 15
def closV = 16
def boxV = 21
def CONS = 17
def MOVED = 99
def boxD = 18
def unboxD = 19
def isBoxD = 20
def setBoxD = 25
def boxK = 22
def isBoxK = 23
def unboxK = 24
def boxValK = 26
def setBoxK = 27
/*
type Value
15 | intV(n :: Int)
16 | closV(body :: ExpD,
env :: Env)
21 | boxV(l :: Location) // Location is just an Int
*/
type BindingC
| bindC(name :: Symbol)
type EnvC = Listof(BindingC)
def mt_env = []
def extend_env = cons
/*
type Cont
0 | doneK()
1 | plusSecondK(r :: ExpD,
env :: Env,
k :: Cont)
2 | doPlusK(v1 :: Value,
k :: Cont)
3 | multSecondK(r :: ExpD,
env :: Env,
k :: Cont)
4 | doMultK(v1 :: Value,
k :: Cont)
5 | appArgK(arg :: ExpD,
env :: Env,
k :: Cont)
6 | doAppK(fun_val :: Value,
k :: Cont)
7 | doIf0K(thn :: ExpD,
els :: ExpD,
env :: Env,
k :: Cont)
*/
/*
17 cons for env
99 moved
*/
// parse ----------------------------------------
fun parse(s :: Syntax) :: Exp:
cond
| syntax_is_integer(s):
intE(syntax_to_integer(s))
| syntax_is_symbol(s):
idE(syntax_to_symbol(s))
| ~else:
match s
| 'if $tst == 0 | $thn | $els':
if0E(parse(tst), parse(thn), parse(els))
| 'let $name = $rhs: $body':
appE(funE(syntax_to_symbol(name), parse(body)),
parse(rhs))
| '$left + $right': plusE(parse(left), parse(right))
| '$left * $right': multE(parse(left), parse(right))
| 'fun ($id): $body': funE(syntax_to_symbol(id),
parse(body))
| 'box($exp)': boxE(parse(exp))
| 'unbox($exp)': unboxE(parse(exp))
| 'is_box($exp)': isBoxE(parse(exp))
| 'set_box($b, $val)': setBoxE(parse(b), parse(val))
| '$fn($arg)': appE(parse(fn), parse(arg))
| '($e)': parse(e)
| ~else: error(#'parse, "invalid input: " +& s)
module test:
check: parse('2')
~is intE(2)
check: parse('x')
~is idE(#'x)
check: parse('2 + 1')
~is plusE(intE(2), intE (1))
check: parse('3 * 4')
~is multE(intE(3), intE(4))
check: parse('3 * 4 + 8')
~is plusE(multE(intE(3), intE(4)), intE(8))
check: parse('fun (x): 9')
~is funE(#'x, intE(9))
check: parse('double(9)')
~is appE(idE(#'double), intE(9))
check: parse('1 + double(9)')
~is plusE(intE(1), appE(idE(#'double), intE(9)))
check: parse('3 * (4 + 8)')
~is multE(intE(3), plusE(intE(4), intE(8)))
check: parse('let x = 1 + 2: y')
~is appE(funE(#'x, idE(#'y)), plusE(intE(1), intE(2)))
check: parse('if 1 == 0 | 2 | 3')
~is if0E(intE(1), intE(2), intE(3))
check: parse('1 2')
~raises "invalid input"
check: parse('box(box(5))')
~is boxE(boxE(intE(5)))
check: parse('unbox(box(5))')
~is unboxE(boxE(intE(5)))
check: parse('is_box(box(5))')
~is isBoxE(boxE(intE(5)))
// ----------------------------------------
// Allocator for code, which is never freed;
// use `code_ref` instead of `ref` to refer to code
def code_memory = make_array(2048, 0)
def mutable code_ptr = 0
fun code_incptr(n):
code_ptr := code_ptr + n
code_ptr - n
fun code_malloc1(tag, a):
code_memory[code_ptr] := tag
code_memory[code_ptr + 1] := a
code_incptr(2)
fun code_malloc2(tag, a, b):
code_memory[code_ptr] := tag
code_memory[code_ptr + 1] := a
code_memory[code_ptr + 2] := b
code_incptr(3)
fun code_malloc3(tag, a, b, c):
code_memory[code_ptr] := tag
code_memory[code_ptr + 1] := a
code_memory[code_ptr + 2] := b
code_memory[code_ptr + 3] := c
code_incptr(4)
fun code_ref(n, d):
code_memory[n+d]
// ----------------------------------------
fun compile(a, env):
match a
| intE(n): code_malloc1(intD, n)
| idE(name): code_malloc1(atD, locate(name, env))
| plusE(l, r): code_malloc2(plusD, compile(l, env), compile(r, env))
| multE(l, r): code_malloc2(multD, compile(l, env), compile(r, env))
| funE(n, body_expr): code_malloc1(funD,
compile(body_expr,
extend_env(bindC(n), env)))
| appE(fn, arg): code_malloc2(appD,
compile(fn, env),
compile(arg, env))
| if0E(tst, thn, els): code_malloc3(if0D,
compile(tst, env),
compile(thn, env),
compile(els, env))
| boxE(exp): code_malloc1(boxD, compile(exp, env))
| unboxE(exp): code_malloc1(unboxD, compile(exp, env))
| isBoxE(exp): code_malloc1(isBoxD, compile(exp, env))
| setBoxE(b, val): code_malloc2(setBoxD,
compile(b, env),
compile(val, env))
fun locate(name, env):
match env
| []: error(#'locate, "free variable: " +& name)
| cons(fst_b, rst_env): if name == bindC.name(fst_b)
| 0
| 1 + locate(name, rst_env)
// ----------------------------------------
// Memory allocator with a 2-space collector
// for run-time allocation
def MEMORY_SIZE = 128
def space1 = make_array(MEMORY_SIZE, 0)
def space2 = make_array(MEMORY_SIZE, 0)
def mutable memory = space1
def mutable ptr_reg = 0
def empty_memory = make_array(0, 0)
def mutable from_memory = empty_memory
def mutable result_reg = 0
fun incptr(n):
// Increment the allocation pointer, and
// if there's not enough room for the next
// allocation, then collect garbage
ptr_reg := ptr_reg + n
if ptr_reg + 5 >= MEMORY_SIZE
| result_reg := ptr_reg - n
if from_memory == empty_memory
| gc()
| // Ran out of space while GCing
// => GCing didn't reclaim anything,
// so we're really out of space
error(#'malloc, "out of memory")
| ptr_reg - n
fun malloc1(tag, a):
memory[ptr_reg] := tag
memory[ptr_reg + 1] := a
incptr(2)
fun malloc2(tag, a, b):
memory[ptr_reg] := tag
memory[ptr_reg + 1] := a
memory[ptr_reg + 2] := b
incptr(3)
fun malloc3(tag, a, b, c):
memory[ptr_reg] := tag
memory[ptr_reg + 1] := a
memory[ptr_reg + 2] := b
memory[ptr_reg + 3] := c
incptr(4)
fun malloc4(tag, a, b, c, d):
memory[ptr_reg] := tag
memory[ptr_reg + 1] := a
memory[ptr_reg + 2] := b
memory[ptr_reg + 3] := c
memory[ptr_reg + 4] := d
incptr(5)
fun ref(n, d):
memory[n+d]
// Pointer in to space; objects before the
// pointer are "black", and object after are "gray"
def mutable updated_ptr_reg = 0
fun gc() :: Int:
println("GCing")
// Swap to and from space:
from_memory := memory
if memory === space1
| memory := space2
| memory := space1
ptr_reg := 0
// Update registers to start:
v_reg := move(v_reg)
env_reg := move(env_reg)
k_reg := move(k_reg)
result_reg := move(result_reg)
updated_ptr_reg := 0
// Loop until there are no gray objects:
update()
fun update():
if updated_ptr_reg == ptr_reg
| // No more gray objects:
from_memory := empty_memory
result_reg
| // updated-ptr points to first gray object:
match ref(updated_ptr_reg, 0)
| 0 || 15:
// Record has just an integer
done(1)
| 1 || 3 || 5:
// Record has two run-time pointers
// in slots 2 and 3 (and an integer in 1)
move_do(2)
move_do(3)
done(3)
| 2 || 4 || 6 || 17:
// Etc.
move_do(1)
move_do(2)
done(2)
| 16:
move_do(2)
done(2)
| 21 || 22 || 23 || 24:
// box continuations and values on have 1 pointer
move_do(1)
done(1)
| 7:
move_do(3)
move_do(4)
done(4)
// | ~else:
// error(#'update, "internal error, unknown tag: " +& ref(updated_ptr_reg, 0))
fun done(n):
updated_ptr_reg := updated_ptr_reg + n + 1
update()
// move_do :: Int -> Void
// Updates pointer at updated-ptr+n, moving the
// target as necessary:
fun move_do(n):
memory[updated_ptr_reg + n] := move(memory[updated_ptr_reg + n])
// move :: Int -> Int
// If n refers to a white record, copy it to to-space and
// insert a forwarding pointer, so now it's gray
// If n refers to a gray/black record, return the forwarding
// pointer.
fun move(n):
if from_memory[n] == 99
| // Gray/black, get forwarded pointer:
from_memory[n+1]
| // White:
match from_memory[n]
| 0 || 15:
// size 1
from_memory[n+1] := malloc1(from_memory[n],
from_memory[n+1])
| 2 || 4 || 6 || 16 || 17:
// size 2
from_memory[n+1] := malloc2(from_memory[n],
from_memory[n+1],
from_memory[n+2])
| 1 || 3 || 5:
// size 3
from_memory[n+1] := malloc3(from_memory[n],
from_memory[n+1],
from_memory[n+2],
from_memory[n+3])
| 7:
// size 4
from_memory[n+1] := malloc4(from_memory[n],
from_memory[n+1],
from_memory[n+2],
from_memory[n+3],
from_memory[n+4])
| 21 || 22 || 23 || 24:
// size 1
from_memory[n+1] := malloc1(from_memory[n],
from_memory[n+1])
// | ~else:
// error(#'move, "internal error, unknown tag: " +& from_memory[n])
// Change to gray:
from_memory[n] := 99
// Return forwarding porter (that we just installed):
from_memory[n+1]
// ----------------------------------------
def mutable exp_reg = 0
def mutable env_reg = 0
// interp :: (ExpD, Env, Cont) -> Value
fun interp():
match code_ref(exp_reg, 0)
| 8: // intD
v_reg := malloc1(intV, code_ref(exp_reg, 1))
continue()
| 9: // atD
env2_reg := env_reg
n_reg := code_ref(exp_reg, 1)
env_ref()
| 10: // plusD
k_reg := malloc3(plusSecondK, code_ref(exp_reg, 2), env_reg, k_reg)
exp_reg := code_ref(exp_reg, 1)
interp()
| 11: // multD
k_reg := malloc3(multSecondK, code_ref(exp_reg, 2), env_reg, k_reg)
exp_reg := code_ref(exp_reg, 1)
interp()
| 12: // funD
v_reg := malloc2(closV, code_ref(exp_reg, 1), env_reg)
continue()
| 13: // appD
k_reg := malloc3(appArgK, code_ref(exp_reg, 2), env_reg, k_reg)
exp_reg := code_ref(exp_reg, 1)
interp()
| 14: // if0D
k_reg := malloc4(doIf0K, code_ref(exp_reg, 2), code_ref(exp_reg, 3), env_reg, k_reg)
exp_reg := code_ref(exp_reg, 1)
interp()
| 18: // boxD
exp_reg := code_ref(exp_reg, 1)
k_reg := malloc1(boxK, k_reg)
interp()
| 19: // unboxD
exp_reg := code_ref(exp_reg, 1)
k_reg := malloc1(unboxK, k_reg)
interp()
| 20: // isBoxD
exp_reg := code_ref(exp_reg, 1)
k_reg := malloc1(isBoxK, k_reg)
interp()
| 25: // setBoxD
k_reg := malloc3(boxValK,
code_ref(exp_reg, 2),
env_reg,
k_reg)
exp_reg := code_ref(exp_reg, 1)
interp()
| ~else: error(#'interp, "bad expression " +& code_ref(exp_reg, 0))
def mutable k_reg = 0
def mutable v_reg = 0
// continue :: (Cont, Value) -> Value
fun continue():
match ref(k_reg, 0)
| 0: // doneK
v_reg
| 1: // plusSecondK
exp_reg := ref(k_reg, 1)
env_reg := ref(k_reg, 2)
k_reg := malloc2(doPlusK, v_reg, ref(k_reg, 3))
interp()
| 2: // doPlusK
v_reg := num_plus(ref(k_reg, 1), v_reg)
k_reg := ref(k_reg, 2)
continue()
| 3: // multSecondK
exp_reg := ref(k_reg, 1)
env_reg := ref(k_reg, 2)
k_reg := malloc2(doMultK, v_reg, ref(k_reg, 3))
interp()
| 4: // doMultK
v_reg := num_mult(ref(k_reg, 1), v_reg)
k_reg := ref(k_reg, 2)
continue()
| 5: // appArgK
exp_reg := ref(k_reg, 1)
env_reg := ref(k_reg, 2)
k_reg := malloc2(doAppK, v_reg, ref(k_reg, 3))
interp()
| 6: // doAppK
exp_reg := ref(ref(k_reg, 1), 1)
env_reg := malloc2(CONS, v_reg, ref(ref(k_reg, 1), 2))
k_reg := ref(k_reg, 2)
interp()
| 7: // doIf0K
if num_is_zero(v_reg)
| exp_reg := ref(k_reg, 1)
| exp_reg := ref(k_reg, 2)
env_reg := ref(k_reg, 3)
k_reg := ref(k_reg, 4)
interp()
| 22: // boxK
v_reg := malloc1(boxV, v_reg)
k_reg := ref(k_reg, 1)
continue()
| 23: // isBoxK
if ref(v_reg, 0) == boxV
| v_reg := malloc1(intV, 0)
| v_reg := malloc1(intV, 1)
k_reg := ref(k_reg, 1)
continue()
| 24: // unboxK
v_reg := ref(v_reg, 1)
k_reg := ref(k_reg, 1)
continue()
| 26: // boxValK
exp_reg := ref(k_reg, 1)
env_reg := ref(k_reg, 2)
k_reg := malloc2(setBoxK, v_reg, k_reg)
interp()
| 27: // setBoxK
// val: v_reg
// box: k_reg+1
memory[ref(k_reg, 1)] := v_reg
k_reg := ref(k_reg, 2)
continue()
| ~else:
error(#'continue, "bad continuation " +& ref(k_reg, 0))
fun num_op(op :: (Int, Int) -> Int):
fun (l, r):
malloc1(15, op(ref(l, 1), ref(r, 1)))
def num_plus = num_op(fun (a, b): a+b)
def num_mult = num_op(fun (a, b): a*b)
fun num_is_zero(v):
ref(v, 1) == 0
def mutable env2_reg = 0
def mutable n_reg = 0
fun env_ref():
if n_reg == 0
| v_reg := ref(env2_reg, 1)
continue()
| env2_reg := ref(env2_reg, 2)
n_reg := n_reg - 1
env_ref()
// ----------------------------------------
fun init_k(): malloc1(0, 0)
fun interpx(a, env, k):
exp_reg := a
env_reg := env
k_reg := k
interp()
def empty_env = malloc1(0, 0)
macro 'N $check: $expr
~is $n':
'$check: ref($expr, 1)
~is $n'
fun reset():
code_ptr := 0
ptr_reg := 0
v_reg := 0
exp_reg := 0
k_reg := 0
env_reg := 0
result_reg := 0
from_memory := empty_memory
module test:
N check: interpx(compile(parse('2'), mt_env),
empty_env,
init_k())
~is 2
reset()
check: compile(parse('x'), mt_env)
~raises "free variable"
reset()
N check: interpx(compile(parse('2 + 1'), mt_env),
empty_env,
init_k())
~is 3
reset()
N check: interpx(compile(parse('2 * 1'), mt_env),
empty_env,
init_k())
~is 2
reset()
N check: interpx(compile(parse('(2 * 3) + (5 + 8)'), mt_env),
empty_env,
init_k())
~is 19
reset()
N check: interpx(compile(parse('(fun (x): x + x)(17)'),
mt_env),
empty_env,
init_k())
~is 34
reset()
N check: interpx(compile(parse('let x = 5: x + x'),
mt_env),
empty_env,
init_k())
~is 10
reset()
N check: interpx(compile(parse('let x = 5:
let y = 6:
x'),
mt_env),
empty_env,
init_k())
~is 5
reset()
N check: interpx(compile(parse('(fun (x): x + x)(8)'),
mt_env),
empty_env,
init_k())
~is 16
reset()
N check: interpx(compile(parse('if 0 == 0 | 1 | 2'),
mt_env),
empty_env,
init_k())
~is 1
reset()
N check: interpx(compile(parse('if 1 == 0 | 1 | 2'),
mt_env),
empty_env,
init_k())
~is 2
reset()
N check: interpx(compile(
parse(
'let mkrec = (fun (body_proc):
(fun (fX):
fX(fX))(fun (fX):
body_proc(fun (x):
fX(fX)(x)))):
let fib = mkrec(fun (fib):
fun (n):
if n == 0:
| 1
| if (n + -1) == 0
| 1
| fib(n + -1) + fib(n + -2)):
fib(4)'
),
mt_env),
empty_env,
init_k())
~is 5
// coverage for error cases:
reset()
check: block:
def exp = compile(parse('1'), mt_env)
interpx(exp + 1, empty_env, init_k())
~raises "bad expression"
check: block:
def exp = compile(parse('1'), mt_env)
interpx(exp, empty_env, exp)
~raises "bad continuation"
reset()
check: interpx(compile(parse('let f = (fun (f): 1 + f(f)):
f(f)'),
mt_env),
empty_env,
init_k())
~raises "out of memory"
// Part 1
module test:
reset()
N check: interpx(compile(parse('unbox(unbox(box(box(3))))'),
mt_env),
empty_env,
init_k())
~is 3
reset()
N check: interpx(compile(parse('is_box(3)'),
mt_env),
empty_env,
init_k())
~is 1
reset()
N check: interpx(compile(parse('is_box(box(3))'),
mt_env),
empty_env,
init_k())
~is 0
reset()
N check: interpx(compile(parse('is_box(fun (x): box(x))'),
mt_env),
empty_env,
init_k())
~is 1
reset()
N check: interpx(compile(parse('unbox(box(unbox(box(5))))'),
mt_env),
empty_env,
init_k())
~is 5
reset()
N check: interpx(compile(
parse(
'let mkrec = (fun (body_proc):
(fun (fX):
fX(fX))(fun (fX):
body_proc(fun (x):
fX(fX)(x)))):
let chain = mkrec(fun (chain):
fun (n):
if n == 0
| 1
| box(chain(n + -1))):
let unchain = mkrec(fun (unchain):
fun (n):
fun (b):
if n == 0
| b
| unchain(n + -1)(unbox(b))):
// Make a chain of boxes, then traverse them:
unchain(13)(chain(13))'
),
mt_env),
empty_env,
init_k())
~is 1
// Part 2 (Bonus Credit)
module test:
reset()
N check: interpx(compile(parse('set_box(box(5), 10)'),
mt_env),
empty_env,
init_k())
~is 4
reset()
N check: interpx(compile(parse('let b = box(3):
let dummy = set_box(b, 4):
unbox(b)'),
mt_env),
empty_env,
init_k())
~is 4
|
HW9
Difficulty: ★★★☆
Start with class_inherit.rhm
, which combines class.rhm
, inherit.rhm
, and inherit_parse.rhm
into a single file.
You may find it nicer to work with class.rhm
, inherit.rhm
, and inherit_parse.rhm
as separate files, but you must combine them into a single file for handin. To combine the files: append them in the listed order, then remove the second and third #lang shplait
lines and remove all import
forms.
Part 1 — Instantiating Object
In the starting code, new Object()
doesn’t work, even though Object
is supposed to be a built-in class with no fields and no methods. Fix the implementation so that new Object()
produces an instance of Object
.
check: interp_prog([],
'new Object()')
~is 'object'
check: interp_prog(['class Fish(size, color):
extends Object'],
'new Fish(1, 2)')
~is 'object'
Part 2 — Conditional via select
Add select
:
<Exp> = ...
| select <Exp>: <Exp>
The first expression in select
should produce a number result, while the second expression should produce an object. If the number is 0
, select
calls the zero
method of the object. If the number is not 0
, select
calls the nonzero
method of the object. In each case, select
calls the method with the argument 0
. The select
form has lowest precedence.
check: interp_prog(['class Snowball(size):
extends Object
method zero(arg): this
method nonzero(arg):
new Snowball(1 + this.size)'],
'(select 0: new Snowball(1)).size')
~is '1'
check: interp_prog(['class Snowball(size):
extends Object
method zero(arg): this
method nonzero(arg):
new Snowball(1 + this.size)'],
'(select 1 + 2: new Snowball(1)).size')
~is '2'
The result is up to you when select
gets a non-number result for its first subexpression, but a “not a number” error is sensible. Similarly, the result is up to you when the second subexpression’s result is not an object or does not have a zero
or nonzero
method.
You will probably find it easiest to extend and test the Exp
layer, then the ExpI
layer, and then the parse
and interp_prog
layer.
Part 3 — instanceof
Add is_a
:
<Exp> = ...
| <Exp> is_a <Sym>
An expression <Exp> is_a <Sym>
produces 0
if the result of <Exp>
is an object that is an instance of the class named by <Sym>
or any of its subclasses. It should produce 1
if the result of <Exp>
is any other object. Note that <Exp> is_a Object
should produce 0
as long as the value of <Exp>
is an object. The is_a
operator’s precedence should be immediately lower than +
.
Note that you will have to introduce a notion of superclasses into the Class
layer, even though method inheritance remains the job of the ClassI
layer.
Example:
check: interp_prog(['class Fish(size, color):
extends Object'],
'new Fish(1, 2) is_a Object')
~is '0'
check: interp_prog(['class Fish(size, color):
extends Object'],
'new Object() is_a Fish')
~is '1'
check: interp_prog(['class Fish(size, color):
extends Object'],
'new Fish(1, 2) is_a Fish')
~is '0'
check: interp_prog(['class Fish(size, color):
extends Object',
'class Shark(teeth):
extends Fish'],
'new Shark(1, 2, 3) is_a Fish')
~is '0'
check: interp_prog(['class Fish(size, color):
extends Object',
'class Shark(teeth):
extends Fish',
'class Hammerhead():
extends Shark'],
'new Hammerhead(1, 2, 3) is_a Fish')
~is '0'
check: interp_prog(['class PlainFish(size):
extends Object',
'class ColorFish(color):
extends PlainFish',
'class Bear(size, color):
extends Object
method rate_food(arg):
(arg is_a ColorFish) + arg.color'],
'(new Bear(100, 5)).rate_food(new ColorFish(10, 3))')
~is '3'
The result is up to you if <Exp>
in <Exp> is_a <Sym>
does not produce an object or if <Sym>
is not the name of a class.
Solution
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
| #lang shplait
// ----------------------------------------
// "class.rhm"
type Exp
| intE(n :: Int)
| plusE(lhs :: Exp,
rhs :: Exp)
| multE(lhs :: Exp,
rhs :: Exp)
| argE()
| thisE()
| newE(class_name :: Symbol,
args :: Listof(Exp))
| getE(obj_exp :: Exp,
field_name :: Symbol)
| isaE(exp :: Exp,
class_name :: Symbol)
| selectE(left :: Exp,
right :: Exp)
| sendE(obj_exp :: Exp,
method_name :: Symbol,
arg_exp :: Exp)
| ssendE(obj_exp :: Exp,
class_name :: Symbol,
method_name :: Symbol,
arg_exp :: Exp)
type Class
| classC(super_name :: Symbol,
field_names :: Listof(Symbol),
methods :: Listof(Symbol * Exp))
type Value
| intV(n :: Int)
| objV(class_name :: Symbol,
fields :: Listof(Value))
// ----------------------------------------
fun find(l :: Listof(Symbol * ?a), name :: Symbol) :: ?a:
match l
| []:
error(#'find, "not found: " +& name)
| cons(p, rst_l):
if fst(p) == name
| snd(p)
| find(rst_l, name)
module test:
check: find([values(#'a, 1)], #'a)
~is 1
check: find([values(#'a, "apple")], #'a)
~is "apple"
check: find([values(#'a, 1), values(#'b, 2)], #'b)
~is 2
check: find([], #'a)
~raises "not found: a"
check: find([values(#'a, 1)], #'x)
~raises "not found: x"
// ----------------------------------------
fun check_inherited(classes :: Listof(Symbol * Class), class_to_check :: (Symbol * Class), isa_class_name :: Symbol):
if fst(class_to_check) == isa_class_name
| #true
| if fst(class_to_check) == #'Object
| #false
| match snd(class_to_check)
| classC(super_name, field_names, methods):
def parent = values(super_name, find(classes, super_name))
check_inherited(classes, parent, isa_class_name)
def object_class: values(#'Object, classC(#'Object, [], []))
def interp :: (Exp, Listof(Symbol * Class), Value, Value) -> Value:
fun (a, classes, this_val, arg_val):
fun recur(exp):
interp(exp, classes, this_val, arg_val)
match a
| intE(n): intV(n)
| plusE(l, r): num_plus(recur(l), recur(r))
| multE(l, r): num_mult(recur(l), recur(r))
| thisE(): this_val
| argE(): arg_val
| newE(class_name, field_exps):
def c = find(cons(object_class, classes), class_name)
def vals = map(recur, field_exps)
if length(vals) == length(classC.field_names(c))
| objV(class_name, vals)
| error(#'interp, "wrong field count")
| isaE(exp, class_to_check):
match recur(exp)
| objV(class_name, fields):
def classes_with_Object = cons(object_class, classes)
def c = find(classes_with_Object, class_name)
if check_inherited(classes_with_Object, values(class_name, c), class_to_check)
| intV(0)
| intV(1)
| ~else: error(#'interp, "exp must resolve to objV")
| selectE(left, right):
match recur(left)
| intV(n): def obj = recur(right)
match obj
| objV(class_name, fields):
if n == 0
| call_method(class_name, #'zero, classes, obj, intV(0))
| call_method(class_name, #'nonzero, classes, obj, intV(0))
| ~else: error(#'interp, "right exp must resolve to objV")
| ~else: error(#'interp, "left exp must resolve to intV")
| getE(obj_exp, field_name):
match recur(obj_exp)
| objV(class_name, fields):
match find(classes, class_name)
| classC(super_name, field_names, methods):
find(map2(fun (n, v): values(n, v), field_names, fields),
field_name)
| ~else:
error(#'interp, "not an object")
| sendE(obj_exp, method_name, arg_exp):
def obj = recur(obj_exp)
def arg_val = recur(arg_exp)
match obj
| objV(class_name, fields):
call_method(class_name, method_name, classes, obj, arg_val)
| ~else:
error(#'interp, "not an object")
| ssendE(obj_exp, class_name, method_name, arg_exp):
def obj = recur(obj_exp)
def arg_val = recur(arg_exp)
call_method(class_name, method_name, classes, obj, arg_val)
fun call_method(class_name, method_name, classes, obj, arg_val):
match find(classes, class_name)
| classC(super_name, field_names, methods):
let body_exp = find(methods, method_name):
interp(body_exp, classes, obj, arg_val)
fun num_op(op :: (Int, Int) -> Int, l :: Value, r :: Value) :: Value:
cond
| l is_a intV && r is_a intV:
intV(op(intV.n(l), intV.n(r)))
| ~else:
error(#'interp, "not a number")
fun num_plus(l :: Value, r :: Value) :: Value:
num_op(fun (a, b): a+b, l, r)
fun num_mult(l :: Value, r :: Value) :: Value:
num_op(fun (a, b): a*b, l, r)
// ----------------------------------------
// Examples
module test:
def posn_class:
values(
#'Posn,
classC(#'Object,
[#'x,#'y],
[
values(#'mdist,
plusE(getE(thisE(), #'x), getE(thisE(), #'y))),
values(#'addDist,
plusE(sendE(thisE(), #'mdist, intE(0)),
sendE(argE(), #'mdist, intE(0)))),
values(#'addX,
plusE(getE(thisE(), #'x), argE())),
values(#'multY,
multE(argE(), getE(thisE(), #'y))),
values(#'factory12,
newE(#'Posn, [intE(1), intE(2)]))
])
)
def posn3D_class:
values(
#'Posn3D,
classC(#'Object,
[#'x,#'y, #'z],
[
values(#'mdist,
plusE(getE(thisE(), #'z),
ssendE(thisE(), #'Posn, #'mdist, argE()))),
values(#'addDist,
ssendE(thisE(), #'Posn, #'addDist, argE()))
])
)
def posn27 = newE(#'Posn, [intE(2), intE(7)])
def posn531 = newE(#'Posn3D, [intE(5), intE(3), intE(1)])
fun interp_posn(a):
interp(a, [posn_class, posn3D_class], intV(-1), intV(-1))
// ----------------------------------------
module test:
check: interp(intE(10),
[], objV(#'Object, []), intV(0))
~is intV(10)
check: interp(plusE(intE(10), intE(17)),
[], objV(#'Object, []), intV(0))
~is intV(27)
check: interp(multE(intE(10), intE(7)),
[], objV(#'Object, []), intV(0))
~is intV(70)
check: interp_posn(newE(#'Posn, [intE(2), intE(7)]))
~is objV(#'Posn, [intV(2), intV(7)])
check: interp_posn(sendE(posn27, #'mdist, intE(0)))
~is intV(9)
check: interp_posn(sendE(posn27, #'addX, intE(10)))
~is intV(12)
check: interp_posn(sendE(ssendE(posn27, #'Posn, #'factory12, intE(0)),
#'multY,
intE(15)))
~is intV(30)
check: interp_posn(sendE(posn531, #'addDist, posn27))
~is intV(18)
check: interp_posn(plusE(intE(1), posn27))
~raises "not a number"
check: interp_posn(getE(intE(1), #'x))
~raises "not an object"
check: interp_posn(sendE(intE(1), #'mdist, intE(0)))
~raises "not an object"
check: interp_posn(newE(#'Posn, [intE(0)]))
~raises "wrong field count"
// ----------------------------------------
// "inherit.rhm"
type ExpI
| intI(n :: Int)
| plusI(lhs :: ExpI,
rhs :: ExpI)
| multI(lhs :: ExpI,
rhs :: ExpI)
| argI()
| thisI()
| newI(class_name :: Symbol,
args :: Listof(ExpI))
| selectI(left :: ExpI,
right :: ExpI)
| isaI(exp :: ExpI,
class_name :: Symbol)
| getI(obj_exp :: ExpI,
field_name :: Symbol)
| sendI(obj_exp :: ExpI,
method_name :: Symbol,
arg_exp :: ExpI)
| superI(method_name :: Symbol,
arg_exp :: ExpI)
type ClassI
| classI(super_name :: Symbol,
field_names :: Listof(Symbol),
methods :: Listof(Symbol * ExpI))
// ----------------------------------------
fun exp_i_to_c(a :: ExpI, super_name :: Symbol) :: Exp:
block:
fun recur(exp):
exp_i_to_c(exp, super_name)
match a
| intI(n): intE(n)
| plusI(l, r): plusE(recur(l), recur(r))
| multI(l, r): multE(recur(l), recur(r))
| argI(): argE()
| thisI(): thisE()
| newI(class_name, field_exps):
newE(class_name, map(recur, field_exps))
| isaI(exp, sym):
isaE(recur(exp), sym)
| selectI(left, right):
selectE(recur(left), recur(right))
| getI(exp, field_name):
getE(recur(exp), field_name)
| sendI(exp, method_name, arg_exp):
sendE(recur(exp), method_name, recur(arg_exp))
| superI(method_name, arg_exp):
ssendE(thisE(), super_name, method_name, recur(arg_exp))
module test:
check: exp_i_to_c(intI(10), #'Object)
~is intE(10)
check: exp_i_to_c(plusI(intI(10), intI(2)), #'Object)
~is plusE(intE(10), intE(2))
check: exp_i_to_c(multI(intI(10), intI(2)), #'Object)
~is multE(intE(10), intE(2))
check: exp_i_to_c(argI(), #'Object)
~is argE()
check: exp_i_to_c(thisI(), #'Object)
~is thisE()
check: exp_i_to_c (newI(#'Object, [intI(1)]), #'Object)
~is newE(#'Object, [intE(1)])
check: exp_i_to_c(getI(intI(1), #'x), #'Object)
~is getE(intE(1), #'x)
check: exp_i_to_c(sendI(intI(1), #'mdist, intI(2)), #'Object)
~is sendE(intE(1), #'mdist, intE(2))
check: exp_i_to_c(superI(#'mdist, intI(2)), #'Posn)
~is ssendE(thisE(), #'Posn, #'mdist, intE(2))
// ----------------------------------------
fun class_i_to_c_not_flat(c :: ClassI) :: Class:
match c
| classI(super_name, field_names, methods):
classC(super_name,
field_names,
map(fun (m):
values(fst(m),
exp_i_to_c(snd(m), super_name)),
methods))
module test:
def posn3d_mdist_i_method:
values(#'mdist,
plusI(getI(thisI(), #'z),
superI(#'mdist, argI())))
def posn3d_mdist_c_method:
values(#'mdist,
plusE(getE(thisE(), #'z),
ssendE(thisE(), #'Posn, #'mdist, argE())))
def posn3d_i_class:
values(#'Posn3D,
classI(#'Posn,
[#'z],
[posn3d_mdist_i_method]))
def posn3d_c_class_not_flat:
values(#'Posn3D,
classC(#'Posn,
[#'z],
[posn3d_mdist_c_method]))
check: class_i_to_c_not_flat(snd(posn3d_i_class))
~is snd(posn3d_c_class_not_flat)
// ----------------------------------------
fun flatten_class(name :: Symbol,
classes_not_flat :: Listof(Symbol * Class),
i_classes :: Listof(Symbol * ClassI)) :: Class:
match find(classes_not_flat, name)
| classC(original_super_name, field_names, methods):
match flatten_super(name, classes_not_flat, i_classes)
| classC(super_name, super_field_names, super_methods):
classC(original_super_name,
add_fields(super_field_names, field_names),
add_or_replace_methods(super_methods, methods))
fun flatten_super(name :: Symbol,
classes_not_flat :: Listof(Symbol * Class),
i_classes :: Listof(Symbol * ClassI)) :: Class:
match find(i_classes, name)
| classI(super_name, field_names, i_methods):
if super_name == #'Object
| classC(super_name, [], [])
| flatten_class(super_name, classes_not_flat, i_classes)
module test:
def posn_i_class:
values(#'Posn,
classI(#'Object,
[#'x, #'y],
[values(#'mdist,
plusI(getI(thisI(), #'x),
getI(thisI(), #'y))),
values(#'addDist,
plusI(sendI(thisI(), #'mdist, intI(0)),
sendI(argI(), #'mdist, intI(0))))]))
def addDist_c_method:
values(#'addDist,
plusE(sendE(thisE(), #'mdist, intE(0)),
sendE(argE(), #'mdist, intE(0))))
def posn_c_class_not_flat:
values(#'Posn,
classC(#'Object,
[#'x, #'y],
[values(#'mdist,
plusE(getE(thisE(), #'x),
getE(thisE(), #'y))),
addDist_c_method]))
def posn3d_c_class:
values(#'Posn3D,
classC(#'Posn,
[#'x, #'y, #'z],
[posn3d_mdist_c_method,
addDist_c_method]))
check: flatten_class(#'Posn3D,
[posn_c_class_not_flat,
posn3d_c_class_not_flat],
[posn_i_class,
posn3d_i_class])
~is snd(posn3d_c_class)
// ----------------------------------------
def add_fields = append
fun add_or_replace_methods(methods :: Listof(Symbol * Exp),
new_methods :: Listof(Symbol * Exp))
:: (Listof (Symbol * Exp)):
match new_methods
| []: methods
| cons(fst_method, rst_new_methods):
add_or_replace_methods(add_or_replace_method(methods,
fst_method),
rst_new_methods)
fun add_or_replace_method(methods :: Listof(Symbol * Exp),
new_method :: Symbol * Exp)
:: (Listof (Symbol * Exp)):
match methods
| []: [new_method]
| cons(fst_method, rst_methods):
if fst(fst_method) == fst(new_method)
| cons(new_method, rst_methods)
| cons(fst_method,
add_or_replace_method(rst_methods,
new_method))
module test:
check: add_fields([#'x, #'y], [#'z])
~is [#'x, #'y, #'z]
check: add_or_replace_methods([], [])
~is []
check: add_or_replace_methods([], [values(#'m, intE(0))])
~is [values(#'m, intE(0))]
check: add_or_replace_methods([values(#'m, intE(0))], [])
~is [values(#'m, intE(0))]
check: add_or_replace_methods([values(#'m, intE(0))],
[values(#'m, intE(1))])
~is [values(#'m, intE(1))]
check: add_or_replace_methods([values(#'m, intE(0)),
values(#'n, intE(2))],
[values(#'m, intE(1))])
~is [values(#'m, intE(1)),
values(#'n, intE(2))]
check: add_or_replace_methods([values(#'m, intE(0))],
[values(#'m, intE(1)),
values(#'n, intE(2))])
~is [values(#'m, intE(1)),
values(#'n, intE(2))]
check: add_or_replace_method([values(#'m, intE(0))],
values(#'m, intE(1)))
~is [values(#'m, intE(1))]
check: add_or_replace_method([values(#'m, intE(0))],
values(#'n, intE(2)))
~is [values(#'m, intE(0)),
values(#'n, intE(2))]
// ----------------------------------------
fun interp_i(i_a :: ExpI, i_classes :: Listof(Symbol * ClassI)) :: Value:
block:
def a = exp_i_to_c(i_a, #'Object)
def classes_not_flat:
map(fun (i): values(fst(i),
class_i_to_c_not_flat(snd(i))),
i_classes)
def classes:
map(fun (c):
let name = fst(c):
values(name,
flatten_class(name, classes_not_flat, i_classes)),
classes_not_flat)
interp(a, classes, objV(#'Object, []), intV(0))
module test:
check: interp_i(intI(0), [])
~is intV(0)
check: interp_i(
sendI(newI(#'Posn3D, [intI(5), intI(3), intI(1)]),
#'addDist,
newI(#'Posn, [intI(2), intI(7)])),
[posn_i_class,
posn3d_i_class]
)
~is intV(18)
// ----------------------------------------
// "inherit_parse.rhm"
fun parse_class(s :: Syntax) :: (Symbol * ClassI):
match s
| 'class $name($field, ...):
extends $parent_name
$method
...':
values(syntax_to_symbol(name),
classI(syntax_to_symbol(parent_name),
map(parse_field,
syntax_to_list('[$field, ...]')),
map(parse_method,
syntax_to_list('[$method, ...]'))))
| ~else: error(#'parse_class, "invalid input: " +& s)
fun parse_field(s :: Syntax) :: Symbol:
cond
| syntax_is_symbol(s):
syntax_to_symbol(s)
| ~else: error(#'parse_field, "invalid input: " +& s)
fun parse_method(s :: Syntax) :: (Symbol * ExpI):
match s
|'method $name(arg): $body':
values(syntax_to_symbol(name),
parse(body))
| ~else: error(#'parse_method, "invalid input: " +& s)
fun parse(s :: Syntax) :: ExpI:
cond
| syntax_is_integer(s):
intI(syntax_to_integer(s))
| ~else:
match s
| 'arg': argI()
| 'this': thisI()
| '$exp is_a $sym':
isaI(parse(exp), syntax_to_symbol(sym))
| 'select $left : $right':
selectI(parse(left), parse(right))
| '$left + $right':
plusI(parse(left),
parse(right))
| '$left * $right':
multI(parse(left),
parse(right))
| 'new $id($arg, ...)':
newI(syntax_to_symbol(id),
map(parse, syntax_to_list('[$arg, ...]')))
| 'super . $method_name ($arg)':
superI(syntax_to_symbol(method_name),
parse(arg))
| '$obj . $method_name ($arg)':
sendI(parse(obj),
syntax_to_symbol(method_name),
parse(arg))
| '$obj . $field_name':
getI(parse(obj),
syntax_to_symbol(field_name))
| '($e)':
parse(e)
| ~else:
error(#'parse, "invalid input: " +& s)
module test:
check: parse('0')
~is intI(0)
check: parse('arg')
~is argI()
check: parse('this')
~is thisI()
check: parse('1 + 2')
~is plusI(intI(1), intI(2))
check: parse('1 * 2')
~is multI(intI(1), intI(2))
check: parse('new Posn(1, 2)')
~is newI(#'Posn, [intI(1), intI(2)])
check: parse('this.x')
~is getI(thisI(), #'x)
check: parse('this.m(2)')
~is sendI(thisI(), #'m, intI(2))
check: parse('super.m(1)')
~is superI(#'m, intI(1))
check: parse('x')
~raises "invalid input"
check: parse_field('x')
~is #'x
check: parse_field('x 1')
~raises "invalid input"
check: parse_method('method m(arg): this')
~is values(#'m, thisI())
check: parse_method('m(arg): 1 2')
~raises "invalid input"
check: parse_class('class Posn3D(x, y, z):
extends Posn
method m1(arg): arg
method m2(arg): this')
~is values(#'Posn3D,
classI(#'Posn,
[#'x, #'y, #'z],
[values(#'m1, argI()),
values(#'m2, thisI())]))
check: parse_class('class')
~raises "invalid input"
// ----------------------------------------
fun interp_prog(classes :: Listof(Syntax), s :: Syntax) :: Syntax:
let v = interp_i(parse(s),
map(parse_class, classes)):
match v
| intV(n): integer_to_syntax(n)
| objV(class_name, field_vals): 'object'
module test:
check: interp_prog(
['class Empty():
extends Object'],
'new Empty()'
)
~is 'object'
check: interp_prog(
['class Posn(x, y):
extends Object
method mdist(arg): this.x + this.y
method addDist(arg): arg.mdist(0) + this.mdist(0)',
'class Posn3D(z):
extends Posn
method mdist(arg): this.z + super.mdist(arg)'],
'(new Posn3D(5, 3, 1)).addDist(new Posn(2, 7))'
)
~is '18'
// Part 1
module test:
check: interp_prog([],
'new Object()')
~is 'object'
check: interp_prog(['class Fish(size, color):
extends Object'],
'new Fish(1, 2)')
~is 'object'
// Part 2
module test:
check: interp_prog(['class Snowball(size):
extends Object
method zero(arg): this
method nonzero(arg):
new Snowball(1 + this.size)'],
'(select 0: new Snowball(1)).size')
~is '1'
check: interp_prog(['class Snowball(size):
extends Object
method zero(arg): this
method nonzero(arg):
new Snowball(1 + this.size)'],
'(select 1 + 2: new Snowball(1)).size')
~is '2'
// To cover the error cases
check: interp_prog(['class Snowball(size):
extends Object
method zero(arg): this
method nonzero(arg):
new Snowball(1 + this.size)'],
'(select (new Snowball(1)): new Snowball(1)).size')
~raises "left exp must resolve to intV"
check: interp_prog(['class Snowball(size):
extends Object
method zero(arg): this
method nonzero(arg):
new Snowball(1 + this.size)'],
'(select 1 + 2: 6)')
~raises "right exp must resolve to objV"
// Part 3
module test:
check: interp_prog(['class Fish(size, color):
extends Object'],
'new Fish(1, 2) is_a Object')
~is '0'
check: interp_prog(['class Fish(size, color):
extends Object'],
'new Object() is_a Fish')
~is '1'
check: interp_prog(['class Fish(size, color):
extends Object'],
'new Fish(1, 2) is_a Fish')
~is '0'
check: interp_prog(['class Fish(size, color):
extends Object',
'class Shark(teeth):
extends Fish'],
'new Shark(1, 2, 3) is_a Fish')
~is '0'
check: interp_prog(['class Fish(size, color):
extends Object',
'class Shark(teeth):
extends Fish',
'class Hammerhead():
extends Shark'],
'new Hammerhead(1, 2, 3) is_a Fish')
~is '0'
check: interp_prog(['class PlainFish(size):
extends Object',
'class ColorFish(color):
extends PlainFish',
'class Bear(size, color):
extends Object
method rate_food(arg):
(arg is_a ColorFish) + arg.color'],
'(new Bear(100, 5)).rate_food(new ColorFish(10, 3))')
~is '3'
// To cover the error cases
check: interp_prog(['class Fish(size, color):
extends Object'],
'5 is_a Object')
~raises "exp must resolve to objV"
|
HW10
Difficulty: ★★☆☆
Part 1 — true
, false
, =
, and if
Start with typed_lambda.rhm
.
The implementation already includes a Boolean
type, but no expressions of Boolean
type.
Add support for #true
, #false
, <Exp> == <Exp>
, and if <Exp> | <Exp> | <Exp>
expressions, where ==
produces a boolean given two numbers, and if
requires a boolean expression for the test. The precedence of ==
should be just below +
, and the precedence of if
should be lowest.
Examples:
check: interp(parse('if #true | 4 | 5'),
mt_env)
~is intV(4)
check: interp(parse('if #false | 4 | 5'),
mt_env)
~is intV(5)
check: interp(parse('if 13 == (if 1 == -1 + 2
| 12
| 13)
| 4
| 5'),
mt_env)
~is intV(5)
check: typecheck(parse('13 == (if 1 == -1 + 2
| 12
| 13)'),
mt_env)
~is boolT()
check: typecheck(parse('if 1 == -1 + 2
| fun (x :: Int): x + 1
| fun (y :: Int): y'),
mt_env)
~is arrowT(intT(), intT())
check: typecheck(parse('1 + if #true | #true | #false'),
mt_env)
~raises "no type"
Part 2 — Pairs
Implement pair(<Exp>, <Exp>)
, fst(<Exp>)
, and snd(<Exp>)
expressions with the usual precedence, as well as <Type> * <Type>
types where *
in types has precedence just above ->
. The relevant type rules are shown in video 9. These are eager pairs: pair
evaluates its arguments eagerly.
Examples:
check: interp(parse('pair(10, 8)'),
mt_env)
~is pairV(intV(10), intV(8))
check: interp(parse('fst(pair(10, 8))'),
mt_env)
~is intV(10)
check: interp(parse('snd(pair(10, 8))'),
mt_env)
~is intV(8)
check: interp(parse('let p :: Int * Int = pair(10, 8):
fst(p)'),
mt_env)
~is intV(10)
check: interp(parse('let f :: Int -> Int * Int
= (fun (n :: Int):
pair(n, n+1)):
snd(f(10))'),
mt_env)
~is intV(11)
check: interp(parse('let f :: Int * Boolean -> Int
= (fun (p :: Int * Boolean):
fst(p)):
f(pair(10, #false))'),
mt_env)
~is intV(10)
check: typecheck(parse('pair(10, 8)'),
mt_env)
~is crossT(intT(), intT())
check: typecheck(parse('fst(pair(10, 8))'),
mt_env)
~is intT()
check: typecheck(parse('snd(pair(10, 8))'),
mt_env)
~is intT()
check: typecheck(parse('let p :: Int * Int = pair(10, 8):
fst(p)'),
mt_env)
~is intT()
check: typecheck(parse('let f :: Int -> Int * Int
= (fun (n :: Int):
pair(n, n+1)):
snd(f(10))'),
mt_env)
~is intT()
check: typecheck(parse('let f :: Int * Boolean -> Int
= (fun (p :: Int * Boolean):
fst(p)):
f(pair(10, #false))'),
mt_env)
~is intT()
check: typecheck(parse('let f :: Int * Boolean -> Boolean
= (fun (p :: Int * Boolean):
snd(p)):
f(pair(10, #false))'),
mt_env)
~is boolT()
check: typecheck(parse('fun (x :: Int * Boolean):
if snd(x)
| fst(x)
| 0'),
mt_env)
~is arrowT(crossT(intT(), boolT()), intT())
check: typecheck(parse('fst(10)'),
mt_env)
~raises "no type"
check: typecheck(parse('1 + fst(pair(#false, 10))'),
mt_env)
~raises "no type"
check: typecheck(parse('fun (x :: Int * Boolean):
if fst(x)
| 1
| 2'),
mt_env)
~raises "no type"
Part 3 — Functions that Accept Multiple Arguments, Yet Again
With pairs, functions can accept multiple arguments by accepting paired values, but we can also add direct support for multiple arguments.
Change the interpreter to allow multiple function arguments and multiple arguments at function calls. The grammar of the language is now as follows:
<Exp> = <Number>
| #true
| #false
| <Exp> + <Exp>
| <Exp> * <Exp>
| <Exp> == <Exp>
| <Symbol>
| if <Exp> | <Exp> | <Exp>
| fun (<Sym> :: <Type>, ...): <Exp>
| <Exp>(<Exp>, ...)
| let <Sym> :: <Type> = <Exp>: <Exp>
| pair(<Exp>, <Exp>)
| fst(<Exp>)
| snd(<Exp>)
| (<Exp>)
<Type> = Int
| Boolean
| <Type> -> <Type>
| (<Type>, ...) -> <Type>
| <Type> * <Type>
| (<Type>)
The <Type> -> <Type>
type form is a shorthand for (<Type>) -> <Type>
in the case of a single argument. In parse_type
, match (<Type>, ...) -> <Type>
before <Type> -> <Type>
, because a pattern for the latter single-single case will also match the former multi-argument case.
Examples:
check: interp(parse('(fun (): 10)()'),
mt_env)
~is intV(10)
check: interp(parse('(fun (x :: Int, y :: Int): x+y)(10, 20)'),
mt_env)
~is intV(30)
check: typecheck(parse('(fun (x :: Int, y :: Int): x+y)(10, 20)'),
mt_env)
~is intT()
check: typecheck(parse('(fun (x :: Int, y :: Boolean): y)(10, #false)'),
mt_env)
~is boolT()
check: typecheck(parse('(fun (x :: Int, y :: Boolean): y)(#false, 10)'),
mt_env)
~raises "no type"
check: typecheck(parse('(fun (): 10)(1, 2, 3)'),
mt_env)
~raises "no type"
check: typecheck(parse('let x :: Int = 4:
let f :: (Int, Int) -> Int
= (fun (y :: Int, z :: Int):
z + y):
f(x, x)'),
mt_env)
~is intT()
check: typecheck(parse('let x :: Int = 4:
let f :: (Boolean, Int) -> Int
= (fun (sel :: Boolean, z :: Int):
if sel | x | z):
f(x == 5, 0)'),
mt_env)
~is intT()
Solution
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
| #lang shplait
type Value
| intV(n :: Int)
| closV(args :: Listof(Symbol),
body :: Exp,
env :: Env)
| boolV(n :: Boolean)
| pairV(fst :: Value,
snd :: Value)
type Exp
| intE(n :: Int)
| boolE(n :: Boolean)
| idE(s :: Symbol)
| plusE(l :: Exp,
r :: Exp)
| multE(l :: Exp,
r :: Exp)
| funE(ns :: Listof(Symbol),
args_type :: Listof(Type),
body :: Exp)
| appE(fn :: Exp,
args :: Listof(Exp))
| ifE(exp :: Exp,
true :: Exp,
false :: Exp)
| equalE(left :: Exp,
right :: Exp)
| pairE(fst :: Exp,
snd :: Exp)
| fstE(exp :: Exp)
| sndE(exp :: Exp)
type Type
| intT()
| boolT()
| arrowT(args :: Listof(Type),
result :: Type)
| crossT(fst :: Type,
snd :: Type)
type Binding
| bind(name :: Symbol,
val :: Value)
type Env = Listof(Binding)
type TypeBinding
| tbind(name :: Symbol,
ty :: Type)
type TypeEnv = Listof(TypeBinding)
def mt_env = []
def extend_env = cons
fun parse_fun_args(args :: Listof(Syntax)) :: Listof(Symbol):
match args
| []: []
| cons(arg, rest_args):
match arg
| '$arg_name :: $arg_type': cons(syntax_to_symbol(arg_name), parse_fun_args(rest_args))
| ~else: error(#'parse, "invalid input")
fun parse_fun_args_types(args :: Listof(Syntax)) :: Listof(Type):
match args
| []: []
| cons(arg, rest_args):
match arg
| '$arg_name :: $arg_type': cons(parse_type(arg_type), parse_fun_args_types(rest_args))
| ~else: error(#'parse, "invalid input")
// There is no way to reach the error line without explicit call to parse_fun_args_types
module test:
check: parse_fun_args(['a :: Int', 'b somethign'])
~raises "invalid input"
check: parse_fun_args_types(['a :: Int', 'b somethign'])
~raises "invalid input"
// parse ----------------------------------------
fun parse(s :: Syntax) :: Exp:
cond
| syntax_is_integer(s):
intE(syntax_to_integer(s))
| syntax_is_symbol(s):
idE(syntax_to_symbol(s))
| ~else:
match s
| '#true': boolE(#true)
| '#false': boolE(#false)
| 'pair($fst, $snd)':
pairE(parse(fst), parse(snd))
| 'fst($exp)': fstE(parse(exp))
| 'snd($exp)': sndE(parse(exp))
| 'if $exp | $true | $false':
ifE(parse(exp), parse(true), parse(false))
| 'let $name :: $ty = $rhs:
$body':
appE(funE([syntax_to_symbol(name)],
[parse_type(ty)],
parse(body)),
[parse(rhs)])
| '$left == $right':
equalE(parse(left), parse(right))
| '$left + $right':
plusE(parse(left),
parse(right))
| '$left * $right':
multE(parse(left),
parse(right))
| 'fun ($args, ...): $body':
funE(check_unique(parse_fun_args(syntax_to_list('[$args, ...]'))),
parse_fun_args_types(syntax_to_list('[$args, ...]')),
parse(body))
| '$fn($args, ...)':
appE(parse(fn),
map(parse, syntax_to_list('[$args, ...]')))
| '($e)':
parse(e)
| ~else:
error(#'parse, "invalid input: " +& s)
fun check_unique(syms :: Listof(Symbol)):
match syms
| []: []
| cons(sym, rst_syms):
if member(sym, rst_syms)
| error(#'parse, "bad syntax, duplicate argument: " +& sym)
| cons(sym, check_unique(rst_syms))
fun parse_type(s :: Syntax) :: Type:
match s
| 'Int': intT()
| 'Boolean': boolT()
| '($args, ...) -> $result':
arrowT(map(parse_type, syntax_to_list('[$args, ...]')),
parse_type(result))
| '$arg1 -> $arg2 -> $result':
// make `->` right-associative
parse_type('$arg1 -> ($arg2 -> $result)')
| '$arg -> $result':
arrowT([parse_type(arg)], parse_type(result))
| '$fst * $snd': crossT(parse_type(fst), parse_type(snd))
| '($ty)': parse_type(ty)
| ~else: error(#'parse_type, "invalid input: " +& s)
module test:
check: parse('2')
~is intE(2)
check: parse('x')
~is idE(#'x)
check: parse('2 + 1')
~is plusE(intE(2), intE (1))
check: parse('3 * 4')
~is multE(intE(3), intE(4))
check: parse('3 * 4 + 8')
~is plusE(multE(intE(3), intE(4)),
intE(8))
check: parse('fun (x :: Int): 9')
~is funE([#'x], [intT()], intE(9))
check: parse('double(9)')
~is appE(idE(#'double), [intE(9)])
check: parse('1 + double(9)')
~is plusE(intE(1),
appE(idE(#'double), [intE(9)]))
check: parse('3 * (4 + 8)')
~is multE(intE(3),
plusE(intE(4), intE(8)))
check: parse('let x :: Int = 1 + 2:
y')
~is appE(funE([#'x],
[intT()],
idE(#'y)),
[plusE(intE(1), intE(2))])
check: parse('1 2')
~raises "invalid input"
check: parse_type('Int')
~is intT()
check: parse_type('Boolean')
~is boolT()
check: parse_type('Int -> Boolean')
~is arrowT([intT()], boolT())
check: parse_type('Int -> (Boolean -> Int)')
~is arrowT([intT()], arrowT([boolT()], intT()))
check: parse_type('Int -> Boolean -> Int')
~is arrowT([intT()], arrowT([boolT()], intT()))
check: parse_type('1')
~raises "invalid input"
fun extend_env_with_args(ns :: Listof(Symbol), args :: Listof(Value), env :: Env) :: Env:
match ns
| []:
match args
| []: env
| cons(arg, rest_args): error(#'interp, "wrong number of args")
| cons(n, rest_ns):
match args
| []: error(#'interp, "wrong number of args")
| cons(arg, rest_args):
extend_env_with_args(rest_ns,
rest_args,
extend_env(bind(n, arg), env))
// interp ----------------------------------------
fun interp(a :: Exp, env :: Env) :: Value:
match a
| intE(n): intV(n)
| idE(s): lookup(s, env)
| plusE(l, r): num_plus(interp(l, env), interp(r, env))
| multE(l, r): num_mult(interp(l, env), interp(r, env))
| funE(ns, arg_types, body): closV(ns, body, env)
| appE(fn, args):
match interp(fn, env)
| closV(ns, body, c_env):
interp(body,
extend_env_with_args(ns,
map(fun (a1): interp(a1, env), args),
c_env))
| ~else: error(#'interp, "not a function")
| boolE(n): boolV(n)
| ifE(exp, true, false):
match interp(exp, env)
| boolV(n): if n:
| interp(true, env)
| interp(false, env)
| ~else: error(#'interp, "not a boolean")
| equalE(left, right):
match interp(left, env)
| intV(leftV): match interp(right, env)
| intV(rightV): boolV(leftV == rightV)
| ~else: error(#'interp, "not an Int")
| ~else: error(#'interp, "not an Int")
| pairE(fst, snd):
pairV(interp(fst, env), interp(snd, env))
| fstE(exp):
match interp(exp, env)
| pairV(fst, second): fst
| ~else: error(#'interp, "not a pair")
| sndE(exp):
match interp(exp, env)
| pairV(fst, snd): snd
| ~else: error(#'interp, "not a pair")
module test:
check: interp(parse('2'), mt_env)
~is intV(2)
check: interp(parse('x'), mt_env)
~raises "free variable"
check: interp(parse('x'),
extend_env(bind(#'x, intV(9)), mt_env))
~is intV(9)
check: interp(parse('2 + 1'), mt_env)
~is intV(3)
check: interp(parse('2 * 1'), mt_env)
~is intV(2)
check: interp(parse('(2 * 3) + (5 + 8)'), mt_env)
~is intV(19)
check: interp(parse('fun (x :: Int):
x + x'),
mt_env)
~is closV([#'x], plusE(idE(#'x), idE(#'x)), mt_env)
check: interp(parse('let x :: Int = 5: x + x'),
mt_env)
~is intV(10)
check: interp(parse('let x :: Int = 5:
let x :: Int = x + 1:
x + x'),
mt_env)
~is intV(12)
check: interp(parse('let x :: Int = 5:
let y :: Int = 6:
x'),
mt_env)
~is intV(5)
check: interp(parse('(fun (x :: Int): x + x)(8)'),
mt_env)
~is intV(16)
check: interp(parse('1(2)'), mt_env)
~raises "not a function"
check: interp(parse('1 + (fun (x :: Int): x)'), mt_env)
~raises "not a number"
check: interp(parse('let bad :: Int -> Int = (fun (x :: Int):
x + y):
let y :: Int = 5:
bad(2)'),
mt_env)
~raises "free variable"
// num_plus and num_mult ----------------------------------------
fun num_op(op :: (Int, Int) -> Int, l :: Value, r :: Value) :: Value:
cond
| l is_a intV && r is_a intV:
intV(op(intV.n(l), intV.n(r)))
| ~else:
error(#'interp, "not a number")
fun num_plus(l :: Value, r :: Value) :: Value:
num_op(fun (a, b): a+b, l, r)
fun num_mult(l :: Value, r :: Value) :: Value:
num_op(fun (a, b): a*b, l, r)
module test:
check: num_plus(intV(1), intV(2))
~is intV(3)
check: num_mult(intV(3), intV(2))
~is intV(6)
// lookup ----------------------------------------
fun make_lookup(get_name :: ?a -> Symbol, get_val :: ?a -> ?b):
fun (n :: Symbol, env :: Listof(?a)) :: ?b:
match env
| []: error(#'lookup, "free variable")
| cons(b, rst_env):
cond
| n == get_name(b):
get_val(b)
| ~else:
make_lookup(get_name, get_val)(n, rst_env)
def lookup = make_lookup(bind.name, bind.val)
module test:
check: lookup(#'x, mt_env)
~raises "free variable"
check: lookup(#'x, extend_env(bind(#'x, intV(8)), mt_env))
~is intV(8)
check: lookup(#'x, extend_env(bind(#'x, intV(9)),
extend_env(bind(#'x, intV(8)),
mt_env)))
~is intV(9)
check: lookup(#'y, extend_env(bind(#'x, intV(9)),
extend_env(bind(#'y, intV(8)),
mt_env)))
~is intV(8)
fun check_arg_type_match(fn_arg_types :: Listof(Type), app_arg_types :: Listof(Type)):
match fn_arg_types
| []: match app_arg_types
| []: #true
| ~else: #false
| cons(fn_arg_type, rest_fn_arg_types):
match app_arg_types
| []: #false
| cons(app_arg_type, rest_app_arg_types):
if fn_arg_type == app_arg_type:
| check_arg_type_match(rest_fn_arg_types, rest_app_arg_types)
| #false
fun extend_tenv_with_args(ns :: Listof(Symbol), args :: Listof(Type), tenv :: TypeEnv) :: TypeEnv:
match ns
| []:
match args
| []: tenv
| cons(arg, rest_args): type_error(arg, "wrong number of args")
| cons(n, rest_ns):
match args
| []: type_error(n, "wrong number of args")
| cons(arg, rest_args):
extend_tenv_with_args(rest_ns,
rest_args,
extend_env(tbind(n, arg), tenv))
module test:
check: extend_tenv_with_args([#'a, #'b], [intT()], mt_env)
~raises "wrong number of args"
check: extend_tenv_with_args([#'a], [intT(), intT()], mt_env)
~raises "wrong number of args"
// typecheck ----------------------------------------
fun typecheck(a :: Exp, tenv :: TypeEnv) :: Type:
match a
| intE(n): intT()
| idE(n): type_lookup(n, tenv)
| plusE(l, r): typecheck_nums(l, r, tenv)
| multE(l, r): typecheck_nums(l, r, tenv)
| funE(ns, arg_types, body):
arrowT(arg_types,
typecheck(body,
extend_tenv_with_args(ns, arg_types, tenv)))
| appE(fn, args):
match typecheck(fn, tenv)
| arrowT(arg_types, result_type):
if check_arg_type_match(arg_types, map(fun (a1): typecheck(a1, tenv), args)):
| result_type
| type_error(arg_types, to_string(arg_types))
| ~else: type_error(fn, "function")
| boolE(n): boolT()
| equalE(l, r):
match typecheck(l, tenv)
| intT():
match typecheck(r, tenv)
| intT(): boolT()
| ~else: type_error(r, "num")
| ~else: type_error(l, "num")
| ifE(exp, true, false):
match typecheck(exp, tenv)
| boolT():
def trueT = typecheck(true, tenv)
def falseT = typecheck(false, tenv)
if trueT == falseT:
| trueT
| type_error(false, "true and false not same")
| ~else: type_error(exp, "bool")
| pairE(fst, snd):
crossT(typecheck(fst, tenv), typecheck(snd, tenv))
| fstE(exp):
match typecheck(exp, tenv)
| crossT(fst, snd): fst
| ~else: type_error(exp, "pair")
| sndE(exp):
match typecheck(exp, tenv)
| crossT(fst, snd): snd
| ~else: type_error(exp, "pair")
fun typecheck_nums(l, r, tenv):
match typecheck(l, tenv)
| intT():
match typecheck(r, tenv)
| intT():
intT()
| ~else: type_error(r, "num")
| ~else: type_error(l, "num")
fun type_error(a, msg):
error(#'typecheck, "no type: " +& a +& " not " +& msg)
def type_lookup: make_lookup(tbind.name, tbind.ty)
module test:
check: typecheck(parse('10'), mt_env)
~is intT()
check: typecheck(parse('10 + 17'), mt_env)
~is intT()
check: typecheck(parse('10 * 17'), mt_env)
~is intT()
check: typecheck(parse('fun (x :: Int): 12'), mt_env)
~is arrowT([intT()], intT())
check: typecheck(parse('fun (x :: Int): fun (y :: Boolean): x'),
mt_env)
~is arrowT([intT()], arrowT([boolT()], intT()))
check: typecheck(parse('(fun (x :: Int): 12)(1 + 17)'),
mt_env)
~is intT()
check: typecheck(parse('let x :: Int = 4:
let f :: (Int -> Int) = (fun (y :: Int):
x + y):
f(x)'),
mt_env)
~is intT()
check: typecheck(parse('1(2)'),
mt_env)
~raises "no type"
check: typecheck(parse('(fun (x :: Boolean): x)(2)'),
mt_env)
~raises "no type"
check: typecheck(parse('1 + (fun (x :: Int): x)'),
mt_env)
~raises "no type"
check: typecheck(parse('(fun (x :: Int): x) * 1'),
mt_env)
~raises "no type"
// Part 1
module test:
check: interp(parse('if #true | 4 | 5'),
mt_env)
~is intV(4)
check: interp(parse('if #false | 4 | 5'),
mt_env)
~is intV(5)
check: interp(parse('if 13 == (if 1 == -1 + 2
| 12
| 13)
| 4
| 5'),
mt_env)
~is intV(5)
check: typecheck(parse('13 == (if 1 == -1 + 2
| 12
| 13)'),
mt_env)
~is boolT()
check: typecheck(parse('if 1 == -1 + 2
| fun (x :: Int): x + 1
| fun (y :: Int): y'),
mt_env)
// This result may need to be adjusted after part 3:
~is arrowT([intT()], intT())
check: typecheck(parse('1 + if #true | #true | #false'),
mt_env)
~raises "no type"
// To cover error cases in interp
check: typecheck(parse('if #true | 4 | #false'), mt_env)
~raises "true and false not same"
check: typecheck(parse('if 5 | #true | #false'), mt_env)
~raises "bool"
check: typecheck(parse('#true == 5'), mt_env)
~raises "num"
check: typecheck(parse('5 == #true'), mt_env)
~raises "num"
check: interp(parse('if 5 | #true | #false'), mt_env)
~raises "not a boolean"
check: interp(parse('#true == 5'), mt_env)
~raises "not an Int"
check: interp(parse('5 == #true'), mt_env)
~raises "not an Int"
// Part 2
module test:
check: interp(parse('pair(10, 8)'),
mt_env)
// Your constructor might be different than pairV:
~is pairV(intV(10), intV(8))
check: interp(parse('fst(pair(10, 8))'),
mt_env)
~is intV(10)
check: interp(parse('snd(pair(10, 8))'),
mt_env)
~is intV(8)
check: interp(parse('let p :: Int * Int = pair(10, 8):
fst(p)'),
mt_env)
~is intV(10)
check: interp(parse('let f :: Int -> Int * Int
= (fun (n :: Int):
pair(n, n+1)):
snd(f(10))'),
mt_env)
~is intV(11)
check: interp(parse('let f :: Int * Boolean -> Int
= (fun (p :: Int * Boolean):
fst(p)):
f(pair(10, #false))'),
mt_env)
~is intV(10)
check: typecheck(parse('pair(10, 8)'),
mt_env)
// Your constructor might be different than crossT:
~is crossT(intT(), intT())
check: typecheck(parse('fst(pair(10, 8))'),
mt_env)
~is intT()
check: typecheck(parse('snd(pair(10, 8))'),
mt_env)
~is intT()
check: typecheck(parse('let p :: Int * Int = pair(10, 8):
fst(p)'),
mt_env)
~is intT()
check: typecheck(parse('let f :: Int -> Int * Int
= (fun (n :: Int):
pair(n, n+1)):
snd(f(10))'),
mt_env)
~is intT()
check: typecheck(parse('let f :: Int * Boolean -> Int
= (fun (p :: Int * Boolean):
fst(p)):
f(pair(10, #false))'),
mt_env)
~is intT()
check: typecheck(parse('let f :: Int * Boolean -> Boolean
= (fun (p :: Int * Boolean):
snd(p)):
f(pair(10, #false))'),
mt_env)
~is boolT()
check: typecheck(parse('fun (x :: Int * Boolean):
if snd(x)
| fst(x)
| 0'),
mt_env)
// Your constructor might be different than crossT:
~is arrowT([crossT(intT(), boolT())], intT())
check: typecheck(parse('fst(10)'),
mt_env)
~raises "no type"
check: typecheck(parse('1 + fst(pair(#false, 10))'),
mt_env)
~raises "no type"
check: typecheck(parse('fun (x :: Int * Boolean):
if fst(x)
| 1
| 2'),
mt_env)
~raises "no type"
// To cover error cases
check: typecheck(parse('fst(5)'), mt_env)
~raises "pair"
check: typecheck(parse('snd(5)'), mt_env)
~raises "pair"
check: interp(parse('fst(5)'), mt_env)
~raises "not a pair"
check: interp(parse('snd(5)'), mt_env)
~raises "not a pair"
// Part 3
module test:
check: interp(parse('(fun (): 10)()'),
mt_env)
~is intV(10)
check: interp(parse('(fun (x :: Int, y :: Int): x+y)(10, 20)'),
mt_env)
~is intV(30)
check: typecheck(parse('(fun (x :: Int, y :: Int): x+y)(10, 20)'),
mt_env)
~is intT()
check: typecheck(parse('(fun (x :: Int, y :: Boolean): y)(10, #false)'),
mt_env)
~is boolT()
check: typecheck(parse('(fun (x :: Int, y :: Boolean): y)(#false, 10)'),
mt_env)
~raises "no type"
check: typecheck(parse('(fun (): 10)(1, 2, 3)'),
mt_env)
~raises "no type"
check: typecheck(parse('let x :: Int = 4:
let f :: (Int, Int) -> Int
= (fun (y :: Int, z :: Int):
z + y):
f(x, x)'),
mt_env)
~is intT()
check: typecheck(parse('let x :: Int = 4:
let f :: (Boolean, Int) -> Int
= (fun (sel :: Boolean, z :: Int):
if sel | x | z):
f(x == 5, 0)'),
mt_env)
~is intT()
// To cover error cases
check: typecheck(parse('(fun (x :: Int, y :: Int): 5)(5)'), mt_env)
~raises "no type"
check: typecheck(parse('(fun (x :: Int): 5)(5, 6)'), mt_env)
~raises "no type"
check: interp(parse('(fun (x :: Int, y :: Int): 5)(5)'), mt_env)
~raises "wrong number of args"
check: interp(parse('(fun (x :: Int): 5)(5, 6)'), mt_env)
~raises "wrong number of args"
check: parse('fun (x :: Int, x :: Int): 5')
~raises "bad syntax"
|
HW11
Difficulty: ★★☆☆
Start with hw11_starter.rhm
, which is based on infer_lambda.rhm
.
The language implemented by hw11_starter.rhm
adds []
, cons
, first
, and rest
expressions to the language, and a Listof
type constructor:
<Exp> = <Number>
| <Exp> + <Exp>
| <Exp> * <Exp>
| <Symbol>
| fun (<Symbol> :: <Type>): <Exp>
| <Exp>(<Exp>)
| []
| cons(<Exp>, <Exp>)
| first(<Exp>)
| rest(<Exp>)
| (<Exp>)
<Type> = Int
| Boolean
| <Type> -> <Type>
| ?
| Listof(<Type>)
| (<Type>)
Only the interp
part of the language is implemented so far. The typecheck
part is incomplete (there are ....
s), and your job will be to complete it. First, however, you’ll add if
.
Your solution should not add any calls to resolve
, except maybe in tests, because it’s not useful outside of the one place in unify
. That is, the only call to resolve
that’s not in a test should be the one that’s in unify
in the starting code.
Part 1 — Inferring Conditional Types
Extend the language with an if
form with its usual meaning and form constrained to == 0
:
<Exp> = ....
| if <Exp> == 0 | <Exp> | <Exp>
Also, add a run_prog
function that takes a syntax object, parses it, typechecks it, and interprets it. If the parsed syntax has no type, run_prog
should raise an expression including the words “no type.” Otherwise, the result from run_prog
should be a syntax object: a syntax number if interp
produces any number, the syntax object 'function'
if interp
produces a closure, or the syntax object 'list'
if interp
produces a list.
Examples:
check: run_prog('if 0 == 0 | 1 | 2')
~is '1'
check: run_prog('if 3 == 0 | 1 | 2')
~is '2'
check: run_prog('if 2 == 0 | (fun (x :: ?): x) | (fun (x :: ?): 1 + x)')
~is 'function'
check: run_prog('if (fun (x :: ?): x) == 0 | 1 | 2')
~raises "no type"
check: run_prog('if 0 == 0 | (fun (x :: ?): x) | 2')
~raises "no type"
check: run_prog('let f :: ? = (fun (x :: ?):
fun (y :: ?):
fun (z :: ?):
if x == 0 | y | z):
f(1)(2)(3)')
~is '3'
check: run_prog('let f :: ? = (fun (x :: ?):
fun (y :: ?):
fun (z :: ?):
if x == 0
| y
| fun (x :: ?): z):
f(1)(fun (x :: Int): 2)(3)(4)')
~is '3'
check: run_prog('let f :: ? = (fun (x :: ?):
if x == 0 | x | x(1)):
f(1)')
~raises "no type"
Part 2 — Inferring List Types
Complete typecheck
for lists. Your typecheck
must ensure that an expression with a type never triggers a “not a list” or “not a number” error from interp
, although an expression with a type may still trigger a “list is empty” error.
The Listof
type constructor takes another type for the elements of a list. For example, the expression cons(1, [])
should have type Listof(Int)
. Similarly, the expression cons(fun (x :: Int): x, [])
should have type Listof(Int -> Int)
.
The expression []
can have type Listof(<Type>)
for any <Type>
. Similarly, cons
should work on arguments of type <Type>
and Listof(<Type>)
for any <Type>
, while first
and rest
work on an argument of type Listof(<Type>)
.
$$Γ ⊢ [] : Listof(τ)$$
$$\frac{Γ ⊢ e₁ : τ Γ ⊢ e₂ : Listof(τ)}{Γ ⊢ cons(e₁, e₂) : Listof(τ)}$$
$$\frac{Γ ⊢ e : Listof(τ)}{Γ ⊢ first(e) : τ}$$
$$\frac{Γ ⊢ e : Listof(τ)}{Γ ⊢ first(e) : τ}$$
$$\frac{Γ ⊢ e : Listof(τ)}{Γ ⊢ rest(e) : Listof(τ)}$$
A list is somewhat like a pair that you added to the language in HW 10, but it is treated differently by the type system. Note that type inference is needed for a plain []
expression form to make sense (or else we’d need one []
for every type of list element). Type-inferring and checking a first
or rest
expression will be similar to the application case, in that you’ll need to invent a type variable to stand for the list element’s type.
Examples:
check: run_prog('[]')
~is 'list'
check: run_prog('cons(1, [])')
~is 'list'
check: run_prog('cons([], [])')
~is 'list'
check: run_prog('cons(1, cons([], []))')
~raises "no type"
check: run_prog('first(1)')
~raises "no type"
check: run_prog('rest(1)')
~raises "no type"
check: run_prog('first([])')
~raises "list is empty"
check: run_prog('rest([])')
~raises "list is empty"
check: run_prog('let f :: ? = (fun (x :: ?):
first(x)):
f(cons(1, [])) + 3')
~is '4'
check: run_prog('let f :: ? = (fun (x :: ?):
rest(x)):
first(f(cons(1, cons(2, [])))) + 3')
~is '5'
check: run_prog('let f :: ? = (fun (x :: ?):
fun (y :: ?):
cons(x, y)):
first(rest(f(1)(cons(2, []))))')
~is '2'
check: run_prog('fun (x :: ?):
cons(x, x)')
~raises "no type"
check: run_prog('let f :: ? = (fun (x :: ?): x):
cons(f(1), f([]))')
~raises "no type"
Solution
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
| #lang shplait
type Value
| intV(n :: Int)
| closV(arg :: Symbol,
body :: Exp,
env :: Env)
| listV(elems :: Listof(Value))
type Exp
| intE(n :: Int)
| idE(s :: Symbol)
| plusE(l :: Exp,
r :: Exp)
| multE(l :: Exp,
r :: Exp)
| funE(n :: Symbol,
arg_type :: Type,
body :: Exp)
| appE(fn :: Exp,
arg :: Exp)
| emptyE()
| consE(l :: Exp,
r :: Exp)
| firstE(arg :: Exp)
| restE(arg :: Exp)
| if0E(exp :: Exp,
true :: Exp,
false :: Exp)
type Type
| intT()
| boolT()
| arrowT(arg :: Type,
result :: Type)
| varT(is :: Boxof(Optionof(Type)))
| listofT(elem :: Type)
type Binding
| bind(name :: Symbol,
val :: Value)
type Env = Listof(Binding)
type TypeBinding
| tbind(name :: Symbol,
ty :: Type)
type TypeEnv = Listof(TypeBinding)
def mt_env = []
def extend_env = cons
// parse ----------------------------------------
fun parse(s :: Syntax) :: Exp:
cond
| syntax_is_integer(s):
intE(syntax_to_integer(s))
| syntax_is_symbol(s):
idE(syntax_to_symbol(s))
| ~else:
match s
| '[]':
emptyE()
| 'if $exp == 0 | $true | $false':
if0E(parse(exp), parse(true), parse(false))
| 'let $name :: $ty = $rhs:
$body':
appE(funE(syntax_to_symbol(name),
parse_type(ty),
parse(body)),
parse(rhs))
| '$left + $right':
plusE(parse(left),
parse(right))
| '$left * $right':
multE(parse(left),
parse(right))
| 'fun ($id :: $ty): $body':
funE(syntax_to_symbol(id),
parse_type(ty),
parse(body))
| 'cons($left, $right)':
consE(parse(left), parse(right))
| 'first($arg)':
firstE(parse(arg))
| 'rest($arg)':
restE(parse(arg))
| '$fn($arg)':
appE(parse(fn),
parse(arg))
| '($e)':
parse(e)
| ~else:
error(#'parse, "invalid input: " +& s)
fun parse_type(s :: Syntax) :: Type:
match s
| '?': varT(box(none()))
| 'Int': intT()
| 'Boolean': boolT()
| '$arg1 -> $arg2 -> $result':
// make `->` right-associative
parse_type('$arg1 -> ($arg2 -> $result)')
| '$arg -> $result':
arrowT(parse_type(arg), parse_type(result))
| 'Listof($elem)':
listofT(parse_type(elem))
| '($ty)': parse_type(ty)
| ~else: error(#'parse_type, "invalid input: " +& s)
module test:
check: parse('2')
~is intE(2)
check: parse('x')
~is idE(#'x)
check: parse('2 + 1')
~is plusE(intE(2), intE (1))
check: parse('3 * 4')
~is multE(intE(3), intE(4))
check: parse('3 * 4 + 8')
~is plusE(multE(intE(3), intE(4)),
intE(8))
check: parse('fun (x :: Int): 9')
~is funE(#'x, intT(), intE(9))
check: parse('double(9)')
~is appE(idE(#'double), intE(9))
check: parse('1 + double(9)')
~is plusE(intE(1),
appE(idE(#'double), intE(9)))
check: parse('3 * (4 + 8)')
~is multE(intE(3),
plusE(intE(4), intE(8)))
check: parse('let x :: Int = 1 + 2:
y')
~is appE(funE(#'x,
intT(),
idE(#'y)),
plusE(intE(1), intE(2)))
check: parse('[]')
~is emptyE()
check: parse('cons([], [])')
~is consE(emptyE(), emptyE())
check: parse('first([])')
~is firstE(emptyE())
check: parse('rest([])')
~is restE(emptyE())
check: parse('1 2')
~raises "invalid input"
check: parse_type('Int')
~is intT()
check: parse_type('Boolean')
~is boolT()
check: parse_type('Int -> Boolean')
~is arrowT(intT(), boolT())
check: parse_type('Int -> (Boolean -> Int)')
~is arrowT(intT(), arrowT(boolT(), intT()))
check: parse_type('Int -> Boolean -> Int')
~is arrowT(intT(), arrowT(boolT(), intT()))
check: parse_type('?')
~is varT(box(none()))
check: parse_type('Listof(Int)')
~is listofT(intT())
check: parse_type('1')
~raises "invalid input"
// interp ----------------------------------------
fun interp(a :: Exp, env :: Env) :: Value:
match a
| intE(n): intV(n)
| idE(s): lookup(s, env)
| plusE(l, r): num_plus(interp(l, env), interp(r, env))
| multE(l, r): num_mult(interp(l, env), interp(r, env))
| funE(n, arg_type, body): closV(n, body, env)
| appE(fn, arg):
match interp(fn, env)
| closV(n, body, c_env):
interp(body,
extend_env(bind(n, interp(arg, env)),
c_env))
| ~else: error(#'interp, "not a function")
| emptyE(): listV([])
| consE(left, right):
def v_l = interp(left, env)
def v_r = interp(right, env)
match v_r
| listV(elems): listV(cons(v_l, elems))
| ~else: error(#'interp, "not a list")
| firstE(arg):
match interp(arg, env)
| listV(elems):
match elems
| []: error(#'interp , "list is empty")
| cons(f, r): f
| ~else: error(#'interp, "not a list")
| restE(arg):
match interp(arg, env)
| listV(elems):
match elems
| []: error(#'interp , "list is empty")
| cons(f, r): listV(r)
| ~else: error(#'interp, "not a list")
| if0E(exp, true, false):
match interp(exp, env)
| intV(n): if n == 0:
| interp(true, env)
| interp(false, env)
| ~else: error(#'interp, "not a number")
module test:
check: interp(parse('2'), mt_env)
~is intV(2)
check: interp(parse('x'), mt_env)
~raises "free variable"
check: interp(parse('x'),
extend_env(bind(#'x, intV(9)), mt_env))
~is intV(9)
check: interp(parse('2 + 1'), mt_env)
~is intV(3)
check: interp(parse('2 * 1'), mt_env)
~is intV(2)
check: interp(parse('(2 * 3) + (5 + 8)'), mt_env)
~is intV(19)
check: interp(parse('fun (x :: Int):
x + x'),
mt_env)
~is closV(#'x, plusE(idE(#'x), idE(#'x)), mt_env)
check: interp(parse('let x :: Int = 5: x + x'),
mt_env)
~is intV(10)
check: interp(parse('let x :: Int = 5:
let x :: Int = x + 1:
x + x'),
mt_env)
~is intV(12)
check: interp(parse('let x :: Int = 5:
let y :: Int = 6:
x'),
mt_env)
~is intV(5)
check: interp(parse('(fun (x :: Int): x + x)(8)'),
mt_env)
~is intV(16)
check: interp(parse('[]'), mt_env)
~is listV([])
check: interp(parse('cons(1, [])'), mt_env)
~is listV([intV(1)])
check: interp(parse('first(cons(1, []))'), mt_env)
~is intV(1)
check: interp(parse('rest(cons(1, []))'), mt_env)
~is listV([])
check: interp(parse('cons(1, 2)'), mt_env)
~raises "not a list"
check: interp(parse('first(1)'), mt_env)
~raises "not a list"
check: interp(parse('rest(2)'), mt_env)
~raises "not a list"
check: interp(parse('first([])'), mt_env)
~raises "list is empty"
check: interp(parse('rest([])'), mt_env)
~raises "list is empty"
check: interp(parse('1(2)'), mt_env)
~raises "not a function"
check: interp(parse('1 + (fun (x :: Int): x)'), mt_env)
~raises "not a number"
check: interp(parse('let bad :: Int -> Int = (fun (x :: Int):
x + y):
let y :: Int = 5:
bad(2)'),
mt_env)
~raises "free variable"
// num_plus and num_mult ----------------------------------------
fun num_op(op :: (Int, Int) -> Int, l :: Value, r :: Value) :: Value:
cond
| l is_a intV && r is_a intV:
intV(op(intV.n(l), intV.n(r)))
| ~else:
error(#'interp, "not a number")
fun num_plus(l :: Value, r :: Value) :: Value:
num_op(fun (a, b): a+b, l, r)
fun num_mult(l :: Value, r :: Value) :: Value:
num_op(fun (a, b): a*b, l, r)
module test:
check: num_plus(intV(1), intV(2))
~is intV(3)
check: num_mult(intV(3), intV(2))
~is intV(6)
// lookup ----------------------------------------
fun make_lookup(get_name :: ?a -> Symbol, get_val :: ?a -> ?b):
fun (n :: Symbol, env :: Listof(?a)) :: ?b:
match env
| []: error(#'lookup, "free variable")
| cons(b, rst_env):
cond
| n == get_name(b):
get_val(b)
| ~else:
make_lookup(get_name, get_val)(n, rst_env)
def lookup = make_lookup(bind.name, bind.val)
module test:
check: lookup(#'x, mt_env)
~raises "free variable"
check: lookup(#'x, extend_env(bind(#'x, intV(8)), mt_env))
~is intV(8)
check: lookup(#'x, extend_env(bind(#'x, intV(9)),
extend_env(bind(#'x, intV(8)),
mt_env)))
~is intV(9)
check: lookup(#'y, extend_env(bind(#'x, intV(9)),
extend_env(bind(#'y, intV(8)),
mt_env)))
~is intV(8)
// typecheck ----------------------------------------
fun typecheck(a :: Exp, tenv :: TypeEnv) :: Type:
match a
| intE(n): intT()
| idE(n): type_lookup(n, tenv)
| plusE(l, r): typecheck_nums(l, r, tenv)
| multE(l, r): typecheck_nums(l, r, tenv)
| funE(n, arg_type, body):
arrowT(arg_type,
typecheck(body,
extend_env(tbind(n, arg_type),
tenv)))
| appE(fn, arg):
def result_type = varT(box(none()))
unify(arrowT(typecheck(arg, tenv),
result_type),
typecheck(fn, tenv),
fn)
result_type
| if0E(exp, true, false):
unify(typecheck(exp, tenv), intT(), exp)
def result_type = varT(box(none()))
unify(typecheck(true, tenv), result_type, true)
unify(typecheck(false, tenv), result_type, false)
result_type
| emptyE(): listofT(varT(box(none())))
| consE(left, right):
def rightT = typecheck(right, tenv)
def leftT = typecheck(left, tenv)
match rightT
| listofT(rightElemT):
unify(leftT, rightElemT, left)
| varT(t):
unify(listofT(leftT), rightT, left)
| ~else: type_error(left, leftT, rightT)
listofT(leftT)
| firstE(arg): def argT = typecheck(arg, tenv)
match argT
| listofT(elemT): elemT
| varT(t):
def result_type = varT(box(none()))
unify(argT, listofT(result_type), arg)
result_type
| ~else: error(#'typecheck, "no type")
| restE(arg): def argT = typecheck(arg, tenv)
match argT
| listofT(elemT): listofT(elemT)
| varT(t):
def result_type = listofT(varT(box(none())))
unify(argT, result_type, arg)
result_type
| ~else: error(#'typecheck, "no type")
fun typecheck_nums(l, r, tenv):
unify(typecheck(l, tenv), intT(), l)
unify(typecheck(r, tenv), intT(), r)
intT()
def type_lookup: make_lookup(tbind.name, tbind.ty)
module test:
check: typecheck(parse('10'), mt_env)
~is intT()
check: typecheck(parse('10 + 17'), mt_env)
~is intT()
check: typecheck(parse('10 * 17'), mt_env)
~is intT()
check: typecheck(parse('fun (x :: Int): 12'), mt_env)
~is arrowT(intT(), intT())
check: typecheck(parse('fun (x :: Int): fun (y :: Boolean): x'),
mt_env)
~is arrowT(intT(), arrowT(boolT(), intT()))
check: resolve(typecheck(parse('(fun (x :: Int): 12)(1 + 17)'),
mt_env))
~is intT()
check: resolve(typecheck(parse('let x :: Int = 4:
let f :: (Int -> Int) = (fun (y :: Int):
x + y):
f(x)'),
mt_env))
~is intT()
check: typecheck(parse('1(2)'),
mt_env)
~raises "no type"
check: typecheck(parse('(fun (x :: Boolean): x)(2)'),
mt_env)
~raises "no type"
check: typecheck(parse('1 + (fun (x :: Int): x)'),
mt_env)
~raises "no type"
check: typecheck(parse('(fun (x :: Int): x) * 1'),
mt_env)
~raises "no type"
// unify ----------------------------------------
fun unify(t1 :: Type, t2 :: Type, exp :: Exp):
match t1
| varT(is1):
match unbox(is1)
| some(t3): unify(t3, t2, exp)
| none():
let t3 = resolve(t2):
if t1 === t3
| #void
| if occurs(t1, t3)
| type_error(exp, t1, t3)
| set_box(is1, some(t3))
| ~else:
match t2
| varT(is2): unify(t2, t1, exp)
| intT(): match t1
| intT(): #void
| ~else: type_error(exp, t1, t2)
| boolT(): match t1
| boolT(): #void
| ~else: type_error(exp, t1, t2)
| arrowT(a2, b2): match t1
| arrowT(a1, b1):
unify(a1, a2, exp)
unify(b1, b2, exp)
| ~else: type_error(exp, t1, t2)
| listofT(e2): match t1
| listofT(e1): unify(e1, e2, exp)
| ~else: type_error(exp, t1, t2)
fun resolve(t :: Type) :: Type:
match t
| varT(is):
match unbox(is)
| none(): t
| some(t2): resolve(t2)
| ~else: t
fun occurs(r :: Type, t :: Type) :: Boolean:
match t
| intT(): #false
| boolT(): #false
| arrowT(a, b): occurs(r, a) || occurs(r, b)
| varT(is):
// `===` checks for the same box
(r === t) || (match unbox(is)
| none(): #false
| some(t2): occurs(r, t2))
| listofT(e): occurs(r, e)
fun type_error(a :: Exp, t1 :: Type, t2 :: Type):
error(#'typecheck, "no type: "
+& a
+& " type " +& t1
+& " vs. " +& t2)
module test:
def a_type_var = varT(box(none()))
def an_exp = intE(0)
check: unify(intT(), intT(), an_exp)
~is #void
check: unify(boolT(), boolT(), an_exp)
~is #void
check: unify(arrowT(intT(), boolT()), arrowT(intT(), boolT()), an_exp)
~is #void
check: unify(varT(box(some(boolT()))), boolT(), an_exp)
~is #void
check: unify(boolT(), varT(box(some(boolT()))), an_exp)
~is #void
check: unify(a_type_var, a_type_var, an_exp)
~is #void
check: unify(a_type_var, varT(box(some(a_type_var))), an_exp)
~is #void
check: block:
def t = varT(box(none()))
unify(t, boolT(), an_exp)
unify(t, boolT(), an_exp)
~is #void
check: unify(intT(), boolT(), an_exp)
~raises "no type"
check: unify(intT(), arrowT(intT(), boolT()), an_exp)
~raises "no type"
check: unify(arrowT(intT(), intT()), arrowT(intT(), boolT()), an_exp)
~raises "no type"
check: block:
def t = varT(box(none()))
unify(t, boolT(), an_exp)
unify(t, intT(), an_exp)
~raises "no type"
check: unify(a_type_var, arrowT(a_type_var, boolT()), an_exp)
~raises "no type"
check: unify(a_type_var, arrowT(boolT(), a_type_var), an_exp)
~raises "no type"
check: resolve(a_type_var)
~is a_type_var
check: resolve(varT(box(some(intT()))))
~is intT()
check: occurs(a_type_var, a_type_var)
~is #true
check: occurs(a_type_var, varT(box(none())))
~is #false
check: occurs(a_type_var, varT(box(some(a_type_var))))
~is #true
check: occurs(a_type_var, intT())
~is #false
check: occurs(a_type_var, boolT())
~is #false
check: occurs(a_type_var, arrowT(a_type_var, intT()))
~is #true
check: occurs(a_type_var, arrowT(intT(), a_type_var))
~is #true
// Part 1
fun run_prog(exp :: Syntax):
def ast = parse(exp)
def ast_type = typecheck(ast, mt_env)
match interp(ast, mt_env)
| intV(n): integer_to_syntax(n)
| closV(arg, body, c_env): 'function'
| listV(elems): 'list'
module test:
check: run_prog('if 0 == 0 | 1 | 2')
~is '1'
check: run_prog('if 3 == 0 | 1 | 2')
~is '2'
check: run_prog('if 2 == 0 | (fun (x :: ?): x) | (fun (x :: ?): 1 + x)')
~is 'function'
check: run_prog('if (fun (x :: ?): x) == 0 | 1 | 2')
~raises "no type"
check: run_prog('if 0 == 0 | (fun (x :: ?): x) | 2')
~raises "no type"
check: run_prog('let f :: ? = (fun (x :: ?):
fun (y :: ?):
fun (z :: ?):
if x == 0 | y | z):
f(1)(2)(3)')
~is '3'
check: run_prog('let f :: ? = (fun (x :: ?):
fun (y :: ?):
fun (z :: ?):
if x == 0
| y
| fun (x :: ?): z):
f(1)(fun (x :: Int): 2)(3)(4)')
~is '3'
check: run_prog('let f :: ? = (fun (x :: ?):
if x == 0 | x | x(1)):
f(1)')
~raises "no type"
// To cover all
check: interp(parse('if (fun (x :: Int): x) == 0 | 0 | 1'), mt_env)
~raises "not a number"
// Part 2
module test:
check: run_prog('[]')
~is 'list'
check: run_prog('cons(1, [])')
~is 'list'
check: run_prog('cons([], [])')
~is 'list'
check: run_prog('cons(1, cons([], []))')
~raises "no type"
check: run_prog('first(1)')
~raises "no type"
check: run_prog('rest(1)')
~raises "no type"
check: run_prog('first([])')
~raises "list is empty"
check: run_prog('rest([])')
~raises "list is empty"
check: run_prog('(fun (x :: ?): first(x))(cons(1, []))')
~is '1'
check: run_prog('let f :: ? = (fun (x :: ?):
first(x)):
f(cons(1, [])) + 3')
~is '4'
check: run_prog('let f :: ? = (fun (x :: ?):
rest(x)):
first(f(cons(1, cons(2, [])))) + 3')
~is '5'
check: run_prog('(fun (x :: ?):
fun (y :: ?):
cons(x, y))(1)(cons(2, []))')
~is 'list'
check: run_prog('let f :: ? = (fun (x :: ?):
fun (y :: ?):
cons(x, y)):
first(rest(f(1)(cons(2, []))))')
~is '2'
check: run_prog('fun (x :: ?):
cons(x, x)')
~raises "no type"
check: run_prog('let f :: ? = (fun (x :: ?): x):
cons(f(1), f([]))')
~raises "no type"
// To cover everything
check: run_prog('cons(5, 6)')
~raises "no type"
|
Final Assignment
Submit your work by scheduling a meeting with an instructor on the sign-up spreadsheet, preferably at a time well before the final available date. You’ll show your code and answer questions in the meeting. You don’t need to hand in your code at all, other than showing it at the meeting.
Implementation
Start with the typed-class interpreter and typechecker: typed_parse.rhm
, typed_class.rhm
, inherit.rhm
, inherit_parse.rhm
, and class.rhm
. Since you won’t use the “Handin” button to submit your work, there’s no need to collapse the modules into a single file.
Implement some of the following additions, which are of varying difficulty. Each addition is annotated by a star rating; implement enough additions so that the sum of the star ratings is:
- CS 3520 students: at least 8 ★s
- CS 6520 students: at least 10 ★s
Extra credit will be awarded for rating sums beyond the required level, where the extra-point value of an extra star will be 50% of the point value of a required star (e.g., 16 ★s would be 150% for a CS 3520 student).
The options are somewhat underspecified and open-ended, so you will need to make some reasonable choices and be ready to defend them, particularly with respect to whether your choices are sound (i.e., interpreter and type checker are consistent). The intent in most cases is to imitate Java, so you may find it useful to browse the Java Language Specification: https://docs.oracle.com/javase/specs/.
Options:
★ | Task Description |
---|
★ | 1. Add a (Symbol)ExpI cast form to the language. At run-time, the cast expression reports an error if ExpI does not produce an instance of Symbol or one of its subclasses. Otherwise, the result is just the result of ExpI . The type checker must reject the expression if the type of ExpI is neither a subtype nor supertype of Symbol , and runtime checks must ensure the validity of the cast. |
★★ | 2. Change type checking so that an overridden method’s result type can vary covariantly and its argument types can vary contravariantly. Provide tests that show how the new type checker soundly allows previously rejected programs and still rejects unsound ones. |
★★ | 3. Add an `if ExpI == 0 |
★★ | 4. Add support for declaring private methods that can only be called via this. or super. . Other invocations should be statically rejected. Ensure private methods cannot be overridden by public ones and vice versa. |
★★ | 5. Add a local-binding expression form like let Symbol :: Type = ExpI: ExpI with identifier support in expressions. Disallow this as a local variable or method argument. |
★★ | 6. [Prerequisite: 5] Allow fields to be referenced directly within methods using the field name without this. . Implement shadowing for local variables and method arguments over field names. |
★★ | 7. Add imperative field assignment (ExpI.Symbol := ExpI ). Ensure the type checker guarantees safe assignments and include tests to show imperativeness. |
★★ | 8. Add a Java-style null expression. Ensure null works with object types but raises runtime errors when accessed incorrectly. Type checkers should allow null as a method argument or field value. |
★★ | 9. [Works with 7 and 8] Add support for constructors in class definitions, allowing both default and user-defined constructors. Include constructor behavior for subclasses and support default constructors. |
★★★ | 10. [Prerequisite: 8] Add Java-style object arrays (new Type[ExpI] , ExpI[ExpI] , ExpI[ExpI] := ExpI ). The typechecker must allow any type for arrays and ensure assignment operations are imperative. |
★★ | 11. [Prerequisite: 10] Allow array-of-Type1 to be a subtype of array-of-Type2 when Type1 is a subtype of Type2. Implement runtime checks for subtyping array assignments. |
★★★★ | 12. Add support for declaring final classes and optimize method calls that are definitely dispatched to a final method to static method calls instead of dynamic ones. |
★★★★★ | 13. Add Java-style interfaces to the language. Implement support for subinterfaces and classes that implement interfaces, extending the type system to include interface names. |
★★★★★★ | 14. Add Java-style overloading, allowing multiple methods with the same name but different argument types. Implement type-checking logic to handle overloading based on argument types. |
★★★★★★ | 15. Add Java-style generics (parametric polymorphism) where classes are parameterized by types. Ignore support for generic methods or using a generic type directly as a type. |
Solution
class.rhm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
| #lang shplait
type Exp
| intE(n :: Int)
| plusE(lhs :: Exp,
rhs :: Exp)
| multE(lhs :: Exp,
rhs :: Exp)
| negE(n :: Exp)
| argE()
| thisE()
| idE(id :: Symbol)
| newE(class_name :: Symbol,
args :: Listof(Exp))
| newArrayE(t :: Symbol,
lens :: Listof(Exp))
| getE(obj_exp :: Exp,
field_name :: Symbol)
| sendE(obj_exp :: Exp,
method_name :: Symbol,
arg_exp :: Exp)
| ssendE(obj_exp :: Exp,
class_name :: Symbol,
method_name :: Symbol,
arg_exp :: Exp)
| castE(obj_exp :: Exp,
class_name :: Symbol)
| if0E(exp :: Exp,
true :: Exp,
false :: Exp)
| setE(obj_exp :: Exp,
field_name :: Symbol,
right_exp :: Exp)
| letE(var :: Symbol,
right :: Exp,
body :: Exp)
| nullE()
| getArrayE(exp :: Exp,
index :: Exp)
| setArrayE(exp :: Exp,
index :: Exp,
val :: Exp)
type Class
| classC(field_names :: Listof(Symbol),
methods :: Listof(Symbol * Exp))
type Value
| intV(n :: Int)
| objV(class_name :: Symbol,
fields :: Listof(Boxof(Value)))
| nullV()
| arrayV(val :: Listof(Boxof(Value)))
type Binding
| bind(name :: Symbol,
val :: Value)
type Env = Listof(Binding)
def mt_env = []
def extend_env = cons
// ----------------------------------------
fun find(l :: Listof(Symbol * ?a), name :: Symbol) :: ?a:
match l
| []:
error(#'find, "not found: " +& name)
| cons(p, rst_l):
if fst(p) == name
| snd(p)
| find(rst_l, name)
module test:
check: find([values(#'a, 1)], #'a)
~is 1
check: find([values(#'a, "apple")], #'a)
~is "apple"
check: find([values(#'a, 1), values(#'b, 2)], #'b)
~is 2
check: find([], #'a)
~raises "not found: a"
check: find([values(#'a, 1)], #'x)
~raises "not found: x"
// ----------------------------------------
def interp :: (Exp, Listof(Symbol * Class), Value, Value, Env) -> Value:
fun (a, classes, this_val, arg_val, env):
fun recur(exp):
interp(exp, classes, this_val, arg_val, env)
match a
| intE(n): intV(n)
| plusE(l, r): num_plus(recur(l), recur(r))
| multE(l, r): num_mult(recur(l), recur(r))
| negE(e): match recur(e)
| intV(n): intV(n * -1)
| ~else: error(#'interp, "not a number")
| idE(s): lookup(s, env)
| thisE(): this_val
| argE(): arg_val
| newE(class_name, field_exps):
def c = find(classes, class_name)
def vals = map(recur, field_exps)
if length(vals) == length(classC.field_names(c))
| objV(class_name, map(box, vals))
| error(#'interp, "wrong field count")
| getE(obj_exp, field_name):
match recur(obj_exp)
| objV(class_name, fields):
match find(classes, class_name)
| classC(field_names, methods):
unbox(find(map2(fun (n, v): values(n, v), field_names, fields),
field_name))
| ~else: error(#'interp, "not an object")
| sendE(obj_exp, method_name, arg_exp):
def obj = recur(obj_exp)
def arg_val = recur(arg_exp)
match obj
| objV(class_name, fields):
call_method(class_name, method_name, classes, obj, arg_val, env)
| ~else:
error(#'interp, "not an object")
| ssendE(obj_exp, class_name, method_name, arg_exp):
def obj = recur(obj_exp)
def arg_val = recur(arg_exp)
call_method(class_name, method_name, classes, obj, arg_val, env)
| castE(obj_exp, cast_class_name):
match recur(obj_exp)
| objV(obj_class_name, fields):
def cast_class = find(classes, cast_class_name)
def cast_fields_len = length(classC.field_names(cast_class))
objV(cast_class_name,
get_n_sublist(fields, cast_fields_len))
| intV(n): intV(n)
| nullV(): if cast_class_name == #'null:
| nullV()
| error(#'interp, "cannot cast " +& obj_exp)
| arrayV(vals): error(#'interp, "cannot cast " +& obj_exp)
| if0E(exp, true, false):
match recur(exp)
| intV(n): if n == 0:
| recur(true)
| recur(false)
| ~else: error(#'interp, "not a number")
| setE(obj_exp, field_name, right_exp):
match recur(obj_exp)
| objV(class_name, fields):
match find(classes, class_name)
| classC(field_names, methods):
set_box(find(map2(fun (n, v): values(n, v), field_names, fields),
field_name),
recur(right_exp))
intV(0)
| ~else: error(#'interp, "not an object")
| letE(s, right, body):
interp(body, classes, this_val, arg_val, extend_env(bind(s, recur(right)), env))
| nullE(): nullV()
| newArrayE(s, lens):
def lens_vals = map(fun (len): match recur(len)
| intV(n): if n >= 0:
| n
| error(#'interp, "length of array must be Positive")
| ~else: error(#'interp, "length of array must be Int"),
lens)
if length(lens_vals) == 1:
| arrayV(make_list(s, first(lens_vals)))
| arrayV(do_n(fun (): box(recur(newArrayE(s, rest(lens)))),
first(lens_vals)))
| getArrayE(exp, index):
match recur(exp)
| arrayV(vals): match recur(index)
| intV(n): if n < length(vals)
| unbox(list_get(vals, n))
| error(#'interp, "out of bound")
| ~else: error(#'interp, "not an int")
| ~else: error(#'interp, "not an array")
| setArrayE(exp, index, val):
match recur(exp)
| arrayV(vals): match recur(index)
| intV(n): if n < length(vals)
| set_box(list_get(vals, n), recur(val))
intV(0)
| error(#'interp, "out of bound")
| ~else: error(#'interp, "not an int")
| ~else: error(#'interp, "not an array")
fun do_n(fn, n):
if n == 0:
| []
| cons(fn(), do_n(fn, n-1))
fun make_list(t :: Symbol, len :: Int) :: Listof(Boxof(Value)):
if len == 0:
| []
| if t == #'Int:
| cons(box(intV(0)), make_list(t, len - 1))
| cons(box(nullV()), make_list(t, len - 1))
fun last(list :: Listof(?a)) :: ?a:
match list
| []: error(#'last, "list is empty")
| cons(elem, rest_elem):
if length(rest_elem) == 0:
| elem
| last(rest_elem)
check: last([1, 2, 3])
~is 3
check: last([])
~raises "list is empty"
fun get_n_sublist(list :: Listof(?a), n :: Int) :: Listof(?a):
match list
| []: if n <= 0:
| list
| error(#'get_n_sublist, "not enough element")
| cons(elem, elem_rest):
if n <= 0:
| []
| cons(elem, get_n_sublist(elem_rest, n-1))
check: get_n_sublist([1, 2, 3, 4, 5], 0)
~is []
check: get_n_sublist([1, 2, 3, 4, 5], 2)
~is [1, 2]
check: get_n_sublist([1, 2, 3, 4, 5], 100)
~raises "not enough element"
check: get_n_sublist([1, 2, 3, 4, 5], -2)
~is []
check: get_n_sublist([intV(1), intV(2), intV(3), intV(4)], 2)
~is [intV(1), intV(2)]
check: get_n_sublist([], -2)
~is []
// lookup ----------------------------------------
fun make_lookup(get_name :: ?a -> Symbol, get_val :: ?a -> ?b):
fun (n :: Symbol, env :: Listof(?a)) :: ?b:
match env
| []: error(#'lookup, "free variable " +& n)
| cons(b, rst_env):
cond
| n == get_name(b):
get_val(b)
| ~else:
make_lookup(get_name, get_val)(n, rst_env)
def lookup = make_lookup(bind.name, bind.val)
check: lookup(#'x, [bind(#'a, intV(5)), bind(#'x, intV(6))])
~is intV(6)
check: lookup(#'x, [bind(#'a, intV(5))])
~raises "free variable"
fun call_method(class_name, method_name, classes, obj, arg_val, env):
match find(classes, class_name)
| classC(field_names, methods):
let body_exp = find(methods, method_name):
interp(body_exp, classes, obj, arg_val, env)
fun num_op(op :: (Int, Int) -> Int, l :: Value, r :: Value) :: Value:
cond
| l is_a intV && r is_a intV:
intV(op(intV.n(l), intV.n(r)))
| ~else:
error(#'interp, "not a number")
fun num_plus(l :: Value, r :: Value) :: Value:
num_op(fun (a, b): a+b, l, r)
fun num_mult(l :: Value, r :: Value) :: Value:
num_op(fun (a, b): a*b, l, r)
// ----------------------------------------
// Examples
module test:
def posn_class:
values(
#'Posn,
classC([#'x,#'y],
[
values(#'mdist,
plusE(getE(thisE(), #'x), getE(thisE(), #'y))),
values(#'addDist,
plusE(sendE(thisE(), #'mdist, intE(0)),
sendE(argE(), #'mdist, intE(0)))),
values(#'addX,
plusE(getE(thisE(), #'x), argE())),
values(#'multY,
multE(argE(), getE(thisE(), #'y))),
values(#'factory12,
newE(#'Posn, [intE(1), intE(2)]))
])
)
def posn3D_class:
values(
#'Posn3D,
classC([#'x,#'y, #'z],
[
values(#'mdist,
plusE(getE(thisE(), #'z),
ssendE(thisE(), #'Posn, #'mdist, argE()))),
values(#'addDist,
ssendE(thisE(), #'Posn, #'addDist, argE()))
])
)
def posn27 = newE(#'Posn, [intE(2), intE(7)])
def posn531 = newE(#'Posn3D, [intE(5), intE(3), intE(1)])
fun interp_posn(a):
interp(a, [posn_class, posn3D_class], intV(-1), intV(-1), mt_env)
// ----------------------------------------
module test:
check: interp(intE(10),
[], objV(#'Object, []), intV(0), mt_env)
~is intV(10)
check: interp(plusE(intE(10), intE(17)),
[], objV(#'Object, []), intV(0), mt_env)
~is intV(27)
check: interp(multE(intE(10), intE(7)),
[], objV(#'Object, []), intV(0), mt_env)
~is intV(70)
check: interp_posn(newE(#'Posn, [intE(2), intE(7)]))
~is objV(#'Posn, [box(intV(2)), box(intV(7))])
check: interp_posn(sendE(posn27, #'mdist, intE(0)))
~is intV(9)
check: interp_posn(sendE(posn27, #'addX, intE(10)))
~is intV(12)
check: interp_posn(sendE(ssendE(posn27, #'Posn, #'factory12, intE(0)),
#'multY,
intE(15)))
~is intV(30)
check: interp_posn(sendE(posn531, #'addDist, posn27))
~is intV(18)
check: interp_posn(plusE(intE(1), posn27))
~raises "not a number"
check: interp_posn(getE(intE(1), #'x))
~raises "not an object"
check: interp_posn(sendE(intE(1), #'mdist, intE(0)))
~raises "not an object"
check: interp_posn(newE(#'Posn, [intE(0)]))
~raises "wrong field count"
// 1
module test:
check: interp_posn(castE(posn531, #'Posn))
~is objV(#'Posn, [box(intV(5)), box(intV(3))])
check: interp_posn(castE(intE(5), #'Int))
~is intV(5)
check: interp_posn(negE(intE(1)))
~is intV(-1)
check: interp_posn(negE(nullE()))
~raises "not a number"
check: interp_posn(castE(nullE(), #'null))
~is nullV()
// 3
module test:
check: interp(if0E(intE(10), intE(1), intE(0)), [], objV(#'Object, []), intV(0), mt_env)
~is intV(0)
check: interp(if0E(intE(0), intE(1), intE(0)), [], objV(#'Object, []), intV(0), mt_env)
~is intV(1)
check: interp_posn(if0E(posn531, intE(1), intE(0)))
~raises "not a number"
// 7
module test:
check: interp_posn(letE(#'x, newE(#'Posn, [intE(2), intE(7)]), letE(#'y, setE(idE(#'x), #'x, intE(1)), getE(idE(#'x), #'x))))
~is intV(1)
check: interp_posn(setE(intE(5), #'x, intE(10)))
~raises "not an object"
// 8
module test:
check: interp_posn(nullE())
~is nullV()
check: interp_posn(castE(nullE(), #'Posn))
~raises "cannot cast"
check: interp_posn(castE(newArrayE(#'Int, [intE(0)]), #'Posn))
~raises "cannot cast"
// 10
module test:
check: interp_posn(newArrayE(#'Int, [intE(0)]))
~is arrayV([])
check: interp_posn(newArrayE(#'Posn, [intE(0)]))
~is arrayV([])
check: interp_posn(newArrayE(#'Int, [intE(3)]))
~is arrayV([box(intV(0)), box(intV(0)), box(intV(0))])
check: interp_posn(newArrayE(#'Posn, [intE(3)]))
~is arrayV([box(nullV()), box(nullV()), box(nullV())])
check: interp_posn(newArrayE(#'Int, [intE(3), intE(2)]))
~is arrayV([
box(arrayV([box(intV(0)), box(intV(0))])),
box(arrayV([box(intV(0)), box(intV(0))])),
box(arrayV([box(intV(0)), box(intV(0))]))
])
check: interp_posn(newArrayE(#'Posn, [intE(3), intE(2)]))
~is arrayV([
box(arrayV([box(nullV()), box(nullV())])),
box(arrayV([box(nullV()), box(nullV())])),
box(arrayV([box(nullV()), box(nullV())]))
])
check: interp_posn(newArrayE(#'Posn, [intE(0), intE(10)]))
~is arrayV([])
check: interp_posn(newArrayE(#'Posn, [intE(3), intE(-2)]))
~raises "length of array must be Positive"
check: interp_posn(newArrayE(#'Posn, [intE(3), posn531]))
~raises "length of array must be Int"
check: interp_posn(getArrayE(newArrayE(#'Posn, [intE(3), intE(2)]), intE(2)))
~is arrayV([box(nullV()), box(nullV())])
check: interp_posn(getArrayE(newArrayE(#'Posn, [intE(3), intE(2)]), intE(3)))
~raises "out of bound"
check: interp_posn(getArrayE(getArrayE(newArrayE(#'Posn, [intE(3), intE(2)]), intE(2)), intE(1)))
~is nullV()
check: interp_posn(getArrayE(getArrayE(newArrayE(#'Posn, [intE(3), intE(2)]), intE(2)), posn27))
~raises "not an int"
check: interp_posn(getArrayE(posn531, intE(2)))
~raises "not an array"
check: interp_posn(letE(#'x,
newArrayE(#'Posn, [intE(3)]),
letE(#'y, setArrayE(idE(#'x), intE(1), posn27),
getArrayE(idE(#'x), intE(1)))))
~is objV(#'Posn, [box(intV(2)), box(intV(7))])
check: interp_posn(letE(#'x,
newArrayE(#'Posn, [intE(3)]),
letE(#'y, setArrayE(idE(#'x), intE(1), posn27),
getArrayE(idE(#'x), intE(2)))))
~is nullV()
check: interp_posn(letE(#'x,
posn27,
letE(#'y, setArrayE(idE(#'x), intE(1), posn27),
getArrayE(idE(#'x), intE(2)))))
~raises "not an array"
check: interp_posn(letE(#'x,
newArrayE(#'Posn, [intE(3)]),
letE(#'y, setArrayE(idE(#'x), intE(10), posn27),
getArrayE(idE(#'x), intE(2)))))
~raises "out of bound"
check: interp_posn(letE(#'x,
newArrayE(#'Posn, [intE(3)]),
letE(#'y, setArrayE(idE(#'x), posn27, posn27),
getArrayE(idE(#'x), intE(2)))))
~raises "not an int"
|
inherit_parse.rhm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
| #lang shplait
import:
open:
"class.rhm"
"inherit.rhm"
// ----------------------------------------
fun parse_class(s :: Syntax) :: (Symbol * ClassI):
match s
| 'class $name($field, ...):
extends $parent_name
$method
...':
values(syntax_to_symbol(name),
classI(syntax_to_symbol(parent_name),
map(parse_field,
syntax_to_list('[$field, ...]')),
map(parse_method,
syntax_to_list('[$method, ...]'))))
| ~else: error(#'parse_class, "invalid input: " +& s)
fun parse_field(s :: Syntax) :: Symbol:
cond
| syntax_is_symbol(s):
syntax_to_symbol(s)
| ~else: error(#'parse_field, "invalid input: " +& s)
fun parse_method(s :: Syntax) :: (Symbol * ExpI):
match s
|'method $name(arg): $body':
values(syntax_to_symbol(name),
parse(body))
| ~else: error(#'parse_method, "invalid input: " +& s)
fun parse(s :: Syntax) :: ExpI:
cond
| syntax_is_integer(s):
intI(syntax_to_integer(s))
| syntax_is_symbol(s):
match s:
| 'arg': argI()
| 'this': thisI()
| 'null': nullI()
| ~else: idI(syntax_to_symbol(s))
| ~else:
match s
| 'if $exp == 0 | $true | $false':
if0I(parse(exp), parse(true), parse(false))
| 'let $(id :: Identifier) = $right:
$body':
letI(syntax_to_symbol(id), parse(right), parse(body))
| '$left - $right':
plusI(parse(left), negI(parse(right)))
| '$left + $right':
plusI(parse(left),
parse(right))
| '$left * $right':
multI(parse(left),
parse(right))
| '- $exp':
negI(parse(exp))
| 'new $(id :: Identifier) [$flen] [$rlens] ...':
newArrayI(syntax_to_symbol(id),
map(parse, syntax_to_list('[$flen, $rlens, ...]')))
| '$exp [$index] := $val':
setArrayI(parse(exp), parse(index), parse(val))
| '$exp [$index]':
getArrayI(parse(exp), parse(index))
| 'new $id($arg, ...)':
newI(syntax_to_symbol(id),
map(parse, syntax_to_list('[$arg, ...]')))
| 'super . $method_name ($arg)':
superI(syntax_to_symbol(method_name),
parse(arg))
| '$obj . $(field_name :: Identifier) := $exp':
setI(parse(obj), syntax_to_symbol(field_name), parse(exp))
| '$obj . $method_name ($arg)':
sendI(parse(obj),
syntax_to_symbol(method_name),
parse(arg))
| '$obj . $field_name':
getI(parse(obj),
syntax_to_symbol(field_name))
| '($(id :: Identifier))$exp':
castI(parse(exp), syntax_to_symbol(id))
| '($e)':
parse(e)
| ~else:
error(#'parse, "invalid input: " +& s)
module test:
check: parse('0')
~is intI(0)
check: parse('arg')
~is argI()
check: parse('this')
~is thisI()
check: parse('1 + 2')
~is plusI(intI(1), intI(2))
check: parse('1 * 2')
~is multI(intI(1), intI(2))
check: parse('new Posn(1, 2)')
~is newI(#'Posn, [intI(1), intI(2)])
check: parse('this.x')
~is getI(thisI(), #'x)
check: parse('this.m(2)')
~is sendI(thisI(), #'m, intI(2))
check: parse('super.m(1)')
~is superI(#'m, intI(1))
check: parse('x')
~is idI(#'x)
check: parse('43 55')
~raises "invalid input"
check: parse('5 - 2')
~is plusI(intI(5), negI(intI(2)))
check: parse('new Posn[5][6+3]')
~is newArrayI(#'Posn, [intI(5), plusI(intI(6), intI(3))])
check: parse('null')
~is nullI()
check: parse_field('x')
~is #'x
check: parse_field('x 1')
~raises "invalid input"
check: parse_method('method m(arg): this')
~is values(#'m, thisI())
check: parse_method('m(arg): 1 2')
~raises "invalid input"
check: parse_class('class Posn3D(x, y, z):
extends Posn
method m1(arg): arg
method m2(arg): this')
~is values(#'Posn3D,
classI(#'Posn,
[#'x, #'y, #'z],
[values(#'m1, argI()),
values(#'m2, thisI())]))
check: parse_class('class')
~raises "invalid input"
// ----------------------------------------
fun interp_prog(classes :: Listof(Syntax), s :: Syntax) :: Syntax:
let v = interp_i(parse(s),
map(parse_class, classes), mt_env):
match v
| intV(n): integer_to_syntax(n)
| objV(class_name, field_vals): 'object'
| nullV(): 'null'
| arrayV(vals): 'array'
module test:
check: interp_prog(
['class Empty():
extends Object'],
'new Empty()'
)
~is 'object'
check: interp_prog(
['class Posn(x, y):
extends Object
method mdist(arg): this.x + this.y
method addDist(arg): arg.mdist(0) + this.mdist(0)',
'class Posn3D(z):
extends Posn
method mdist(arg): this.z + super.mdist(arg)'],
'(new Posn3D(5, 3, 1)).addDist(new Posn(2, 7))'
)
~is '18'
// 1
module test:
def class_syntaxes = ['class Posn(x, y):
extends Object
method mdist(arg): this.x + this.y
method addDist(arg): arg.mdist(0) + this.mdist(0)',
'class Posn3D(z):
extends Posn
method mdist(arg): this.z + super.mdist(arg)',
'class A():
extends Object',
'class B():
extends A',
'class C():
extends B',
'class D():
extends A',
'class E():
extends Object']
check: interp_prog(class_syntaxes, '((Posn)new Posn3D(2, 3, 4)).x')
~is '2'
check: interp_prog(class_syntaxes, '((Posn)new Posn3D(2, 3, 4)).z')
~raises "not found"
check: interp_prog(class_syntaxes, '((Posn3D)new Posn(2, 3)).x')
~raises "not enough element"
check: interp_prog(class_syntaxes, '((Int)new Posn3D(2, 3, 4))')
~raises "not found"
check: interp_prog(class_syntaxes, 'if 5 == 0 | 5 | 10')
~is '10'
// 7
module test:
check: interp_prog(class_syntaxes,
'let x = 5:
x')
~is '5'
check: interp_prog(class_syntaxes,
'let x = new Posn(1, 2):
let _ = x.x := (- 5):
x.x + x.y')
~is '-3'
check: interp_prog(['class X(a):
extends Object',
'class Y(b):
extends X'],
'let y = new Y(0, new X(5)):
let _ = y.b := new X(1):
y.b')
~is 'object'
// 10
module test:
check: interp_prog(class_syntaxes,
'let x = new Posn[3][2]:
x')
~is 'array'
check: interp_prog(class_syntaxes,
'let x = new Posn[3]:
let _ = x[0] := new Posn(0, 0):
let _ = x[1] := new Posn(1, 1):
x[0]')
~is 'object'
check: interp_prog(class_syntaxes,
'let x = new Posn[3]:
let _ = x[0] := new Posn(0, 0):
let _ = x[1] := new Posn(1, 1):
x[2]')
~is 'null'
check: interp_prog(class_syntaxes,
'let x = new Posn[3]:
let _ = x[0] := new Posn(0, 0):
let _ = x[1] := new Posn(1, 1):
x[5]')
~raises "out of bound"
check: interp_prog(class_syntaxes,
'let x = new Posn[3][2]:
let _ = x[0][0] := new Posn(0, 0):
let _ = x[1][1] := new Posn(1, 1):
x[0][0]')
~is 'object'
|
inherit.rhm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
| #lang shplait
// Make all "class.rhm" definitions available here, where
// the "class.rhm" file must be in the same directory
// as this one:
import:
open: "class.rhm"
type ExpI
| intI(n :: Int)
| idI(s :: Symbol)
| negI(e :: ExpI)
| nullI()
| plusI(lhs :: ExpI,
rhs :: ExpI)
| multI(lhs :: ExpI,
rhs :: ExpI)
| argI()
| thisI()
| newI(class_name :: Symbol,
args :: Listof(ExpI))
| getI(obj_exp :: ExpI,
field_name :: Symbol)
| sendI(obj_exp :: ExpI,
method_name :: Symbol,
arg_exp :: ExpI)
| superI(method_name :: Symbol,
arg_exp :: ExpI)
| castI(exp :: ExpI,
id :: Symbol)
| if0I(exp :: ExpI,
true :: ExpI,
false :: ExpI)
| letI(s :: Symbol,
right :: ExpI,
body :: ExpI)
| setI(obj :: ExpI,
s :: Symbol,
val :: ExpI)
| newArrayI(t :: Symbol,
lens :: Listof(ExpI))
| getArrayI(array :: ExpI,
index :: ExpI)
| setArrayI(array :: ExpI,
index :: ExpI,
val :: ExpI)
type ClassI
| classI(super_name :: Symbol,
field_names :: Listof(Symbol),
methods :: Listof(Symbol * ExpI))
// ----------------------------------------
fun exp_i_to_c(a :: ExpI, super_name :: Symbol) :: Exp:
block:
fun recur(exp):
exp_i_to_c(exp, super_name)
match a
| intI(n): intE(n)
| nullI(): nullE()
| idI(s): idE(s)
| negI(e): negE(recur(e))
| plusI(l, r): plusE(recur(l), recur(r))
| multI(l, r): multE(recur(l), recur(r))
| argI(): argE()
| thisI(): thisE()
| newI(class_name, field_exps):
newE(class_name, map(recur, field_exps))
| getI(exp, field_name):
getE(recur(exp), field_name)
| sendI(exp, method_name, arg_exp):
sendE(recur(exp), method_name, recur(arg_exp))
| superI(method_name, arg_exp):
ssendE(thisE(), super_name, method_name, recur(arg_exp))
| castI(exp, id):
castE(recur(exp), id)
| if0I(exp, true, false):
if0E(recur(exp), recur(true), recur(false))
| letI(s, right, body):
letE(s, recur(right), recur(body))
| setI(obj, s, val):
setE(recur(obj), s, recur(val))
| newArrayI(s, lens):
newArrayE(s, map(recur, lens))
| getArrayI(exp, index):
getArrayE(recur(exp), recur(index))
| setArrayI(exp, index, val):
setArrayE(recur(exp), recur(index), recur(val))
module test:
check: exp_i_to_c(intI(10), #'Object)
~is intE(10)
check: exp_i_to_c(plusI(intI(10), intI(2)), #'Object)
~is plusE(intE(10), intE(2))
check: exp_i_to_c(multI(intI(10), intI(2)), #'Object)
~is multE(intE(10), intE(2))
check: exp_i_to_c(argI(), #'Object)
~is argE()
check: exp_i_to_c(thisI(), #'Object)
~is thisE()
check: exp_i_to_c (newI(#'Object, [intI(1)]), #'Object)
~is newE(#'Object, [intE(1)])
check: exp_i_to_c(getI(intI(1), #'x), #'Object)
~is getE(intE(1), #'x)
check: exp_i_to_c(sendI(intI(1), #'mdist, intI(2)), #'Object)
~is sendE(intE(1), #'mdist, intE(2))
check: exp_i_to_c(superI(#'mdist, intI(2)), #'Posn)
~is ssendE(thisE(), #'Posn, #'mdist, intE(2))
check: exp_i_to_c(castI(intI(5), #'Int), #'Object)
~is castE(intE(5), #'Int)
check: exp_i_to_c(if0I(intI(0), intI(1), intI(2)), #'Object)
~is if0E(intE(0), intE(1), intE(2))
check: exp_i_to_c(letI(#'a, intI(0), intI(1)), #'Object)
~is letE(#'a, intE(0), intE(1))
check: exp_i_to_c(setI(intI(0), #'a, intI(1)), #'Object)
~is setE(intE(0), #'a, intE(1))
check: exp_i_to_c(idI(#'a), #'Object)
~is idE(#'a)
check: exp_i_to_c(nullI(), #'Object)
~is nullE()
check: exp_i_to_c(negI(intI(5)), #'Object)
~is negE(intE(5))
check: exp_i_to_c(newArrayI(#'Posn, [intI(5)]), #'Object)
~is newArrayE(#'Posn, [intE(5)])
check: exp_i_to_c(getArrayI(intI(5), intI(5)), #'Object)
~is getArrayE(intE(5), intE(5))
check: exp_i_to_c(setArrayI(intI(5), intI(5), intI(1)), #'Object)
~is setArrayE(intE(5), intE(5), intE(1))
// ----------------------------------------
fun class_i_to_c_not_flat(c :: ClassI) :: Class:
match c
| classI(super_name, field_names, methods):
classC(field_names,
map(fun (m):
values(fst(m),
exp_i_to_c(snd(m), super_name)),
methods))
module test:
def posn3d_mdist_i_method:
values(#'mdist,
plusI(getI(thisI(), #'z),
superI(#'mdist, argI())))
def posn3d_mdist_c_method:
values(#'mdist,
plusE(getE(thisE(), #'z),
ssendE(thisE(), #'Posn, #'mdist, argE())))
def posn3d_i_class:
values(#'Posn3D,
classI(#'Posn,
[#'z],
[posn3d_mdist_i_method]))
def posn3d_c_class_not_flat:
values(#'Posn3D,
classC([#'z],
[posn3d_mdist_c_method]))
check: class_i_to_c_not_flat(snd(posn3d_i_class))
~is snd(posn3d_c_class_not_flat)
// ----------------------------------------
fun flatten_class(name :: Symbol,
classes_not_flat :: Listof(Symbol * Class),
i_classes :: Listof(Symbol * ClassI)) :: Class:
match find(classes_not_flat, name)
| classC(field_names, methods):
match flatten_super(name, classes_not_flat, i_classes)
| classC(super_field_names, super_methods):
classC(add_fields(super_field_names, field_names),
add_or_replace_methods(super_methods, methods))
fun flatten_super(name :: Symbol,
classes_not_flat :: Listof(Symbol * Class),
i_classes :: Listof(Symbol * ClassI)) :: Class:
match find(i_classes, name)
| classI(super_name, field_names, i_methods):
if super_name == #'Object
| classC([], [])
| flatten_class(super_name, classes_not_flat, i_classes)
module test:
def posn_i_class:
values(#'Posn,
classI(#'Object,
[#'x, #'y],
[values(#'mdist,
plusI(getI(thisI(), #'x),
getI(thisI(), #'y))),
values(#'addDist,
plusI(sendI(thisI(), #'mdist, intI(0)),
sendI(argI(), #'mdist, intI(0))))]))
def addDist_c_method:
values(#'addDist,
plusE(sendE(thisE(), #'mdist, intE(0)),
sendE(argE(), #'mdist, intE(0))))
def posn_c_class_not_flat:
values(#'Posn,
classC([#'x, #'y],
[values(#'mdist,
plusE(getE(thisE(), #'x),
getE(thisE(), #'y))),
addDist_c_method]))
def posn3d_c_class:
values(#'Posn3D,
classC([#'x, #'y, #'z],
[posn3d_mdist_c_method,
addDist_c_method]))
check: flatten_class(#'Posn3D,
[posn_c_class_not_flat,
posn3d_c_class_not_flat],
[posn_i_class,
posn3d_i_class])
~is snd(posn3d_c_class)
// ----------------------------------------
def add_fields = append
fun add_or_replace_methods(methods :: Listof(Symbol * Exp),
new_methods :: Listof(Symbol * Exp))
:: (Listof (Symbol * Exp)):
match new_methods
| []: methods
| cons(fst_method, rst_new_methods):
add_or_replace_methods(add_or_replace_method(methods,
fst_method),
rst_new_methods)
fun add_or_replace_method(methods :: Listof(Symbol * Exp),
new_method :: Symbol * Exp)
:: (Listof (Symbol * Exp)):
match methods
| []: [new_method]
| cons(fst_method, rst_methods):
if fst(fst_method) == fst(new_method)
| cons(new_method, rst_methods)
| cons(fst_method,
add_or_replace_method(rst_methods,
new_method))
module test:
check: add_fields([#'x, #'y], [#'z])
~is [#'x, #'y, #'z]
check: add_or_replace_methods([], [])
~is []
check: add_or_replace_methods([], [values(#'m, intE(0))])
~is [values(#'m, intE(0))]
check: add_or_replace_methods([values(#'m, intE(0))], [])
~is [values(#'m, intE(0))]
check: add_or_replace_methods([values(#'m, intE(0))],
[values(#'m, intE(1))])
~is [values(#'m, intE(1))]
check: add_or_replace_methods([values(#'m, intE(0)),
values(#'n, intE(2))],
[values(#'m, intE(1))])
~is [values(#'m, intE(1)),
values(#'n, intE(2))]
check: add_or_replace_methods([values(#'m, intE(0))],
[values(#'m, intE(1)),
values(#'n, intE(2))])
~is [values(#'m, intE(1)),
values(#'n, intE(2))]
check: add_or_replace_method([values(#'m, intE(0))],
values(#'m, intE(1)))
~is [values(#'m, intE(1))]
check: add_or_replace_method([values(#'m, intE(0))],
values(#'n, intE(2)))
~is [values(#'m, intE(0)),
values(#'n, intE(2))]
// ----------------------------------------
fun interp_i(i_a :: ExpI, i_classes :: Listof(Symbol * ClassI), env :: Env) :: Value:
block:
def a = exp_i_to_c(i_a, #'Object)
def classes_not_flat:
map(fun (i): values(fst(i),
class_i_to_c_not_flat(snd(i))),
i_classes)
def classes:
map(fun (c):
let name = fst(c):
values(name,
flatten_class(name, classes_not_flat, i_classes)),
classes_not_flat)
interp(a, classes, objV(#'Object, []), intV(0), env)
module test:
check: interp_i(intI(0), [], mt_env)
~is intV(0)
check: interp_i(
sendI(newI(#'Posn3D, [intI(5), intI(3), intI(1)]),
#'addDist,
newI(#'Posn, [intI(2), intI(7)])),
[posn_i_class,
posn3d_i_class],
mt_env
)
~is intV(18)
|
typed_class.rhm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
| #lang shplait
import:
open:
"class.rhm"
"inherit.rhm"
type ClassT
| classT(super_name :: Symbol,
fields :: Listof(Symbol * Type),
methods :: Listof(Symbol * MethodT))
type MethodT
| methodT(arg_type :: Type,
result_type :: Type,
body_exp :: ExpI)
type Type
| intT()
| objT(class_name :: Symbol)
| nullT()
| arrayT(t :: Symbol,
dim :: Int)
type TypeBinding
| tbind(name :: Symbol,
ty :: Type)
type TypeEnv = Listof(TypeBinding)
// ----------------------------------------
fun type_error(a, msg):
error(#'typecheck, "no type: " +& a +& " not " +& msg)
fun get_all_field_types(class_name, t_classes):
if class_name == #'Object
| []
| match find(t_classes, class_name)
| classT(super_name, fields, methods):
append(get_all_field_types(super_name, t_classes),
map(snd, fields))
// ----------------------------------------
fun make_find_in_tree(class_items):
fun (name, class_name, t_classes):
def t_class = find(t_classes, class_name)
def items = class_items(t_class)
def super_name = classT.super_name(t_class)
if super_name == #'Object
| find(items, name)
| try:
find(items, name)
~catch:
make_find_in_tree(class_items)(name,
super_name,
t_classes)
def find_field_in_tree = make_find_in_tree(classT.fields)
def find_method_in_tree = make_find_in_tree(classT.methods)
// ----------------------------------------
fun is_subclass(name1, name2, t_classes):
cond
| name1 == name2: #true
| name1 == #'Object: #false
| ~else:
match find(t_classes, name1)
| classT(super_name, fields, methods):
is_subclass(super_name, name2, t_classes)
fun is_subtype(t1, t2, t_classes):
match t1
| objT(name1):
match t2
| objT(name2):
is_subclass(name1, name2, t_classes)
| ~else: #false
| arrayT(name1, dims1):
match t2
| arrayT(name2, dims2):
is_subclass(name1, name2, t_classes) && dims1 == dims2
| ~else: #false
| ~else: t1 == t2
check: is_subtype(nullT(), nullT(), [])
~is #true
module test:
def a_t_class = values(#'A, classT(#'Object, [], []))
def b_t_class = values(#'B, classT(#'A, [], []))
check: is_subclass(#'Object, #'Object, [])
~is #true
check: is_subclass(#'A, #'B, [a_t_class, b_t_class])
~is #false
check: is_subclass(#'B, #'A, [a_t_class, b_t_class])
~is #true
check: is_subtype(intT(), intT(), [])
~is #true
check: is_subtype(intT(), objT(#'Object), [])
~is #false
check: is_subtype(objT(#'Object), intT(), [])
~is #false
check: is_subtype(objT(#'A), objT(#'B), [a_t_class, b_t_class])
~is #false
check: is_subtype(objT(#'B), objT(#'A), [a_t_class, b_t_class])
~is #true
check: is_subtype(arrayT(#'B, 3), arrayT(#'A, 3), [a_t_class, b_t_class])
~is #true
check: is_subtype(arrayT(#'A, 2), arrayT(#'A, 3), [a_t_class, b_t_class])
~is #false
check: is_subtype(arrayT(#'B, 2), arrayT(#'A, 3), [a_t_class, b_t_class])
~is #false
check: is_subtype(arrayT(#'A, 3), arrayT(#'B, 3), [a_t_class, b_t_class])
~is #false
check: is_subtype(arrayT(#'A, 2), objT(#'A), [a_t_class, b_t_class])
~is #false
check: is_subtype(objT(#'A), arrayT(#'A, 2), [a_t_class, b_t_class])
~is #false
fun check_array_dims(lens :: Listof(ExpI), recur, exp):
if length(lens) < 1:
| type_error(exp, "array must be at least 1 dimensional")
| def _ = map(fun (len):
match recur(len)
| intT(): intT()
| ~else: type_error(exp, "array length must be Int"),
lens)
#void
// ----------------------------------------
def type_lookup: make_lookup(tbind.name, tbind.ty)
def typecheck_exp :: (ExpI, Listof(Symbol * ClassT), Type, Type, TypeEnv) -> Type:
fun (exp, t_classes, this_type, arg_type, tenv):
fun recur(exp):
typecheck_exp(exp, t_classes, this_type, arg_type, tenv)
fun typecheck_nums(l, r):
match recur(l)
| intT():
match recur(r)
| intT(): intT()
| ~else: type_error(r, "Int")
| ~else: type_error(l, "Int")
match exp
| intI(n): intT()
| nullI(): nullT()
| negI(e): match recur(e)
| intT(): intT()
| ~else: type_error(e, "Int")
| idI(s): type_lookup(s, tenv)
| plusI(l, r): typecheck_nums(l, r)
| multI(l, r): typecheck_nums(l, r)
| argI(): arg_type
| thisI(): this_type
| newI(class_name, exps):
def arg_types = map(recur, exps)
def field_types = get_all_field_types(class_name, t_classes)
if (length(arg_types) == length(field_types)
&& foldl(fun (b, r): b && r,
#true,
map2(fun (t1, t2):
(is_subtype(t1, t2, t_classes) || (match t2
| objT(_): match t1
| nullT(): #true
| ~else: #false
| arrayT(_1, _2): match t1
| nullT(): #true
| ~else: #false
| ~else: #false)),
arg_types,
field_types)))
| objT(class_name)
| type_error(exp, "field type mismatch")
| newArrayI(t, lens):
check_array_dims(lens, recur, exp)
if t == #'Int:
| arrayT(t, length(lens))
| match find(t_classes, t)
| classT(super_name, fields, methods): arrayT(t, length(lens))
| getArrayI(array, index):
match recur(index)
| intT(): match recur(array)
| arrayT(t, dim): if dim > 1:
| arrayT(t, dim - 1)
| if t == #'Int:
| intT()
| objT(t)
| ~else: type_error(array, "not an array")
| ~else: type_error(index, "index must be Int")
| setArrayI(array, index, val):
match recur(index)
| intT():
match recur(array)
| arrayT(t, dim):
match recur(val)
| arrayT(t_val, dim_val):
if dim-1 == dim_val && (is_subclass(t_val, t, t_classes) || (t_val == #'Int && t == #'Int)):
| intT()
| type_error(array, "wrong type assignment")
| intT(): if dim == 1 && t == #'Int:
| intT()
| type_error(array, "wrong type assignment")
| nullT(): if dim == 1 && t != #'Int:
| intT()
| type_error(array, "wrong type assignment")
| objT(t_val_class): if dim == 1 && is_subclass(t_val_class, t, t_classes):
| intT()
| type_error(array, "wrong type assignment")
| ~else: type_error(array, "not an array")
| ~else: type_error(index, "index must be Int")
| getI(obj_exp, field_name):
match recur(obj_exp):
| objT(class_name):
find_field_in_tree(field_name,
class_name,
t_classes)
| ~else: type_error(obj_exp, "object")
| sendI(obj_exp, method_name, arg_exp):
def obj_type = recur(obj_exp)
def arg_type = recur(arg_exp)
match obj_type
| objT(class_name):
typecheck_send(class_name, method_name,
arg_exp, arg_type,
t_classes)
| ~else:
type_error(obj_exp, "object")
| superI(method_name, arg_exp):
def arg_type = recur(arg_exp)
def this_class = find(t_classes, objT.class_name(this_type))
typecheck_send(classT.super_name(this_class),
method_name,
arg_exp, arg_type,
t_classes)
| castI(obj_exp, cast_class_name):
match recur(obj_exp)
| objT(obj_class_name):
if cast_class_name == #'null:
| nullT()
| if is_subclass(obj_class_name, cast_class_name, t_classes):
| objT(cast_class_name)
| type_error(exp, "not a subclass")
| intT(): if cast_class_name == #'Int:
| intT()
| type_error(exp, "cannot cast Int to object")
| nullT(): if cast_class_name == #'null:
| nullT()
| type_error(exp, "cannot cast null to object")
| arrayT(t, dim): type_error(exp, "cannot cast array")
| if0I(exp, true, false):
match recur(exp)
| intT():
def t_true = recur(true)
def t_false = recur(false)
match t_true
| intT(): match t_false
| intT(): intT()
| ~else: type_error(exp, "then and else type mismatch")
| objT(class1_name): match t_false
| objT(class2_name): objT(get_most_specific_classT(class1_name, class2_name, t_classes))
| nullT(): t_true
| ~else: type_error(exp, "then and else type mismatch")
| nullT(): match t_false
| nullT(): nullT()
| objT(class_name): t_false
| ~else: type_error(exp, "then and else type mismatch")
| arrayT(t1, dim1): match t_false
| arrayT(t2, dim2): if dim1 == dim2:
| arrayT(get_most_specific_classT(t1, t2, t_classes), dim1)
| type_error(exp, "then and else type mismatch")
| ~else: type_error(exp, "then and else type mismatch")
| ~else: type_error(exp, "not a number")
| letI(s, right, body):
typecheck_exp(body, t_classes, this_type, arg_type,
extend_env(tbind(s, recur(right)), tenv))
| setI(exp, field_name, val):
match recur(exp)
| objT(class_name):
def item = find_field_in_tree(field_name,
class_name,
t_classes)
intT()
| ~else: type_error(exp, "not an object")
fun get_most_specific_classT(c1 :: Symbol, c2 :: Symbol, t_classes :: Listof(Symbol * ClassT)) :: Symbol:
if is_subclass(c1, c2, t_classes):
| c2
| if is_subclass(c2, c1, t_classes):
| c1
| match find(t_classes, c1)
| classT(super_name, fields, methods):
if super_name == #'Object:
| #'Object
| get_most_specific_classT(super_name, c2, t_classes)
fun typecheck_send(class_name :: Symbol,
method_name :: Symbol,
arg_exp :: ExpI,
arg_type :: Type,
t_classes :: Listof(Symbol * ClassT)):
match find_method_in_tree(method_name,
class_name,
t_classes)
| methodT(arg_type_m, result_type, body_exp):
if is_subtype(arg_type, arg_type_m, t_classes)
| result_type
| match arg_type_m
| objT(_): if arg_type == nullT():
| result_type
| type_error(arg_exp, to_string(arg_type_m))
| ~else: type_error(arg_exp, to_string(arg_type_m))
check: typecheck_send(#'A, #'call, intI(0), nullT(),
[values(#'A,
classT(#'Object,
[],
[values(#'call, methodT(objT(#'A), intT(), intI(0)))]))
])
~is intT()
check: typecheck_send(#'A, #'call, intI(0), objT(#'B),
[values(#'A,
classT(#'Object,
[],
[values(#'call, methodT(objT(#'A), intT(), intI(0)))])),
values(#'B, classT(#'Object, [], []))
])
~raises "no type"
fun typecheck_method(method :: MethodT,
this_type :: Type,
t_classes :: Listof(Symbol * ClassT),
tenv :: TypeEnv) :: Void:
match method
| methodT(arg_type, result_type, body_exp):
if is_subtype(typecheck_exp(body_exp, t_classes,
this_type, arg_type, tenv),
result_type,
t_classes)
| #void
| type_error(body_exp, to_string(result_type))
fun check_override(method_name :: Symbol,
method :: MethodT,
this_class :: ClassT,
t_classes :: Listof(Symbol * ClassT)):
def super_name = classT.super_name(this_class)
def super_method:
try:
// Look for method in superclass:
find_method_in_tree(method_name,
super_name,
t_classes)
~catch:
// no such method in superclass:
method
if (methodT.arg_type(method) == methodT.arg_type(super_method)
&& methodT.result_type(method) == methodT.result_type(super_method))
| #void
| error(#'typecheck, "bad override of " +& method_name)
fun typecheck_class(class_name :: Symbol,
t_class :: ClassT,
t_classes :: Listof(Symbol * ClassT),
tenv :: TypeEnv):
match t_class
| classT(super_name, fields, methods):
map(fun (m):
typecheck_method(snd(m), objT(class_name), t_classes, tenv)
check_override(fst(m), snd(m), t_class, t_classes),
methods)
fun typecheck(a :: ExpI,
t_classes :: Listof(Symbol * ClassT), tenv :: TypeEnv) :: Type:
begin:
map(fun (tc):
typecheck_class(fst(tc), snd(tc), t_classes, tenv),
t_classes)
typecheck_exp(a, t_classes, objT(#'Object), intT(), tenv)
// ----------------------------------------
module test:
def posn_t_class:
values(#'Posn,
classT(#'Object,
[values(#'x, intT()), values(#'y, intT())],
[values(#'mdist,
methodT(intT(), intT(),
plusI(getI(thisI(), #'x), getI(thisI(), #'y)))),
values(#'addDist,
methodT(objT(#'Posn), intT(),
plusI(sendI(thisI(), #'mdist, intI(0)),
sendI(argI(), #'mdist, intI(0)))))]))
def posn3D_t_class:
values(#'Posn3D,
classT(#'Posn,
[values(#'z, intT())],
[values(#'mdist,
methodT(intT(), intT(),
plusI(getI(thisI(), #'z),
superI(#'mdist, argI()))))]))
def square_t_class :
values(#'Square,
classT(#'Object,
[values(#'topleft, objT(#'Posn))],
[]))
fun typecheck_posn(a):
typecheck(a, [posn_t_class, posn3D_t_class, square_t_class], mt_env)
def new_posn27 = newI(#'Posn, [intI(2), intI(7)])
def new_posn531 = newI(#'Posn3D, [intI(5), intI(3), intI(1)])
check: typecheck_posn(sendI(new_posn27, #'mdist, intI(0)))
~is intT()
check: typecheck_posn(sendI(new_posn531, #'mdist, intI(0)))
~is intT()
check: typecheck_posn(sendI(new_posn531, #'addDist, new_posn27))
~is intT()
check: typecheck_posn(sendI(new_posn27, #'addDist, new_posn531))
~is intT()
check: typecheck_posn(newI(#'Square, [newI(#'Posn, [intI(0), intI(1)])]))
~is objT(#'Square)
check: typecheck_posn(newI(#'Square, [newI(#'Posn3D, [intI(0), intI(1), intI(3)])]))
~is objT(#'Square)
check: typecheck(multI(intI(1), intI(2)),
[], mt_env)
~is intT()
check: typecheck_posn(sendI(intI(10), #'mdist, intI(0)))
~raises "no type"
check: typecheck_posn(sendI(new_posn27, #'mdist, new_posn27))
~raises "no type"
check: typecheck(plusI(intI(1), newI(#'Object, [])),
[], mt_env)
~raises "no type"
check: typecheck(plusI(newI(#'Object, []), intI(1)),
[], mt_env)
~raises "no type"
check: typecheck(plusI(intI(1), newI(#'Object, [intI(1)])),
[], mt_env)
~raises "no type"
check: typecheck(getI(intI(1), #'x),
[], mt_env)
~raises "no type"
check: typecheck(intI(10),
[posn_t_class,
values(#'Other,
classT(#'Posn,
[],
[values(#'mdist,
methodT(objT(#'Object), intT(),
intI(10)))]))],
mt_env)
~raises "bad override"
check: typecheck_method(methodT(intT(), objT(#'Object), intI(0)), objT(#'Object), [], mt_env)
~raises "no type"
check: typecheck(intI(0),
[square_t_class,
values(#'Cube,
classT(#'Square,
[],
[values(#'m,
methodT(intT(), intT(),
// No such method in superclass:
superI(#'m, intI(0))))]))],
mt_env)
~raises "not found"
// ----------------------------------------
def strip_types :: ClassT -> ClassI:
fun (t_class):
match t_class
| classT(super_name, fields, methods):
classI(super_name,
map(fst, fields),
map(fun (m):
values(fst(m),
match snd(m)
| methodT(arg_type, result_type, body_exp):
body_exp),
methods))
def interp_t :: (ExpI, Listof(Symbol * ClassT), Env) -> Value:
fun (a, t_classes, env):
interp_i(a,
map(fun (c):
values(fst(c), strip_types(snd(c))),
t_classes),
env)
module test:
fun interp_t_posn(a):
interp_t(a, [posn_t_class, posn3D_t_class], mt_env)
check: interp_t_posn(sendI(new_posn27, #'mdist, intI(0)))
~is intV(9)
check: interp_t_posn(sendI(new_posn531, #'mdist, intI(0)))
~is intV(9)
check: interp_t_posn(sendI(new_posn531, #'addDist, new_posn27))
~is intV(18)
check: interp_t_posn(sendI(new_posn27, #'addDist, new_posn531))
~is intV(18)
// 1
module test:
check: interp_t_posn(negI(intI(5)))
~is intV(-5)
check: interp_t_posn(negI(nullI()))
~raises "not a number"
// I know it doesn't make sense to add Int type casting on an Int,
// but this will help for other types if there were floats, bools etc.
check: interp_t_posn(castI(intI(5), #'Int))
~is intV(5)
check: interp_t_posn(getI(castI(new_posn531, #'Posn), #'x))
~is intV(5)
check: interp_t_posn(getI(castI(new_posn531, #'Posn), #'y))
~is intV(3)
check: interp_t_posn(getI(castI(new_posn27, #'Posn), #'x))
~is intV(2)
check: typecheck_posn(castI(intI(5), #'Int))
~is intT()
check: typecheck_posn(getI(castI(new_posn531, #'Posn), #'x))
~is intT()
check: typecheck_posn(castI(new_posn531, #'Int))
~raises "no type"
check: typecheck_posn(getI(castI(new_posn531, #'Posn), #'y))
~is intT()
check: typecheck_posn(getI(castI(new_posn531, #'Posn), #'z))
~raises "not found"
check: typecheck_posn(getI(castI(new_posn27, #'Posn), #'x))
~is intT()
check: typecheck_posn(getI(castI(new_posn27, #'Posn3D), #'x))
~raises "not a subclass"
check: typecheck_posn(getI(castI(new_posn27, #'Posn3D), #'z))
~raises "not a subclass"
check: typecheck_posn(castI(intI(5), #'Posn))
~raises "cannot cast"
// 3
module test:
// Object
// A E
// B D
// C
def a_t = values(#'A, classT(#'Object, [], []))
def b_t = values(#'B, classT(#'A, [], []))
def c_t = values(#'C, classT(#'B, [], []))
def d_t = values(#'D, classT(#'A, [], []))
def e_t = values(#'E, classT(#'Object, [], []))
fun get_most_specific_test(x, y):
get_most_specific_classT(x, y, [a_t, b_t, c_t, d_t, e_t])
check: get_most_specific_test(#'A, #'B)
~is #'A
check: get_most_specific_test(#'C, #'B)
~is #'B
check: get_most_specific_test(#'B, #'D)
~is #'A
check: get_most_specific_test(#'C, #'D)
~is #'A
check: get_most_specific_test(#'C, #'E)
~is #'Object
fun typecheck_ABCDE(exp, true, false):
typecheck(if0I(intI(exp), newI(true, []), newI(false, [])), [a_t, b_t, c_t, d_t, e_t], mt_env)
check: typecheck_ABCDE(0, #'A, #'B)
~is objT(#'A)
check: typecheck_ABCDE(0, #'C, #'B)
~is objT(#'B)
check: typecheck_ABCDE(0, #'B, #'D)
~is objT(#'A)
check: typecheck_ABCDE(0, #'C, #'D)
~is objT(#'A)
check: typecheck_ABCDE(0, #'C, #'E)
~is objT(#'Object)
check: typecheck(if0I(thisI(), intI(10), intI(11)), [a_t, b_t, c_t, d_t, e_t], mt_env)
~raises "not a number"
check: typecheck(if0I(intI(1), newI(#'A, []), intI(11)), [a_t, b_t, c_t, d_t, e_t], mt_env)
~raises "then and else type mismatch"
check: typecheck(if0I(intI(1), intI(11), newI(#'A, [])), [a_t, b_t, c_t, d_t, e_t], mt_env)
~raises "then and else type mismatch"
check: typecheck(if0I(intI(0), intI(10), intI(11)), [a_t, b_t, c_t, d_t, e_t], mt_env)
~is intT()
// 7
module test:
check: typecheck(letI(#'x, thisI(), intI(5)), [], mt_env)
~is intT()
check: typecheck(setI(intI(10), #'x, intI(5)), [], mt_env)
~raises "not an object"
check: typecheck_posn(setI(new_posn531, #'x, intI(5)))
~is intT()
check: typecheck(letI(#'x, intI(5), idI(#'x)), [], mt_env)
~is intT()
|
typed_parse.rhm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
| #lang shplait
import:
open:
"class.rhm"
"inherit.rhm"
"typed_class.rhm"
"inherit_parse.rhm"
// ----------------------------------------
fun parse_t_class(s :: Syntax) :: (Symbol * ClassT):
match s
| 'class $name($field, ...):
extends $parent_name
$method
...':
values(syntax_to_symbol(name),
classT(syntax_to_symbol(parent_name),
map(parse_t_field,
syntax_to_list('[$field, ...]')),
map(parse_t_method,
syntax_to_list('[$method, ...]'))))
| ~else: error(#'parse_class, "invalid input: " +& s)
fun parse_t_field(s :: Syntax) :: (Symbol * Type):
match s
| '$name :: $ty':
values(syntax_to_symbol(name),
parse_type(ty))
| ~else: error(#'parse_t_field, "invalid input: " +& s)
fun parse_t_method(s :: Syntax) :: (Symbol * MethodT):
match s
|'method $name(arg :: $arg_ty) :: $res_ty: $body':
values(syntax_to_symbol(name),
methodT(parse_type(arg_ty),
parse_type(res_ty),
parse(body)))
| ~else: error(#'parse_t_method, "invalid input: " +& s)
fun parse_type(s :: Syntax) :: Type:
cond
| s == 'Int': intT()
| syntax_is_symbol(s): objT(syntax_to_symbol(s))
| ~else: match s
| '$(name :: Identifier) [$nothing] ...': // I don't know how to parse Posn[][][], so doing Posn[1][2][3] instead
arrayT(syntax_to_symbol(name), length(syntax_to_list('[$nothing, ...]')))
| ~else: error(#'parse_type, "invalid input: " +& s)
module test:
check: parse_type('Int')
~is intT()
check: parse_type('Object')
~is objT(#'Object)
check: parse_type('()')
~raises "invalid input"
check: parse_type('Posn[1][2][3]')
~is arrayT(#'Posn, 3)
check: parse_t_field('x :: Int')
~is values(#'x, intT())
check: parse_t_field('x')
~raises "invalid input"
check: parse_t_method('method m(arg :: Int) :: Object: this')
~is values(#'m, methodT(intT(), objT(#'Object), thisI()))
check: parse_t_method('m 1')
~raises "invalid input"
check: parse_t_class('class Posn3D(x :: Int,
y :: Int):
extends Posn
method m1(arg :: Int) :: Int:
arg
method m2(arg :: Int) :: Object:
this')
~is values(#'Posn3D,
classT(#'Posn,
[values(#'x, intT()),
values(#'y, intT())],
[values(#'m1, methodT(intT(), intT(), argI())),
values(#'m2, methodT(intT(), objT(#'Object), thisI()))]))
check: parse_t_class('class')
~raises "invalid input"
// ----------------------------------------
fun interp_t_prog(classes :: Listof(Syntax), a :: Syntax) :: Syntax:
let v = interp_t(parse(a),
map(parse_t_class, classes),
mt_env):
match v
| intV(n): integer_to_syntax(n)
| objV(class_name, field_vals): 'object'
| nullV(): 'null'
| arrayV(vals): 'array $(integer_to_syntax(length(vals)))'
module test:
check: interp_t_prog(['class Empty():
extends Object'],
'new Empty()')
~is 'object'
check: interp_t_prog(
[
'class Posn(x :: Int,
y :: Int):
extends Object
method mdist(arg :: Int) :: Int:
this.x + this.y
method addDist(arg :: Posn) :: Int:
arg.mdist(0) + this.mdist(0)',
'class Posn3D(z :: Int):
extends Posn
method mdist(arg :: Int) :: Int:
this.z + super.mdist(arg)'
],
'(new Posn3D(5, 3, 1)).addDist(new Posn(2, 7))'
)
~is '18'
// 8
module test:
def classes = [
'class Posn(x :: Int,
y :: Int):
extends Object
method mdist(arg :: Int) :: Int:
this.x + this.y
method addDist(arg :: Posn) :: Int:
arg.mdist(0) + this.mdist(0)',
'class Char(ascii :: Int):
extends Object',
'class Button(char :: Int):
extends Object
method press(arg :: Char) :: Char: // arg is dummy
new Char(this.char)',
'class Keyboard3(btn1 :: Button, btn2 :: Button, btn3 :: Button):
extends Object
method press(arg :: Int) :: Char:
if arg - 1 == 0
| this.btn1.press(null)
| if arg - 2 == 0
| this.btn2.press(null)
| if arg - 3 == 0
| this.btn3.press(null)
| null',
'class KeyboardWithSpace(btn_space :: Button):
extends Keyboard3
method press_ascii(arg :: Int) :: Int:
if arg == 0 // 0 is space
| this.btn_space.press(null).ascii
| super.press(arg).ascii',
'class GeneralKeyboard(btns :: Button[10][10],
num_keys_row :: Int,
num_keys_col :: Int,
curr_row :: Int,
curr_col :: Int):
extends Object
method replace_row(arg :: Button[10]) :: Int:
this.btns[this.curr_row] := arg
method press_btn(arg :: Posn) :: Char:
this.btns[arg.x][arg.y].press(new Char(0))
method print_char_on_btn(arg :: Char) :: Int:
this.btns[this.curr_row][this.curr_col].char := arg
method add_btn(arg :: Button) :: Int:
this.btns[this.curr_row][this.curr_col] := arg
method update_row_col(arg :: Int) :: Int:
let _1 = this.curr_col := (this.curr_col + arg):
if this.curr_col - this.num_keys_col == 0
| let _2 = this.curr_row := (this.curr_row + 1):
let _3 = this.curr_col := 0:
1
| 1'
]
def parsed_classes = map(parse_t_class, classes)
fun typecheck_keyboard_class(i):
typecheck_class(fst(list_get(parsed_classes, i)),
snd(list_get(parsed_classes, i)),
parsed_classes,
mt_env)
fun typecheck_keyboard(a):
typecheck(parse(a), parsed_classes, mt_env)
check: typecheck_keyboard_class(0)
~is [#void, #void]
check: typecheck_keyboard_class(1)
~is []
check: typecheck_keyboard_class(2)
~is [#void]
check: typecheck_keyboard_class(3)
~is [#void]
check: typecheck_keyboard_class(4)
~is [#void]
check: typecheck_keyboard_class(5)
~is [#void, #void, #void, #void, #void]
fun typecheck_interp(a):
snd(values(typecheck_keyboard(a),
match interp_t(parse(a), parsed_classes, mt_env)
| intV(n): integer_to_syntax(n)
| objV(class_name, field_vals): symbol_to_syntax(class_name)
| nullV(): 'null'
| arrayV(vals): 'array $(integer_to_syntax(length(vals)))'))
check: typecheck_interp('let kb = new GeneralKeyboard(new Button[10][10], 10, 10, 0, 0):
let _ = kb.add_btn(new Button(0)):
let res = kb.press_btn(new Posn(0, 0)):
res')
~is 'Char'
check: typecheck_interp('let kb = new GeneralKeyboard(new Button[10][10], 10, 10, 0, 0):
let _ = kb.add_btn(null):
let res = kb.press_btn(new Posn(0, 0)):
res')
~raises "not an object" // during interp
check: typecheck_interp('let kb = new GeneralKeyboard(new Button[10][10], 10, 10, 0, 0):
let _ = kb.add_btn(new Button(2)):
let _ = kb.update_row_col(1):
let _ = kb.add_btn(new Button(3)):
let res = kb.press_btn(new Posn(0, 0)).ascii + kb.press_btn(new Posn(0, 1)).ascii:
res')
~is '5'
check: typecheck_interp('let kb = new GeneralKeyboard(new Button[10][10], 10, 10, 0, 0):
let _ = kb.add_btn(new Button(2)):
let _ = kb.update_row_col(1):
let _ = kb.add_btn(new Button(3)):
kb.btns')
~is 'array 10'
check: typecheck_keyboard('new Keyboard3(new Button(1), new Button(2), new Button(3))')
~is objT(#'Keyboard3)
check: typecheck_keyboard('new KeyboardWithSpace(new Button(1), new Button(2), new Button(3), new Button(0))')
~is objT(#'KeyboardWithSpace)
check: typecheck_keyboard('let x = new KeyboardWithSpace(new Button(1), null, new Button(3), new Button(0)):
if x.press_ascii(0) == 0
| (let dummy = x.btn2 := (new Button(22)):
x.press_ascii(2))
| 404')
~is intT()
check: typecheck(parse('5'),
map(parse_t_class,
[
'class A(x :: Int):
extends Object
method add(arg :: Int) :: Int:
this.x + arg'
]),
mt_env)
~is intT()
// 10
module test:
check: typecheck(parse('new A[2][3]'), map(parse_t_class,
['class A(x :: Int):
extends Object']), mt_env)
~is arrayT(#'A, 2)
check: interp_t_prog(['class Empty():
extends Object'],
'new Empty[5][10]')
~is 'array 5'
fun typecheck_posn(a):
typecheck(parse(a), map(parse_t_class, [
'class Posn(x :: Int,
y :: Int):
extends Object
method mdist(arg :: Int) :: Int:
this.x + this.y
method addDist(arg :: Posn) :: Int:
arg.mdist(0) + this.mdist(0)',
'class Posn3D(z :: Int):
extends Posn
method mdist(arg :: Int) :: Int:
this.z + super.mdist(arg)'
]),
mt_env)
check: typecheck_posn('let x = new Posn[3]:
x[0] := new Posn(0, 0)')
~is intT()
check: typecheck_posn('let x = new Posn[3]:
let _ = x[0] := new Posn(0, 0):
let _ = x[1] := new Posn(1, 1):
x[0]')
~is objT(#'Posn)
check: typecheck_posn('let x = new Posn[3]:
let _ = x[0] := new Posn(0, 0):
let _ = x[1] := new Posn(1, 1):
x[2]')
~is objT(#'Posn)
check: typecheck_posn('let x = new Posn[3]:
let _ = x[0] := new Posn(0, 0):
let _ = x[1] := new Posn(1, 1):
x[5]')
~is objT(#'Posn)
check: typecheck_posn('let x = new Posn[3]:
let _ = x[0] := new Posn(0, 0):
let _ = x[1] := 5:
x[1]')
~raises "wrong type assignment"
check: typecheck_posn('let x = new Posn[3][2]:
let _ = x[0][0] := new Posn(0, 0):
let _ = x[1][1] := new Posn(1, 1):
x[0][0]')
~is objT(#'Posn)
|