├── .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() --------------------------------------------------------------------------------