├── .gitignore
├── .gitmodules
├── LICENSE.md
├── README.md
├── data
├── compiler
│ └── parser
│ │ └── ast.fn
└── utils
│ ├── compiler.fn
│ ├── lists.fn
│ ├── sort.fn
│ └── tree.fn
├── docs
├── analysis.md
├── compiler.md
└── typedef.md
├── inference
├── README.md
├── __init__.py
└── inference.py
├── pyscheme
├── __init__.py
├── ambivalence.py
├── compiler
│ ├── __init__.py
│ ├── cps.py
│ └── irtl.py
├── environment.py
├── exceptions.py
├── expr.py
├── inference.py
├── reader.py
├── repl.py
├── singleton.py
├── tests
│ ├── __init__.py
│ ├── integration
│ │ ├── __init__.py
│ │ ├── base.py
│ │ ├── test_amb.py
│ │ ├── test_closure.py
│ │ ├── test_composite.py
│ │ ├── test_currying.py
│ │ ├── test_data.py
│ │ ├── test_define.py
│ │ ├── test_env.py
│ │ ├── test_error.py
│ │ ├── test_exit.py
│ │ ├── test_here.py
│ │ ├── test_inference.py
│ │ ├── test_lists.py
│ │ ├── test_load.py
│ │ ├── test_metacircular.py
│ │ ├── test_nothing.py
│ │ ├── test_print.py
│ │ ├── test_prototype.py
│ │ ├── test_sort.py
│ │ ├── test_spawn.py
│ │ ├── test_strings.py
│ │ ├── test_switch.py
│ │ ├── test_typedef.py
│ │ └── test_wildcard.py
│ └── unit
│ │ ├── __init__.py
│ │ ├── test_environment.py
│ │ ├── test_expr.py
│ │ ├── test_inference.py
│ │ └── test_reader.py
├── trace.py
└── types.py
└── run_coverage.py
/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__
2 | venv
3 | htmlcov
4 | .coverage
5 | .idea
6 | parser.out
7 | parsetab.py
8 | .DS_Store
9 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "Bytecode-Interpreter"]
2 | path = Bytecode-Interpreter
3 | url = git@github.com:billhails/FN-Bytecode-Interpreter
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PyScheme
2 |
3 | A small lambda-language interpreter written in Python
4 |
5 | Syntax is very much in the javascript/C/Java style, and I'm currently parsing with a hand-written recursive descent parser,
6 | which isn't ideal.
7 |
8 | ## First Impressions
9 |
10 | To get a feel for the language, first check out the [wiki](https://github.com/billhails/PyScheme/wiki), then
11 | read through the integration tests in [`pyscheme/tests/integration`](https://github.com/billhails/PyScheme/tree/master/pyscheme/tests/integration)
12 |
13 | ## Cloning
14 |
15 | I'm new to Python so if anyone has any better way of doing this please comment.
16 |
17 | In order to get this running on my laptop after pushing to GitHub from my home computer I did the following:
18 |
19 | 1. Use PyCharm to create a new project called PyScheme.
20 | 1. go to your pycharm projects root directory:
21 | * `cd ~/PycharmProjects`
22 | 1. clone this repository to a temporary location alongside (not in) the PyScheme project:
23 | * `git clone git@github.com:billhails/PyScheme.git pyscheme-tmp`
24 | 1. Copy everything from that temp location into the PyScheme directory (note the trailing slashes):
25 | * `cp -R pyscheme-tmp/ PyScheme/`
26 | 1. delete the unneeded temporary clone:
27 | * `rm -rf pyscheme-tmp`
28 | 1. check that it worked:
29 | * `cd PyScheme`
30 | * `git status`
31 |
32 | If, like me, you're using PyCharm CE, You'll additionally need to install `coverage`. To install `coverage`
33 | go to the root of the distro and do
34 | ```
35 | $ source ./venv/bin/activate
36 | $ pip install coverage
37 | ```
38 |
39 | ## Test Coverage
40 |
41 | Once those packages are installed, to see test coverage just run the `run_coverage.py` script, then open
42 | `htmlcov/index.html` in your browser.
43 |
44 | I believe that the PyCharm Professional edition has built-in coverage support.
45 |
--------------------------------------------------------------------------------
/data/compiler/parser/ast.fn:
--------------------------------------------------------------------------------
1 | typedef expr {
2 | num(int) |
3 | str(string) |
4 | boolean(bool) |
5 | cond(expr, expr, expr) |
6 | function(list(lambda)) |
7 | application(expr, list(expr)) |
8 | arith(arith_op, expr, expr) |
9 | logic(logic_op, expr, expr)
10 | }
11 |
12 | typedef lambda {
13 | func(list(expr), list(expr))
14 | }
15 |
16 | typedef arith_op {
17 | add | sub | mul | div | mod | pow
18 | }
19 |
20 | typedef logic_op {
21 | and | or | xor
22 | }
23 |
--------------------------------------------------------------------------------
/data/utils/compiler.fn:
--------------------------------------------------------------------------------
1 | // very simple AST
2 | typedef Expression {
3 | addition(Expression, Expression) |
4 | subtraction(Expression, Expression) |
5 | multiplication(Expression, Expression) |
6 | division(Expression, Expression) |
7 | number(int) |
8 | variable(string) |
9 | conditional(Expression, Expression, Expression) |
10 | lambda(Expression, Expression) |
11 | application(Expression, Expression) |
12 | definition(Expression, Expression)
13 | }
14 |
15 | // registers
16 | typedef Register {
17 | envt | val | continue | argl | proc | val1 | val2
18 | }
19 |
20 | all_regs = [envt, val, continue, argl, proc, val1, val2];
21 |
22 | // binary operators
23 | typedef Binop {
24 | add | sub | mul | div
25 | }
26 |
27 | // labels
28 | typedef Label { label(string) }
29 |
30 | // linkage
31 | typedef Linkage {
32 | next |
33 | return |
34 | jump(Label)
35 | }
36 |
37 | // locations
38 | typedef Location {
39 | location_register(Register) |
40 | location_env(string)
41 | }
42 |
43 | // machine addresses
44 | typedef Address {
45 | address_register(Register) |
46 | address_label(Label)
47 | }
48 |
49 | // values
50 | typedef Value {
51 | value_const(int) |
52 | value_register(Register) |
53 | value_lookup(string) |
54 | value_procedure(Label, Register) |
55 | value_procedure_env(Register) |
56 | value_extend_env(string, Register, Register) |
57 | value_binop(Binop, Value, Value) |
58 | value_isprimitive |
59 | value_apply_primitive(Register, Register) |
60 | value_label(Label) |
61 | value_compiled_procedure_entry(Register)
62 | }
63 |
64 | typedef Instruction {
65 | goto(Address) |
66 | assign(Register, Value) |
67 | set(Location, Value) |
68 | test(Location, Value, Label) |
69 | tag(Label) |
70 | save(Register) |
71 | restore(Register)
72 | }
73 |
74 | typedef InstructionSequence {
75 | sequence(list(Register), list(Register), list(Instruction)) | empty
76 | }
77 |
78 | fn compile_linkage {
79 | (return) {
80 | sequence([continue], [], [goto(address_register(continue))])
81 | }
82 | (next) {
83 | empty
84 | }
85 | (jump(l)) {
86 | sequence([], [], [goto(address_label(l))])
87 | }
88 | }
89 |
90 | fn end_with_linkage(linkage, seq) {
91 | preserving([continue], seq, compile_linkage(linkage))
92 | }
93 |
94 | fn preserving {
95 | ([], seq1, seq2) {
96 | append_instruction_sequences([seq1, seq2])
97 | }
98 | (_, empty, seq2) {
99 | seq2
100 | }
101 | (first_reg @ rest_regs, seq1 = sequence(needed, modified, seq), seq2) {
102 | if(needs_register(seq2, first_reg) and modifies_register(seq1, first_reg)) {
103 | preserving(
104 | rest_regs,
105 | sequence(
106 | union([first_reg], needed),
107 | difference(modified, [first_reg]),
108 | [save(first_reg)] @@ seq @@ [restore(first_reg)]
109 | ),
110 | seq2
111 | )
112 | } else {
113 | preserving(rest_regs, seq1, seq2)
114 | }
115 | }
116 | }
117 |
118 | fn needs_register {
119 | (empty, reg) { false }
120 | (sequence(needed, _, _), reg) {
121 | member(reg, needed)
122 | }
123 | }
124 |
125 | fn modifies_register {
126 | (empty, reg) { false }
127 | (sequence(_, modifies, _), reg) {
128 | member(reg, modifies)
129 | }
130 | }
131 |
132 | // a compiler
133 | fn compile (expr, target, linkage) {
134 | switch(expr) {
135 | (addition(l, r)) {
136 | compile_binop(add, l, r, target, linkage)
137 | }
138 | (subtraction(l, r)) {
139 | compile_binop(sub, l, r, target, linkage)
140 | }
141 | (multiplication(l, r)) {
142 | compile_binop(mul, l, r, target, linkage)
143 | }
144 | (division(l, r)) {
145 | compile_binop(div, l, r, target, linkage)
146 | }
147 | (number(i)) {
148 | compile_number(i, target, linkage)
149 | }
150 | (variable(s)) {
151 | compile_variable(s, target, linkage)
152 | }
153 | (conditional(t3st, consequent, alternative)) {
154 | compile_conditional(t3st, consequent, alternative, target, linkage)
155 | }
156 | (lambda(variable(v), body)) {
157 | compile_lambda(v, body, target, linkage)
158 | }
159 | (application(func, arg)) {
160 | compile_application(func, arg, target, linkage)
161 | }
162 | (definition(variable(v), expr)) {
163 | compile_definition(v, expr, target, linkage)
164 | }
165 | }
166 | }
167 |
168 | fn compile_binop(op, l, r, target, linkage) {
169 | end_with_linkage(
170 | linkage,
171 | append_instruction_sequences(
172 | [
173 | compile(l, val1, next),
174 | compile(r, val2, next),
175 | sequence(
176 | [val1, val2],
177 | [target],
178 | [assign(target, value_binop(op, value_register(val1), value_register(val2)))]
179 | )
180 | ]
181 | )
182 | )
183 | }
184 |
185 | fn compile_number(n, target, linkage) {
186 | end_with_linkage(
187 | linkage,
188 | sequence([], [target], [assign(target, value_const(n))])
189 | )
190 | }
191 |
192 | fn compile_variable(v, target, linkage) {
193 | end_with_linkage(
194 | linkage,
195 | sequence(
196 | [envt],
197 | [target],
198 | [assign(target, value_lookup(v))]
199 | )
200 | )
201 | }
202 |
203 | fn compile_definition(v, expr, target, linkage) {
204 | end_with_linkage(
205 | linkage,
206 | preserving(
207 | [envt],
208 | compile(expr, val, next),
209 | sequence(
210 | [envt, val],
211 | [target],
212 | [
213 | set(location_env(v), value_register(val)),
214 | assign(target, value_const(0))
215 | ]
216 | )
217 | )
218 | )
219 | }
220 |
221 | fn compile_conditional(t3st, consequent, alternative, target, linkage) {
222 | t_branch = label("true-branch");
223 | f_branch = label("false-branch");
224 | after_if = label("after-if");
225 | consequent_linkage =
226 | if (linkage == next) {
227 | jump(after_if)
228 | } else {
229 | linkage
230 | };
231 | p_code = compile(t3st, val, next);
232 | c_code = compile(consequent, target, consequent_linkage);
233 | a_code = compile(alternative, target, linkage);
234 | preserving(
235 | [envt, continue],
236 | p_code,
237 | append_instruction_sequences(
238 | [
239 | sequence(
240 | [val],
241 | [],
242 | [
243 | test(location_register(val), value_const(0), f_branch)
244 | ]
245 | ),
246 | parallel_instruction_sequences(
247 | label_instruction_sequence(t_branch, c_code),
248 | label_instruction_sequence(f_branch, a_code)
249 | ),
250 | label_instruction_sequence(after_if, empty)
251 | ]
252 | )
253 | )
254 | }
255 |
256 | fn label_instruction_sequence {
257 | (l = label(_), sequence(needs, modifies, instructions)) {
258 | sequence(needs, modifies, tag(l) @ instructions)
259 | }
260 | (l = label(_), empty) {
261 | sequence([], [], [tag(l)])
262 | }
263 | }
264 |
265 | fn compile_lambda (arg, body, target, linkage) {
266 | proc_entry = label("entry");
267 | after_lambda = label("after-lambda");
268 | lambda_linkage =
269 | if (linkage == next) {
270 | jump(after_lambda)
271 | } else {
272 | linkage
273 | };
274 | append_instruction_sequences(
275 | [
276 | tack_on_instruction_sequence(
277 | end_with_linkage(
278 | lambda_linkage,
279 | sequence(
280 | [envt],
281 | [target],
282 | [assign(target, value_procedure(proc_entry, envt))]
283 | )
284 | ),
285 | compile_lambda_body(arg, body, proc_entry)
286 | ),
287 | label_instruction_sequence(after_lambda, empty)
288 | ]
289 | )
290 | }
291 |
292 | fn compile_lambda_body(arg, body, proc_entry) {
293 | append_instruction_sequences(
294 | [
295 | sequence(
296 | [envt, proc, argl],
297 | [envt],
298 | [
299 | tag(proc_entry),
300 | assign(envt, value_procedure_env(proc)),
301 | assign(envt, value_extend_env(arg, argl, envt)),
302 | ]
303 | ),
304 | compile(body, val, return)
305 | ]
306 | )
307 | }
308 |
309 | fn compile_application(func, arg, target, linkage) {
310 | proc_code = compile(func, proc, next);
311 | arg_code = compile(arg, val, next);
312 | preserving(
313 | [envt, continue],
314 | proc_code,
315 | preserving(
316 | [proc, continue],
317 | arg_code,
318 | compile_procedure_call(target, linkage)
319 | )
320 | )
321 | }
322 |
323 | fn compile_procedure_call(target, linkage) {
324 | primitive_branch = label("primitive-branch");
325 | compiled_branch = label("compiled-branch");
326 | after_call = label("after-call");
327 | compiled_linkage =
328 | if (linkage == next) {
329 | jump(after_call)
330 | } else {
331 | linkage
332 | };
333 | append_instruction_sequences(
334 | [
335 | sequence(
336 | [proc],
337 | [],
338 | [test(location_register(proc), value_isprimitive, primitive_branch)]
339 | ),
340 | parallel_instruction_sequences(
341 | label_instruction_sequence(
342 | compiled_branch,
343 | compile_proc_appl(target, compiled_linkage)
344 | ),
345 | label_instruction_sequence(
346 | primitive_branch,
347 | end_with_linkage(
348 | linkage,
349 | sequence(
350 | [proc, argl],
351 | [target],
352 | [
353 | assign(target, value_apply_primitive(proc, argl))
354 | ]
355 | )
356 | )
357 | )
358 | ),
359 | label_instruction_sequence(after_call, empty)
360 | ]
361 | )
362 | }
363 |
364 | fn compile_proc_appl {
365 | (val, return) {
366 | sequence(
367 | [proc, continue],
368 | all_regs,
369 | [assign(val, value_compiled_procedure_entry(proc))]
370 | )
371 | }
372 | (val, jump(lab)) {
373 | sequence(
374 | [proc],
375 | all_regs,
376 | [
377 | assign(continue, value_label(lab)),
378 | assign(val, value_compiled_procedure_entry(proc))
379 | ]
380 | )
381 | }
382 | (_, return) {
383 | error("MCC - return linkage, but target is not val")
384 | }
385 | (target, linkage) {
386 | proc_return = label("proc-return");
387 | sequence(
388 | [proc],
389 | all_regs,
390 | [
391 | assign(continue, value_label(proc_return)),
392 | assign(val, value_compiled_procedure_entry(proc))
393 | ]
394 | )
395 | }
396 | }
397 |
398 | fn append_two_sequences {
399 | (empty, b) { b }
400 | (a, empty) { a }
401 | (sequence(need1, used1, instr1), sequence(need2, used2, instr2)) {
402 | sequence(
403 | union(need1, difference(need2, used1)),
404 | union(used1, used2),
405 | instr1 @@ instr2
406 | )
407 | }
408 | }
409 |
410 | fn append_instruction_sequences {
411 | ([]) { empty }
412 | (h @ t) {
413 | append_two_sequences(h, append_instruction_sequences(t))
414 | }
415 | }
416 |
417 | fn parallel_instruction_sequences {
418 | (empty, b) { b }
419 | (a, empty) { a }
420 | (sequence(need1, used1, instr1),
421 | sequence(need2, used2, instr2)) {
422 | sequence(
423 | union(need1, need2),
424 | union(used1, used2),
425 | instr1 @@ instr2
426 | )
427 | }
428 | }
429 |
430 | fn tack_on_instruction_sequence {
431 | (empty, b) { b }
432 | (a, empty) { a }
433 | (sequence(need1, used1, instr1),
434 | sequence(_, _, instr2)) {
435 | sequence(
436 | need1,
437 | used1,
438 | instr1 @@ instr2
439 | )
440 | }
441 | }
442 |
443 | fn member {
444 | (s, []) { false }
445 | (s, s @ _) { true }
446 | (s, h @ t) { member(s, t) }
447 | }
448 |
449 | fn union {
450 | ([], l) { l }
451 | (h @ t, l) {
452 | if (member(h, l)) {
453 | union(t, l)
454 | } else {
455 | h @ union(t, l)
456 | }
457 | }
458 | }
459 |
460 | fn difference {
461 | ([], _) { [] }
462 | (h @ t, l) {
463 | if (member(h, l)) {
464 | difference(t, l)
465 | } else {
466 | h @ difference(t, l)
467 | }
468 | }
469 | }
470 |
--------------------------------------------------------------------------------
/data/utils/lists.fn:
--------------------------------------------------------------------------------
1 | // PyScheme lambda language written in Python
2 | //
3 | // Copyright (C) 2018 Bill Hails
4 | //
5 | // This program is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // This program is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with this program. If not, see .
17 |
18 | fn map {
19 | (f, []) { [] }
20 | (f, h @ t) {
21 | f(h) @ map(f, t)
22 | }
23 | }
24 |
25 | fn filter {
26 | (f, []) { [] }
27 | (f, h @ t) {
28 | if (f(h)) {
29 | h @ filter(f, t)
30 | } else {
31 | filter(f, t)
32 | }
33 | }
34 | }
35 |
36 | fn member {
37 | (item, []) { false }
38 | (item, item @ t) { true }
39 | (item, _ @ t) { member(item, t) }
40 | }
41 |
42 | fn exclude(items, lst) {
43 | filter(fn (x) { not member(x, items) }, lst)
44 | }
45 |
46 | fn reduce(binop, final, lst) {
47 | fn helper {
48 | ([]) { final }
49 | (h @ t) { binop(h, helper(t)) }
50 | }
51 | helper(lst)
52 | }
53 |
--------------------------------------------------------------------------------
/data/utils/sort.fn:
--------------------------------------------------------------------------------
1 | // PyScheme lambda language written in Python
2 | //
3 | // Copyright (C) 2018 Bill Hails
4 | //
5 | // This program is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // This program is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with this program. If not, see .
17 |
18 | load utils.lists as lst;
19 |
20 | fn qsort {
21 | ([]) { [] }
22 | (pivot @ rest) {
23 | define lesser = lst.filter(pivot >=, rest);
24 | define greater = lst.filter(pivot <, rest);
25 | qsort(lesser) @@ [pivot] @@ qsort(greater);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/data/utils/tree.fn:
--------------------------------------------------------------------------------
1 | // PyScheme lambda language written in Python
2 | //
3 | // Copyright (C) 2018 Bill Hails
4 | //
5 | // This program is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // This program is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with this program. If not, see .
17 |
18 | typedef tree(t) { branch(tree(t), t, tree(t)) | leaf }
19 |
20 | fn insert {
21 | (t, leaf) { branch(leaf, t, leaf) }
22 | (t, branch(left, u, right)) {
23 | if (t < u) {
24 | branch(insert(t, left), u, right)
25 | } else if (t == u) {
26 | branch(left, u, right)
27 | } else {
28 | branch(left, u, insert(t, right))
29 | }
30 | }
31 | }
32 |
33 | fn contains {
34 | (t, leaf) { false }
35 | (t, branch(left, u, right)) {
36 | if (t < u) {
37 | contains(t, left)
38 | } else if (t == u) {
39 | true
40 | } else {
41 | contains(t, right)
42 | }
43 | }
44 | }
45 |
46 | fn flatten {
47 | (leaf) { [] }
48 | (branch(l, u, r)) { flatten(l) @@ [u] @@ flatten(r) }
49 | }
50 |
--------------------------------------------------------------------------------
/docs/analysis.md:
--------------------------------------------------------------------------------
1 | # `prepare_analysis`
2 |
3 | This method is intended to run through a sequence before it is
4 | analysed. It looks for definitions and pre-installs them as
5 | `TypeVariable`s in the current type environment, so that within the
6 | current scope we can type-check forward references.
7 |
8 | There are only two kinds of definition:
9 | 1. standard `define` statements (and `fn` and `env` statements
10 | that are rewritten to `define` statements.)
11 | 1. type constructors in the body of a `typedef`.
12 |
13 | We can lump these both together under the general heading of
14 | `definition`.
15 |
16 | It would be an error for `prepare_analysis` to recurse in to
17 | nests, or anything else really, so to avoid this it calls
18 | `prepare_definition` on its immediate children.
19 | `prepare_definition` is a no-op in the base `Expr` class and
20 | is only redefined for `Definition`, `Typedef` and
21 | `TypeConstructor` (which `Typedef` calls.)
22 |
23 | `prepare_analysis` should only be called **by `analyse_internal`**
24 | as the first preliminary step when analysing a sequence.
25 |
26 | However definitions can also occur at the top-level, and
27 | in this case the `analyse_internal` method of the definitions
28 | must call their own `prepare_definition` methods.
29 |
30 | The problem is that within a sequence, `prepare_definition` will
31 | already have been called (and needs to have been called) by the
32 | parent sequence:
33 |
34 | ```text
35 | top-level definition
36 | | |
37 | |--analyse_internal-->+---+
38 | | | |
39 | | | | prepare_definition
40 | | | |
41 | | +<--+
42 | | |
43 | ```
44 |
45 | but:
46 |
47 | ```text
48 | top-level sequence definition
49 | | | |
50 | |--analyse_internal-->+---+ |
51 | | | | |
52 | | | | prepare_analysis |
53 | | | | |
54 | | | +--prepare_definition-->+
55 | | | | |
56 | | +<--+ |
57 | | | |
58 | | +--analyse_internal-------->+---+
59 | | | | |
60 | | | | | prepare_definition
61 | | | | |
62 | | | +<--+
63 | | | |
64 | ```
65 |
66 | the repl actually calls `analyse` on whatever the parser returns,
67 | and `analyse` calls `analyse_internal` on itself. Of course
68 | `prepare_definition` could check to see if the symbol being defined
69 | already had an entry in the current environment frame, and do nothing
70 | in that case, but then that would allow/ignore real re-definitions,
71 | which should throw an error.
72 |
73 | Solution, I think is that `analyse_internal` should not call
74 | `prepare_analysis` or `prepare_definition` on itself, but it should be
75 | the caller's responsibility:
76 |
77 | ##### `Expr.analyse`
78 | 1. call `prepare_analysis` on `self`
79 | 1. call `analyse_internal` on `self`
80 |
81 | ##### `Expr.prepare_analysis`
82 | 1. no-op
83 |
84 | ##### `Expr.prepare_definition`
85 | 1. no-op
86 |
87 | ##### `Expr.analyse_internal`
88 | 1. basic get static type method
89 |
90 | ##### `Sequence.prepare_analysis`
91 | 1. call `prepare_definition` on each of its children.
92 |
93 | ##### `Sequence.analyse_internal`
94 | 1. call `analyse_internal` on each of its children.
95 | 1. return the type of the last child.
96 |
97 | ##### `{Typedef | Definition}.prepare_definition`
98 | 1. pre-instates the TypeVriable in the type environment
99 |
--------------------------------------------------------------------------------
/docs/compiler.md:
--------------------------------------------------------------------------------
1 | # thoughts for an abstract machine
2 |
3 | Reminder:
4 |
5 |
6 | | closure | cont | fail | am |
7 | |---------|---------|---------|---------|
8 | | `PC` | `PC` | `PC` | `PC` |
9 | | `ENV` | `ENV` | `ENV` | `ENV` |
10 | | | `CONT` | `CONT` | `CONT` |
11 | | | `temps` | `temps` | `temps` |
12 | | | | `AM` | `AM` |
13 | | | | `RET` | `RET` |
14 |
15 | # primitive operations for basic language functionality:
16 | * unconditional branching
17 | * `JMP label`
18 | * arithmetic
19 | * `ADD 1 2 3` reg[3] = reg[1] + reg[2]
20 | * _etc._
21 | * boolean logic with branches
22 | * `AND reg1 reg2 true_label false_label`
23 | * `EQ reg1 reg2 true_label false_label`
24 | * _etc._
25 | * record manipulation
26 | * `MAKEREC size kind target_register` works for cons
27 | too, kind is i.e. pair or null not type (hint - BBOP)
28 | * `GETREC reg offset target`
29 | * `SETREC reg offset value`
30 | * we could have some shortcuts for common cases:
31 | * `MAKEREC0 kind target`
32 | * `MAKEREC1 kind val target`
33 | * `MAKEREC2 kind val21 val2 target`
34 | * _etc._
35 | * to avoid having to separately fill it, but it
36 | may not be much of an optimisation
37 | * environment manipulation
38 | * `GETENV frame index target` get value from
39 | environment
40 | * `SETENV frame index value` set value in
41 | environment
42 | * `PUSHENV size` create new environment frame
43 | * threading
44 | * `SPAWN label1 label2` ? the two branches would
45 | need to set `RET` to true or false appropriately
46 | then both goto the same continuation.
47 | * `EXIT` unlink the current thread from the circular
48 | list of threads
49 | * closure operations
50 | * `MAKECLOS label target` uses the current
51 | environment
52 | * `CALLCLOS reg` closure in reg overwrites
53 | `AM.closure`
54 | * continuation operations
55 | * `MAKECONT label` copies `AM.continuation` to new
56 | `AM.continuation.cont` and makes
57 | `AM.continuation.cont->closure.pc = label`
58 | * `CONTINUE` `copies AM.continuation.cont` over
59 | `AM.continuation`
60 | * amb operations
61 | * `MAKEBACK label` copies `AM` to new `AM.fail` and
62 | makes `AM.fail->pc = label`
63 | * `BACK` overwrites `AM` with current `AM.fail`
64 |
65 | # Compiling Pattern Matching
66 |
67 | * Where are the fargs? nowhere - abstract
68 | * where are the aargs? in temps? or in a new register
69 | arglist? or as a list in a single temp? temps is more
70 | efficient (?) single arglist is easier.
71 |
72 | concrete example:
73 | ```
74 | fn map {
75 | (f, []) { [] }
76 | (f, h @ t) {
77 | f(h) @ map(f, t)
78 | }
79 | }
80 | ```
81 | Given f is unchanging and we have type-checked already,
82 | we only need inspect the second aarg.
83 |
84 | the code we want to generate is
85 | ```
86 | EQ type[farg2] 0 null-branch
87 | // not-null code
88 | CONTINUE
89 | null-branch
90 | // null-code
91 | CONTINUE
92 | ```
93 | # AMB
94 |
95 | * `a then b`
96 | ```
97 | // code for a
98 | MAKEBACK label-b
99 | CONTINUE
100 | label-b:
101 | // code for b
102 | CONTINUE
103 | ```
104 | * `a then b then c`
105 | * `a then (b then c)`
106 |
107 | ```
108 | MAKEBACK label-b
109 | // code for a
110 | CONTINUE
111 | label-b:
112 |
113 | MAKEBACK label-c
114 | // code for b
115 | CONTINUE
116 | label-c
117 | // code for c
118 | CONTINUE
119 |
120 | CONTINUE // oops, dead code, unless CONTINUE
121 | // is part of "code for a" etc.
122 | ```
123 |
124 | * `a = 10;`
125 |
126 | ```
127 | SETENV frame index value
128 | ```
129 | That's it, typechecking has already disallowed
130 | redefinition, so no need to undo.
131 | the code being backtracked to cannot have any assumption
132 | that `a` is defined.
133 |
134 | # Boxed vs Unboxed record fields
135 |
136 | `list(char)` should be:
137 |
138 | ```
139 | +---------+---+
140 | | wchar_t | * |--->
141 | +---------+---+
142 | ```
143 |
144 | If all fields are the same size then we actually don't care.
145 |
146 | `list(some_record)` should be:
147 |
148 | ```
149 | +---+---+
150 | | * | * |--->
151 | +---+---+
152 | |
153 | v
154 | ```
155 |
156 |
--------------------------------------------------------------------------------
/docs/typedef.md:
--------------------------------------------------------------------------------
1 | # Typedef
2 |
3 | Informal notes to clarify the syntax, semantics and proposed implementation
4 | of this language feature.
5 |
6 | ## Predefined types
7 |
8 | * `list(t)`
9 | * `int`
10 | * `char`
11 | * `string`
12 | * `bool`
13 | * `nothing`
14 |
15 | ## Examples
16 |
17 | Use cases and discussion.
18 |
19 | ### Enumerations
20 |
21 | example:
22 | ```
23 | typedef colour { red | green | blue }
24 | ```
25 | This creates a new type `colour` and three type constructors (functions) `red`, `green` and `blue` that,
26 | because they are parameter-less, are effectively values of the type. Because the constructors are
27 | parameter-less they can be used directly in the language (you don't have to say `red()`).
28 |
29 | Usage:
30 | ```
31 | fn foo {
32 | (red) { "red" }
33 | (green) { "green" }
34 | (blue) { "blue" }
35 | }
36 | ```
37 |
38 | The type checker then correctly infers the type of `foo` to be `colour -> string`.
39 |
40 | Having determined the type of `foo`, the type checker should then verify that the function has a
41 | case for each possible value in the enumeration. There would be a run-time error if `foo`
42 | was declared as:
43 | ```
44 | fn foo {
45 | (red) { "red" }
46 | (green) { "green" }
47 | }
48 | ```
49 | and then called with `foo(blue)`.
50 |
51 | ### Discriminated Unions
52 |
53 | It should be possible to create discriminated unions as follows:
54 |
55 | ```
56 | typedef either(p, q) { first(p) | second(q) }
57 | ```
58 | Probably best explained by usage:
59 | ```
60 | fn dissect {
61 | (first(x)) {
62 | first(x + 1)
63 | }
64 | (second(x)) {
65 | if (x == "hello") {
66 | second(true)
67 | } else {
68 | second(false)
69 | }
70 | }
71 | }
72 | ```
73 | infers `dissect` to be `either(int, string) -> either(int, bool)`.
74 |
75 | ### Recursive Types
76 |
77 | If we didn't already have `list`:
78 | ```
79 | typedef list(t) { pair(t, list(t)) | null }
80 | ```
81 | This would do a couple of things:
82 | 1. Creates a new type "`list` of `t`" where `t` is a type variable.
83 | 1. Creates two type constructors (functions) for this type:
84 | * `pair` of a `t` and a `list` of `t`
85 | * `null`
86 |
87 | ### Uses of pre-existing types
88 | ```
89 | typedef named_list(t) { named(string, list(t)) }
90 | ```
91 | allows:
92 | ```
93 | named("map", [1, 2, 3])
94 | ```
95 | which has the type `named_list(int)`
96 |
97 | ## Restrictions
98 |
99 | The formal arguments to the type constructors are existing types, but the type constructor
100 | itself must be a new symbol, i.e.
101 | ```
102 | typedef mystring { list(char) }
103 | ```
104 | is not valid as `list` is a pre-existing type. If you want this you have to say
105 | ```
106 | typedef mystring { str(list(char)) }
107 | ```
108 | where `str` is not an existing definition.
109 |
110 | ## Formal Grammar
111 |
112 | ```
113 | typedef : TYPEDEF flat_type '{' type_body '}'
114 |
115 | flat_type : symbol [ '(' symbols ')' ]
116 |
117 | type_body : type_constructor { '|' type_constructor }
118 |
119 | type_constructor : symbol [ '(' type { ',' type } ')' ]
120 |
121 | type : NOTHING
122 | | KW_LIST '(' type ')'
123 | | KW_INT
124 | | KW_CHAR
125 | | KW_STRING
126 | | KW_BOOL
127 | | symbol [ '(' type { ',' type } ')' ]
128 | ```
129 | and for the composite functions:
130 | ```
131 | construct : ...
132 | | FN symbol composite_body
133 | | ...
134 |
135 | composite_body : '{' sub_functions '}'
136 |
137 | sub_functions : sub_function { sub_function }
138 |
139 | sub_function : sub_function_arguments body
140 |
141 | sub_function_arguments : '(' sub_function_arg_list ')'
142 |
143 | sub_function_arg_list : sub_function_arg [ ',' sub_function_arg_list ]
144 |
145 | sub_function_arg : simple_subfunction_arg '@' sub_function_arg
146 | | simple_subfunction_arg
147 |
148 | sub_function_arg_2 : '[' [ sub_function_arg { ',' sub_function_arg } ] ']'
149 | | sub_function_arg_3
150 |
151 | sub_function_arg_3 : symbol [ '(' sub_function_arg_list ')' ]
152 | | number
153 | | string
154 | | char
155 | | boolean
156 | | '(' sub_function_arg ')'
157 | ```
158 | Semantics:
159 | * each `type-variable` is bound in the `flat-type` declaration and has scope over the rest of the typedef.
160 | * note that type variables are not "meta" i.e. they can't themselves take arguments.
161 | * note also that type variables do not nest in the formal arguments of the `flat-type` either.
162 | * a `predeclared-type` is a type established by a previous `typedef`, and its arguments must agree with its definition.
163 |
164 | # Implementation
165 |
166 | Three phases:
167 | 1. reading
168 | 1. type checking
169 | 1. pre-analysis
170 | 1. analysis
171 | 1. evaluation
172 |
173 | Let's walk through these stages explaining the logic and structures built, for a small
174 | number of example expressions.
175 |
176 | ## Example 1
177 | Given:
178 | ```
179 | typedef lst(t) { pr(t, lst(t)) | nll }
180 |
181 | fn add1(x) {
182 | 1 + x
183 | }
184 | ```
185 | which results in types
186 | * `pr: t -> lst(t) -> lst(t)`
187 | * `nll: lst(t)`
188 | * `add1: int -> int`
189 |
190 | parse, typecheck and evaluate:
191 | ```
192 | fn map {
193 | (fun, nll) { nll }
194 | (fun, pr(h, t)) { pr(fun(h), map(f, t)) }
195 | }
196 |
197 | map(add1, pr(1, nll))
198 | ```
199 | ### parsing
200 | Things are straightforward until we reach the formal arguments to map. `fun` is an
201 | ordinary variable name, and should be parsed as such. However `nll` is a constant,
202 | because of the typedef, but the parser can not know this (because the type
203 | checker hasn't run yet). So the parser assumes all un-paramaterised symbols at the
204 | top-level of a compound function component are `expr.Symbol`, and builds:
205 | ```
206 | Composite(
207 | CompositeLambda(
208 | [ // arguments
209 | Symbol("fun"),
210 | Symbol("nll")
211 | ],
212 | Sequence(...) // body
213 | )
214 | CompositeLambda(
215 | [ // arguments
216 | Symbol("fun"),
217 | NamedTuple(
218 | Symbol("pr"),
219 | [
220 | Symbol("h"),
221 | Symbol("t")
222 | ]
223 | )
224 | ],
225 | Sequence(...) // body
226 | )
227 | )
228 |
229 | Application(
230 | Symbol("map"),
231 | [
232 | Symbol("add1"),
233 | Application(
234 | Symbol("pr"),
235 | [
236 | Number(1),
237 | Symbol("nll")
238 | ]
239 | )
240 | ]
241 | )
242 | ```
243 | We're less interested in the function bodies here, as they are evaluated normally.
244 |
245 | ### Type checking 1. pre-analysis
246 |
247 | This is basically just running through the current sequence of statements (`{...}`)
248 | and pre-installing type variables for the symbols being defined in the current
249 | type environment.
250 | This allows mutually recursive functions to type-check, but additionally it can refuse
251 | to override the definition of a type value, even in a nested scope,
252 | because they behave as constants.
253 |
254 | > The type environment keeps an additional equivalently scoped set of symbols
255 | that have been defined as type constructors. This allows the type checker to
256 | recognise symbols as type constructors / type values.
257 |
258 | ### Type checking 2. analysis
259 |
260 | At the highest level, the `Composite` object merely iterates over its component
261 | `CompositeLambda` objects calling their analysis method, and unifying all of
262 | the results to produce the type of the `Composite` itself. So we can go straight
263 | to the analysis of a `CompositeLambda`. Let's use the example above to discuss.
264 |
265 | The first `CompositeLambda` should result in the following type:
266 | ```
267 | (type of fun) -> lst(a) -> (type of Sequence with fun in scope)
268 | ```
269 | Note particularly that there is no binding introduced for `nll` in the type
270 | environment. It is not a variable, it is a constant. However the type of the
271 | `CompositeLambda` includes `nll` in its arguments.
272 |
273 | This is achieved by the type checker, when encountering a `Symbol`,
274 | checking for it in that set of symbols currently acting as constants, which
275 | is held in the type environment. If it finds such a symbol, it registers its
276 | type in the argument list it is building, but does not add it to the type
277 | environment it is building for the function.
278 |
279 | > To simplify the following, when I say "constant" I mean either a literal
280 | like `1` or `"hello"`, or a constant symbol registered with the type
281 | environment.
282 |
283 | The second `CompositeLambda` should be analysed to the following type:
284 | ```
285 | (type of fun)
286 | -> (type of named tuple ("pr", type of h, type of t))
287 | -> (type of Sequence with fun, h and t in scope)
288 | ```
289 | So the analysis of `fun` is the same, but on encountering the named tuple, it uses the type
290 | environment to look up the type of `pr`:
291 | ```text
292 | a -> lst(a) ->lst(a)
293 | ```
294 | and from that it deduces the type of the tuple itself to be `lst(a)`
295 |
296 | Descending recursively into the components of the named tuple, the same strategy is followed:
297 | constants and type constructors are used for their type, and symbols are given a
298 | type variable in the type environment.
299 |
300 | We therefore build:
301 | ```text
302 | type of h -> type of t -> b
303 | ```
304 | and unify that with the type of `pr`:
305 | ```text
306 | a -> lst(a) ->lst(a)
307 | ```
308 |
309 | giving
310 |
311 | | ls | rhs | result |
312 | | --- | --- | ----- |
313 | | `h` | `a` | `h == a` |
314 | | `t` | `lst(a)` | `t == lst(h)` |
315 | | `b` | `lst(a)` | `b == lst(h)` |
316 |
317 | Now the type of the second `CompositeLambda` is more firmly established as
318 | ```text
319 | (type of fun) -> lst(h) -> (type of Sequence with fun, h and t in scope)
320 | ```
321 | We also know that `t` is `lst(h)` at this point.
322 |
323 | Analysis of the body (Sequence) then proceeds by the standard HM type analysis to give
324 | the final type of this `CompositeLambda`.
325 |
326 | We then unify with the type of the first composite.
327 |
328 | ```text
329 | (type of fun) -> lst(a) -> (type of Sequence with fun in scope)
330 | (type of fun) -> lst(h) -> (type of Sequence with fun, h and t in scope)
331 |
332 | ```
333 |
334 | Type checking of the application of `map` uses the standard HM algorithm.
335 |
336 | #### Type checking lists as formal arguments
337 |
338 | The same process applies when parsing the following alternative to map, which
339 | uses built-in lists rather than declared types:
340 | ```text
341 | fn map {
342 | (f, []) { [] }
343 | (f, h @ t) { f(h) @ map(f, t) }
344 | }
345 | ```
346 | This should parse to:
347 | ```
348 | Composite(
349 | CompositeLambda(
350 | [
351 | Symbol("fun"),
352 | Null()
353 | ],
354 | Sequence(...) // body
355 | )
356 | CompositeLambda(
357 | [
358 | Symbol("fun"),
359 | Pair(
360 | Symbol("h"),
361 | Symbol("t")
362 | )
363 | ],
364 | Sequence(...) // body
365 | )
366 | )
367 | ```
368 | We just treat lists as if they were named tuples, the process is the same.
369 |
370 | ### Evaluation
371 | Now that the type checker has validated the expressions, we can execute them with
372 | a certain confidence. In order to bind values to variables in the run-time,
373 | when some of those variables are embedded inside named tuples, we need to
374 | do a pattern matching of the formal arguments with their actual arguments.
375 |
376 | | actual argument | formal argument | action |
377 | | ----------------| --------------- | ------ |
378 | | constant | constant | fail if they are not equal |
379 | | constant | symbol | bind constant to symbol |
380 | | constant | tuple | fail |
381 | | constant | list | fail |
382 | | tuple | constant | fail |
383 | | tuple | symbol | bind tuple to symbol |
384 | | tuple | tuple | fail if the names are not equal, otherwise recurse on their components |
385 | | tuple | list | fail |
386 | | list | constant | fail |
387 | | list | symbol | bind list to symbol |
388 | | list | tuple | fail |
389 | | list | list | recurse on the components |
390 |
391 | I'm thinking of enlisting `amb` to run the type checking here, so "fail" means `back`, and the `apply` routine
392 | in the application of the composite function tries one function then the next by doing what `then` does. That means
393 | we get a prolog-like behaviour (if we allow the final function mismatch to fail back further.)
--------------------------------------------------------------------------------
/inference/README.md:
--------------------------------------------------------------------------------
1 | This is the original inference implementation by Rob Smallshire, copied
2 | from his github project https://github.com/rob-smallshire/hindley-milner-python
3 |
4 | Slightly modified by me, mostly just refactoring to make it more object-oriented,
5 | I've left it here for now as a reference.
6 |
--------------------------------------------------------------------------------
/inference/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/billhails/PyScheme/165b4eb4714bfc51f079c310725dcf302798ac26/inference/__init__.py
--------------------------------------------------------------------------------
/pyscheme/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/billhails/PyScheme/165b4eb4714bfc51f079c310725dcf302798ac26/pyscheme/__init__.py
--------------------------------------------------------------------------------
/pyscheme/ambivalence.py:
--------------------------------------------------------------------------------
1 | # PyScheme lambda language written in Python
2 | #
3 | # Copyright (C) 2018 Bill Hails
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License
16 | # along with this program. If not, see .
17 |
18 |
19 | class Amb:
20 | """
21 | Object representation of Amb that also carries the cut continuation
22 | Scenarios:
23 | Normal amb creation is by `define`, `then` and the implicit `cut` after arguments are matched.
24 | Cut amb creation is by apply_evaluated_args in CompositeClosure
25 | Normal amb invocation is by `back` and by `match`
26 | Cut amb invocation is by the cut amb if backtracked to?
27 | No. The cut installs the cut amb as the actual amb, it is never invoked directly as a cut.
28 | """
29 | def __init__(self, amb: callable, cut: 'Amb'=None):
30 | self._amb = amb
31 | self._cut = cut
32 |
33 | def __call__(self):
34 | return self._amb()
35 |
36 | def cut(self):
37 | return self._cut
38 |
39 | def cut_point(self):
40 | return Amb(self._amb, Amb(self._amb))
41 |
--------------------------------------------------------------------------------
/pyscheme/compiler/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/billhails/PyScheme/165b4eb4714bfc51f079c310725dcf302798ac26/pyscheme/compiler/__init__.py
--------------------------------------------------------------------------------
/pyscheme/compiler/cps.py:
--------------------------------------------------------------------------------
1 | # PyScheme lambda language written in Python
2 | #
3 | # Copyright (C) 2018 Bill Hails
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License
16 | # along with this program. If not, see .
17 |
18 |
19 | # This code originally translated from Andrew Appel "Compiling with Continuations"
20 |
21 | from enum import Enum, auto
22 | from typing import List, Tuple
23 |
24 |
25 | class var:
26 | def __init__(self, name: str):
27 | self.name = name
28 |
29 |
30 | class value:
31 | """
32 | The union of all different types of atomic arguments
33 | that can be provided to the CPS operators
34 | """
35 | pass
36 |
37 |
38 | class VAR(value):
39 | def __init__(self, v: var):
40 | self.var = v
41 |
42 |
43 | class LABEL(value):
44 | def __init__(self, v: var):
45 | self.var = v
46 |
47 |
48 | class INT(value):
49 | def __init__(self, i: int):
50 | self.int = i
51 |
52 |
53 | class REAL(value):
54 | def __init__(self, r: str):
55 | self.real = r
56 |
57 |
58 | class STRING(value):
59 | def __init__(self, s: str):
60 | self.string = s
61 |
62 |
63 | class accesspath:
64 | pass
65 |
66 |
67 | class OFFp(accesspath):
68 | def __init__(self, i: int):
69 | self.int = i
70 |
71 |
72 | class SELp(accesspath):
73 | def __init__(self, i: int, a: accesspath):
74 | self.int = i
75 | self.accesspath = a
76 |
77 |
78 | class primop(Enum):
79 | mul = auto()
80 | add = auto()
81 | sub = auto()
82 | div = auto()
83 | mod = auto()
84 | exp = auto()
85 | eq = auto()
86 | ne = auto()
87 | gt = auto()
88 | lt = auto()
89 | ge = auto()
90 | le = auto()
91 | # etc.
92 |
93 |
94 | class cexp:
95 | """
96 | The abstract type of continuation expressions
97 | """
98 | pass
99 |
100 |
101 | class RECORD(cexp):
102 | """
103 | RECORD([(VAR(var('a')), OFFp(0)), (INT(2), OFFp(0)), (VAR(var('c')), OFFp(0))] w, E)
104 | makes a 3-word record initialised to (a, 2, c) and puts its address in w, then continues with E
105 | """
106 | def __init__(self, t: List[Tuple[value, accesspath]], v: var, c: cexp):
107 | """
108 | :param t: List[Tuple[value, accesspath]] - fields of the record
109 | :param v: var - result address
110 | :param c: cexp - continuation
111 | """
112 | self.tuples = t
113 | self.var = v
114 | self.cexp = c
115 |
116 |
117 | class SELECT(cexp):
118 | """
119 | SELECT(i, v, w, E) fetches the i'th field of v into w then continues at E
120 | """
121 | def __init__(self, i: int, val: value, w: var, c: cexp):
122 | """
123 | :param i: int - index in to record
124 | :param val: value - address of record
125 | :param w: var - result
126 | :param c: cexp - continuation
127 | """
128 | self.int = i
129 | self.value = val
130 | self.var = w
131 | self.cexp = c
132 |
133 |
134 | class OFFSET(cexp):
135 | """
136 | OFFSET(i, v, w, E)
137 | if v points at the j'th field of a record this makes w point at the j+i'th fiels, and continues at E
138 | """
139 | def __init__(self, i: int, val: value, v: var, c: cexp):
140 | """
141 | :param i: int - offset
142 | :param val: value - current pointer
143 | :param v: var - result
144 | :param c: cexp - continuation
145 | """
146 | self.int = i
147 | self.value = val
148 | self.var = v
149 | self.cexp = c
150 |
151 |
152 | class APP(cexp):
153 | """
154 | APP(var('f'), [a1, a2,..., an])
155 | Apply a function to actual arguments.
156 | N.B the continuation is in each function.
157 | """
158 | def __init__(self, function: value, arguments: List[value]):
159 | """
160 | :param function: value - the function
161 | :param arguments: List[value] - the actual arguments
162 | """
163 | self.function = function
164 | self.arguments = arguments
165 |
166 |
167 | class FIX(cexp):
168 | """
169 | define a set of mutually recursive functions
170 | FIX([(f1, [v11, v12,..., v1m], B1)
171 | (f2, [v21, v22,..., v2m], B2)
172 | ...
173 | (fn, [vn1, vn2,..., vnm], Bn)],
174 | E)
175 | defines 0 or more functions fj callable from E or from one another's bodies Bj
176 | Formal arguments are vnm, fn are the resulting function (closure?) addresses
177 | """
178 | def __init__(self, functions: List[Tuple[var, List[var], cexp]], c: cexp):
179 | """
180 | :param functions: List[Tuple[var, List[var], cexp]] - functions
181 | :param c: cexp - continuation
182 | """
183 | self.functions = functions
184 | self.cexp = c
185 |
186 |
187 | class SWITCH(cexp):
188 | """
189 | multi-way branches
190 | SWITCH(VAR(var('i')), [E0, E1, E2, E3, E4])
191 | chooses Ei
192 | """
193 | def __init__(self, val: value, cexps: List[cexp]):
194 | """
195 | :param val: value - switch
196 | :param cexps: List[cexp] - cases
197 | """
198 | self.value = val
199 | self.cexps = cexps
200 |
201 |
202 | class PRIMOP(cexp):
203 | """
204 | primitive operations, for example
205 | if (a > b) F G
206 | can be expressed as PRIMOP(primop.gt, [VAR(var('a')), VAR(var('b'))], [], [F, G])
207 | """
208 | def __init__(self, prim: primop, args: List[value], results: List[var], cexps: List[cexp]):
209 | """
210 | :param prim: primop - primitive
211 | :param args: List[value] - arguments to the primop
212 | :param results: List[var] - result storage
213 | :param cexps: List[cexp] - continuation(s)
214 | """
215 | self.primop = prim
216 | self.args = args
217 | self.results = results
218 | self.cexps = cexps
219 |
--------------------------------------------------------------------------------
/pyscheme/compiler/irtl.py:
--------------------------------------------------------------------------------
1 | # PyScheme lambda language written in Python
2 | #
3 | # Copyright (C) 2018 Bill Hails
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License
16 | # along with this program. If not, see .
17 |
18 |
19 | class Exp:
20 | pass
21 |
22 |
23 | class Const(Exp):
24 | def __init__(self, value: int):
25 | self.value = value
26 |
27 |
28 | class Name(Exp):
29 | def __init__(self, label: Label):
30 | self.label = label
31 |
32 |
33 | class Temp(Exp):
34 | pass
35 |
--------------------------------------------------------------------------------
/pyscheme/environment.py:
--------------------------------------------------------------------------------
1 | # PyScheme lambda language written in Python
2 | #
3 | # Base Environment class plus a Frame subclass
4 | #
5 | # Copyright (C) 2018 Bill Hails
6 | #
7 | # This program is free software: you can redistribute it and/or modify
8 | # it under the terms of the GNU General Public License as published by
9 | # the Free Software Foundation, either version 3 of the License, or
10 | # (at your option) any later version.
11 | #
12 | # This program is distributed in the hope that it will be useful,
13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | # GNU General Public License for more details.
16 | #
17 | # You should have received a copy of the GNU General Public License
18 | # along with this program. If not, see .
19 |
20 | from .exceptions import SymbolNotFoundError, SymbolAlreadyDefinedError, PySchemeInternalError
21 | from . import types
22 | from typing import Dict
23 | from . import ambivalence
24 |
25 |
26 | class Environment:
27 | counter = [0]
28 |
29 | def extend(self, dictionary: 'types.Maybe[Dict]'=None) -> 'Frame':
30 | return Frame(self, dictionary)
31 |
32 | def lookup(self, symbol, ret: 'types.Continuation', amb: 'types.Amb') -> 'types.Promise':
33 | raise SymbolNotFoundError(symbol)
34 |
35 | def define(self, symbol, value, ret: 'types.Continuation', amb: 'types.Amb', error=False) -> 'types.Promise':
36 | pass
37 |
38 | def contains(self, symbol: 'expr.Symbol'):
39 | return False
40 |
41 | def __str__(self) -> str:
42 | return '[0]'
43 |
44 | def __getitem__(self, symbol):
45 | raise SymbolNotFoundError(symbol)
46 |
47 | def contents(self):
48 | return {}
49 |
50 |
51 | class Frame(Environment):
52 | def __init__(self, parent: Environment, dictionary: 'types.Maybe[Dict]'):
53 | self._parent = parent
54 | if dictionary is None:
55 | dictionary = {}
56 | self._dictionary = dictionary
57 | self.counter[0] += 1
58 | self._number = self.counter[0]
59 |
60 | def lookup(self, symbol, ret: 'types.Continuation', amb: 'types.Amb') -> 'types.Promise':
61 | if symbol in self._dictionary:
62 | return lambda: ret(self._dictionary.get(symbol), amb)
63 | else:
64 | return lambda: self._parent.lookup(symbol, ret, amb)
65 |
66 | def define(self, symbol: 'expr.Symbol',
67 | value: 'expr.Expr',
68 | ret: 'types.Continuation',
69 | amb: ambivalence.Amb,
70 | error: bool=False) -> 'types.Promise':
71 | if symbol in self._dictionary:
72 | if value != self._dictionary[symbol]:
73 | if error:
74 | raise SymbolAlreadyDefinedError(symbol)
75 | else:
76 | return lambda: amb()
77 | else:
78 | return lambda: ret(symbol, amb)
79 | else:
80 | self._dictionary[symbol] = value
81 |
82 | def undo_amb() -> 'types.Promise':
83 | del self._dictionary[symbol]
84 | return lambda: amb()
85 | return lambda: ret(symbol, ambivalence.Amb(undo_amb, amb.cut()))
86 |
87 | def contains(self, symbol: 'expr.Symbol'):
88 | return symbol in self._dictionary or self._parent.contains(symbol)
89 |
90 | def non_eval_context_define(self, symbol: 'expr.Symbol', value: 'expr.Expr'):
91 | """
92 | do NOT call this in general.
93 | it is for internal use by set-up code in the repl.
94 | normal evaluation requires calling define() above.
95 | """
96 | if symbol in self._dictionary:
97 | raise PySchemeInternalError("symbol already defined: " + str(symbol))
98 | self._dictionary[symbol] = value
99 |
100 | def __getitem__(self, symbol):
101 | if symbol in self._dictionary:
102 | return self._dictionary[symbol]
103 | else:
104 | return self._parent[symbol]
105 |
106 | def __str__(self) -> str:
107 | return '[' + str(self._number) + '] -> ' + str(self._parent)
108 |
109 | def contents(self):
110 | return self._dictionary.copy()
111 |
--------------------------------------------------------------------------------
/pyscheme/exceptions.py:
--------------------------------------------------------------------------------
1 | # PyScheme lambda language written in Python
2 | #
3 | # Various Exceptions (likely temporary as we'll handle exceptions through the repl)
4 | #
5 | # Copyright (C) 2018 Bill Hails
6 | #
7 | # This program is free software: you can redistribute it and/or modify
8 | # it under the terms of the GNU General Public License as published by
9 | # the Free Software Foundation, either version 3 of the License, or
10 | # (at your option) any later version.
11 | #
12 | # This program is distributed in the hope that it will be useful,
13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | # GNU General Public License for more details.
16 | #
17 | # You should have received a copy of the GNU General Public License
18 | # along with this program. If not, see .
19 |
20 | import pyscheme.trace as trace
21 |
22 | class PySchemeError(Exception):
23 | pass
24 |
25 |
26 | class SymbolError(PySchemeError):
27 | def __init__(self, symbol):
28 | self._symbol = symbol
29 |
30 |
31 | class SymbolNotFoundError(SymbolError):
32 | def __str__(self):
33 | return 'SymbolNotFoundError: ' + str(self._symbol)
34 |
35 |
36 | class TypeSymbolNotFoundError(SymbolError):
37 | def __str__(self):
38 | return 'TypeSymbolNotFoundError: ' + str(self._symbol)
39 |
40 |
41 | class SymbolAlreadyDefinedError(SymbolError):
42 | def __str__(self):
43 | return 'SymbolAlreadyDefinedError: ' + str(self._symbol)
44 |
45 |
46 | class TypeSymbolAlreadyDefinedError(SymbolError):
47 | def __str__(self):
48 | return 'TypeSymbolAlreadyDefinedError: ' + str(self._symbol)
49 |
50 |
51 | class NonBooleanExpressionError(PySchemeError):
52 | def __str__(self):
53 | return 'NonBooleanExpressionError'
54 |
55 |
56 | class MissingPrototypeError(SymbolError):
57 | def __str__(self):
58 | return 'MissingPrototypeError: ' + str(self._symbol)
59 |
60 |
61 | class PySchemeSyntaxError(Exception):
62 | def __init__(self, msg: str, line: int, next_token):
63 | self.msg = msg
64 | self.line = line
65 | self.next_token = next_token
66 |
67 | def __str__(self) -> str:
68 | return str(self.msg) + ", line: " + str(self.line) + ", next token: " + str(self.next_token)
69 |
70 | __repr__ = __str__
71 |
72 |
73 | class PySchemeTypeError(PySchemeError):
74 | def __init__(self, type1, type2):
75 | self.type1 = type1
76 | self.type2 = type2
77 | self.trace :list = trace.stack.copy()
78 |
79 | def tr(self):
80 | return ''
81 | res = ' trace -\n'
82 | for s in self.trace:
83 | res += str(s)
84 | res += "\n"
85 | return res
86 |
87 | def __str__(self):
88 | return 'PySchemeTypeError: ' + str(self.type1) + " != " + str(self.type2) + self.tr()
89 |
90 |
91 | class PySchemeRunTimeError(PySchemeError):
92 | def __init__(self, message):
93 | self.message = message
94 |
95 | def __str__(self):
96 | return 'PySchemeRunTimeError: ' + self.message
97 |
98 |
99 | class PySchemeInferenceError(PySchemeError):
100 | def __init__(self, message):
101 | self.__message = message
102 |
103 | message = property(lambda self: self.__message)
104 |
105 | def __str__(self):
106 | return 'PySchemeInferenceError: ' + str(self.message)
107 |
108 |
109 | class PySchemeInternalError(PySchemeError):
110 | def __init__(self, message):
111 | self.__message = message
112 |
113 | message = property(lambda self: self.__message)
114 |
115 | def __str__(self):
116 | return 'PySchemeInternalError: ' + str(self.message)
117 |
--------------------------------------------------------------------------------
/pyscheme/inference.py:
--------------------------------------------------------------------------------
1 | # PyScheme lambda language written in Python
2 | #
3 | # Copyright (C) 2018 Bill Hails
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License
16 | # along with this program. If not, see .
17 |
18 | # Credit: Rob Smallshire wrote the original python implementation at
19 | # https://github.com/rob-smallshire/hindley-milner-python
20 |
21 | from typing import Dict
22 | from . import expr
23 | from .exceptions import TypeSymbolNotFoundError, TypeSymbolAlreadyDefinedError, PySchemeInferenceError, PySchemeTypeError
24 | from typing import Union
25 |
26 | TypeOrVar = Union['TypeVariable', 'Type']
27 |
28 |
29 | def debug(*args, **kwargs):
30 | if Config.debug:
31 | print(*args, **kwargs)
32 |
33 |
34 | class Config:
35 | debug = False
36 |
37 |
38 | class Type:
39 | def prune(self):
40 | pass
41 |
42 | def occurs_in(self, types):
43 | return any(self.occurs_in_type(t) for t in types)
44 |
45 | def is_generic(self, non_generic):
46 | return not self.occurs_in(non_generic)
47 |
48 | def occurs_in_type(self, other):
49 | pruned_other = other.prune()
50 | if pruned_other == self:
51 | return True
52 | elif isinstance(pruned_other, TypeOperator):
53 | return self.occurs_in(pruned_other.types)
54 | return False
55 |
56 | def unify(self, other, seen=None):
57 | if seen is None:
58 | seen = set()
59 | self.prune().unify_internal(other.prune(), seen)
60 |
61 | def unify_internal(self, other, seen):
62 | raise PySchemeTypeError(self, other)
63 |
64 | def fresh(self, non_generics):
65 | return self.freshrec(non_generics, {})
66 |
67 | def freshrec(self, non_generics, mapping):
68 | return self.prune().make_fresh(non_generics, mapping)
69 |
70 | def final_result(self):
71 | return self
72 |
73 |
74 | class TypeVariable(Type):
75 | next_variable_id = 0
76 |
77 | def __init__(self):
78 | self.id = TypeVariable.next_variable_id
79 | TypeVariable.next_variable_id += 1
80 | self.instance = None
81 | self.__name = None
82 |
83 | @classmethod
84 | def reset_names(cls):
85 | """used for consistency during testing"""
86 | expr.Symbol.reset()
87 |
88 | @property
89 | def name(self):
90 | if self.__name is None:
91 | self.__name = expr.Symbol.generate().value()
92 | return self.__name
93 |
94 | def make_fresh(self, non_generics, mapping):
95 | if self.is_generic(non_generics):
96 | if self not in mapping:
97 | mapping[self] = TypeVariable()
98 | return mapping[self]
99 | else:
100 | return self
101 |
102 | def prune(self):
103 | if self.instance is not None:
104 | self.instance = self.instance.prune()
105 | return self.instance
106 | return self
107 |
108 | def unify_internal(self, other, seen):
109 | if self != other:
110 | if self.occurs_in_type(other):
111 | raise PySchemeInferenceError("recursive unification")
112 | self.instance = other
113 |
114 | def __str__(self):
115 | if self.instance is not None:
116 | return str(self.instance)
117 | else:
118 | return self.name
119 |
120 | def __repr__(self):
121 | return "TypeVariable(id = {0})".format(self.id)
122 |
123 |
124 | class EnvironmentType(Type):
125 | def __init__(self, env: 'TypeEnvironment'):
126 | self._env = env
127 |
128 | def prune(self):
129 | return self
130 |
131 | def env(self) -> 'TypeEnvironment':
132 | return self._env
133 |
134 | def make_fresh(self, non_generics, mapping):
135 | return self
136 |
137 | def unify_internal(self, other, seen):
138 | if isinstance(other, TypeVariable):
139 | other.unify_internal(self, seen)
140 | elif isinstance(other, PrototypeType):
141 | other.env().unify_half(self.env(), seen)
142 | elif isinstance(other, EnvironmentType):
143 | self.env().unify_internal(other.env(), seen)
144 | else:
145 | raise PySchemeTypeError(self, other)
146 |
147 | def __str__(self):
148 | return "env " + str(self._env) + self._env.dump_dict()
149 |
150 |
151 | class PrototypeType(EnvironmentType):
152 | def unify_internal(self, other, seen):
153 | if isinstance(other, TypeVariable):
154 | other.unify_internal(self, seen)
155 | elif isinstance(other, EnvironmentType):
156 | self.env().unify_half(other.env(), seen)
157 | else:
158 | raise PySchemeTypeError(self, other)
159 |
160 |
161 | class TypeOperator(Type):
162 | def __init__(self, name: str, *types):
163 | self.name = name
164 | self.types = types
165 |
166 | def prune(self):
167 | return self
168 |
169 | def unify_internal(self, other, seen):
170 | if isinstance(other, TypeVariable):
171 | other.unify_internal(self, seen)
172 | elif isinstance(other, TypeOperator):
173 | if self.name != other.name or len(self.types) != len(other.types):
174 | raise PySchemeTypeError(self, other)
175 | for p, q in zip(self.types, other.types):
176 | if p is None:
177 | print("attempt to unify None with", q, "in", self, other)
178 | p.unify(q, seen)
179 | else:
180 | raise PySchemeTypeError(self, other)
181 |
182 | def make_fresh(self, non_generics, mapping):
183 | return TypeOperator(self.name, *[x.freshrec(non_generics, mapping) for x in self.types])
184 |
185 | def __str__(self):
186 | num_types = len(self.types)
187 | if num_types == 0:
188 | return self.name
189 | elif num_types == 2:
190 | return "({0} {1} {2})".format(str(self.types[0]), self.name, str(self.types[1]))
191 | else:
192 | return "{0}({1})".format(self.name, ' '.join([str(t) for t in self.types]))
193 |
194 |
195 | class Function(TypeOperator):
196 | def __init__(self, from_type, to_type):
197 | super(Function, self).__init__("->", from_type, to_type)
198 |
199 | def final_result(self):
200 | return self.types[1].final_result()
201 |
202 |
203 | class TypeEnvironment:
204 | counter = 0
205 |
206 | def extend(self, dictionary: Dict['expr.Symbol', Type]=None) -> 'TypeEnvironment':
207 | if dictionary is None:
208 | dictionary = {}
209 | result = TypeFrame(self, dictionary)
210 | debug("extend -> ", str(result))
211 | return result
212 |
213 | def unify_internal(self, other, seen):
214 | pass
215 |
216 | def flatten(self, definitions):
217 | pass
218 |
219 | def dump_dict(self):
220 | return ''
221 |
222 | def note_type_constructor(self, name: 'expr.Symbol'):
223 | pass
224 |
225 | def noted_type_constructor(self, name: 'expr.Symbol'):
226 | return False
227 |
228 | def __getitem__(self, symbol: 'expr.Symbol'):
229 | raise TypeSymbolNotFoundError(symbol)
230 |
231 | def unify_half(self, other, seen):
232 | pass
233 |
234 | def __contains__(self, symbol: 'expr.Symbol') -> bool:
235 | return False
236 |
237 | def set_or_error(self, symbol: 'expr.Symbol', typevar: Type):
238 | pass
239 |
240 | def __setitem__(self, symbol: 'expr.Symbol', typevar: Type):
241 | pass
242 |
243 | def __str__(self):
244 | return '<0>'
245 |
246 | def __repr__(self):
247 | return "Root"
248 |
249 | def dump(self):
250 | pass
251 |
252 |
253 | class TypeFrame(TypeEnvironment):
254 | def __init__(self, parent: TypeEnvironment, dictionary: Dict['expr.Symbol', Type]):
255 | self._parent = parent
256 | self._dictionary = dictionary
257 | self.type_constructors = set()
258 | TypeEnvironment.counter += 1
259 | self._id = TypeEnvironment.counter
260 |
261 | def unify_internal(self, other, seen):
262 | self.unify_half(other, seen)
263 | other.unify_half(self, seen)
264 |
265 | def unify_half(self, other, seen):
266 | if self in seen or other in seen:
267 | return
268 | seen.add(other)
269 | seen.add(self)
270 | definitions = self.flatten()
271 | for k, v in definitions.items():
272 | v.unify(other[k], seen)
273 |
274 | def flatten(self, definitions=None):
275 | if definitions is None:
276 | definitions = {}
277 | for k in self._dictionary.keys():
278 | if k not in definitions:
279 | definitions[k] = self[k]
280 | self._parent.flatten(definitions)
281 | return definitions
282 |
283 | def note_type_constructor(self, name: 'expr.Symbol'):
284 | self.type_constructors.add(name)
285 |
286 | def noted_type_constructor(self, name: 'expr.Symbol'):
287 | return name in self.type_constructors or self._parent.noted_type_constructor(name)
288 |
289 | def dump_dict(self):
290 | return ''
291 |
292 | def __getitem__(self, symbol: 'expr.Symbol') -> Type:
293 | if symbol in self._dictionary:
294 | return self._dictionary[symbol]
295 | else:
296 | return self._parent[symbol]
297 |
298 | def __contains__(self, symbol: 'expr.Symbol') -> bool:
299 | return symbol in self._dictionary or symbol in self._parent
300 |
301 | def set_or_error(self, symbol: 'expr.Symbol', typevar: Type):
302 | if symbol in self._dictionary:
303 | raise TypeSymbolAlreadyDefinedError(symbol)
304 | else:
305 | self._dictionary[symbol] = typevar
306 |
307 | def __setitem__(self, symbol: 'expr.Symbol', typevar: Type):
308 | if symbol in self._dictionary:
309 | self._dictionary[symbol].unify(typevar)
310 | else:
311 | self._dictionary[symbol] = typevar
312 |
313 | def __str__(self):
314 | return '<' + str(self._id) + '>' + str(self._parent)
315 |
316 | def __repr__(self):
317 | return str(self) + self.dump_dict() + ' ---> ' + repr(self._parent)
318 |
319 | def __hash__(self):
320 | return id(self)
321 |
322 | def dump(self):
323 | for k, v in self._dictionary.items():
324 | print("type:", k, v)
325 |
--------------------------------------------------------------------------------
/pyscheme/repl.py:
--------------------------------------------------------------------------------
1 | # PyScheme lambda language written in Python
2 | #
3 | # The Read-Eval-Print-Loop
4 | #
5 | # Copyright (C) 2018 Bill Hails
6 | #
7 | # This program is free software: you can redistribute it and/or modify
8 | # it under the terms of the GNU General Public License as published by
9 | # the Free Software Foundation, either version 3 of the License, or
10 | # (at your option) any later version.
11 | #
12 | # This program is distributed in the hope that it will be useful,
13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | # GNU General Public License for more details.
16 | #
17 | # You should have received a copy of the GNU General Public License
18 | # along with this program. If not, see .
19 |
20 | from typing import List
21 | from . import types
22 | import pyscheme.environment as environment
23 | import pyscheme.expr as expr
24 | from io import StringIO
25 | import pyscheme.reader as reader
26 | from .inference import TypeEnvironment, EnvironmentType
27 | from .exceptions import PySchemeError
28 | from . import ambivalence
29 |
30 |
31 | class Repl:
32 | def __init__(self, input: StringIO, output: StringIO, error: StringIO):
33 | self.input = input
34 | self.output = output
35 | self.error = error
36 | self.tokeniser = reader.Tokeniser(input)
37 | self.reader = reader.Reader(self.tokeniser, error)
38 | operators = {
39 | "+": expr.Addition(), # int -> int -> int
40 | "-": expr.Subtraction(), # int -> int -> int
41 | "*": expr.Multiplication(), # int -> int -> int
42 | "/": expr.Division(), # int -> int -> int
43 | "%": expr.Modulus(), # int -> int -> int
44 | "**": expr.Exponentiation(), # int -> int -> int
45 | "==": expr.Equality(), # t -> t -> bool
46 | "<": expr.LT(), # t -> t -> bool
47 | ">": expr.GT(), # t -> t -> bool
48 | "<=": expr.LE(), # t -> t -> bool
49 | ">=": expr.GE(), # t -> t -> bool
50 | "!=": expr.NE(), # t -> t -> bool
51 | "and": expr.And(), # bool -> bool -> bool
52 | "or": expr.Or(), # bool -> bool -> bool
53 | "not": expr.Not(), # bool -> bool
54 | "xor": expr.Xor(), # bool -> bool -> bool
55 | "@": expr.Cons(), # t -> list(t) -> list(t)
56 | "@@": expr.Append(), # list(t) -> list(t) -> list(t)
57 | "back": expr.Back(), # _
58 | "then": expr.Then(), # t -> t -> t
59 | "head": expr.Head(), # list(t) -> t
60 | "tail": expr.Tail(), # list(t) -> list(t)
61 | "length": expr.Length(), # list(t) -> int
62 | "print": expr.Print(self.output), # t -> t
63 | "here": expr.CallCC(), # ((t -> _) -> t) -> a ?
64 | "exit": expr.Exit(), # _
65 | "spawn": expr.Spawn(), # bool
66 | "error": expr.Error(
67 | lambda val, amb:
68 | lambda: self.repl(ambivalence.Amb(lambda: None))) # _
69 | }
70 | self.env = environment.Environment().extend(
71 | { expr.Symbol(k): v for k, v in operators.items() }
72 | )
73 |
74 | globalenv = expr.Symbol("globalenv")
75 | self.env.non_eval_context_define(globalenv, expr.EnvironmentWrapper(self.env))
76 |
77 | self.type_env = TypeEnvironment().extend()
78 |
79 | for k, v in operators.items():
80 | if v.static_type():
81 | self.type_env[expr.Symbol(k)] = v.type()
82 |
83 | self.type_env[globalenv] = EnvironmentType(self.type_env)
84 |
85 | def trampoline(self, threads: List['types.Promise']):
86 | while len(threads) > 0:
87 | thunk = threads.pop(0)
88 | next = thunk()
89 | if next is not None:
90 | if type(next) is list: # spawn returns a list of two threads
91 | threads += next
92 | else:
93 | threads += [next]
94 |
95 | def read(self, ret: 'types.Continuation', amb: ambivalence.Amb) -> 'types.Promise':
96 | result = self.reader.read()
97 | if result is None:
98 | return None # stop the trampoline
99 | return lambda: ret(result, amb)
100 |
101 | def analyze(self, expr: expr.Expr, ret: 'types.Continuation', amb: ambivalence.Amb) -> 'types.Promise':
102 | try:
103 | expr.analyse(self.type_env)
104 | except PySchemeError as e:
105 | self.error.write(str(e))
106 | return None
107 | return lambda: ret(expr, amb)
108 |
109 | def eval(self, expr: expr.Expr, ret: 'types.Continuation', amb: ambivalence.Amb) -> 'types.Promise':
110 | return lambda: expr.eval(self.env, ret, amb)
111 |
112 | def print(self, exp: expr.Expr, ret: 'types.Continuation', amb: ambivalence.Amb) -> 'types.Promise':
113 | if type(exp) is not expr.Nothing:
114 | self.output.write(str(exp) + "\n")
115 | return lambda: ret(exp, amb)
116 |
117 | def repl(self, amb: ambivalence.Amb) -> 'types.Promise':
118 | def print_continuation(expr, amb: ambivalence.Amb) -> 'types.Promise':
119 | return lambda: self.repl(ambivalence.Amb(lambda: None))
120 |
121 | def eval_continuation(evaluated_expr: expr.Expr, amb: ambivalence.Amb) -> 'types.Promise':
122 | return lambda: self.print(evaluated_expr, print_continuation, amb)
123 |
124 | def analyze_continuation(analyzed_expr: expr.Expr, amb: ambivalence.Amb) -> 'types.Promise':
125 | return lambda: self.eval(analyzed_expr, eval_continuation, amb)
126 |
127 | def read_continuation(read_expr: expr.Expr, amb: ambivalence.Amb) -> 'types.Promise':
128 | return lambda: self.analyze(read_expr, analyze_continuation, amb)
129 |
130 | return lambda: self.read(read_continuation, amb)
131 |
132 | def run(self):
133 | self.trampoline([lambda: self.repl(ambivalence.Amb(lambda: None))])
134 |
135 |
--------------------------------------------------------------------------------
/pyscheme/singleton.py:
--------------------------------------------------------------------------------
1 | # PyScheme lambda language written in Python
2 | #
3 | # Singleton and FlyWeight meta classes
4 | #
5 | # Copyright (C) 2018 Bill Hails
6 | #
7 | # This program is free software: you can redistribute it and/or modify
8 | # it under the terms of the GNU General Public License as published by
9 | # the Free Software Foundation, either version 3 of the License, or
10 | # (at your option) any later version.
11 | #
12 | # This program is distributed in the hope that it will be useful,
13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | # GNU General Public License for more details.
16 | #
17 | # You should have received a copy of the GNU General Public License
18 | # along with this program. If not, see .
19 |
20 | class Singleton(type):
21 | """Singleton type
22 |
23 | Singletons are *not* an anti-pattern, keeping global state in a singleton is the anti-pattern.
24 | """
25 |
26 | _instances = {}
27 |
28 | def __call__(cls, *args, **kwargs):
29 | if cls not in cls._instances:
30 | cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
31 | return cls._instances[cls]
32 |
33 |
34 | class FlyWeight(type):
35 | """FlyWeight type
36 |
37 | The first argument to the constructor of classes using this as a metaclass must be the string
38 | identifier of the flyweight.
39 | """
40 |
41 | _instances = {}
42 |
43 | def __call__(cls, *args, **kwargs):
44 | name = args[0]
45 | if cls not in cls._instances:
46 | cls._instances[cls] = {}
47 | if name not in cls._instances[cls]:
48 | cls._instances[cls][name] = super(FlyWeight, cls).__call__(*args, **kwargs)
49 | return cls._instances[cls][name]
--------------------------------------------------------------------------------
/pyscheme/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/billhails/PyScheme/165b4eb4714bfc51f079c310725dcf302798ac26/pyscheme/tests/__init__.py
--------------------------------------------------------------------------------
/pyscheme/tests/integration/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/billhails/PyScheme/165b4eb4714bfc51f079c310725dcf302798ac26/pyscheme/tests/integration/__init__.py
--------------------------------------------------------------------------------
/pyscheme/tests/integration/base.py:
--------------------------------------------------------------------------------
1 | # PyScheme lambda language written in Python
2 | #
3 | # Copyright (C) 2018 Bill Hails
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License
16 | # along with this program. If not, see .
17 |
18 | from unittest import TestCase
19 | from pyscheme.repl import Repl
20 | import pyscheme.expr as expr
21 | from pyscheme.exceptions import PySchemeRunTimeError
22 | import io
23 | import re
24 |
25 |
26 | class Base(TestCase):
27 |
28 | @classmethod
29 | def eval(cls, text: str, error_file: io.StringIO) -> tuple:
30 | in_file = io.StringIO(text)
31 | out_file = io.StringIO()
32 | expr.Symbol.reset()
33 | repl = Repl(in_file, out_file, error_file)
34 | repl.run()
35 | return out_file.getvalue(), error_file.getvalue()
36 |
37 | def assertEval(self, expected: str, text: str, msg: str=''):
38 | error_file = io.StringIO()
39 | result = self.eval(text, error_file)
40 | self.assertEqualsIgnoringWhitespace(expected, result[0].rstrip(), msg + ' (stderr: "' + result[1] + '")')
41 |
42 | def assertError(self, expected: str, text: str, msg: str=''):
43 | error_file = io.StringIO()
44 | result = self.eval(text, error_file)
45 | self.assertEqual(expected, result[1].rstrip(), msg)
46 |
47 | def assertRunTimeError(self, expected: str, text: str, msg: str=''):
48 | error_file = io.StringIO()
49 | error = ''
50 | try:
51 | result = self.eval(text, error_file)
52 | except PySchemeRunTimeError as e:
53 | error = e.message
54 | self.assertEqual(error, expected)
55 |
56 | def assertEqualsIgnoringWhitespace(self, expected: str, result: str, msg: str=''):
57 | self.assertEqual(self.removeWhitespace(expected), self.removeWhitespace(result), msg)
58 |
59 | def removeWhitespace(self, text: str):
60 | return re.sub('\s+', '', text)
61 |
--------------------------------------------------------------------------------
/pyscheme/tests/integration/test_amb.py:
--------------------------------------------------------------------------------
1 | # PyScheme lambda language written in Python
2 | #
3 | # Copyright (C) 2018 Bill Hails
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License
16 | # along with this program. If not, see .
17 |
18 | from pyscheme.tests.integration.base import Base
19 |
20 |
21 | class TestAmb(Base):
22 | """
23 | "amb" adds chronological backtracking by doing three things:
24 | 1. A binary operator "then" which:
25 | a. evaluates and returns its lhs.
26 | b. if backtracked to once evaluates and returns its rhs.
27 | c. if backtracked to a second time backtracks further.
28 | 2. A keyword "back" that backtracks immediately.
29 | 3. A change to "define" which undoes its definition (side-effect) before backtracking further.
30 | """
31 |
32 | def test_then_back(self):
33 | self.assertEval(
34 | "here\n12",
35 | """
36 | fn (x) {
37 | if (x == 10) {
38 | print("here");
39 | back;
40 | } else {
41 | x;
42 | }
43 | } (10 then 12);
44 | """
45 | )
46 |
47 | def test_then_back_2(self):
48 | self.assertEval(
49 | "4",
50 | """
51 | fn require(x) {
52 | x or back
53 | }
54 |
55 | fn one_of(lst) {
56 | require(length(lst) > 0);
57 | head(lst) then one_of(tail(lst));
58 | }
59 |
60 | {
61 | x = one_of([1, 2, 3, 4, 5]);
62 | require(x == 4);
63 | x;
64 | }
65 | """
66 | )
67 |
68 | def test_barrels_of_fun(self):
69 | """
70 | A wine merchant has six barrels of wine and beer containing 30, 32, 36, 38, 40 and 62 gallons respectively.
71 | Five barrels are filled with wine, and one with beer.
72 | The first customer purchases two barrels of wine.
73 | The second customer purchases twice as much wine as the first customer.
74 | Which barrel contains beer?
75 | """
76 | self.assertEval(
77 | "40",
78 | """
79 | {
80 | fn require(condition) {
81 | condition or back
82 | }
83 |
84 | fn one_of {
85 | ([]) { back }
86 | (h @ t) { h then one_of(t) }
87 | }
88 |
89 | fn member {
90 | (item, []) { false }
91 | (item, item @ t) { true }
92 | (item, _ @ tail) { member(item, tail) }
93 | }
94 |
95 | fn exclude {
96 | (items, []) { [] }
97 | (items, h @ t) {
98 | if (member(h, items)) {
99 | exclude(items, t)
100 | } else {
101 | h @ exclude(items, t)
102 | }
103 | }
104 | }
105 |
106 | fn some_of {
107 | ([]) { back }
108 | (h @ t) { [h] then some_of(t) then h @ some_of(t) }
109 | }
110 |
111 | fn sum {
112 | ([]) { 0 }
113 | (h @ t) { h + sum(t) }
114 | }
115 |
116 | fn barrels_of_fun() {
117 | barrels = [30, 32, 36, 38, 40, 62];
118 | beer = one_of(barrels);
119 | wine = exclude([beer], barrels);
120 | barrel_1 = one_of(wine);
121 | barrel_2 = one_of(exclude([barrel_1], wine));
122 | purchase = some_of(exclude([barrel_1, barrel_2], wine));
123 | require((barrel_1 + barrel_2) * 2 == sum(purchase));
124 | beer;
125 | }
126 |
127 | barrels_of_fun();
128 | }
129 | """
130 | )
131 |
132 | def test_multiple_dwellings(self):
133 | """
134 | Baker, Cooper, Fletcher, Miller, and Smith live on different floors of an
135 | apartment house that contains only five floors. Baker does not live on the
136 | top floor. Cooper does not live on the bottom floor. Fletcher does not
137 | live on either the top or the bottom floor. Miller lives on a higher floor
138 | than does Cooper. Smith does not live on a floor adjacent to Fletcher's.
139 | Fletcher does not live on a floor adjacent to Cooper's.
140 | Where does everyone live?
141 | """
142 | self.assertEval(
143 | '[result[baker, 3], result[cooper, 2], result[fletcher, 4], result[miller, 5], result[smith, 1]]',
144 | '''
145 | {
146 | typedef named_result { result(list(char), int) }
147 |
148 | fn require(condition) {
149 | condition or back
150 | }
151 |
152 | fn one_of { // demonstrate implicit green cut in composite functions
153 | ([]) { back }
154 | (h @ t) { h then one_of(t) }
155 | }
156 |
157 | fn member {
158 | (x, []) { false }
159 | (x, x @ t) { true }
160 | (x, h @ t) { member(x, t) }
161 | }
162 |
163 | fn exclude {
164 | (l, []) { [] }
165 | (l, h @ t) {
166 | if (member(h, l)) {
167 | exclude(l, t)
168 | } else {
169 | h @ exclude(l, t)
170 | }
171 | }
172 | }
173 |
174 | fn abs(n) {
175 | if (n < 0) { 0 - n } else { n }
176 | }
177 |
178 | floors = [1, 2, 3, 4, 5];
179 | baker = one_of(floors);
180 | cooper = one_of(exclude([baker], floors));
181 | fletcher = one_of(exclude([baker, cooper], floors));
182 | miller = one_of(exclude([baker, cooper, fletcher], floors));
183 | smith = head(exclude([baker, cooper, fletcher, miller], floors));
184 | require(baker != 5);
185 | require(cooper != 1);
186 | require(fletcher != 5);
187 | require(fletcher != 1);
188 | require(miller > cooper);
189 | require(abs(smith - fletcher) != 1);
190 | require(abs(fletcher - cooper) != 1);
191 |
192 | [
193 | result("baker", baker),
194 | result("cooper", cooper),
195 | result("fletcher", fletcher),
196 | result("miller", miller),
197 | result("smith", smith)
198 | ];
199 | }
200 | ''',
201 | 'multiple dwellings'
202 | )
203 |
--------------------------------------------------------------------------------
/pyscheme/tests/integration/test_closure.py:
--------------------------------------------------------------------------------
1 | # PyScheme lambda language written in Python
2 | #
3 | # Copyright (C) 2018 Bill Hails
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License
16 | # along with this program. If not, see .
17 |
18 | from pyscheme.tests.integration.base import Base
19 |
20 |
21 | class TestClosure(Base):
22 | def test_simple_lambda(self):
23 | self.assertEval(
24 | "CompositeClosure{[ComponentClosure([x]:{x})]}",
25 | "fn (x) { x; };"
26 | )
27 |
28 | def test_simple_lambda_application(self):
29 | self.assertEval(
30 | "12",
31 | """
32 | fn (a) {
33 | a + a
34 | }(6);
35 | """
36 | )
37 |
38 | def test_lambda_application(self):
39 | self.assertEval(
40 | "10",
41 | """
42 | fn (double) {
43 | double(5)
44 | }(fn (a) { a + a; });
45 | """
46 | )
47 |
48 | def test_closure_capture(self):
49 | self.assertEval(
50 | "7",
51 | """
52 | fn (add2) {
53 | add2(5)
54 | }(
55 | fn (a) {
56 | fn (b) { a + b }
57 | }(2)
58 | );
59 | """
60 | )
61 |
62 | def test_lambda_string(self):
63 | self.assertEval(
64 | "CompositeClosure{[ComponentClosure([a]:{if((a==2)){{12}}else{{fn{ComponentLambda[]:{{14}}}}}})]}",
65 | """
66 | fn (a) {
67 | if (a == 2) {
68 | 12
69 | } else {
70 | fn () {
71 | 14
72 | }
73 | }
74 | };
75 | """
76 | )
77 |
78 | def test_nested_fn(self):
79 | self.assertEval(
80 | '12',
81 | '''
82 | fn add(n) {
83 | fn (m) { n + m }
84 | }
85 | add(10)(2);
86 | '''
87 | )
88 |
89 | def test_internal_fn(self):
90 | self.assertEval(
91 | '11',
92 | '''
93 | fn a (n) {
94 | fn b(o) {
95 | n + o
96 | }
97 | b(5)
98 | }
99 | a(6);
100 | '''
101 | )
102 |
--------------------------------------------------------------------------------
/pyscheme/tests/integration/test_composite.py:
--------------------------------------------------------------------------------
1 | # PyScheme lambda language written in Python
2 | #
3 | # Copyright (C) 2018 Bill Hails
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License
16 | # along with this program. If not, see .
17 |
18 |
19 | from pyscheme.tests.integration.base import Base
20 |
21 |
22 | class TestComposite(Base):
23 |
24 | def test_composite_1(self):
25 | self.assertEval(
26 | 'branch[branch[branch[leaf, 1, leaf], 2, leaf], 3, branch[leaf, 4, leaf]]',
27 | '''
28 | typedef tree(#t) { branch(tree(#t), #t, tree(#t)) | leaf }
29 |
30 | fn insert {
31 | (t, leaf) { branch(leaf, t, leaf) }
32 | (t, branch(left, u, right)) {
33 | if (t < u) {
34 | branch(insert(t, left), u, right)
35 | } else if (t == u) {
36 | branch(left, u, right)
37 | } else {
38 | branch(left, u, insert(t, right))
39 | }
40 | }
41 | }
42 |
43 | insert(1, insert(3, insert(2, insert(4, insert(3, leaf)))));
44 | ''',
45 | 'btrees'
46 | )
47 |
48 | def test_composite_2(self):
49 | self.assertEval(
50 | '[1, 2, 3, 4]',
51 | '''
52 | typedef tree(#t) { branch(tree(#t), #t, tree(#t)) | leaf }
53 |
54 | fn insert {
55 | (t, leaf) { branch(leaf, t, leaf) }
56 | (t, x = branch(left, u, right)) {
57 | if (t < u) {
58 | branch(insert(t, left), u, right)
59 | } else if (t == u) {
60 | x
61 | } else {
62 | branch(left, u, insert(t, right))
63 | }
64 | }
65 | }
66 |
67 | fn flatten {
68 | (leaf) { [] }
69 | (branch(l, u, r)) { flatten(l) @@ [u] @@ flatten(r) }
70 | }
71 |
72 | flatten(insert(1, insert(3, insert(2, insert(4, insert(3, leaf))))));
73 | ''',
74 | 'btrees'
75 | )
76 |
77 | def test_composite_3(self):
78 | self.assertEval(
79 | 'some[there]',
80 | '''
81 | {
82 | typedef tree(#t) { branch(tree(#t), int, #t, tree(#t)) | leaf }
83 |
84 | fn insert {
85 | (index, val, leaf) { branch(leaf, index, val, leaf) }
86 | (index, val, branch(left, j, w, right)) {
87 | if (index < j) {
88 | branch(insert(index, val, left), j, w, right)
89 | } else if (index == j) {
90 | branch(left, j, val, right)
91 | } else {
92 | branch(left, j, w, insert(index, val, right))
93 | }
94 | }
95 | }
96 |
97 | typedef t_or_fail(#t) {some(#t) | fail}
98 |
99 | fn retrieve {
100 | (index, leaf) { fail }
101 | (index, branch(left, j, val, right)) {
102 | if (index < j) {
103 | retrieve(index, left)
104 | } else if (index > j) {
105 | retrieve(index, right)
106 | } else {
107 | some(val)
108 | }
109 | }
110 | }
111 |
112 | retrieve(3,
113 | insert(1, "hi",
114 | insert(3, "there",
115 | insert(2, "how",
116 | insert(4, "are",
117 | insert(3, "you", leaf))))));
118 | }
119 | ''',
120 | 'btrees'
121 | )
122 |
123 | def test_composite_4(self):
124 | self.assertEval(
125 | 'branch[branch[leaf, true, leaf], true, branch[leaf, true, leaf]]',
126 | '''
127 | {
128 | typedef tree(#t) { branch(tree(#t), #t, tree(#t)) | leaf }
129 |
130 | fn complete {
131 | (v, 0) { leaf }
132 | (v, n) { branch(complete(v, n-1), v, complete(v, n-1)) }
133 | }
134 |
135 | complete(true, 2);
136 | }
137 | '''
138 | )
139 |
140 | def test_composite_44(self):
141 | self.assertEval(
142 | "branch[leaf, 1, branch[leaf, 2, leaf]]",
143 | """
144 | {
145 | typedef tree(#t) { branch(tree(#t), #t, tree(#t)) | leaf }
146 |
147 | fn insert {
148 | (t, leaf) { branch(leaf, t, leaf) }
149 | (t, branch(left, u, right)) {
150 | if (t < u) {
151 | branch(insert(t, left), u, right)
152 | } else {
153 | branch(left, u, insert(t, right))
154 | }
155 | }
156 | }
157 |
158 | i1 = insert(1, leaf);
159 | i2 = insert(2);
160 |
161 | i2(i1);
162 | }
163 | """,
164 | "composites can be curried, but only within a single expression"
165 | )
166 |
167 | def test_wildcard_list(self):
168 | self.assertEval(
169 | 'true',
170 | '''
171 | {
172 | fn member {
173 | (s, []) { false }
174 | (s, s @ _) { true }
175 | (s, _ @ t) { member(s, t) }
176 | }
177 |
178 | member(2, [1, 2, 3]);
179 | }
180 | '''
181 | )
182 |
--------------------------------------------------------------------------------
/pyscheme/tests/integration/test_currying.py:
--------------------------------------------------------------------------------
1 | # PyScheme lambda language written in Python
2 | #
3 | # Copyright (C) 2018 Bill Hails
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License
16 | # along with this program. If not, see .
17 |
18 |
19 | from pyscheme.tests.integration.base import Base
20 |
21 |
22 | class TestCurrying(Base):
23 | def test_currying(self):
24 | self.assertEval(
25 | '10',
26 | '''
27 | fn add (x, y) {
28 | x + y
29 | }
30 |
31 | add2 = add(2);
32 |
33 | add2(8);
34 | '''
35 | )
36 |
37 | def test_over_application(self):
38 | self.assertEval(
39 | '10',
40 | '''
41 | fn adder(x) {
42 | fn (y) { x + y }
43 | }
44 | adder(5, 5);
45 | '''
46 | )
47 |
48 | def test_map_curried(self):
49 | self.assertEval(
50 | '[2, 3, 4, 5]',
51 | '''
52 | {
53 | fn map(fun, lst) {
54 | if (lst == []) {
55 | []
56 | } else {
57 | fun(head(lst)) @ map(fun, tail(lst))
58 | }
59 | }
60 |
61 | fn add(x, y) {
62 | x + y
63 | }
64 |
65 | map(add(1), [1, 2, 3, 4]);
66 | }
67 | '''
68 | )
69 |
70 | def test_map_curried_binop_1(self):
71 | self.assertEval(
72 | '[3, 4, 5, 6]',
73 | '''
74 | fn map {
75 | (f, []) { [] }
76 | (f, h @ t) { f(h) @ map(f, t) }
77 | }
78 |
79 | map(1 + 1 +, [1, 2, 3, 4]);
80 | '''
81 | )
82 |
83 | def test_map_curried_binop_2(self):
84 | self.assertEval(
85 | '[6, 7, 8, 9]',
86 | '''
87 | fn map {
88 | (f, []) { [] }
89 | (f, h @ t) { f(h) @ map(f, t) }
90 | }
91 |
92 | map(2 * 2 + 1 +, [1, 2, 3, 4]);
93 | '''
94 | )
95 |
96 | def test_map_curried_binop_3(self):
97 | self.assertEval(
98 | '[[2, 1], [2, 2], [2, 3], [2, 4]]',
99 | '''
100 | fn map {
101 | (f, []) { [] }
102 | (f, h @ t) { f(h) @ map(f, t) }
103 | }
104 |
105 | map(2 @, [[1], [2], [3], [4]]);
106 | '''
107 | )
108 |
109 | def test_map_curried_binop_4(self):
110 | self.assertError(
111 | 'PySchemeTypeError: (list(int) -> list(int)) != list(int)',
112 | '''
113 | fn map {
114 | (f, []) { [] }
115 | (f, h @ t) { f(h) @ map(f, t) }
116 | }
117 |
118 | map(1 @ 2 @, [[1], [2], [3], [4]]);
119 | '''
120 | )
121 |
122 | def test_zero_arguments(self):
123 | self.assertEval(
124 | '12\n12',
125 | '''
126 | fn x() { // type of x is int, not (() -> int)
127 | 12
128 | }
129 |
130 | 0 + x;
131 |
132 | 0 + x(); // also works, x() == x
133 | '''
134 | )
135 |
136 | def test_noop_apply(self):
137 | self.assertEval(
138 | '12',
139 | '''
140 | 12();
141 | ''',
142 | 'application with no args is a no-op'
143 | )
144 |
--------------------------------------------------------------------------------
/pyscheme/tests/integration/test_data.py:
--------------------------------------------------------------------------------
1 | # PyScheme lambda language written in Python
2 | #
3 | # Copyright (C) 2018 Bill Hails
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License
16 | # along with this program. If not, see .
17 |
18 | from pyscheme.tests.integration.base import Base
19 |
20 |
21 | class TestData(Base):
22 | def test_list(self):
23 | self.assertEval(
24 | "[5, 6]",
25 | "[2 + 3, 3 + 3];"
26 | )
27 |
28 |
29 | class TestOperations(Base):
30 | def test_addition(self):
31 | self.assertEval("10", "5 + 5;")
32 |
33 | def test_subtraction(self):
34 | self.assertEval("3", "10 - 7;")
35 |
36 | def test_multiplication(self):
37 | self.assertEval("70", "10 * 7;")
38 |
39 | def test_division(self):
40 | self.assertEval("3", "10 / 3;")
41 | self.assertEval("2", "10 / 2 / 2;")
42 | self.assertEval("10", "10 / (2 / 2);")
43 |
44 | def test_modulus(self):
45 | self.assertEval("1", "10 % 3;")
46 |
47 | def test_equality(self):
48 | self.assertEval("true", "5 == 5;")
49 |
50 | def test_pow(self):
51 | self.assertEval("64", "(2 ** 2) ** 3;")
52 | self.assertEval("256", "2 ** 2 ** 3;")
53 |
54 | def test_inequality(self):
55 | self.assertEval("false", "5 == 6;")
56 | self.assertError("PySchemeTypeError: bool != int", "5 == unknown;")
57 |
58 | def test_list_equality(self):
59 | self.assertEval("true", "[5, 5] == [5, 5];")
60 |
61 | def test_list_inequality(self):
62 | self.assertEval("false", "[5, 5] == [5, 6];")
63 | self.assertEval("false", "[5, 6] == [5];")
64 |
65 | def test_and(self):
66 | self.assertEval("true", "true and true;")
67 | self.assertEval("false", "true and false;")
68 | self.assertEval("unknown", "true and unknown;")
69 | self.assertEval("false", "false and true;")
70 | self.assertEval("false", "false and false;")
71 | self.assertEval("false", "false and unknown;")
72 | self.assertEval("unknown", "unknown and true;")
73 | self.assertEval("false", "unknown and false;")
74 | self.assertEval("unknown", "unknown and unknown;")
75 |
76 | def test_or(self):
77 | self.assertEval("true", "true or true;")
78 | self.assertEval("true", "true or false;")
79 | self.assertEval("true", "true or unknown;")
80 | self.assertEval("true", "false or true;")
81 | self.assertEval("false", "false or false;")
82 | self.assertEval("unknown", "false or unknown;")
83 | self.assertEval("true", "unknown or true;")
84 | self.assertEval("unknown", "unknown or false;")
85 | self.assertEval("unknown", "unknown or unknown;")
86 |
87 | def test_xor(self):
88 | self.assertEval("false", "true xor true;")
89 | self.assertEval("true", "true xor false;")
90 | self.assertEval("unknown", "true xor unknown;")
91 | self.assertEval("true", "false xor true;")
92 | self.assertEval("false", "false xor false;")
93 | self.assertEval("unknown", "false xor unknown;")
94 | self.assertEval("unknown", "unknown xor true;")
95 | self.assertEval("unknown", "unknown xor false;")
96 | self.assertEval("unknown", "unknown xor unknown;")
97 |
98 | def test_not(self):
99 | self.assertEval("false", "not true;")
100 | self.assertEval("true", "not false;")
101 | self.assertEval("unknown", "not unknown;")
102 |
103 | def test_boolean_equality(self):
104 | for args in [
105 | ["true", "true", "true"],
106 | ["true", "false", "false"],
107 | ["true", "unknown", "false"],
108 | ["false", "true", "false"],
109 | ["false", "false", "true"],
110 | ["false", "unknown", "false"],
111 | ["unknown", "true", "false"],
112 | ["unknown", "false", "false"],
113 | ["unknown", "unknown", "true"],
114 | ]:
115 | with self.subTest(args=args):
116 | self.assertEval(args[2], args[0] + " == " + args[1] + ";")
117 |
118 | def test_cons(self):
119 | self.assertEval(
120 | "[8, 10, 12]",
121 | "8 @ 10 @ [12];"
122 | )
123 |
124 | def test_cons2(self):
125 | self.assertEval(
126 | "[8, 10, 12]",
127 | "8 @ 10 @ 12 @ [];"
128 | )
129 |
130 | def test_append(self):
131 | self.assertEval(
132 | "[8, 10, 12]",
133 | "[8] @@ [10] @@ [12];"
134 | )
135 |
136 | def test_append2(self):
137 | self.assertEval(
138 | "[8, 10, 12]",
139 | "[8, 10] @@ [12] @@ [];"
140 | )
141 |
142 | def test_head(self):
143 | self.assertEval(
144 | "5",
145 | "head([5, 5]);"
146 | )
147 |
148 | def test_tail(self):
149 | self.assertEval(
150 | "[5]",
151 | "tail([5, 5]);"
152 | )
153 |
154 | def test_tail2(self):
155 | self.assertEval(
156 | "[]",
157 | "tail([5]);"
158 | )
159 |
160 | def test_tail3(self):
161 | self.assertRunTimeError(
162 | "cannot take the cdr of null",
163 | "tail([]);"
164 | )
165 |
166 | def test_length(self):
167 | self.assertEval(
168 | "2",
169 | "length([5, 5]);"
170 | )
171 |
172 |
173 | class TestConditional(Base):
174 | def test_if_true(self):
175 | self.assertEval(
176 | "10",
177 | """
178 | if (5 == 5) {
179 | 10;
180 | } else {
181 | 12;
182 | }
183 | """
184 | )
185 |
186 | def test_if_false(self):
187 | self.assertEval(
188 | "12",
189 | """
190 | if (5 == 6) {
191 | 10;
192 | } else {
193 | 12;
194 | }
195 | """
196 | )
197 |
198 | def test_lt(self):
199 | self.assertEval('true', '10 < 12;')
200 |
201 | def test_gt(self):
202 | self.assertEval('false', '10 > 12;')
203 |
204 | def test_le(self):
205 | self.assertEval('true', '10 <= 12;')
206 |
207 | def test_ge(self):
208 | self.assertEval('false', '10 >= 12;')
209 |
210 | def test_ne(self):
211 | self.assertEval('true', '10 != 12;')
212 |
213 | def test_list_comparison_1(self):
214 | self.assertEval('true', '[1, 2, 3] < [1, 2, 4];')
215 |
216 | def test_list_comparison_2(self):
217 | self.assertEval('true', '[1, 2, 3] < [1, 2, 3, 4];')
218 |
219 | def test_list_comparison_3(self):
220 | self.assertEval('true', '[1, 2, 3, 4] > [1, 2, 3];')
221 |
222 | def test_boolean_comparison(self):
223 | self.assertEval('true', 'false < unknown;')
224 |
225 | def test_boolean_comparison_2(self):
226 | self.assertEval('true', 'unknown < true;')
227 |
228 | def test_boolean_comparison_3(self):
229 | self.assertEval('true', 'false < true;')
230 |
231 | def test_string_comparison_1(self):
232 | self.assertEval('true', '"hello" > "goodbye";')
233 |
234 | def test_string_comparison_2(self):
235 | self.assertEval('false', '"hello" < "goodbye";')
236 |
237 | def test_string_comparison_3(self):
238 | self.assertEval('false', '"hello" <= "goodbye";')
239 |
240 | def test_string_comparison_4(self):
241 | self.assertEval('true', '"hello" == "hello";')
242 |
243 | def test_nothing(self):
244 | self.assertEval(
245 | '',
246 | 'nothing;',
247 | 'repl does not print nothing'
248 | )
249 |
250 | def test_nothing_comparison(self):
251 | self.assertEval(
252 | 'true',
253 | 'nothing == nothing;',
254 | 'nothing is nothing'
255 | )
256 |
--------------------------------------------------------------------------------
/pyscheme/tests/integration/test_define.py:
--------------------------------------------------------------------------------
1 | # PyScheme lambda language written in Python
2 | #
3 | # Copyright (C) 2018 Bill Hails
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License
16 | # along with this program. If not, see .
17 |
18 | from pyscheme.tests.integration.base import Base
19 |
20 |
21 | class TestDefine(Base):
22 | def test_define(self):
23 | self.assertEval(
24 | "10",
25 | """
26 | fn () {
27 | t = 10;
28 | t
29 | }();
30 | """
31 | )
32 |
33 | def test_define_fn(self):
34 | self.assertEval(
35 | "4",
36 | """
37 | double = fn (x) { x + x };
38 | double(2);
39 | """
40 | )
41 |
42 | def test_sugar_define_fn(self):
43 | self.assertEval(
44 | "4",
45 | """
46 | fn double(x) { x * 2 }
47 | double(2);
48 | """
49 | )
50 |
--------------------------------------------------------------------------------
/pyscheme/tests/integration/test_env.py:
--------------------------------------------------------------------------------
1 | # PyScheme lambda language written in Python
2 | #
3 | # Copyright (C) 2018 Bill Hails
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License
16 | # along with this program. If not, see .
17 |
18 |
19 | from pyscheme.tests.integration.base import Base
20 |
21 |
22 | class TestEnv(Base):
23 |
24 | def test_anonymous_env(self):
25 | self.assertEval(
26 | "5",
27 | '''
28 | env e { // need a prototype env
29 | x = 0;
30 | y = 0;
31 | }
32 | fn (a:e) {
33 | a.x + a.y
34 | }(
35 | env {
36 | x = 2;
37 | y = 3
38 | }
39 | );
40 | ''',
41 | 'environments are first class'
42 | )
43 |
44 | def test_anonymous_env_2(self):
45 | self.assertEval(
46 | '11',
47 | """
48 | env {
49 | x = 11
50 | }.x;
51 | """,
52 | 'enviroments can be declared and accessed anonymously'
53 | )
54 |
55 | def test_named_env(self):
56 | self.assertEval(
57 | "11",
58 | """
59 | env x {
60 | fn double(x) { x * 2 }
61 | fn add1(x) { x + 1 }
62 | }
63 |
64 | x.add1(x.double(5));
65 | """,
66 | 'environments are first class'
67 | )
68 |
69 | def test_nested_env(self):
70 | self.assertEval(
71 | '10',
72 | '''
73 | env a {
74 | fn bar() { 10 }
75 | env b {
76 | fn foo() {
77 | bar()
78 | }
79 | }
80 | }
81 |
82 | a.b.foo();
83 | ''',
84 | "code can see definitions in enclosing environments"
85 | )
86 |
87 | def test_not_found(self):
88 | self.assertError(
89 | 'TypeSymbolNotFoundError: no_such_symbol',
90 | '''
91 | {
92 | no_such_symbol
93 | }
94 | '''
95 | )
96 |
97 | def test_redefine_error(self):
98 | self.assertError(
99 | 'TypeSymbolAlreadyDefinedError: x',
100 | '''
101 | x = 10;
102 | x = 10;
103 | '''
104 | )
105 |
106 | def test_redefine_scope_ok(self):
107 | self.assertEval(
108 | '10',
109 | '''
110 | {
111 | x = 10;
112 | {
113 | x = 12;
114 | }
115 | x;
116 | }
117 | '''
118 | )
119 |
120 | def test_typedef_in_env(self):
121 | self.assertEval(
122 | 'pair[2, pair[3, null]]',
123 | '''
124 | env e {
125 | typedef lst(#t) {pair(#t, lst(#t)) | null }
126 |
127 | fn map {
128 | (f, null) { null }
129 | (f, pair(h, t)) { pair(f(h), map(f, t)) }
130 | }
131 | }
132 |
133 | fn test(e:e) {
134 | l = e.pair(1, e.pair(2, e.null));
135 | e.map(1 +, l)
136 | }
137 |
138 | test(e);
139 | '''
140 | )
141 |
142 | def test_typedef_in_env_2(self):
143 | self.assertEval(
144 | '''
145 | CompositeClosure{
146 | [
147 | ComponentClosure(
148 | [e:e]:{
149 | definel=e.pair[1,e.pair[2,e.null]];
150 | e.map[fn{ComponentLambda[#c]:{{(1+#c)}}},l]
151 | }
152 | )
153 | ]
154 | }
155 | ''',
156 | '''
157 | env e {
158 | typedef lst(#t) {pair(#t, lst(#t)) | null }
159 |
160 | fn map {
161 | (f, null) { null }
162 | (f, pair(h, t)) { pair(f(h), map(f, t)) }
163 | }
164 | }
165 |
166 | fn test(e:e) {
167 | l = e.pair(1, e.pair(2, e.null));
168 | e.map(1 +, l)
169 | }
170 |
171 | test;
172 | '''
173 | )
174 |
175 | def test_typedef_in_env_3(self):
176 | self.assertEval(
177 | 'Closure([#a, #b]: (TupleConstructor pair)[#a, #b])',
178 | '''
179 | env e {
180 | typedef lst(#t) {pair(#t, lst(#t)) | null }
181 | }
182 |
183 | e.pair;
184 | '''
185 | )
186 |
187 | def test_extended_env_1(self):
188 | self.assertEval(
189 | '10',
190 | '''
191 | {
192 | env a {
193 | fn bar() { 10 }
194 | env b {
195 | fn foo() {
196 | bar()
197 | }
198 | }
199 | }
200 |
201 | env d extends a.b {
202 | fn baz() { foo() }
203 | }
204 |
205 | d.foo();
206 | }
207 | ''',
208 | "environments extending others can see their contents"
209 | )
210 |
211 | def test_extended_env_2(self):
212 | self.assertError(
213 | 'PySchemeTypeError: bool != int',
214 | '''
215 | {
216 | env a {
217 | fn bar(n) { 10 + n }
218 | env b {
219 | fn foo(n) {
220 | bar(n)
221 | }
222 | }
223 | }
224 |
225 | env d extends a.b {
226 | fn baz(n) { foo(n) }
227 | }
228 |
229 | d.foo(true);
230 | }
231 | ''',
232 | "environments extending others can typecheck their contents"
233 | )
234 |
235 | def test_extended_env_3(self):
236 | self.assertEval(
237 | '1',
238 | '''
239 | {
240 | fn head(lst) {
241 | // could just say `globalenv.head(lst)` for the same effect
242 | env extends globalenv {}.head(lst)
243 | }
244 |
245 | head([1, 2, 3]);
246 | }
247 | ''',
248 | "environments extending others can see their contents"
249 | )
250 |
251 |
--------------------------------------------------------------------------------
/pyscheme/tests/integration/test_error.py:
--------------------------------------------------------------------------------
1 | # PyScheme lambda language written in Python
2 | #
3 | # Copyright (C) 2018 Bill Hails
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License
16 | # along with this program. If not, see .
17 |
18 | from pyscheme.tests.integration.base import Base
19 |
20 |
21 | class TestError(Base):
22 | def test_error(self):
23 | self.assertEval(
24 | "message 1\nmessage 2",
25 | '''
26 | fn test_error(message) { error(message) }
27 | test_error("message 1");
28 | test_error("message 2");
29 | '''
30 | )
31 |
--------------------------------------------------------------------------------
/pyscheme/tests/integration/test_exit.py:
--------------------------------------------------------------------------------
1 | # PyScheme lambda language written in Python
2 | #
3 | # Copyright (C) 2018 Bill Hails
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License
16 | # along with this program. If not, see .
17 |
18 | from pyscheme.tests.integration.base import Base
19 |
20 |
21 | class TestExit(Base):
22 | def test_exit(self):
23 | self.assertEval(
24 | "",
25 | """
26 | fn () {
27 | exit();
28 | 1;
29 | }();
30 | """
31 | )
32 |
33 |
--------------------------------------------------------------------------------
/pyscheme/tests/integration/test_here.py:
--------------------------------------------------------------------------------
1 | # PyScheme lambda language written in Python
2 | #
3 | # Copyright (C) 2018 Bill Hails
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License
16 | # along with this program. If not, see .
17 |
18 | from pyscheme.tests.integration.base import Base
19 |
20 |
21 | class TestHere(Base):
22 | """
23 | "here" is really "call-with-current-continuation"
24 | """
25 | def test_here(self):
26 | self.assertEval(
27 | "1",
28 | """
29 | here(
30 | fn (ret) {
31 | if (ret(1)) {
32 | 2
33 | } else {
34 | 3
35 | }
36 | }
37 | );
38 | """
39 | )
40 |
41 |
--------------------------------------------------------------------------------
/pyscheme/tests/integration/test_inference.py:
--------------------------------------------------------------------------------
1 | # PyScheme lambda language written in Python
2 | #
3 | # Copyright (C) 2018 Bill Hails
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License
16 | # along with this program. If not, see .
17 |
18 |
19 | from pyscheme.tests.integration.base import Base
20 |
21 |
22 | class TestInference(Base):
23 |
24 | def test_inference_1(self):
25 | self.assertError(
26 | 'PySchemeTypeError: bool != int',
27 | '''
28 | 1 == true;
29 | '''
30 | )
31 |
32 | def test_inference_2(self):
33 | self.assertError(
34 | 'PySchemeTypeError: bool != int',
35 | '''
36 | fn (x) {
37 | x * 2
38 | }(true);
39 | '''
40 | )
41 |
42 | def test_inference_3(self):
43 | self.assertEval(
44 | "3\n3",
45 | '''
46 | fn len(l) {
47 | if (l == []) {
48 | 0
49 | } else {
50 | 1 + len(tail(l))
51 | }
52 | }
53 |
54 | len([1, 2, 3]);
55 |
56 | len([true, true, false]);
57 | '''
58 | )
59 |
60 | def test_inference_4(self):
61 | self.assertError(
62 | "PySchemeTypeError: int != bool",
63 | '''
64 | [1, 2, false];
65 | '''
66 | )
67 |
68 | def test_inference_5(self):
69 | self.assertError(
70 | "PySchemeTypeError: list(char) != int",
71 | '''
72 | (0 @ []) @@ ("hello" @ []);
73 | '''
74 | )
75 |
76 | def test_inference_6(self):
77 | self.assertError(
78 | "PySchemeInferenceError: recursive unification",
79 | '''
80 | fn (f) { f(f) };
81 | '''
82 | )
83 |
84 | def test_inference_7(self):
85 | self.assertEval(
86 | "5",
87 | '''
88 | fn g (f) { 5 }
89 |
90 | g(g);
91 | '''
92 | )
93 |
94 | def test_inference_8(self):
95 | self.assertEval(
96 | "CompositeClosure{[ComponentClosure([f]:{fn{ComponentLambda[g]:{{fn{ComponentLambda[arg]:{{g[f[arg]]}}}}}}})]}",
97 | '''
98 | fn (f) { fn (g) { fn (arg) { g(f(arg)) } } };
99 | ''',
100 | 'function composition'
101 | )
102 |
103 | def test_inference_101(self):
104 | self.assertError(
105 | "PySchemeTypeError: bool != int",
106 | """
107 | if (length([])) {
108 | true
109 | } else {
110 | false
111 | }
112 | """,
113 | "'if' requires a boolean"
114 | )
115 |
116 | def test_inference_115(self):
117 | self.assertEval(
118 | "9",
119 | """
120 | env e {
121 | fn sum(x, y) { x + y }
122 | }
123 | fn g(x:e) {
124 | x.sum(4, 5)
125 | }
126 | g(e);
127 | """,
128 | ""
129 | )
130 |
131 | def test_inference_116(self):
132 | self.assertError(
133 | "PySchemeTypeError: bool != int",
134 | """
135 | env e {
136 | fn sum(x, y) { x + y }
137 | }
138 | fn g(x:e) {
139 | x.sum(4, true)
140 | }
141 | g(e);
142 | """,
143 | ""
144 | )
145 |
146 | def test_inference_130(self):
147 | self.assertError(
148 | "TypeSymbolNotFoundError: nosumhere",
149 | """
150 | env e1 {
151 | fn sum(x, y) { x + y }
152 | }
153 | env e2 {
154 | fn nosumhere() { false }
155 | }
156 | fn g(e3:e1) {
157 | e3.sum(4, 5)
158 | }
159 | g(e2);
160 | """,
161 | "env type checking"
162 | )
163 |
164 | def test_inference_163(self):
165 | self.assertError(
166 | "MissingPrototypeError: a",
167 | """
168 | fn (a) {
169 | a.x + a.y
170 | }(
171 | env {
172 | x = 2;
173 | y = 3
174 | }
175 | );
176 | """,
177 | ""
178 | )
179 |
180 | def test_inference_179(self):
181 | self.assertEval(
182 | "true",
183 | """
184 | typedef named_list(#t) { named(list(char), list(#t)) }
185 | fn (x) {
186 | x == named("hello", [1, 2, 3])
187 | }(named("hello", [1, 2, 3]));
188 | """,
189 | ""
190 | )
191 |
192 | def test_inference_191(self):
193 | self.assertEval(
194 | "false",
195 | """
196 | typedef named_list(#t) { named(list(char), list(#t)) }
197 | fn (x) {
198 | x == named("hello", [1, 2, 3])
199 | }(named("goodbye", [1, 2, 3]));
200 | """,
201 | ""
202 | )
203 |
204 | def test_inference_204(self):
205 | self.assertError(
206 | "PySchemeTypeError: bool != list(char)",
207 | """
208 | typedef named_list(#t) { named(list(char), list(#t)) }
209 | fn (x) {
210 | x == named("hello", [1, 2, 3])
211 | }(named(false, [1, 2, 3]));
212 | """,
213 | ""
214 | )
215 |
216 | def test_inference_216(self):
217 | self.assertError(
218 | "PySchemeTypeError: bool != int",
219 | """
220 | typedef named_list(#t) { named(list(char), list(#t)) }
221 | fn (x) {
222 | x == named("hello", [1, 2, 3])
223 | }(named("hello", [true, false]));
224 | """,
225 | ""
226 | )
227 |
228 | def test_inference_228(self):
229 | self.assertEval(
230 | "24",
231 | """
232 | fn factorial {
233 | (0) { 1 }
234 | (n) { n * factorial(n - 1) }
235 | }
236 | factorial(4);
237 | """,
238 | ""
239 | )
240 |
241 | def test_inference_241(self):
242 | self.assertEval(
243 | 'pr[2, pr[4, pr[6, nll]]]',
244 | '''
245 | {
246 | typedef lst(#t) { pr(#t, lst(#t)) | nll }
247 |
248 | fn map {
249 | (f, nll) { nll }
250 | (f, pr(h, t)) { pr(f(h), map(f, t)) }
251 | }
252 |
253 | fn mul(x, y) {
254 | x * y
255 | }
256 |
257 | map(mul(2), pr(1, pr(2, pr(3, nll))));
258 | }
259 | '''
260 | )
261 |
262 | def test_inference_262(self):
263 | self.assertEval(
264 | '[2, 4, 6, 8, 10]',
265 | '''
266 | {
267 | fn map {
268 | (f, []) { [] }
269 | (f, h @ t) { f(h) @ map(f, t) }
270 | }
271 |
272 | fn mul(x, y) {
273 | x * y
274 | }
275 |
276 | map(mul(2), [1, 2, 3, 4, 5]);
277 | }
278 | '''
279 | )
280 |
--------------------------------------------------------------------------------
/pyscheme/tests/integration/test_lists.py:
--------------------------------------------------------------------------------
1 | # PyScheme lambda language written in Python
2 | #
3 | # Copyright (C) 2018 Bill Hails
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License
16 | # along with this program. If not, see .
17 |
18 | from pyscheme.tests.integration.base import Base
19 |
20 |
21 | class TestLists(Base):
22 | """
23 | tests exercising various functions over the built-in lists
24 | """
25 | def test_homemade_append(self):
26 | self.assertEval(
27 | '[1, 2, 3, 4, 5, 6]',
28 | '''
29 | fn append {
30 | ([], other) { other }
31 | (h @ t, other) { h @ append(t, other) }
32 | }
33 |
34 | append([1, 2, 3], [4, 5, 6]);
35 | '''
36 | )
37 |
38 | def test_replace(self):
39 | self.assertEval(
40 | '[1, 2, 11, 4, 5]',
41 | '''
42 | fn replace {
43 | ([], i, y) { error("subscript") }
44 | (h @ t, 0, y) { y @ t }
45 | (h @ t, n, y) { h @ replace(t, n - 1, y) }
46 | }
47 |
48 | replace([1, 2, 3, 4, 5], 2, 11);
49 | '''
50 | )
51 |
52 | def test_suffixes(self):
53 | self.assertEval(
54 | '[[1, 2, 3, 4], [2, 3, 4], [3, 4], [4], []]',
55 | '''
56 | fn suffixes {
57 | ([]) { [[]] }
58 | (h @ t) { (h @ t) @ suffixes(t) }
59 | }
60 |
61 | suffixes([1, 2, 3, 4]);
62 | '''
63 | )
64 |
65 | def test_last(self):
66 | self.assertEval(
67 | '4',
68 | '''
69 | fn last {
70 | ([]) { error("empty list") }
71 | (h @ []) { h }
72 | (h @ t) { last(t) }
73 | }
74 |
75 | last([1, 2, 3, 4]);
76 | '''
77 | )
78 |
79 | def test_reverse(self):
80 | self.assertEval(
81 | '[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]',
82 | '''
83 | fn reverse(lst) {
84 | fn rev {
85 | ([], acc) { acc }
86 | (h @ t, acc) { rev(t, h @ acc) }
87 | }
88 | rev(lst, []);
89 | }
90 |
91 | reverse([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
92 | '''
93 | )
94 |
--------------------------------------------------------------------------------
/pyscheme/tests/integration/test_load.py:
--------------------------------------------------------------------------------
1 | # PyScheme lambda language written in Python
2 | #
3 | # Copyright (C) 2018 Bill Hails
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License
16 | # along with this program. If not, see .
17 |
18 |
19 | from pyscheme.tests.integration.base import Base
20 |
21 |
22 | class TestTestLoad(Base):
23 | """
24 | The files being loaded are under PyScheme/data
25 | """
26 | def test_test_load_1(self):
27 | self.assertEval(
28 | '[2, 3, 3, 4, 5, 6, 8]',
29 | '''
30 | load utils.sort as sort;
31 |
32 | unsorted = [5, 4, 6, 2, 3, 3, 8];
33 |
34 | sort.qsort(unsorted);
35 | '''
36 | )
37 |
38 | def test_test_load_2(self):
39 | self.assertEval(
40 | '[3, 4, 4, 5, 6, 7, 9]',
41 | '''
42 | {
43 | load utils.sort;
44 | load utils.lists as lists;
45 |
46 | unsorted = [5, 4, 6, 2, 3, 3, 8];
47 |
48 | lists.map(1+, utils.sort.qsort(unsorted));
49 | }
50 | '''
51 | )
52 |
53 | def test_test_load_3(self):
54 | self.assertEval(
55 | '10',
56 | '''
57 | {
58 | load utils.lists as lists;
59 |
60 | fn add(x, y) { x + y }
61 |
62 | lists.reduce(add, 0, [1, 2, 3, 4]);
63 | }
64 | '''
65 | )
66 |
--------------------------------------------------------------------------------
/pyscheme/tests/integration/test_metacircular.py:
--------------------------------------------------------------------------------
1 | # PyScheme lambda language written in Python
2 | #
3 | # Copyright (C) 2018 Bill Hails
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License
16 | # along with this program. If not, see .
17 |
18 |
19 | from pyscheme.tests.integration.base import Base
20 |
21 |
22 | class TestMetacircular(Base):
23 | """
24 | tentative first steps
25 | """
26 |
27 | def test_metacircular_evaluator(self):
28 | self.assertEval(
29 | 'number[5]',
30 | '''
31 | {
32 | // very simple environment
33 | typedef environment { frame(string, expression, environment) | root }
34 |
35 | // very simple AST
36 | typedef expression {
37 | addition(expression, expression) |
38 | subtraction(expression, expression) |
39 | multiplication(expression, expression) |
40 | division(expression, expression) |
41 | number(int) |
42 | symbol(string) |
43 | conditional(expression, expression, expression) |
44 | lambda(expression, expression) |
45 | closure(expression, environment) |
46 | application(expression, expression)
47 | }
48 |
49 | // an interpreter
50 | fn eval {
51 | (addition(l, r), e) { add(eval(l, e), eval(r, e)) }
52 | (subtraction(l, r), e) { sub(eval(l, e), eval(r, e)) }
53 | (multiplication(l, r), e) { mul(eval(l, e), eval(r, e)) }
54 | (division(l, r), e) { div(eval(l, e), eval(r, e)) }
55 | (i = number(_), e) { i }
56 | (symbol(s), e) { lookup(s, e) }
57 | (conditional(test, pro, con), e) { cond(test, pro, con, e) }
58 | (l = lambda(_, _), e) { closure(l, e) }
59 | (application(function, arg), e) { apply(eval(function, e), eval(arg, e)) }
60 | }
61 |
62 | // function application
63 | fn apply (closure(lambda(symbol(s), body), e), arg) {
64 | eval(body, frame(s, arg, e))
65 | }
66 |
67 | // built-ins
68 | fn add (number(a), number(b)) { number(a + b) }
69 |
70 | fn sub (number(a), number(b)) { number(a - b) }
71 |
72 | fn mul (number(a), number(b)) { number(a * b) }
73 |
74 | fn div (number(a), number(b)) { number(a / b) }
75 |
76 | fn cond(test, pro, con, e) {
77 | switch (eval(test, e)) {
78 | (number(0)) { eval(con, e) } // 0 is false
79 | (number(_)) { eval(pro, e) }
80 | }
81 | }
82 |
83 | // lookup access to the environment
84 | fn lookup {
85 | (s, frame(s, value, _)) { value }
86 | (s, frame(_, _, parent)) { lookup(s, parent) }
87 | (s, root) { error("mce symbol not defined " @@ s) }
88 | }
89 |
90 | // try it out: ((lambda (x) (if x (+ x 2) x)) a) |- a: 3
91 | eval(
92 | application(
93 | lambda(
94 | symbol("x"),
95 | conditional(
96 | symbol("x"),
97 | addition(symbol("x"), number(2)),
98 | symbol("x")
99 | )
100 | ),
101 | symbol("a")
102 | ),
103 | frame("a", number(3), root)
104 | );
105 | }
106 | '''
107 | )
108 |
109 |
110 | def dont_test_metacircular_compiler_it_takes_too_long(self):
111 | self.assertEval(
112 | '''
113 | sequence[
114 | [envt, argl],
115 | [envt, continue, argl, proc, val1, val2, val],
116 | [
117 | assign[proc, value_procedure[label[entry], envt]],
118 | goto[address_label[label[after-lambda]]],
119 | tag[label[entry]],
120 | assign[envt, value_procedure_env[proc]],
121 | assign[envt, value_extend_env[x, argl, envt]],
122 | assign[val, value_lookup[x]],
123 | test[location_register[val], value_const[0], label[false-branch]],
124 | tag[label[true-branch]],
125 | assign[val1, value_lookup[x]],
126 | assign[val2, value_const[2]],
127 | assign[val, value_binop[add, value_register[val1], value_register[val2]]],
128 | goto[address_register[continue]],
129 | tag[label[false-branch]],
130 | assign[val, value_lookup[x]],
131 | goto[address_register[continue]],
132 | tag[label[after-if]],
133 | tag[label[after-lambda]],
134 | assign[val, value_const[3]],
135 | test[location_register[proc], value_isprimitive, label[primitive-branch]],
136 | tag[label[compiled-branch]],
137 | assign[continue, value_label[label[after-call]]],
138 | assign[val, value_compiled_procedure_entry[proc]],
139 | tag[label[primitive-branch]],
140 | assign[val, value_apply_primitive[proc, argl]],
141 | tag[label[after-call]]
142 | ]
143 | ]
144 | ''',
145 | '''
146 |
147 | load utils.compiler as compiler;
148 |
149 | env t extends compiler {
150 | // try it out: ((lambda (x) (if x (+ x 2) x)) 3)
151 | result = compile(
152 | application(
153 | lambda(
154 | variable("x"),
155 | conditional(
156 | variable("x"),
157 | addition(variable("x"), number(2)),
158 | variable("x")
159 | )
160 | ),
161 | number(3)
162 | ),
163 | val,
164 | next
165 | );
166 | }
167 |
168 | t.result;
169 | '''
170 | )
171 |
--------------------------------------------------------------------------------
/pyscheme/tests/integration/test_nothing.py:
--------------------------------------------------------------------------------
1 | # (at your option) any later version.
2 | #
3 | # This program is distributed in the hope that it will be useful,
4 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
5 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
6 | # GNU General Public License for more details.
7 | #
8 | # You should have received a copy of the GNU General Public License
9 | # along with this program. If not, see .
10 |
11 | from pyscheme.tests.integration.base import Base
12 |
13 |
14 | class TestNothing(Base):
15 |
16 | def test_nothing_eq_nothing(self):
17 | self.assertEval(
18 | 'true',
19 | '''
20 | nothing == nothing;
21 | '''
22 | )
23 |
24 | def test_nothing_doesnt_print(self):
25 | self.assertEval(
26 | '',
27 | 'nothing;'
28 | )
29 |
--------------------------------------------------------------------------------
/pyscheme/tests/integration/test_print.py:
--------------------------------------------------------------------------------
1 | # PyScheme lambda language written in Python
2 | #
3 | # Copyright (C) 2018 Bill Hails
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License
16 | # along with this program. If not, see .
17 |
18 | from pyscheme.tests.integration.base import Base
19 |
20 |
21 | class TestPrint(Base):
22 | def test_print(self):
23 | self.assertEval(
24 | "hello\n1",
25 | """
26 | fn hello () {
27 | print("hello");
28 | 1
29 | }
30 |
31 | hello();
32 | """
33 | )
34 |
--------------------------------------------------------------------------------
/pyscheme/tests/integration/test_prototype.py:
--------------------------------------------------------------------------------
1 | # PyScheme lambda language written in Python
2 | #
3 | # Copyright (C) 2018 Bill Hails
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License
16 | # along with this program. If not, see .
17 |
18 |
19 | from pyscheme.tests.integration.base import Base
20 |
21 |
22 | class TestPrototype(Base):
23 | def test_prototype_1(self):
24 | self.assertEval(
25 | '',
26 | '''
27 | prototype foo {
28 | map: (#a -> #b) -> list(#a) -> list(#b);
29 | }
30 | '''
31 | )
32 |
33 | def test_prototype_33(self):
34 | self.assertEval(
35 | "[2, 3, 4]",
36 | """
37 | prototype foo {
38 | map: (#a -> #b) -> list(#a) -> list(#b);
39 | }
40 |
41 | env bar {
42 | fn map {
43 | (f, []) { [] }
44 | (f, h @ t) { f(h) @ map(f, t) }
45 | }
46 | }
47 |
48 | fn x (mapper: foo) {
49 | mapper.map(1+, [1, 2, 3])
50 | }
51 |
52 | x(bar);
53 | """,
54 | ""
55 | )
56 |
57 | def test_prototype_57(self):
58 | self.assertEval(
59 | "[2, 3, 4]",
60 | """
61 | prototype foo {
62 | map: (#a -> #b) -> list(#a) -> list(#b);
63 | }
64 |
65 | env bar {
66 | fn map {
67 | (f, []) { [] }
68 | (f, h @ t) { f(h) @ map(f, t) }
69 | }
70 |
71 | fn other(x) { x }
72 | }
73 |
74 | fn x (mapper: foo) {
75 | mapper.map(1+, [1, 2, 3])
76 | }
77 |
78 | x(bar);
79 | """,
80 | ""
81 | )
82 |
83 | def test_prototype_83(self):
84 | self.assertError(
85 | "PySchemeTypeError: int != list(#a)",
86 | """
87 | prototype foo {
88 | len : list(#t) -> int;
89 | }
90 |
91 | env bar {
92 | fn len(a) { a }
93 | }
94 |
95 | fn x (lenner:foo) { lenner }
96 |
97 | x(bar);
98 | """,
99 | ""
100 | )
101 |
102 | def test_prototype_182(self):
103 | self.assertEval(
104 | '[2, 3, 4]',
105 | '''
106 | prototype foo {
107 | prototype bar {
108 | map: (#a -> #b) -> list(#a) -> list(#b);
109 | };
110 | }
111 |
112 | env a {
113 | env bar {
114 | fn map {
115 | (f, []) { [] }
116 | (f, h @ t) { f(h) @ map(f, t) }
117 | }
118 | }
119 | }
120 |
121 | fn x (e: foo, f) {
122 | e.bar.map(f, [1, 2, 3])
123 | }
124 |
125 | x(a, 1+);
126 | ''',
127 | 'nested prototype'
128 | )
129 |
--------------------------------------------------------------------------------
/pyscheme/tests/integration/test_sort.py:
--------------------------------------------------------------------------------
1 | # PyScheme lambda language written in Python
2 | #
3 | # Copyright (C) 2018 Bill Hails
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License
16 | # along with this program. If not, see .
17 |
18 |
19 | from pyscheme.tests.integration.base import Base
20 |
21 |
22 | class TestSort(Base):
23 |
24 | def test_sort(self):
25 | self.assertEval(
26 | '[everywhere, goodbye, hello, here, there]',
27 | '''
28 | {
29 | unsorted = ["hello", "goodbye", "here", "there", "everywhere"];
30 |
31 | fn qsort {
32 | ([]) { [] }
33 | (pivot @ rest) {
34 | lesser = filter(pivot >=, rest);
35 | greater = filter(pivot <, rest);
36 | qsort(lesser) @@ [pivot] @@ qsort(greater);
37 | }
38 | }
39 |
40 | fn filter {
41 | (f, []) { [] }
42 | (f, h @ t) {
43 | if (f(h)) {
44 | h @ filter(f, t)
45 | } else {
46 | filter(f, t)
47 | }
48 | }
49 | }
50 |
51 | qsort(unsorted);
52 | }
53 | '''
54 | )
55 |
56 | def test_sort_2(self):
57 | self.assertEval(
58 | '[everywhere, goodbye, hello, here, there]',
59 | '''
60 | {
61 | unsorted = ["hello", "goodbye", "here", "there", "everywhere"];
62 |
63 | fn qsort {
64 | ([]) { [] }
65 | (pivot @ rest) {
66 | partition(pivot, rest)
67 | }
68 | }
69 |
70 | // only read the list once, breaking it into two lists
71 | fn partition(pivot, rest) {
72 | fn helper {
73 | (pivot, [], lt, gt) { qsort(lt) @@ [pivot] @@ qsort(gt) }
74 | (pivot, h @ t, lt, gt) {
75 | if (pivot > h) {
76 | helper(pivot, t, h @ lt, gt)
77 | } else {
78 | helper(pivot, t, lt, h @ gt)
79 | }
80 | }
81 | }
82 | helper(pivot, rest, [], [])
83 | }
84 |
85 | qsort(unsorted);
86 | }
87 | '''
88 | )
89 |
--------------------------------------------------------------------------------
/pyscheme/tests/integration/test_spawn.py:
--------------------------------------------------------------------------------
1 | # PyScheme lambda language written in Python
2 | #
3 | # Copyright (C) 2018 Bill Hails
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License
16 | # along with this program. If not, see .
17 |
18 |
19 | from pyscheme.tests.integration.base import Base
20 |
21 |
22 | class TestSpawn(Base):
23 | def test_spawn(self):
24 | self.assertEval(
25 | 'a\nb\na\nb\na\nb\na\nb\na\nb\na\nb\nb\n120\n720',
26 | '''
27 | {
28 | fn factorial {
29 | (label, 0) {
30 | print(label);
31 | 1
32 | }
33 | (label, n) {
34 | print(label);
35 | n * factorial(label, n - 1)
36 | }
37 | }
38 | if (spawn) {
39 | factorial("a", 5)
40 | } else {
41 | factorial("b", 6);
42 | }
43 | }
44 | '''
45 | )
46 |
47 |
--------------------------------------------------------------------------------
/pyscheme/tests/integration/test_strings.py:
--------------------------------------------------------------------------------
1 | # PyScheme lambda language written in Python
2 | #
3 | # Copyright (C) 2018 Bill Hails
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License
16 | # along with this program. If not, see .
17 |
18 |
19 | from pyscheme.tests.integration.base import Base
20 |
21 |
22 | class TestStrings(Base):
23 | def test_strings_23(self):
24 | self.assertEval(
25 | "Hello, world!",
26 | """
27 | "Hello," @@ " world!";
28 | """,
29 | "append works on strings"
30 | )
31 |
32 | def test_strings_32(self):
33 | self.assertEval(
34 | "Hello",
35 | """
36 | 'H' @ "ello";
37 | """,
38 | "cons works with chars and strings"
39 | )
40 |
41 | def test_strings_41(self):
42 | self.assertEval(
43 | "5",
44 | """
45 | length("hello");
46 | """,
47 | "length works on string"
48 | )
49 |
50 | def test_strings_50(self):
51 | self.assertEval(
52 | '[h, e, l, l, o]',
53 | """
54 | {
55 | fn map {
56 | (f, []) { [] }
57 | (f, h @ t) { f(h) @ map(f, t) }
58 | }
59 |
60 | map( fn(c) { [c] }, "hello")
61 | }
62 | """,
63 | ""
64 | )
65 |
--------------------------------------------------------------------------------
/pyscheme/tests/integration/test_switch.py:
--------------------------------------------------------------------------------
1 | # PyScheme lambda language written in Python
2 | #
3 | # Copyright (C) 2018 Bill Hails
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License
16 | # along with this program. If not, see .
17 |
18 |
19 | from pyscheme.tests.integration.base import Base
20 |
21 |
22 | class TestSwitch(Base):
23 | def test_switch(self):
24 | self.assertEval(
25 | 'true',
26 | '''
27 | a = [1, 2];
28 | b = [1, 2];
29 | switch (a, b) {
30 | ([], []) { false }
31 | (h @ t, []) { false }
32 | ([], h @ t) { false }
33 | (h @ t, h @ t) { true }
34 | }
35 | '''
36 | )
37 |
38 |
--------------------------------------------------------------------------------
/pyscheme/tests/integration/test_typedef.py:
--------------------------------------------------------------------------------
1 | # PyScheme lambda language written in Python
2 | #
3 | # Copyright (C) 2018 Bill Hails
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License
16 | # along with this program. If not, see .
17 |
18 |
19 | from pyscheme.tests.integration.base import Base
20 |
21 |
22 | class TestTypedef(Base):
23 | def test_typedef_23(self):
24 | self.assertEval(
25 | 'red',
26 | '''
27 | typedef colour { red }
28 |
29 | red;
30 | ''',
31 | "type constructors with no arguments are constants"
32 | )
33 |
34 | def test_typedef_33(self):
35 | self.assertEval(
36 | "true",
37 | """
38 | typedef colour { red | green }
39 |
40 | red == red;
41 | """,
42 | "type values can be compared"
43 | )
44 |
45 | def test_typedef_48(self):
46 | self.assertEval(
47 | "false",
48 | """
49 | typedef colour { red | green }
50 |
51 | red == green;
52 | """,
53 | "type values can be compared"
54 | )
55 |
56 | def test_typedef_63(self):
57 | self.assertEval(
58 | "pair[1, null]",
59 | """
60 | typedef lst(#t) { pair(#t, lst(#t)) | null }
61 |
62 | pair(1, null);
63 |
64 | """,
65 | "constructed types have sensible print values"
66 | )
67 |
68 | def test_typedef_75(self):
69 | self.assertEval(
70 | "true",
71 | """
72 | typedef lst(#t) { pair(#t, lst(#t)) | null }
73 |
74 | pair(1, null) == pair(1, null);
75 | """,
76 | "lists of the same type and equal values can be compared and are equal"
77 | )
78 |
79 | def test_typedef_90(self):
80 | self.assertEval(
81 | "ok",
82 | """
83 | typedef lst(#t) { pair(#t, lst(#t)) | null }
84 |
85 | if (pair(1, null) == pair(2, null)) {
86 | "nok"
87 | } else {
88 | "ok"
89 | }
90 | """,
91 | "lists of the same type and different values can be compared and are unequal"
92 | )
93 |
94 | def test_typedef_105(self):
95 | self.assertError(
96 | "PySchemeTypeError: bool != int",
97 | """
98 | typedef lst(#t) { pair(#t, lst(#t)) | null }
99 |
100 | pair(1, null) == pair(true, null);
101 | """,
102 | "cannot compare lists of different types"
103 | )
104 |
105 | def test_typedef_116(self):
106 | self.assertEval(
107 | "pair[1, pair[2, null]]",
108 | """
109 | typedef lst(#t) { pair(#t, lst(#t)) | null }
110 |
111 | pair(1, pair(2, null));
112 | """,
113 | "lists can only contain the same types"
114 | )
115 |
116 | def test_typedef_127(self):
117 | self.assertError(
118 | "PySchemeTypeError: bool != int",
119 | """
120 | typedef lst(#t) { pair(#t, lst(#t)) | null }
121 |
122 | pair(1, pair(true, null));
123 | """,
124 | "lst cannot contain mixed types"
125 | )
126 |
127 | def test_typedef_139(self):
128 | self.assertError(
129 | "PySchemeTypeError: lst(int) != colour",
130 | """
131 | typedef lst(#t) { pair(#t, lst(#t)) | null }
132 | typedef colour { red | green }
133 |
134 | red == pair(1, null);
135 | """,
136 | "cannot compare different types"
137 | )
138 |
139 | def test_typedef_151(self):
140 | self.assertEval(
141 | "false",
142 | """
143 | typedef colour { red | green }
144 |
145 | fn(x, y) {
146 | x == y
147 | }(red, green);
148 | """,
149 | ""
150 | )
151 |
152 | def test_typedef_152(self):
153 | self.assertError(
154 | "PySchemeTypeError: bool != colour",
155 | """
156 | typedef colour { red | green }
157 |
158 | fn(x, y) {
159 | x == y
160 | }(red, true);
161 | """,
162 | "type inference works for user defined types"
163 | )
164 |
165 | def test_curried_type(self):
166 | self.assertEval(
167 | 'group[1, 2]',
168 | '''
169 | typedef some(#n) { group(#n, #n) }
170 |
171 | start = group(1);
172 |
173 | start(2);
174 | ''',
175 | 'type constructors can be curried'
176 | )
177 |
178 | def test_lst_append(self):
179 | self.assertEval(
180 | 'pair[1, pair[2, pair[3, pair[4, pair[5, pair[6, null]]]]]]',
181 | '''
182 | {
183 | typedef lst(#t) { pair(#t, lst(#t)) | null }
184 |
185 | fn append {
186 | (null, ys) { ys }
187 | (pair(h, t), ys) { pair(h, append(t, ys)) }
188 | }
189 |
190 | xs = pair(1, pair(2, pair(3, null)));
191 | ys = pair(4, pair(5, pair(6, null)));
192 |
193 | append(xs, ys);
194 | }
195 | '''
196 | )
197 |
--------------------------------------------------------------------------------
/pyscheme/tests/integration/test_wildcard.py:
--------------------------------------------------------------------------------
1 | # PyScheme lambda language written in Python
2 | #
3 | # Copyright (C) 2018 Bill Hails
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License
16 | # along with this program. If not, see .
17 |
18 |
19 | from pyscheme.tests.integration.base import Base
20 |
21 |
22 | class TestWildcard(Base):
23 | """
24 | wildcard '_' in formal arguments behaves like a constant that
25 | matches (equals) anything
26 | """
27 |
28 | def test_wildcard_1(self):
29 | self.assertEval(
30 | 'hello',
31 | '''
32 | fn ignore (_, _, foo) { foo }
33 |
34 | ignore(true, 10, "hello");
35 | '''
36 | )
37 |
38 | def test_wildcard_2(self):
39 | self.assertEval(
40 | '3',
41 | '''
42 | {
43 | fn len {
44 | ([]) { 0 }
45 | (_ @ t) { 1 + len(t) }
46 | }
47 |
48 | len([1, 2, 3]);
49 | }
50 | '''
51 | )
52 |
53 | def test_wildcard_3(self):
54 | self.assertEval(
55 | '2',
56 | '''
57 | typedef tree(#t) { branch(tree(#t), #t, tree(#t)) | leaf }
58 |
59 | fn ignore {
60 | (leaf) { 0 }
61 | (branch(_, t, _)) { t }
62 | }
63 |
64 | ignore(branch(leaf, 2, leaf));
65 | '''
66 | )
67 |
--------------------------------------------------------------------------------
/pyscheme/tests/unit/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/billhails/PyScheme/165b4eb4714bfc51f079c310725dcf302798ac26/pyscheme/tests/unit/__init__.py
--------------------------------------------------------------------------------
/pyscheme/tests/unit/test_environment.py:
--------------------------------------------------------------------------------
1 | # PyScheme lambda language written in Python
2 | #
3 | # Copyright (C) 2018 Bill Hails
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License
16 | # along with this program. If not, see .
17 |
18 | import unittest
19 | import pyscheme.environment as env
20 | import pyscheme.expr as expr
21 | from pyscheme.exceptions import SymbolNotFoundError
22 |
23 |
24 | class TestEnvironment(unittest.TestCase):
25 | def setUp(self):
26 | self.env = env.Environment()
27 |
28 | def tearDown(self):
29 | self.env = None
30 |
31 | def test_environment_exists(self):
32 | self.assertIsInstance(self.env, env.Environment, "environment should be set up")
33 |
34 | def test_lookup(self):
35 | a = expr.Symbol("a")
36 | b = expr.Constant(10)
37 | new_env = self.env.extend({a: b})
38 | c = None
39 |
40 | def cont(v, amb):
41 | nonlocal c
42 | c = v
43 |
44 | new_env.lookup(a, cont, lambda: None)() # we have to bounce the result to evaluate cont.
45 | self.assertEqual(b, c, "lookup should find a = 10")
46 |
47 | def test_failed_lookup(self):
48 | a = expr.Symbol("a")
49 |
50 | def cont(_, amb):
51 | pass
52 |
53 | with self.assertRaises(SymbolNotFoundError):
54 | self.env.lookup(a, cont, lambda: None)
55 |
56 |
57 | if __name__ == "__main__":
58 | unittest.main()
59 |
--------------------------------------------------------------------------------
/pyscheme/tests/unit/test_expr.py:
--------------------------------------------------------------------------------
1 | # PyScheme lambda language written in Python
2 | #
3 | # Copyright (C) 2018 Bill Hails
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License
16 | # along with this program. If not, see .
17 |
18 | from unittest import TestCase
19 | import pyscheme.expr as expr
20 | from pyscheme.exceptions import NonBooleanExpressionError
21 |
22 |
23 | class TestExpr(TestCase):
24 | def setUp(self):
25 | self.expr = expr.Expr()
26 |
27 | def tearDown(self):
28 | self.expr = None
29 |
30 | def test_equality(self):
31 | self.assertEqual(self.expr, self.expr, "op_funcall should equal itself")
32 |
33 | def test_non_equality(self):
34 | self.assertNotEqual(self.expr, expr.Expr(), "different objects should not normally compare equal")
35 |
36 | def test_eq(self):
37 | self.assertIsInstance(self.expr.eq(self.expr), expr.T, "eq method returns boolean T")
38 |
39 | def test_not_null(self):
40 | self.assertFalse(self.expr.is_null(), "only null should be null")
41 |
42 | def test_non_boolean_exception(self):
43 | with self.assertRaises(NonBooleanExpressionError):
44 | self.expr.is_true()
45 |
46 |
47 | class TestConstant(TestCase):
48 | def setUp(self):
49 | self.constant_10 = expr.Constant(10)
50 | self.constant_10b = expr.Constant(10)
51 | self.constant_12 = expr.Constant(12)
52 |
53 | def tearDown(self):
54 | self.constant_10 = None
55 | self.constant_10b = None
56 | self.constant_12 = None
57 |
58 | def test_value(self):
59 | self.assertEqual(10, self.constant_10.value(), "value should return underlying value")
60 |
61 | def test_equality(self):
62 | self.assertEqual(self.constant_10, self.constant_10b, "two constants with the same value should compare equal")
63 |
64 | def test_non_equality(self):
65 | self.assertNotEqual(self.constant_10,
66 | self.constant_12,
67 | "two constants with different values should not compare equal")
68 |
69 | def test_string(self):
70 | self.assertEqual("10", str(self.constant_10), "string value should be sensible")
71 |
72 |
73 | class TestBoolean(TestCase):
74 | def setUp(self):
75 | self.t = expr.T()
76 | self.f = expr.F()
77 | self.u = expr.U()
78 |
79 | def tearDown(self):
80 | self.t = None
81 | self.f = None
82 | self.u = None
83 |
84 | def test_not(self):
85 | t = self.t
86 | f = self.f
87 | u = self.u
88 | for args in [
89 | [t, f],
90 | [f, t],
91 | [u, u]
92 | ]:
93 | with self.subTest(args = args):
94 | self.assertEqual(args[1], ~args[0])
95 |
96 | def test_and(self):
97 | t = self.t
98 | f = self.f
99 | u = self.u
100 | for args in [
101 | [t, t, t],
102 | [t, f, f],
103 | [t, u, u],
104 | [f, t, f],
105 | [f, f, f],
106 | [f, u, f],
107 | [u, t, u],
108 | [u, f, f],
109 | [u, u, u]
110 | ]:
111 | with self.subTest(args = args):
112 | self.assertEqual(args[2], args[0] & args[1])
113 |
114 | def test_or(self):
115 | t = self.t
116 | f = self.f
117 | u = self.u
118 | for args in [
119 | [t, t, t],
120 | [t, f, t],
121 | [t, u, t],
122 | [f, t, t],
123 | [f, f, f],
124 | [f, u, u],
125 | [u, t, t],
126 | [u, f, u],
127 | [u, u, u]
128 | ]:
129 | with self.subTest(args = args):
130 | self.assertEqual(args[2], args[0] | args[1])
131 |
132 | def test_xor(self):
133 | t = self.t
134 | f = self.f
135 | u = self.u
136 | for args in [
137 | [t, t, f],
138 | [t, f, t],
139 | [t, u, u],
140 | [f, t, t],
141 | [f, f, f],
142 | [f, u, u],
143 | [u, t, u],
144 | [u, f, u],
145 | [u, u, u]
146 | ]:
147 | with self.subTest(args = args):
148 | self.assertEqual(args[2], args[0] ^ args[1])
149 |
150 | def test_eq(self):
151 | t = self.t
152 | f = self.f
153 | u = self.u
154 | for args in [
155 | [t, t, t],
156 | [t, f, f],
157 | [t, u, f],
158 | [f, t, f],
159 | [f, f, t],
160 | [f, u, f],
161 | [u, t, f],
162 | [u, f, f],
163 | [u, u, t]
164 | ]:
165 | with self.subTest(args=args):
166 | self.assertEqual(args[2], args[0].eq(args[1]))
167 |
168 | def test_str(self):
169 | self.assertEqual("true", str(self.t))
170 | self.assertEqual("false", str(self.f))
171 | self.assertEqual("unknown", str(self.u))
172 |
173 |
174 | class TestSymbol(TestCase):
175 | def setUp(self):
176 | self.a = expr.Symbol("a")
177 | self.b = expr.Symbol("b")
178 | self.b2 = expr.Symbol("b")
179 |
180 | def tearDown(self):
181 | self.a = None
182 | self.b = None
183 | self.b2 = None
184 |
185 | def test_equality(self):
186 | self.assertEqual(self.b, self.b2, "two symbols with the same name should compare equal")
187 |
188 | def test_non_equality(self):
189 | self.assertNotEqual(self.a, self.b, "two symbols with different names should not compare equal")
190 |
191 | def test_comparison(self):
192 | self.assertIsInstance(self.a.eq(self.b), expr.F, "boolean object should be false")
193 | self.assertIsInstance(self.b.eq(self.b2), expr.T, "boolean object should be true")
194 |
195 | def test_hash(self):
196 | self. assertEqual(hash(self.a), id(self.a), "hash function should use id")
197 |
198 | def test_str(self):
199 | self.assertEqual("a", str(self.a), "string representation should be sensible")
200 |
201 |
202 | class TestList(TestCase):
203 | def setUp(self):
204 | self.a = expr.Symbol("a")
205 | self.b = expr.Symbol("b")
206 | self.c = expr.Symbol("c")
207 | self.list = expr.LinkedList.list([self.a, self.b, self.c])
208 | self.null = expr.Null()
209 |
210 | def tearDown(self):
211 | self.a = None
212 | self.b = None
213 | self.c = None
214 | self.list = None
215 | self.null = None
216 |
217 | def test_null(self):
218 | self.assertTrue(self.null.is_null(), "null class method should return null")
219 | self.assertFalse(self.list.is_null(), "non-empty list should not be null")
220 |
221 | def test_car(self):
222 | self.assertEqual(self.a, self.list.car(), "car of list should be a")
223 |
224 | def test_cdr(self):
225 | self.assertEqual(self.b, self.list.cdr().car(), "car of cdr of list should be b")
226 |
227 | def test_len(self):
228 | self.assertEqual(3, len(self.list), "list should be length three")
229 |
230 | def test_str(self):
231 | self.assertEqual("[a, b, c]",
232 | str(self.list),
233 | "list as string should be sensible")
234 |
235 | def test_append(self):
236 | self.assertEqual("[a, b, c, a, b, c]",
237 | str(self.list.append(self.list)),
238 | "append to self should double size")
239 |
240 | def test_iter(self):
241 | result = []
242 | for s in self.list:
243 | result += [s]
244 | self.assertEqual([self.a, self.b, self.c], result, "iteration should work")
245 |
246 | def test_getitem(self):
247 | self.assertEqual(self.b,
248 | self.list[1])
249 |
250 | def test_getitem2(self):
251 | with self.assertRaises(KeyError):
252 | print(self.null[0])
253 |
254 | def test_getitem3(self):
255 | with self.assertRaises(TypeError):
256 | print(self.list["a"])
257 |
258 | def test_getitem4(self):
259 | with self.assertRaises(TypeError):
260 | print(self.null["a"])
261 |
262 | def test_getitem5(self):
263 | with self.assertRaises(KeyError):
264 | print(self.list[5])
265 |
266 | def test_getitem6(self):
267 | with self.assertRaises(KeyError):
268 | print(self.list[-1])
269 |
270 | def test_last1(self):
271 | self.assertEqual(self.c, self.list.last(), "last item of list should be c")
272 |
273 | def test_last2(self):
274 | self.assertEqual(self.null, self.null.last(), "last item of null should be null")
--------------------------------------------------------------------------------
/pyscheme/tests/unit/test_inference.py:
--------------------------------------------------------------------------------
1 | # PyScheme lambda language written in Python
2 | #
3 | # Copyright (C) 2018 Bill Hails
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License
16 | # along with this program. If not, see .
17 |
18 | from unittest import TestCase
19 | import pyscheme.repl as repl
20 | import io
21 | from pyscheme.exceptions import TypeSymbolNotFoundError, PySchemeInferenceError, PySchemeTypeError
22 | from pyscheme.inference import TypeVariable
23 | import re
24 |
25 |
26 | class TestInference(TestCase):
27 |
28 | def setUp(self):
29 | self.input = io.StringIO()
30 | self.output = io.StringIO()
31 | self.error = io.StringIO()
32 | # we use a Repl because it sets up the type environment for the type checker
33 | self.repl = repl.Repl(self.input, self.output, self.error)
34 | TypeVariable.reset_names()
35 |
36 | def tearDown(self):
37 | self.input = None
38 | self.output = None
39 | self.error = None
40 | self.repl = None
41 |
42 | def normalize_type(self, type_str: str) -> str:
43 | """
44 | convert a string like ...#b...#b...#c... to ...#0...#0...#1...
45 | """
46 | seen = {}
47 | counter = 0
48 |
49 | def replace(matchobj):
50 | nonlocal seen
51 | nonlocal counter
52 | id = matchobj.group(0)
53 | if id not in seen:
54 | seen[id] = '#' + str(counter)
55 | counter += 1
56 | return seen[id]
57 |
58 | return re.sub(r'(#\w+)', replace, type_str)
59 |
60 | def assertEqualTypes(self, type1: str, type2: str):
61 | self.assertEqual(self.normalize_type(type1), self.normalize_type(type2))
62 |
63 | def assertType(self, expected_type: str, expression: str):
64 | self.input.write(expression)
65 | self.input.seek(0)
66 | result = self.repl.reader.read()
67 | if result is None:
68 | self.fail("parse of '" + expression + "' failed: " + self.error.getvalue())
69 | analysis = result.analyse(self.repl.type_env)
70 | self.assertEqualTypes(expected_type, str(analysis))
71 |
72 | def assertTypes(self, expected_types: list, expression: str):
73 | self.input.write(expression)
74 | self.input.seek(0)
75 | analysis = []
76 | while True:
77 | result = self.repl.reader.read()
78 | if result is None:
79 | break
80 | analysis += [str(result.analyse(self.repl.type_env))]
81 | for pair in zip(expected_types, analysis):
82 | self.assertEqualTypes(pair[0], pair[1])
83 |
84 | def assertTypeFailure(self, expected_exception, expression: str):
85 | self.input.write(expression)
86 | self.input.seek(0)
87 | result = self.repl.reader.read()
88 | if result is None:
89 | self.fail("parse of '" + expression + "' failed: " + self.error.getvalue())
90 | with self.assertRaises(expected_exception):
91 | result.analyse(self.repl.type_env)
92 |
93 | def test_and(self):
94 | self.assertType('bool', '1 == 2 and true;')
95 |
96 | def test_length(self):
97 | self.assertType('int', 'length([]);')
98 |
99 | def test_add(self):
100 | self.assertType('int', '1 + 2;')
101 |
102 | def test_list_int(self):
103 | self.assertType('list(int)', '[1, 2];')
104 |
105 | def test_list_string(self):
106 | self.assertType('list(list(char))', '["a", "b"];')
107 |
108 | def test_nested_list(self):
109 | self.assertType('list(list(int))', '[[0, 1, 2], []];')
110 |
111 | def test_cons(self):
112 | self.assertType('list(int)', '1 @ [2];')
113 |
114 | def test_append(self):
115 | self.assertType('list(int)', '[1] @@ [2];')
116 |
117 | def test_factorial(self):
118 | self.assertType(
119 | '(int -> int)',
120 | """
121 | {
122 | fn factorial (n) {
123 | if (n == 0) {
124 | 1
125 | } else {
126 | n * factorial(n - 1)
127 | }
128 | }
129 | factorial
130 | }
131 | """
132 | )
133 |
134 | def test_polymorphic_len(self):
135 | self.assertType(
136 | '(list(#b) -> int)',
137 | '''
138 | {
139 | fn len(lst) {
140 | if (lst == []) {
141 | 0
142 | } else {
143 | 1 + len(tail(lst))
144 | }
145 | }
146 | len
147 | }
148 | '''
149 | )
150 |
151 | def test_polymorphic_map(self):
152 | self.assertType(
153 | '((#c -> #d) -> (list(#c) -> list(#d)))',
154 | '''
155 | {
156 | fn map(func, lst) {
157 | if (lst == []) {
158 | []
159 | } else {
160 | func(head(lst)) @ map(func, tail(lst))
161 | }
162 | }
163 | map
164 | }
165 | '''
166 | )
167 |
168 | def test_mixed_lists_fail(self):
169 | self.assertTypeFailure(PySchemeTypeError, '[0] @@ [true];')
170 | self.assertTypeFailure(PySchemeTypeError, '0 @ [true];')
171 |
172 | def test_mixed_nested_lists_fail(self):
173 | self.assertTypeFailure(PySchemeTypeError, '[[0]] @@ [[true]];')
174 |
175 | def test_non_polymorphic_fail(self):
176 | self.assertTypeFailure(PySchemeTypeError, 'fn (f) { [f(3), f(true)] };')
177 |
178 | def test_undefined_symbol(self):
179 | self.assertTypeFailure(TypeSymbolNotFoundError, '[f(3), f(true)];')
180 |
181 | def test_occurs_check(self):
182 | self.assertTypeFailure(PySchemeInferenceError, 'fn (f) { f(f) };')
183 |
184 | def test_override_check(self):
185 | self.assertTypeFailure(
186 | PySchemeInferenceError,
187 | '''
188 | {
189 | typedef colour { red | green }
190 | {
191 | red = 5;
192 | }
193 | }
194 | '''
195 | )
196 |
197 | def test_override_check_2(self):
198 | self.assertTypeFailure(
199 | PySchemeInferenceError,
200 | '''
201 | {
202 | typedef colour { red | green }
203 | {
204 | typedef colours { red | blue }
205 | }
206 | }
207 | '''
208 | )
209 |
210 | def test_generic_non_generic(self):
211 | self.assertType(
212 | '(#a -> list(#a))',
213 | '''
214 | fn (g) {
215 | fn (f) {
216 | [ f(2), f(3) ]
217 | }(fn(x) { g })
218 | };
219 | '''
220 | )
221 |
222 | def test_function_comp(self):
223 | self.assertType(
224 | '((#a -> #b) -> ((#c -> #a) -> (#c -> #b)))',
225 | '''
226 | fn (f) { fn (g) { fn (arg) { f(g(arg)) } } };
227 | '''
228 | )
229 |
230 | def test_builtin_type(self):
231 | self.assertType(
232 | '(list(char) -> (list(#b) -> named_list(#b)))',
233 | '''
234 | {
235 | typedef named_list(#t) { named(list(char), list(#t)) }
236 | named;
237 | }
238 | '''
239 | )
240 |
241 | def test_composite_type(self):
242 | self.assertType(
243 | '((#c -> #d) -> (list(#c) -> list(#d)))',
244 | '''
245 | {
246 | fn map {
247 | (f, []) { [] }
248 | (f, h @ t) { f(h) @ map(f, t) }
249 | }
250 | map;
251 | }
252 | '''
253 | )
254 |
255 | def test_composite_with_user_types(self):
256 | self.assertType(
257 | '((#c -> #d) -> (l(#c) -> l(#d)))',
258 | '''
259 | {
260 | typedef l(#t) { p(#t, l(#t)) | n }
261 | fn map {
262 | (f, n) { n }
263 | (f, p(h, t)) { p(f(h), map(f, t)) }
264 | }
265 | map;
266 | }
267 | '''
268 | )
269 |
270 | def test_composite_with_constants(self):
271 | self.assertType(
272 | '(int -> int)',
273 | '''
274 | {
275 | fn factorial {
276 | (0) { 1 }
277 | (n) { n * factorial(n - 1) }
278 | }
279 | factorial;
280 | }
281 | '''
282 | )
283 |
284 | def test_composite_with_constants_2(self):
285 | self.assertType(
286 | '(l(#b) -> int)',
287 | '''
288 | {
289 | typedef l(#t) { p(#t, l(#t)) | n }
290 | fn len {
291 | (n) { 0 }
292 | (p(h, t)) { 1 + len(t) }
293 | }
294 | len;
295 | }
296 | '''
297 | )
298 |
299 | def test_composite_with_call(self):
300 | self.assertType(
301 | 'int',
302 | '''
303 | {
304 | typedef lst(#t) { pair(#t, lst(#t)) | null }
305 | fn len {
306 | (null) { 0 }
307 | (pair(h, t)) { 1 + len(t) }
308 | }
309 | len(pair(1, pair(2, pair(3, null))));
310 | }
311 | '''
312 | )
313 |
314 | def test_composite_with_call_type(self):
315 | self.assertTypes(
316 | ['lst(#a)', 'nothing', '(lst(#b) -> int)'],
317 | '''
318 | typedef lst(#t) { pair(#t, lst(#t)) | null }
319 |
320 | fn len {
321 | (null) { 0 }
322 | (pair(h, t)) { 1 + len(t) }
323 | }
324 |
325 | len;
326 |
327 | '''
328 | )
329 |
330 | def test_filter(self):
331 | self.assertType(
332 | '((#b -> bool) -> (list(#b) -> list(#b)))',
333 | '''
334 | {
335 | fn filter {
336 | (f, []) { [] }
337 | (f, h @ t) {
338 | if (f(h)) {
339 | h @ filter(f, t)
340 | } else {
341 | filter(f, t)
342 | }
343 | }
344 | }
345 | filter
346 | }
347 | '''
348 | )
349 |
350 | def test_qsort(self):
351 | self.assertType(
352 | '(list(#e) -> list(#e))',
353 | '''
354 | {
355 | fn qsort {
356 | ([]) { [] }
357 | (pivot @ rest) {
358 | lesser = filter(ge(pivot), rest);
359 | greater = filter(lt(pivot), rest);
360 | qsort(lesser) @@ [pivot] @@ qsort(greater)
361 | }
362 | }
363 |
364 | fn lt(a, b) { a < b }
365 |
366 | fn ge(a, b) { a >= b }
367 |
368 | fn filter {
369 | (f, []) { [] }
370 | (f, h @ t) {
371 | if (f(h)) {
372 | h @ filter(f, t)
373 | } else {
374 | filter(f, t)
375 | }
376 | }
377 | }
378 |
379 | qsort
380 | }
381 | '''
382 | )
383 |
384 | def test_filter_type(self):
385 | self.assertTypes(
386 | [
387 | 'nothing',
388 | '((#a -> bool) -> (list(#a) -> list(#a)))',
389 | ],
390 | '''
391 | fn filter {
392 | (f, []) { [] }
393 | (f, h @ t) {
394 | if (f(h)) {
395 | h @ filter(f, t)
396 | } else {
397 | filter(f, t)
398 | }
399 | }
400 | }
401 |
402 | filter;
403 |
404 | '''
405 | )
406 |
407 | def test_ge(self):
408 | self.assertType(
409 | '(#b -> (#b -> bool))',
410 | '''
411 | {
412 | fn ge(a, b) { a >= b }
413 | ge
414 | }
415 | '''
416 | )
417 |
418 | def test_env(self):
419 | self.assertTypes(
420 | [
421 | 'nothing',
422 | '(#a -> (lst(#a) -> lst(#a)))'
423 | ],
424 | '''
425 | env e {
426 | typedef lst(#t) {pair(#t, lst(#t)) | null }
427 | }
428 |
429 | e.pair;
430 | '''
431 | )
432 |
433 | def test_meta(self):
434 | self.assertType(
435 | '(expression(#b) -> (expression(#b) -> expression(#b)))',
436 | '''
437 | {
438 | typedef expression(#t) {
439 | plus(expression(#t), expression(#t)) |
440 | minus(expression(#t), expression(#t)) |
441 | times(expression(#t), expression(#t)) |
442 | divide(expression(#t), expression(#t)) |
443 | number(#t) |
444 | symbol(list(char))
445 | }
446 |
447 | plus;
448 | }
449 | '''
450 | )
451 |
452 | def test_meta_2(self):
453 | self.assertType(
454 | '(expression -> (expression -> expression))',
455 | '''
456 | {
457 | typedef expression {
458 | plus(expression, expression) |
459 | minus(expression, expression) |
460 | times(expression, expression) |
461 | divide(expression, expression) |
462 | number(int) |
463 | symbol(list(char))
464 | }
465 |
466 | plus;
467 | }
468 | '''
469 | )
470 |
471 | def test_inference_multiple_functions(self):
472 | self.assertType(
473 | '(int -> (int -> int))',
474 | '''
475 | {
476 | fn add (x, y) {
477 | x + y
478 | }
479 |
480 | fn f1 (a, b) {
481 | add(a, b)
482 | }
483 |
484 | f1
485 | }
486 | '''
487 | )
488 |
--------------------------------------------------------------------------------
/pyscheme/tests/unit/test_reader.py:
--------------------------------------------------------------------------------
1 | # PyScheme lambda language written in Python
2 | #
3 | # Copyright (C) 2018 Bill Hails
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License
16 | # along with this program. If not, see .
17 |
18 | from unittest import TestCase
19 | import pyscheme.reader as reader
20 | import io
21 | from pyscheme.exceptions import PySchemeSyntaxError
22 |
23 |
24 | class TestReader(TestCase):
25 |
26 | def assertParse(self, expected, code, message=''):
27 | input_file = io.StringIO(code)
28 | stderr = io.StringIO()
29 | tokeniser = reader.Tokeniser(input_file)
30 | parser = reader.Reader(tokeniser, stderr)
31 | for expect in expected:
32 | self.assertEqual(expect, str(parser.read()), message + ": " + code)
33 |
34 | def assertSyntaxError(self, code, message=''):
35 | input_file = io.StringIO(code)
36 | stderr = io.StringIO()
37 | tokeniser = reader.Tokeniser(input_file)
38 | parser = reader.Reader(tokeniser, stderr)
39 | exception_caught = False
40 | try:
41 | result = parser.read()
42 | except PySchemeSyntaxError as e:
43 | exception_caught = True
44 | self.assertTrue(exception_caught, message)
45 |
46 | def test_parse_basic(self):
47 | self.assertParse(["1"], "1;")
48 |
49 | def test_syntax_error(self):
50 | self.assertSyntaxError(
51 | '''
52 | some garbage } nonsense
53 | '''
54 | )
55 | def test_parse_arithmetic(self):
56 | self.assertParse(
57 | ["(1 + (2 * 3))"],
58 | "1 + 2 * 3;",
59 | "basic arithmetic precedence works"
60 | )
61 |
62 | def test_parse_multiple(self):
63 | self.assertParse(
64 | [
65 | "(1 + (2 * 3))",
66 | "(5 + 4)"
67 | ],
68 | "1 + 2 * 3;\n5 + 4;",
69 | "basic arithmetic precedence works"
70 | )
71 |
72 | def test_parse_not(self):
73 | self.assertParse(
74 | ["not[not[a]]"],
75 | "not not a;"
76 | )
77 |
78 | def test_parse_funcall(self):
79 | self.assertParse(
80 | ["foo[bar]"],
81 | "foo(bar);",
82 | "function application works"
83 | )
84 |
85 | def test_parse_then_funcall(self):
86 | self.assertParse(
87 | ["(foo then bar[baz])"],
88 | "foo then bar(baz);"
89 | )
90 |
91 | def test_parse_lassoc_add(self):
92 | self.assertParse(
93 | ["((1 + 2) + 3)"],
94 | "1 + 2 + 3;"
95 | )
96 |
97 | def test_parse_lassoc_mul(self):
98 | self.assertParse(
99 | ['(((1 / 2) * 3) % 4)'],
100 | "1 / 2 * 3 % 4;"
101 | )
102 |
103 | def test_parse_lassoc_and(self):
104 | self.assertParse(
105 | ["xor[or[and[a, b], c], d]"],
106 | "a and b or c xor d;"
107 | )
108 |
109 | def test_parse_rassoc_then(self):
110 | self.assertParse(
111 | ['(a then (b then c))'],
112 | 'a then b then c;'
113 | )
114 |
115 | def test_parse_rassoc_cons(self):
116 | self.assertParse(
117 | ['(a @ (b @@ c))'],
118 | 'a @ b @@ c;'
119 | )
120 |
121 | def test_parse_then_funcall_2(self):
122 | self.assertParse(
123 | ["(foo then bar)[baz]"],
124 | "(foo then bar)(baz);"
125 | )
126 |
127 | def test_parse_nested_funcall(self):
128 | self.assertParse(
129 | ["foo[a][b]"],
130 | "foo(a)(b);"
131 | )
132 |
133 | def test_parse_typedef_1(self):
134 | self.assertParse(
135 | ['typedef(lst[#t] : [pair[#t, lst[#t]], null])'],
136 | "typedef lst(#t) { pair(#t, lst(#t)) | null }"
137 | # ---- ---- : type constructors
138 | # ------ - ------ : types
139 | # - - - : typevars
140 | )
141 |
142 | def test_parse_typedef_2(self):
143 | self.assertParse(
144 | ['typedef(colour : [red, green, blue])'],
145 | "typedef colour { red | green | blue }"
146 | )
147 |
148 | def test_parse_typedef_3(self):
149 | self.assertParse(
150 | ['typedef(union[#t, #u] : [first[#t], second[#u]])'],
151 | "typedef union(#t, #u) { first(#t) | second(#u) }"
152 | )
153 |
154 | def test_parse_typedef_4(self):
155 | self.assertParse(
156 | ['typedef(funny[#t, #u] : [pair[#t, lst[lst[#u]]]])'],
157 | "typedef funny(#t, #u) { pair(#t, lst(lst(#u))) }"
158 | )
159 |
160 | def test_parse_composite_1(self):
161 | self.assertParse(
162 | ['define map = fn {ComponentLambda [f, null]: { { null } } ComponentLambda [f, pair[h, t]]: { { pair[f[h], map[f, t]] } }}'],
163 | """
164 | fn map {
165 | (f, null) { null }
166 | (f, pair(h, t)) { pair(f(h), map(f, t)) }
167 | }
168 | """
169 | )
170 |
171 | def test_parse_composite_2(self):
172 | self.assertParse(
173 | ['define factorial = fn {ComponentLambda [0]: { { 1 } } ComponentLambda [n]: { { (n * factorial[(n - 1)]) } }}'],
174 | """
175 | fn factorial {
176 | (0) { 1 }
177 | (n) { n * factorial(n - 1) }
178 | }
179 | """
180 | )
181 |
182 | def test_parse_composite_3(self):
183 | self.assertParse(
184 | ['define len = fn {ComponentLambda [[]]: { { 0 } } ComponentLambda [[_ . t]]: { { (1 + len[t]) } }}'],
185 | """
186 | fn len {
187 | ([]) { 0 }
188 | (_ @ t) { 1 + len(t) }
189 | }
190 | """
191 | )
192 |
193 | def test_parse_sub_function_arg_2(self):
194 | self.assertParse(
195 | ['define foo = fn {ComponentLambda [[a, b]]: { { b } }}'],
196 | '''
197 | fn foo ([a, b]) {
198 | b
199 | }
200 | '''
201 | )
202 |
--------------------------------------------------------------------------------
/pyscheme/trace.py:
--------------------------------------------------------------------------------
1 | # PyScheme lambda language written in Python
2 | #
3 | # Expressions (created by the parser) for evaluation
4 | #
5 | # Copyright (C) 2018 Bill Hails
6 | #
7 | # This program is free software: you can redistribute it and/or modify
8 | # it under the terms of the GNU General Public License as published by
9 | # the Free Software Foundation, either version 3 of the License, or
10 | # (at your option) any later version.
11 | #
12 | # This program is distributed in the hope that it will be useful,
13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | # GNU General Public License for more details.
16 | #
17 | # You should have received a copy of the GNU General Public License
18 | # along with this program. If not, see .
19 |
20 | stack = []
21 |
22 | def trace(method):
23 | def wrapper(*args, **kwargs):
24 | global stack
25 | stack += [args[0]]
26 | ret = method(*args, **kwargs)
27 | stack.pop()
28 | return ret
29 | return wrapper
30 |
--------------------------------------------------------------------------------
/pyscheme/types.py:
--------------------------------------------------------------------------------
1 | # PyScheme lambda language written in Python
2 | #
3 | # Expressions (created by the parser) for evaluation
4 | #
5 | # Copyright (C) 2018 Bill Hails
6 | #
7 | # This program is free software: you can redistribute it and/or modify
8 | # it under the terms of the GNU General Public License as published by
9 | # the Free Software Foundation, either version 3 of the License, or
10 | # (at your option) any later version.
11 | #
12 | # This program is distributed in the hope that it will be useful,
13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | # GNU General Public License for more details.
16 | #
17 | # You should have received a copy of the GNU General Public License
18 | # along with this program. If not, see .
19 |
20 | from typing import Callable, Union, TypeVar, List
21 |
22 | S = TypeVar('S')
23 | Maybe = Union[S, None]
24 |
25 | # Promises are used by the trampoline in the repl.
26 | # A promise is either None or a callable of no arguments that returns a Promise, or a list of those callables.
27 | # If the trampoline sees None it will terminate the current thread (used by `exit).
28 | # If the trampoline sees a list, it will install each element as a separate thread (used by `spawn`).
29 | CallablePromise = Callable[[], 'Promise']
30 | Promise = Union[None, CallablePromise, List[CallablePromise]]
31 |
32 | # A Continuation is a callable that takes an Expr and an Amb (backtracking continuation)
33 | # and returns a promise.
34 | Continuation = Callable[[Maybe['expr.Expr'], 'Amb'], Promise]
35 |
36 | # An Amb (backtracking continuation) is a callable of no arguments that returns a Promise to resume a
37 | # chronologically previous operation.
38 | Amb = Callable[[], Promise]
--------------------------------------------------------------------------------
/run_coverage.py:
--------------------------------------------------------------------------------
1 | # PyScheme lambda language written in Python
2 | #
3 | # Copyright (C) 2018 Bill Hails
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License
16 | # along with this program. If not, see .
17 |
18 | import unittest
19 | import coverage
20 | import os
21 |
22 | def cov():
23 | """Runs the unit tests with coverage."""
24 | COV = coverage.Coverage(omit='pyscheme/tests/*/*.py')
25 | COV.start()
26 | tests = unittest.TestLoader().discover('pyscheme/tests')
27 | result = unittest.TextTestRunner(verbosity=2).run(tests)
28 | if result.wasSuccessful():
29 | COV.stop()
30 | COV.save()
31 | print('Coverage Summary:')
32 | COV.report()
33 | basedir = os.path.abspath(os.path.dirname(__file__))
34 | covdir = os.path.join(basedir, 'htmlcov')
35 | COV.html_report(directory=covdir)
36 | print('HTML version: file://%s/index.html' % covdir)
37 | COV.erase()
38 | return 0
39 | return 1
40 |
41 | cov()
--------------------------------------------------------------------------------