├── .gitignore ├── LICENSE ├── README.md ├── examples ├── basics.pylambda ├── binop.pylambda ├── bool_binop.pylambda ├── conditions.pylambda ├── factorial.pylambda ├── input.pylambda ├── loops.pylambda ├── printing.pylambda └── unop.pylambda ├── pylambda.py ├── specification.md ├── src ├── __init__.py ├── ast.py ├── emitter.py ├── lc_constants.py ├── lc_defs │ ├── README.md │ ├── lc_constants_macro.py │ └── lc_defs.py ├── parser.py ├── typechecker.py ├── typed_ast.py └── util.py ├── test.py ├── tests ├── assignment.pylambda ├── binop.pylambda ├── bool_binop.pylambda ├── elif.pylambda ├── else.pylambda ├── if.pylambda ├── input.pylambda ├── parsing_errors │ ├── bad_assign.pylambda │ ├── bad_assign2.pylambda │ ├── binop.pylambda │ ├── else.pylambda │ ├── if.pylambda │ ├── if2.pylambda │ ├── if3.pylambda │ ├── reserved.pylambda │ ├── reserved2.pylambda │ ├── unmatched1.pylambda │ ├── unmatched2.pylambda │ ├── unop.pylambda │ └── while.pylambda ├── printing.pylambda ├── scope.pylambda ├── skip.pylambda ├── type_errors │ ├── binop.pylambda │ ├── binop2.pylambda │ ├── binop3.pylambda │ ├── binop4.pylambda │ ├── binop5.pylambda │ ├── elif.pylambda │ ├── elif1.pylambda │ ├── elif2.pylambda │ ├── else.pylambda │ ├── else2.pylambda │ ├── if.pylambda │ ├── if2.pylambda │ ├── input.pylambda │ ├── unop.pylambda │ ├── unop2.pylambda │ ├── var.pylambda │ ├── var2.pylambda │ └── while.pylambda ├── unop.pylambda └── while.pylambda └── tools ├── README.md ├── pylambda_to_python.py └── test_util.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | # Cruft 132 | a.py -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Checkmate50 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PyLambda 2 | 3 | ## Overview 4 | 5 | PyLambda is a language and compiler for if you want to mess around with various lambda expression encodings (as emitted into _valid_ python code). In particular, PyLambda takes in a simple imperative language frontend and produces terms of lambda calculus to be interpreted by Python. The full specification of the language can be seen in [specification.md](https://github.com/Checkmate50/pylambda/blob/main/specification.md) 6 | 7 | All expressions generated by PyLambda are either boilerplate, definitions, interpretations, or, of course, lambda expressions. There are a few cases where exceptions come up, but that's about it. 8 | 9 | WARNING: This is not a serious language and should not be used as such. Code will be slow and may cause python to crash very easily. Any number over 1000 or so, along with significant control flow...will crash. So keep that in mind 10 | 11 | ## Installation and Execution 12 | 13 | To install PyLambda, simply `git clone` this GitHub and have Python 3.7+ installed (tested on 3.9.4, may not work below this) 14 | 15 | To test that the installation works as intended, run: 16 | 17 | `python test.py tests` 18 | 19 | To interpret a pylambda file _without any input_, run: 20 | 21 | `python pylambda.py file.pylambda | python -` 22 | 23 | To write the compiled results to a file (for inspection or running a code with input): 24 | 25 | `python pylambda.py file.pylambda > out.py` 26 | 27 | To get a (somewhat) readable translation, use the `--debug` flag, e.g.: 28 | 29 | `python pylambda.py file.pylambda --debug > out.py` 30 | 31 | All errors can be reported directly to Checkmate#0888 on discord or filed as an issue in this GitHub 32 | 33 | ## Examples 34 | 35 | To understand the basics of the lambda calculus and encodings, I recommend either a [good PL book](https://cis.upenn.edu/~bcpierce/tapl/) or the [intro Wikipedia article](https://en.wikipedia.org/wiki/Lambda_calculus). 36 | 37 | PyLambda can generate code for a variety of standard commands. For example, you may write 38 | 39 | ``` 40 | print true; 41 | ``` 42 | 43 | PyLambda will produce the valid python code 44 | 45 | ```py 46 | # Boilerplate 47 | print((lambda _x : lambda _y : _x) (True) (False)) 48 | ``` 49 | 50 | Which is just the usual lambda calculus encoding of True. When run, this produces, of course, the single term `True`. 51 | 52 | A more complicated example may include an operation, say `print true or false;`. PyLambda will produce: 53 | 54 | ```py 55 | # boilerplate 56 | print((lambda _b1 : lambda _b2 : ((lambda _b : lambda _c : lambda _d : (_b (_c) (_d))) (_b1) ((lambda _x : lambda _y : _x)) (_b2))) ((lambda _x : lambda _y : _x)) ((lambda _x : lambda _y : _y)) (True) (False)) 57 | ``` 58 | 59 | Which can be read as `if b1 then true else b2`, where `b1=true` and `b2=false` in this case. If in debug mode, in particular, this same code will produce: 60 | 61 | ```py 62 | # boilerplate and declarations 63 | _value = LOR (TRUE) (FALSE) 64 | print(_value (True) (False)) 65 | ``` 66 | 67 | Which highlights the "interpretive" nature of the PyLambda compiler on constants. 68 | 69 | ## Flags 70 | 71 | PyLambda, at the time of this writing, supports three flags. All flags must be given after the filename, in any order: 72 | 73 | | Flag | Description | 74 | | ----------- | ----------- | 75 | | --debug | Outputs "readable code" | 76 | | --raw | Prints raw lambdas | 77 | | --no-input | Replaces input with 0 | 78 | 79 | In more detail, raw causes the resulting python code to print out the lambda expression of each term rather than the actual value. 80 | 81 | No-input replaces all cases of `input x` with `x = ZERO`, for example, where `ZERO` is the Lambda-calculus encoding of 0. -------------------------------------------------------------------------------- /examples/basics.pylambda: -------------------------------------------------------------------------------- 1 | // Only integers and booleans 2 | // Print and input both work 3 | // Input requires integer input 4 | // Every line must end with a semicolon 5 | 6 | print true; 7 | input x; 8 | print x; 9 | -------------------------------------------------------------------------------- /examples/binop.pylambda: -------------------------------------------------------------------------------- 1 | // 6 binary integer ops: +, -, *, ^, //, % 2 | 3 | print 1 + 2; 4 | print (2 - 1); 5 | x = 3 - 1; 6 | print x * 3; 7 | print 3 + x ^ 2; 8 | print (3 + x) ^ 2; 9 | print 3 - 2 - 1; 10 | print 3 - (2 - 1); 11 | print 5 / 3; 12 | print 5 % 3; -------------------------------------------------------------------------------- /examples/bool_binop.pylambda: -------------------------------------------------------------------------------- 1 | print not true; 2 | print 1 == 1; 3 | print 5 > 3; 4 | print 5 < 3; 5 | x = 2 + 3; 6 | print x >= 4; 7 | print x <= 1; 8 | print not true; 9 | print true and false; 10 | print -1 < 3 or true == false; -------------------------------------------------------------------------------- /examples/conditions.pylambda: -------------------------------------------------------------------------------- 1 | x = 5; 2 | y = true; 3 | if true { 4 | y = false; 5 | if y { 6 | print y; 7 | } elif x > 3 { 8 | print true; 9 | x = x + 1; 10 | } else { 11 | print false; 12 | x = x - 1; 13 | } 14 | y = not y; 15 | if y { 16 | print y; 17 | x = x + 3; 18 | } else { 19 | print false; 20 | } 21 | } 22 | print x; -------------------------------------------------------------------------------- /examples/factorial.pylambda: -------------------------------------------------------------------------------- 1 | x = 4; // this fails on my machine with x = 5 2 | result = 1; 3 | while x > 0 { 4 | result = result * x; 5 | x = x - 1; 6 | } 7 | print result; -------------------------------------------------------------------------------- /examples/input.pylambda: -------------------------------------------------------------------------------- 1 | input x; 2 | print x; -------------------------------------------------------------------------------- /examples/loops.pylambda: -------------------------------------------------------------------------------- 1 | x = 3; 2 | while x > 0 { 3 | y = 5; 4 | while y > 0 { 5 | print y; 6 | y = y - 1; 7 | } 8 | x = x - 1; 9 | } 10 | print true; -------------------------------------------------------------------------------- /examples/printing.pylambda: -------------------------------------------------------------------------------- 1 | print true or false; -------------------------------------------------------------------------------- /examples/unop.pylambda: -------------------------------------------------------------------------------- 1 | print not true; 2 | print -5; 3 | print --7; 4 | -------------------------------------------------------------------------------- /pylambda.py: -------------------------------------------------------------------------------- 1 | from sys import argv 2 | from src.parser import parse_file 3 | from src.typechecker import typecheck_program 4 | from src.emitter import emit, CLI 5 | 6 | def help(): 7 | print("Expected args: python pylambda.py name_of_file.pylambda [--debug | --raw | --no-input]") 8 | 9 | def main(): 10 | if len(argv) < 2: 11 | help() 12 | exit() 13 | cli = CLI() 14 | if len(argv) > 2: 15 | cli.set_args(argv[2:]) 16 | program = parse_file(argv[1]) 17 | typed = typecheck_program(program) 18 | emit(typed, cli) 19 | 20 | if __name__=="__main__": 21 | main() -------------------------------------------------------------------------------- /specification.md: -------------------------------------------------------------------------------- 1 | # PyLambda Language Specification 2 | 3 | ## Overview 4 | 5 | This document outlines the core syntax and language design of PyLambda. How values are represented, some basic operations, and how control flow works are all specified here. I would appreciate hearing about any ambiguity in this document in the issues! 6 | 7 | All lambda calculus definitions used can be inspected by looking at the boilerplate definitions emitted with the `--debug` flag. They are also defined in reasonably nice syntax in [src\lc_defs\lc_defs.py](https://github.com/Checkmate50/pylambda/blob/main/src/lc_defs/lc_defs.py), though note that this is not a "real" Python file. 8 | 9 | ## Syntax 10 | 11 | The syntax of PyLambda is pretty much that of IMP (`examples` is the best reference for current syntax). Internally, this syntax is separated into commands and expressions. So, for example: 12 | 13 | ``` 14 | c := print e | input e | c1 ; c2 | ... 15 | e := x | e1 + e2 | e1 ^ e2 | ... 16 | ``` 17 | 18 | Note that bitwise operations are nonsensical and so not supported, so `^` denotes exponentiation. Note also that `e1 / e2` produces integer division currently, though real division is in the works at the time of writing. 19 | 20 | ## Type Interpretations (how it works) 21 | 22 | There are two types of values supported in PyLambda: integers and booleans. Since (ironically), you cannot currently define or use lambdas or functions in the PyLambda language, these are actually the full set of supported types along with the unit type for commands. Values cannot be of the unit type, and expressions cannot operate over unit values syntactically. 23 | 24 | ### Booleans 25 | 26 | Booleans are represented in the usual way to work with if-else expressions. In particular, we define: 27 | 28 | ``` 29 | TRUE := lambda x : lambda y : x 30 | FALSE := lambda x : lambda y : y 31 | ``` 32 | 33 | We observe that if-then-else follows from these definitions somewhat trivially: 34 | 35 | ``` 36 | LIF := lambda b : lambda c1 : lambda c2 : b (c1) (c2) 37 | ``` 38 | 39 | Which in turn allows straightforward definitions of boolean operations: 40 | 41 | ``` 42 | NOT := lambda b : LIF (b) (false) (true) 43 | OR := lambda b1 : lambda b2 : LIF (b1) (true) (b2) 44 | AND := lambda b1 : lambda b2 : LIF (b1) (b2) (false) 45 | ... 46 | ``` 47 | 48 | ### Pairs 49 | 50 | To define numbers, we first need the definition of _pairs_. These will also be helpful when arrays are eventually included in the language. Pairs are defined per [Wikipedia's definition](https://en.wikipedia.org/wiki/Lambda_calculus): 51 | 52 | ``` 53 | PAIR := lambda x : lambda y : lambda f : f (x) (y) 54 | FIRST := lambda p : p (TRUE) 55 | SECOND := lambda p : p (TRUE) 56 | ``` 57 | 58 | ### Church Numerals 59 | 60 | We use as a baseline for numbers the church numeral representation. In particular, we define the following, where `C` denotes "Church": 61 | 62 | ``` 63 | C0 := lambda f : lambda x : x 64 | C1 := lambda f : lambda x : f (x) 65 | C2 := lambda f : lambda x : f (f (x)) 66 | ``` 67 | 68 | We use the definitions given by the usual [Wikipedia article](https://en.wikipedia.org/wiki/Lambda_calculus) for operations. All church operations are prepended with a `C` in PyLambda. 69 | 70 | ### Integers 71 | 72 | PyLambda represents integers as pairs of church numerals `(a, b)`. When interpreting these integers, we interpret `n := a - b`. More precisely, for a given integer `n`, we print with the following Python interpretation: 73 | 74 | ```py 75 | (FIRST(n) (lambda x : x + 1) (0)) - (SECOND(n) (lambda x : x + 1) (0)) 76 | ``` 77 | 78 | This complicates our arithmetic operations somewhat. Some are relatively simple, such as negation and addition: 79 | 80 | ``` 81 | NEG := lambda (a, b) : (b, a) 82 | PLUS := lambda (a, b) : lambda (c, d) : (a + c, b + d) 83 | ``` 84 | 85 | Which can be written in the lambda calculus as follows: 86 | 87 | ``` 88 | NEG := lambda n : PAIR (SECOND (n)) (FIRST (n)) 89 | PLUS := lambda n : lambda m : PAIR (CPLUS (FIRST (n)) (FIRST (m))) (CPLUS (SECOND (n)) (SECOND (m))) 90 | ``` 91 | 92 | Multiplication, division, and exponentiation are more complicated. They will not be written out here, but to give a sense of how this might work, multiplication is defined as follows: 93 | 94 | ``` 95 | MULT := lambda (a, b) : lambda (c, d) : (a * c + b * d, a * d + b * c) 96 | ``` 97 | 98 | Finally, comparison operations with integers are fairly complicated. We use the well-known principle of church LEQ as a baseline, but we need to extend this to pairs. This is done with the helper function COLLAPSE, which returns `if a <= b then b-a else a-b`. This allows us to define LEQ, for example: 99 | 100 | ``` 101 | LEQ := lambda (a, b) : lambda (c, d) : if a <= b and c <= d then COLLAPSE(a, b) <= COLLAPSE(c, d) else ... 102 | ``` 103 | 104 | ## Control Flow 105 | 106 | Finally, the last bit of magic we do in PyLambda is allowing control flow. This is done with multiple lines in Python code (though an intended feature is one-liners at some point) via declaring and using Python functions. Note that using `if` and `while` statements _in Python_ would fundamentally defeat PyLambda's goal of being as silly as possible, so they are avoided. In particular, we use a boilerplate class for scope along with some function semantic magic to get the intended if/while behavior: 107 | 108 | ``` 109 | IF := lambda b : lambda c : LIF (b) (c()) (()) 110 | WHILE := lambda b : lambda c : Z(lambda rec : LIF (b) (rec(c())) (())) 111 | ``` 112 | 113 | Note that we both use the unit value here `()` since commands can't be changed and the Z-combinator, as usually defined. We use the Z-combinator instead of the Y-combinator since Python has greedy lambda evaluation. 114 | 115 | The last trick we do is for emitting If/Elif/Else chains, which basically is more of a compiler trick than a lambda calculus trick. If/Elif/Else chains must be Seqs of each other in the PyLambda AST, which means that we can look ahead and replace the `else` condition of the `IF` definition with the appropriate follow-up. This can, of course, be chained indefinitely. -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Checkmate50/pylambda/c5e0f88823114b159a73be440f14ca683f51d0e7/src/__init__.py -------------------------------------------------------------------------------- /src/ast.py: -------------------------------------------------------------------------------- 1 | import src.util as util 2 | from typing import Union 3 | from src.typed_ast import Typed 4 | 5 | # Yes, I will manually typecheck all of these cause they're _so_ important to get right 6 | 7 | class Expr(util.BaseClass): 8 | pass 9 | 10 | class Value(util.BaseClass): 11 | pass 12 | 13 | class Statement(util.BaseClass): 14 | pass 15 | 16 | class Number(Value): 17 | def __init__(self, v : int): 18 | util.typecheck(v, int) 19 | self.v = v 20 | def __repr__(self): 21 | return "Num " + str(self.v) 22 | 23 | class Bool(Value): 24 | def __init__(self, v : bool): 25 | util.typecheck(v, bool) 26 | self.v = v 27 | def __repr__(self): 28 | return "Bool " + str(self.v) 29 | 30 | class Op(util.BaseClass): 31 | def __init__(self, op : str): 32 | util.typecheck(op, str) 33 | self.op = op 34 | def __repr__(self): 35 | return "Op " + str(self.op) 36 | 37 | class Const(Expr): 38 | def __init__(self, v : Union[Value, Typed[Value]]): 39 | util.typecheck_any(v, [Value, Typed]) 40 | self.v = v 41 | def __repr__(self): 42 | return "Const " + str(self.v) 43 | 44 | class Var(Expr): 45 | def __init__(self, v : str): 46 | util.typecheck(v, str) 47 | self.v = v 48 | def __repr__(self): 49 | return "Var " + str(self.v) 50 | 51 | class Unop(Expr): 52 | def __init__(self, op : Op, exp : Union[Expr, Typed[Expr]]): 53 | util.typecheck(op, Op) 54 | util.typecheck_any(exp, [Expr, Typed]) 55 | self.op = op 56 | self.exp = exp 57 | def __repr__(self): 58 | return "Unop " + str(self.op) + "(" + str(self.exp) + ")" 59 | 60 | class Binop(Expr): 61 | def __init__(self, op : Op, left : Union[Expr, Typed[Expr]], right : Union[Expr, Typed[Expr]]): 62 | # Note that op comes first cause of precedence stack crap 63 | util.typecheck(op, Op) 64 | util.typecheck_any(left, [Expr, Typed]) 65 | util.typecheck_any(right, [Expr, Typed]) 66 | self.op = op 67 | self.left = left 68 | self.right = right 69 | def __repr__(self): 70 | return "Binop " + str(self.op) + "(" + str(self.left) + ", " + str(self.right) + ")" 71 | 72 | class Skip(Statement): 73 | def __init__(self, ln : int): 74 | util.typecheck(ln, int) 75 | self.ln = ln 76 | def __repr__(self): 77 | return "Skip\n" 78 | 79 | class Assign(Statement): 80 | def __init__(self, ln : int, var : Union[Var, Typed[Var]], exp : Union[Expr, Typed[Expr]]): 81 | util.typecheck(ln, int) 82 | util.typecheck_any(var, [Var, Typed]) 83 | util.typecheck_any(exp, [Expr, Typed]) 84 | 85 | self.ln = ln 86 | self.var = var 87 | self.exp = exp 88 | def __repr__(self): 89 | return "Assign\n" + str(self.var) + "\n=\n" + str(self.exp) 90 | 91 | class Seq(Statement): 92 | def __init__(self, ln : int, s1 : Union[Statement, Typed[Statement]], s2 : Union[Statement, Typed[Statement]] = Skip(-1)): 93 | util.typecheck(ln, int) 94 | util.typecheck_any(s1, [Statement, Typed]) 95 | util.typecheck_any(s2, [Statement, Typed]) 96 | 97 | self.ln = ln 98 | self.s1 = s1 99 | self.s2 = s2 100 | def __repr__(self): 101 | return "" + str(self.s1) + "\n;\n" + str(self.s2) + "" 102 | 103 | class If(Statement): 104 | def __init__(self, ln : int, b : Union[Expr, Typed[Expr]], s : Union[Statement, Typed[Statement]]): 105 | util.typecheck(ln, int) 106 | util.typecheck_any(b, [Expr, Typed]) 107 | util.typecheck_any(s, [Statement, Typed]) 108 | 109 | self.ln = ln 110 | self.b = b 111 | self.s = s 112 | def __repr__(self): 113 | return "If\n" + str(self.b) + "\n{\n" + str(self.s) + "}" 114 | 115 | class Elif(Statement): 116 | def __init__(self, ln : int, b : Union[Expr, Typed[Expr]], s : Union[Statement, Typed[Statement]]): 117 | util.typecheck(ln, int) 118 | util.typecheck_any(b, [Expr, Typed]) 119 | util.typecheck_any(s, [Statement, Typed]) 120 | 121 | self.ln = ln 122 | self.b = b 123 | self.s = s 124 | def __repr__(self): 125 | return "Elif\n" + str(self.b) + "\n{\n" + str(self.s) + "}" 126 | 127 | class Else(Statement): 128 | def __init__(self, ln : int, s : Union[Statement, Typed[Statement]]): 129 | util.typecheck(ln, int) 130 | util.typecheck_any(s, [Statement, Typed]) 131 | 132 | self.ln = ln 133 | self.s = s 134 | def __repr__(self): 135 | return "Else" + "\n{\n" + str(self.s) + "}" 136 | 137 | class While(Statement): 138 | def __init__(self, ln : int, b : Union[Expr, Typed[Expr]], s : Union[Statement, Typed[Statement]]): 139 | util.typecheck(ln, int) 140 | util.typecheck_any(b, [Expr, Typed]) 141 | util.typecheck_any(s, [Statement, Typed]) 142 | 143 | self.ln = ln 144 | self.b = b 145 | self.s = s 146 | def __repr__(self): 147 | return "While\n" + str(self.b) + "\n{\n" + str(self.s) + "}" 148 | 149 | class Print(Statement): 150 | def __init__(self, ln : int, exp : Union[Expr, Typed[Expr]]): 151 | util.typecheck(ln, int) 152 | util.typecheck_any(exp, [Expr, Typed]) 153 | 154 | self.ln = ln 155 | self.exp = exp 156 | def __repr__(self): 157 | return "Print\n" + str(self.exp) 158 | 159 | class Input(Statement): 160 | def __init__(self, ln : int, var : Union[Var, Typed[Var]]): 161 | util.typecheck(ln, int) 162 | util.typecheck_any(var, [Var, Typed]) 163 | 164 | self.ln = ln 165 | self.var = var 166 | def __repr__(self): 167 | return "Input\n" + str(self.var) 168 | 169 | class Program(util.BaseClass): 170 | def __init__(self, s : Union[Statement, Typed[Statement]]): 171 | util.typecheck_any(s, [Statement, Typed]) 172 | self.s = s 173 | def __repr__(self): 174 | return "Program\n" + str(self.s) -------------------------------------------------------------------------------- /src/emitter.py: -------------------------------------------------------------------------------- 1 | import src.ast as ast 2 | from src.util import * 3 | from src.typed_ast import Typed, IntType, BoolType, UnitType, BaseType 4 | from typing import TypeVar, Union, Optional 5 | from src.lc_constants import * 6 | 7 | internal_consts = [] 8 | 9 | T = TypeVar('T') 10 | 11 | def check_typed(term : Union[BaseClass, Typed[T]]) -> Typed[T]: 12 | typecheck(term, Typed) 13 | return term 14 | 15 | def print_assignment(var : str, value : str, context : EmitContext): 16 | print(f"{var} = {value}", end="") 17 | 18 | def interpret_bool(b : str, context : EmitContext): 19 | if context.cli.contains("raw"): 20 | print(f"{b}", end="") 21 | else: 22 | print(f"{b} (True) (False)", end="") 23 | 24 | def int_element_str(i : str, op : str, context : EmitContext): 25 | return f"({op(i, context)} (lambda x : x + 1) (0))" 26 | 27 | def interpret_int(i : str, context : EmitContext): 28 | if context.cli.contains("raw"): 29 | print(f"{i}", end="") 30 | else: 31 | print(int_element_str(i, first, context) + " - " + int_element_str(i, second, context), end="") 32 | 33 | def string_of_value(val : Typed[ast.Value], context : EmitContext) -> str: 34 | if isinstance(val.element, ast.Number): 35 | num = bin(val.element.v)[2:] 36 | result = zero(context) 37 | while len(num) > 0: 38 | next = result if result == zero(context) else mult(result, two(context), context) 39 | result = succ(next, context) if num[0] == "1" else next 40 | num = num[1:] 41 | return f"{result}" 42 | if isinstance(val.element, ast.Bool): 43 | return f"{true(context) if val.element.v else false(context)}" 44 | raise InternalException("Unknown value " + str(val)) 45 | 46 | def string_of_unop(exp : Typed[ast.Unop], context : EmitContext) -> str: 47 | val = check_typed(exp.element.exp) 48 | val = string_of_expr(val, context) 49 | if exp.element.op.op == "-": 50 | return neg(val, context) 51 | if exp.element.op.op == "not": 52 | return lnot(val, context) 53 | 54 | def string_of_binop(exp : Typed[ast.Binop], context : EmitContext) -> str: 55 | left = check_typed(exp.element.left) 56 | right = check_typed(exp.element.right) 57 | left = string_of_expr(left, context) 58 | right = string_of_expr(right, context) 59 | if exp.element.op.op == "+": 60 | return plus(left, right, context) 61 | if exp.element.op.op == "-": 62 | return minus(left, right, context) 63 | if exp.element.op.op == "*": 64 | return mult(left, right, context) 65 | if exp.element.op.op == "/": 66 | return div(left, right, context) 67 | if exp.element.op.op == "%": 68 | return mod(left, right, context) 69 | if exp.element.op.op == "^": 70 | return exponent(left, right, context) 71 | if exp.element.op.op == "and": 72 | return land(left, right, context) 73 | if exp.element.op.op == "or": 74 | return lor(left, right, context) 75 | if exp.element.op.op == "==": 76 | return eq(left, right, context) 77 | if exp.element.op.op == "<": 78 | return lt(left, right, context) 79 | if exp.element.op.op == ">": 80 | return gt(left, right, context) 81 | if exp.element.op.op == "<=": 82 | return leq(left, right, context) 83 | if exp.element.op.op == ">=": 84 | return geq(left, right, context) 85 | raise InternalException("unknown op " + str(exp.element.op)) 86 | 87 | def string_of_expr(exp : Typed[ast.Expr], context : EmitContext) -> str: 88 | if isinstance(exp.element, ast.Const): 89 | return string_of_value(check_typed(exp.element.v), context) 90 | if isinstance(exp.element, ast.Var): 91 | return exp.element.v 92 | if isinstance(exp.element, ast.Unop): 93 | return string_of_unop(exp, context) 94 | if isinstance(exp.element, ast.Binop): 95 | return string_of_binop(exp, context) 96 | 97 | def str_of_vars(context : EmitContext) -> str: 98 | return f"({', '.join(context.vars)})" 99 | 100 | def emit_control_function(statement : Typed[ast.Statement], context : EmitContext) -> str: 101 | print(" "*context.scope,end='') 102 | fn_name = f"_{context.fn_count}" 103 | args = str_of_vars(context) 104 | print(f"def {fn_name+args}:") 105 | emit_statement(statement, context.copy()) # increments scope by one 106 | context.fn_count += 1 107 | return fn_name 108 | 109 | def emit_assign(statement : Typed[ast.Assign], context : EmitContext): 110 | var = check_typed(statement.element.var) 111 | exp = check_typed(statement.element.exp) 112 | print(" "*context.scope,end='') 113 | if var.element.v in internal_consts: 114 | raise Exception("Compile-time error: use of reserved name " + var.element.v) 115 | if var.element.v in context.vars: 116 | print(f"{var.element.v}.val = {string_of_expr(exp, context)}",end="") 117 | else: 118 | context.vars.append(var.element.v) 119 | print(f"{var.element.v} = _Var({string_of_expr(exp, context)})",end="") 120 | 121 | def emit_seq(statement : ast.Seq, context : EmitContext): 122 | s1 = check_typed(statement.s1) 123 | s2 = check_typed(statement.s2) 124 | emit_statement(s1, context, s2) 125 | if not isinstance(s1.element, ast.Elif) and not isinstance(s1.element, ast.Else): 126 | print() 127 | emit_statement(s2, context) 128 | 129 | def get_chain(current : Optional[Typed[ast.Statement]], context : EmitContext, follows : Optional[Typed[ast.Statement]] = None) -> str: 130 | if current is None: 131 | return 'lambda:()' 132 | if isinstance(current.element, ast.Seq): 133 | return f"{get_chain(current.element.s1, context, current.element.s2)}" 134 | elif isinstance(current.element, ast.Else): 135 | s = check_typed(current.element.s) 136 | fn_name = emit_control_function(s, context) 137 | args = str_of_vars(context) 138 | return f'lambda:'+fn_name+args 139 | elif isinstance(current.element, ast.Elif): 140 | s = check_typed(current.element.s) 141 | b = check_typed(current.element.b) 142 | fn_name = emit_control_function(s, context) 143 | args = str_of_vars(context) 144 | return f"{lif(string_of_expr(b, context), 'lambda:'+fn_name+args, get_chain(follows, context), context)}" 145 | return 'lambda:()' 146 | 147 | def emit_if_chain(statement : ast.If, follows : Optional[Typed[ast.Statement]], context : EmitContext): 148 | s = check_typed(statement.s) 149 | b = check_typed(statement.b) 150 | fn_name = emit_control_function(s, context) 151 | chain = get_chain(follows, context) 152 | print(" "*context.scope,end='') 153 | args = str_of_vars(context) 154 | print(f"({lif(string_of_expr(b, context), 'lambda:'+fn_name+args, chain, context)})()",end='') 155 | 156 | def emit_while(statement : ast.While, context : EmitContext): 157 | s = check_typed(statement.s) 158 | b = check_typed(statement.b) 159 | fn_name = emit_control_function(s, context) 160 | print(" "*context.scope,end='') 161 | print(f"_dummy={false(context)}") # do this for argument passing reasons 162 | print(" "*context.scope,end='') 163 | args = str_of_vars(context) 164 | inner_lif = lif(string_of_expr(b, context), 'lambda:_rec('+fn_name+args+')', 'lambda:()', context) 165 | # ok, this is super evil, but we can actually recurse in this way cause Python is _greedy_ 166 | print(f"({z('lambda _rec: lambda _: (' + inner_lif + ')()', context)})(_dummy)") 167 | 168 | def emit_print(statement : ast.Print, context : EmitContext): 169 | exp = check_typed(statement.exp) 170 | result = string_of_expr(exp, context) 171 | if context.debug(): 172 | print(" "*context.scope,end='') 173 | print_assignment("_value", result, context) 174 | print() 175 | result = "_value" 176 | if context.cli.contains("raw"): 177 | print(" "*context.scope,end='') 178 | print("_func = ", end="") 179 | else: 180 | print(" "*context.scope,end='') 181 | print("print(", end="") 182 | if isinstance(exp.typ, BoolType): 183 | interpret_bool(result, context) 184 | elif isinstance(exp.typ, IntType): 185 | interpret_int(result, context) 186 | elif isinstance(exp.typ, UnitType): 187 | print("()",end="") 188 | else: 189 | raise UnimplementedException(statement) 190 | if context.cli.contains("raw"): 191 | print("\nprint(LambdaInspect()(_func).resolve(set(), set()).output(set()))", end="") 192 | else: 193 | print(")", end="") 194 | 195 | def emit_input(statement : ast.Input, context : EmitContext): 196 | var = check_typed(statement.var) 197 | if var.element.v in internal_consts: 198 | raise Exception("Compile-time error: use of reserved name " + var.element.v) 199 | if context.no_input(): 200 | print_assignment(var.element.v, zero(context), context) 201 | else: 202 | if context.debug(): 203 | print("# Ugh, we have to use actual Python to interpret this thing? How annoying") 204 | inp_bin = "bin(int(input()))[2:]" 205 | mul = mult('_rec(_bin[:-1])', two(context), context) 206 | check_zero = f'{mul} if _bin[-1] == "0" else {succ(mul, context)}' 207 | nxt_value = f"{check_zero}" 208 | rec = f"lambda _rec : lambda _bin : (({nxt_value}) if _bin else {zero(context)})" 209 | expr = f"({z(rec, context)})({inp_bin})" 210 | print_assignment(var.element.v, expr, context) 211 | 212 | def emit_statement(statement : Typed[ast.Statement], context : EmitContext, follows : Optional[Typed[ast.Statement]] = None): 213 | if isinstance(statement.element, ast.Skip): 214 | print() 215 | return 216 | if isinstance(statement.element, ast.Assign): 217 | emit_assign(statement, context) 218 | elif isinstance(statement.element, ast.Assign): 219 | emit_assign(statement.element, context) 220 | elif isinstance(statement.element, ast.Seq): 221 | emit_seq(statement.element, context) 222 | elif isinstance(statement.element, ast.If): 223 | emit_if_chain(statement.element, follows, context) 224 | elif isinstance(statement.element, ast.Elif) or isinstance(statement.element, ast.Else): 225 | pass # Already emitted by the original `if` statement 226 | elif isinstance(statement.element, ast.While): 227 | emit_while(statement.element, context) 228 | elif isinstance(statement.element, ast.Print): 229 | emit_print(statement.element, context) 230 | elif isinstance(statement.element, ast.Input): 231 | emit_input(statement.element, context) 232 | else: 233 | raise InternalException("Unimplemented statement " + str(statement)) 234 | 235 | def emit(program : Typed[ast.Program], cli : CLI): 236 | context = EmitContext(cli) 237 | print("import sys") 238 | print("sys.setrecursionlimit(100000) # This is fine, nothing can possibly go wrong") 239 | print("def _raise(x): raise x # stupid lambdas and stupid statements") 240 | print(scope_manager) 241 | if context.raw(): 242 | print(lc_inspector) 243 | if context.debug(): 244 | for c in functions_to_write: 245 | # Get c(context)[1:-2] to clean up unneeded parens 246 | s = c(context) 247 | if not s.startswith("("): 248 | raise InternalException("Badly formatted definition function " + s) 249 | s = s[1:-1] 250 | internal_consts.append(f"{retrieve_name_constant(c)[0].upper().split('_')[0]}") 251 | print_assignment(internal_consts[-1], s, context) 252 | print() 253 | context.interp = True 254 | emit_statement(check_typed(program.element.s), context) -------------------------------------------------------------------------------- /src/lc_constants.py: -------------------------------------------------------------------------------- 1 | 2 | """ 3 | This is an auto-generated file, DO NOT TOUCH 4 | Definitions are made by lc_defs.py and generated with lc_constants_macro.py 5 | To update, see lc_defs/README.md 6 | """ 7 | 8 | from src.util import * 9 | import inspect 10 | from typing import Set, List 11 | 12 | scope_manager = """class _Var: 13 | def __init__(self, val): 14 | self.val = val 15 | def __call__(self, other): # it's just a lambda ;) 16 | return self.val(other)""" 17 | 18 | lc_inspector = """ 19 | import inspect 20 | 21 | class LambdaInspect: 22 | def __init__(self, name : str = ""): 23 | self.name = name 24 | self.abstractions = [] 25 | self.callees = [] 26 | self.executing = False 27 | 28 | def __str__(self): 29 | return self.__repr__() 30 | 31 | def __repr__(self): 32 | return self.name 33 | 34 | def output(self, calls): 35 | if self in calls: 36 | return self.name 37 | calls.add(self) 38 | a = "" 39 | for layer in self.abstractions: 40 | for item in layer: 41 | a += f"lambda {item} : " 42 | if len(a) > 0: 43 | a += "(" 44 | clsp = ')' if len(a) > 0 else '' 45 | if not self.name: 46 | return f"{a}{' '.join(f'{x.output(calls)}' for x in self.callees)}{clsp}" 47 | 48 | spc = ' ' if len(self.callees) > 0 else '' 49 | cs = ' '.join(['({})'.format(x.output(calls)) for x in self.callees]) 50 | return f"{a}{self.name}{spc}{cs}{clsp}" 51 | 52 | def resolve(self, vars, calls): 53 | if self in calls: 54 | return self 55 | calls.add(self) 56 | for index in range(len(self.callees)): 57 | while inspect.isfunction(self.callees[index]): 58 | var = str(self.callees[index].__code__.co_varnames[0]) + "0" 59 | while var in vars: 60 | last_number = -1 61 | for i in range(len(var)-1,-1,-1): 62 | if not var[i].isdigit(): 63 | last_number = i 64 | break 65 | var = var[:last_number+1] + str(int(var[last_number+1:]) + 1) 66 | vars.add(var) 67 | self.abstractions[index].append(var) 68 | self.callees[index] = self.callees[index](LambdaInspect(var)) 69 | for callee in self.callees: 70 | if isinstance(callee, LambdaInspect): 71 | callee.resolve(vars, calls) 72 | return self 73 | 74 | def __call__(self, other): 75 | self.abstractions.append([]) 76 | self.callees.append(other) 77 | return self 78 | """ 79 | 80 | class CLI: 81 | def __init__(self, args : Set[str] = set()): 82 | self.set_args(args) 83 | 84 | def set_args(self, args : List[str]): 85 | for i in range(len(args)): 86 | if not args[i].startswith("--") or len(args[i]) < 2: 87 | raise Exception("Invalid argument " + str(args[i])) 88 | args[i] = args[i][2:] 89 | self.args = set(args) 90 | 91 | def contains(self, arg : str) -> bool: 92 | return arg in self.args 93 | 94 | def __repr__(self): 95 | return "CLI: " + str(self.args) 96 | 97 | class EmitContext(BaseClass): 98 | def __init__(self, cli : CLI): 99 | self.cli = cli 100 | self.interp = False 101 | self.fn_count = 0 102 | self.scope = 0 103 | self.vars = [] 104 | 105 | def raw(self): 106 | return self.cli.contains("raw") 107 | 108 | def debug(self): 109 | return self.cli.contains("debug") 110 | 111 | def no_input(self): 112 | return self.cli.contains("no-input") 113 | 114 | def copy(self): 115 | result = EmitContext(self.cli) 116 | result.interp = self.interp 117 | result.fn_count = 0 118 | result.scope = self.scope + 1 119 | result.vars = [var for var in self.vars] 120 | return result 121 | 122 | def __repr__(self): 123 | return "EMIT_CONTEXT" + str(self.cli) 124 | 125 | functions_to_write = [] 126 | 127 | def retrieve_name_constant(var : any) -> List[str]: 128 | callers_local_vars = inspect.currentframe().f_globals.items() 129 | return [var_name for var_name, var_val in callers_local_vars if var_val is var] 130 | 131 | # maybe_replace is too long to type 132 | def mr(val, context : EmitContext) -> str: 133 | if context.debug(): 134 | return f"{retrieve_name_constant(val)[0].upper().split('_')[0]}" 135 | def_name = val.__name__ 136 | if not def_name.endswith("_def"): 137 | def_name = f"{def_name}_def" 138 | return f"{globals()[def_name](context)}" 139 | 140 | def app_lc(f, args : List[str], context : EmitContext) -> str: 141 | return f"{mr(f, context)}{' ' if len(args) > 0 else ''}{' '.join(['({})'.format(x) for x in args])}" 142 | 143 | def emit_runtime_error_thunk(message : str, context : EmitContext) -> str: 144 | return f"lambda:_raise(RuntimeError(\"{message}\"))" 145 | 146 | def app_def(context : EmitContext) -> str: 147 | return f"(lambda _x : lambda _y : (_x (_y)))" 148 | def app(x : str, y : str, context : EmitContext) -> str: 149 | return f"{app_lc(app_def, [x, y], context)}" 150 | 151 | functions_to_write.append(app_def) 152 | 153 | def app2_def(context : EmitContext) -> str: 154 | return f"(lambda _x : lambda _y : lambda _z : (_x (_y) (_z)))" 155 | def app2(x : str, y : str, z : str, context : EmitContext) -> str: 156 | return f"{app_lc(app2_def, [x, y, z], context)}" 157 | 158 | functions_to_write.append(app2_def) 159 | 160 | def thunk_def(context : EmitContext) -> str: 161 | return f"(lambda _x : (lambda:_x))" 162 | def thunk(x : str, context : EmitContext) -> str: 163 | return f"{app_lc(thunk_def, [x], context)}" 164 | 165 | functions_to_write.append(thunk_def) 166 | 167 | def false_def(context : EmitContext) -> str: 168 | return f"(lambda _x : lambda _y : _y)" 169 | def false(context : EmitContext) -> str: 170 | return f"{app_lc(false_def, [], context)}" 171 | 172 | functions_to_write.append(false_def) 173 | 174 | def true_def(context : EmitContext) -> str: 175 | return f"(lambda _x : lambda _y : _x)" 176 | def true(context : EmitContext) -> str: 177 | return f"{app_lc(true_def, [], context)}" 178 | 179 | functions_to_write.append(true_def) 180 | 181 | def lif_def(context : EmitContext) -> str: 182 | return f"(lambda _b : lambda _c : lambda _d : (_b (_c) (_d)))" 183 | def lif(b : str, c : str, d : str, context : EmitContext) -> str: 184 | return f"{app_lc(lif_def, [b, c, d], context)}" 185 | 186 | functions_to_write.append(lif_def) 187 | 188 | def lnot_def(context : EmitContext) -> str: 189 | return f"(lambda _b : ({lif('_b', false(context), true(context), context)}))" 190 | def lnot(b : str, context : EmitContext) -> str: 191 | return f"{app_lc(lnot_def, [b], context)}" 192 | 193 | functions_to_write.append(lnot_def) 194 | 195 | def land_def(context : EmitContext) -> str: 196 | return f"(lambda _b1 : lambda _b2 : ({lif('_b1', '_b2', false(context), context)}))" 197 | def land(b1 : str, b2 : str, context : EmitContext) -> str: 198 | return f"{app_lc(land_def, [b1, b2], context)}" 199 | 200 | functions_to_write.append(land_def) 201 | 202 | def lor_def(context : EmitContext) -> str: 203 | return f"(lambda _b1 : lambda _b2 : ({lif('_b1', true(context), '_b2', context)}))" 204 | def lor(b1 : str, b2 : str, context : EmitContext) -> str: 205 | return f"{app_lc(lor_def, [b1, b2], context)}" 206 | 207 | functions_to_write.append(lor_def) 208 | 209 | def xor_def(context : EmitContext) -> str: 210 | return f"(lambda _b1 : lambda _b2 : ({lif('_b1', lnot('_b2', context), '_b2', context)}))" 211 | def xor(b1 : str, b2 : str, context : EmitContext) -> str: 212 | return f"{app_lc(xor_def, [b1, b2], context)}" 213 | 214 | functions_to_write.append(xor_def) 215 | 216 | def pair_def(context : EmitContext) -> str: 217 | return f"(lambda _x : lambda _y : (lambda _f : (_f (_x) (_y))))" 218 | def pair(x : str, y : str, context : EmitContext) -> str: 219 | return f"{app_lc(pair_def, [x, y], context)}" 220 | 221 | functions_to_write.append(pair_def) 222 | 223 | def first_def(context : EmitContext) -> str: 224 | return f"(lambda _x : (_x ({true(context)})))" 225 | def first(x : str, context : EmitContext) -> str: 226 | return f"{app_lc(first_def, [x], context)}" 227 | 228 | functions_to_write.append(first_def) 229 | 230 | def second_def(context : EmitContext) -> str: 231 | return f"(lambda _x : (_x ({false(context)})))" 232 | def second(x : str, context : EmitContext) -> str: 233 | return f"{app_lc(second_def, [x], context)}" 234 | 235 | functions_to_write.append(second_def) 236 | 237 | def czero_def(context : EmitContext) -> str: 238 | return f"(lambda _f : lambda _x : _x)" 239 | def czero(context : EmitContext) -> str: 240 | return f"{app_lc(czero_def, [], context)}" 241 | 242 | functions_to_write.append(czero_def) 243 | 244 | def csucc_def(context : EmitContext) -> str: 245 | return f"(lambda _n : (lambda _f : lambda _x : (_f (_n (_f) (_x)))))" 246 | def csucc(n : str, context : EmitContext) -> str: 247 | return f"{app_lc(csucc_def, [n], context)}" 248 | 249 | functions_to_write.append(csucc_def) 250 | 251 | def cplus_def(context : EmitContext) -> str: 252 | return f"(lambda _n : lambda _m : ({app2('_n', mr(csucc, context), '_m', context)}))" 253 | def cplus(n : str, m : str, context : EmitContext) -> str: 254 | return f"{app_lc(cplus_def, [n, m], context)}" 255 | 256 | functions_to_write.append(cplus_def) 257 | 258 | def cmult_def(context : EmitContext) -> str: 259 | return f"(lambda _n : lambda _m : (lambda _f : (_n (_m (_f)))))" 260 | def cmult(n : str, m : str, context : EmitContext) -> str: 261 | return f"{app_lc(cmult_def, [n, m], context)}" 262 | 263 | functions_to_write.append(cmult_def) 264 | 265 | def cexp_def(context : EmitContext) -> str: 266 | return f"(lambda _b : lambda _e : (_e (_b)))" 267 | def cexp(b : str, e : str, context : EmitContext) -> str: 268 | return f"{app_lc(cexp_def, [b, e], context)}" 269 | 270 | functions_to_write.append(cexp_def) 271 | 272 | def ctwo_def(context : EmitContext) -> str: 273 | return f"({csucc(csucc(czero(context), context), context)})" 274 | def ctwo(context : EmitContext) -> str: 275 | return f"{app_lc(ctwo_def, [], context)}" 276 | 277 | functions_to_write.append(ctwo_def) 278 | 279 | def appfirst_def(context : EmitContext) -> str: 280 | return f"(lambda _f : lambda _p : ({pair(app('_f', first('_p', context), context), second('_p', context), context)}))" 281 | def appfirst(f : str, p : str, context : EmitContext) -> str: 282 | return f"{app_lc(appfirst_def, [f, p], context)}" 283 | 284 | functions_to_write.append(appfirst_def) 285 | 286 | def appsecond_def(context : EmitContext) -> str: 287 | return f"(lambda _f : lambda _p : ({pair(first('_p', context), app('_f', second('_p', context), context), context)}))" 288 | def appsecond(f : str, p : str, context : EmitContext) -> str: 289 | return f"{app_lc(appsecond_def, [f, p], context)}" 290 | 291 | functions_to_write.append(appsecond_def) 292 | 293 | def zero_def(context : EmitContext) -> str: 294 | return f"({pair(czero(context), czero(context), context)})" 295 | def zero(context : EmitContext) -> str: 296 | return f"{app_lc(zero_def, [], context)}" 297 | 298 | functions_to_write.append(zero_def) 299 | 300 | def succ_def(context : EmitContext) -> str: 301 | return f"(lambda _n : ({appfirst(mr(csucc, context), '_n', context)}))" 302 | def succ(n : str, context : EmitContext) -> str: 303 | return f"{app_lc(succ_def, [n], context)}" 304 | 305 | functions_to_write.append(succ_def) 306 | 307 | def pred_def(context : EmitContext) -> str: 308 | return f"(lambda _n : ({appsecond(mr(csucc, context), '_n', context)}))" 309 | def pred(n : str, context : EmitContext) -> str: 310 | return f"{app_lc(pred_def, [n], context)}" 311 | 312 | functions_to_write.append(pred_def) 313 | 314 | def one_def(context : EmitContext) -> str: 315 | return f"({succ(zero(context), context)})" 316 | def one(context : EmitContext) -> str: 317 | return f"{app_lc(one_def, [], context)}" 318 | 319 | functions_to_write.append(one_def) 320 | 321 | def two_def(context : EmitContext) -> str: 322 | return f"({succ(one(context), context)})" 323 | def two(context : EmitContext) -> str: 324 | return f"{app_lc(two_def, [], context)}" 325 | 326 | functions_to_write.append(two_def) 327 | 328 | def neg_def(context : EmitContext) -> str: 329 | return f"(lambda _n : ({pair(second('_n', context), first('_n', context), context)}))" 330 | def neg(n : str, context : EmitContext) -> str: 331 | return f"{app_lc(neg_def, [n], context)}" 332 | 333 | functions_to_write.append(neg_def) 334 | 335 | def plus_def(context : EmitContext) -> str: 336 | return f"(lambda _n : lambda _m : ({pair(app2(mr(cplus, context), first('_n', context), first('_m', context), context), app2(mr(cplus, context), second('_n', context), second('_m', context), context), context)}))" 337 | def plus(n : str, m : str, context : EmitContext) -> str: 338 | return f"{app_lc(plus_def, [n, m], context)}" 339 | 340 | functions_to_write.append(plus_def) 341 | 342 | def minus_def(context : EmitContext) -> str: 343 | return f"(lambda _n : lambda _m : ({pair(app2(mr(cplus, context), first('_n', context), second('_m', context), context), app2(mr(cplus, context), second('_n', context), first('_m', context), context), context)}))" 344 | def minus(n : str, m : str, context : EmitContext) -> str: 345 | return f"{app_lc(minus_def, [n, m], context)}" 346 | 347 | functions_to_write.append(minus_def) 348 | 349 | def mult_def(context : EmitContext) -> str: 350 | return f"(lambda _n : lambda _m : ({pair(app2(mr(cplus, context), app2(mr(cmult, context), first('_n', context), first('_m', context), context), app2(mr(cmult, context), second('_n', context), second('_m', context), context), context), app2(mr(cplus, context), app2(mr(cmult, context), first('_n', context), second('_m', context), context), app2(mr(cmult, context), second('_n', context), first('_m', context), context), context), context)}))" 351 | def mult(n : str, m : str, context : EmitContext) -> str: 352 | return f"{app_lc(mult_def, [n, m], context)}" 353 | 354 | functions_to_write.append(mult_def) 355 | 356 | def ciszero_def(context : EmitContext) -> str: 357 | return f"(lambda _n : (_n (lambda _x : {false(context)}) ({true(context)})))" 358 | def ciszero(n : str, context : EmitContext) -> str: 359 | return f"{app_lc(ciszero_def, [n], context)}" 360 | 361 | functions_to_write.append(ciszero_def) 362 | 363 | def cphi_def(context : EmitContext) -> str: 364 | return f"(lambda _x : ({pair(second('_x', context), csucc(second('_x', context), context), context)}))" 365 | def cphi(x : str, context : EmitContext) -> str: 366 | return f"{app_lc(cphi_def, [x], context)}" 367 | 368 | functions_to_write.append(cphi_def) 369 | 370 | def cpred_def(context : EmitContext) -> str: 371 | return f"(lambda _n : ({first(app2('_n', mr(cphi, context), pair(czero(context), czero(context), context), context), context)}))" 372 | def cpred(n : str, context : EmitContext) -> str: 373 | return f"{app_lc(cpred_def, [n], context)}" 374 | 375 | functions_to_write.append(cpred_def) 376 | 377 | def csub_def(context : EmitContext) -> str: 378 | return f"(lambda _n : lambda _m : ({app2('_m', mr(cpred, context), '_n', context)}))" 379 | def csub(n : str, m : str, context : EmitContext) -> str: 380 | return f"{app_lc(csub_def, [n, m], context)}" 381 | 382 | functions_to_write.append(csub_def) 383 | 384 | def cleq_def(context : EmitContext) -> str: 385 | return f"(lambda _n : lambda _m : ({ciszero(csub('_n', '_m', context), context)}))" 386 | def cleq(n : str, m : str, context : EmitContext) -> str: 387 | return f"{app_lc(cleq_def, [n, m], context)}" 388 | 389 | functions_to_write.append(cleq_def) 390 | 391 | def ceq_def(context : EmitContext) -> str: 392 | return f"(lambda _n : lambda _m : ({land(cleq('_n', '_m', context), cleq('_m', '_n', context), context)}))" 393 | def ceq(n : str, m : str, context : EmitContext) -> str: 394 | return f"{app_lc(ceq_def, [n, m], context)}" 395 | 396 | functions_to_write.append(ceq_def) 397 | 398 | def clt_def(context : EmitContext) -> str: 399 | return f"(lambda _n : lambda _m : ({land(cleq('_n', '_m', context), lnot(cleq('_m', '_n', context), context), context)}))" 400 | def clt(n : str, m : str, context : EmitContext) -> str: 401 | return f"{app_lc(clt_def, [n, m], context)}" 402 | 403 | functions_to_write.append(clt_def) 404 | 405 | def eq_def(context : EmitContext) -> str: 406 | return f"(lambda _n : lambda _m : ({ceq(cplus(first('_n', context), second('_m', context), context), cplus(second('_n', context), first('_m', context), context), context)}))" 407 | def eq(n : str, m : str, context : EmitContext) -> str: 408 | return f"{app_lc(eq_def, [n, m], context)}" 409 | 410 | functions_to_write.append(eq_def) 411 | 412 | def leq_def(context : EmitContext) -> str: 413 | return f"(lambda _n : lambda _m : ({cleq(cplus(first('_n', context), second('_m', context), context), cplus(second('_n', context), first('_m', context), context), context)}))" 414 | def leq(n : str, m : str, context : EmitContext) -> str: 415 | return f"{app_lc(leq_def, [n, m], context)}" 416 | 417 | functions_to_write.append(leq_def) 418 | 419 | def geq_def(context : EmitContext) -> str: 420 | return f"(lambda _n : lambda _m : ({leq('_m', '_n', context)}))" 421 | def geq(n : str, m : str, context : EmitContext) -> str: 422 | return f"{app_lc(geq_def, [n, m], context)}" 423 | 424 | functions_to_write.append(geq_def) 425 | 426 | def lt_def(context : EmitContext) -> str: 427 | return f"(lambda _n : lambda _m : ({land(leq('_n', '_m', context), lnot(eq('_n', '_m', context), context), context)}))" 428 | def lt(n : str, m : str, context : EmitContext) -> str: 429 | return f"{app_lc(lt_def, [n, m], context)}" 430 | 431 | functions_to_write.append(lt_def) 432 | 433 | def gt_def(context : EmitContext) -> str: 434 | return f"(lambda _n : lambda _m : ({lt('_m', '_n', context)}))" 435 | def gt(n : str, m : str, context : EmitContext) -> str: 436 | return f"{app_lc(gt_def, [n, m], context)}" 437 | 438 | functions_to_write.append(gt_def) 439 | 440 | def collapse_def(context : EmitContext) -> str: 441 | return f"(lambda _n : ({lif(clt(second('_n', context), first('_n', context), context), csub(first('_n', context), second('_n', context), context), csub(second('_n', context), first('_n', context), context), context)}))" 442 | def collapse(n : str, context : EmitContext) -> str: 443 | return f"{app_lc(collapse_def, [n], context)}" 444 | 445 | functions_to_write.append(collapse_def) 446 | 447 | def z_def(context : EmitContext) -> str: 448 | return f"(lambda _rec : ((lambda _x : _rec (lambda _v : _x (_x) (_v))) (lambda _x : _rec (lambda _v : _x (_x) (_v)))))" 449 | def z(rec : str, context : EmitContext) -> str: 450 | return f"{app_lc(z_def, [rec], context)}" 451 | 452 | functions_to_write.append(z_def) 453 | 454 | def cdiv_def(context : EmitContext) -> str: 455 | return f"({mr(z_def, context)}(lambda _rec : lambda _n : lambda _m : (({lif(ciszero('_m', context), emit_runtime_error_thunk('Divide by zero', context), thunk('('+lif(clt('_n', '_m', context), thunk(czero(context), context), '(lambda:'+csucc(app2('_rec', csub('_n', '_m', context), '_m', context), context)+')', context), context)+')()', context)})())))" 456 | def cdiv(n : str, m : str, context : EmitContext) -> str: 457 | return f"{app_lc(cdiv_def, [n, m], context)}" 458 | 459 | functions_to_write.append(cdiv_def) 460 | 461 | def cmod_def(context : EmitContext) -> str: 462 | return f"({mr(z_def, context)}(lambda _rec : lambda _n : lambda _m : (({lif(ciszero('_m', context), emit_runtime_error_thunk('Mod by zero', context), thunk('('+lif(clt('_n', '_m', context), thunk('_n', context), '(lambda:'+app2('_rec', csub('_n', '_m', context), '_m', context)+')', context), context)+')()', context)})())))" 463 | def cmod(n : str, m : str, context : EmitContext) -> str: 464 | return f"{app_lc(cmod_def, [n, m], context)}" 465 | 466 | functions_to_write.append(cmod_def) 467 | 468 | def div_def(context : EmitContext) -> str: 469 | return f"(lambda _n : lambda _m : ({lif(xor(clt(second('_n', context), first('_n', context), context), clt(second('_m', context), first('_m', context), context), context), pair(czero(context), cdiv(collapse('_n', context), collapse('_m', context), context), context), pair(cdiv(collapse('_n', context), collapse('_m', context), context), czero(context), context), context)}))" 470 | def div(n : str, m : str, context : EmitContext) -> str: 471 | return f"{app_lc(div_def, [n, m], context)}" 472 | 473 | functions_to_write.append(div_def) 474 | 475 | def mod_def(context : EmitContext) -> str: 476 | return f"(lambda _n : lambda _m : (({lif(lor(clt(first('_n', context), second('_n', context), context), clt(first('_m', context), second('_m', context), context), context), emit_runtime_error_thunk('Mod not permitted with negatives', context), thunk(pair(cmod(collapse('_n', context), collapse('_m', context), context), czero(context), context), context), context)})()))" 477 | def mod(n : str, m : str, context : EmitContext) -> str: 478 | return f"{app_lc(mod_def, [n, m], context)}" 479 | 480 | functions_to_write.append(mod_def) 481 | 482 | def exponent_def(context : EmitContext) -> str: 483 | return f"(lambda _n : lambda _m : (({lif(clt(first('_m', context), second('_m', context), context), emit_runtime_error_thunk('Negative exponent not permitted', context), thunk(lif(lor(cleq(second('_n', context), first('_n', context), context), ciszero(cmod(collapse('_m', context), ctwo(context), context), context), context), pair(cexp(collapse('_n', context), collapse('_m', context), context), czero(context), context), pair(czero(context), cexp(collapse('_n', context), collapse('_m', context), context), context), context), context), context)})()))" 484 | def exponent(n : str, m : str, context : EmitContext) -> str: 485 | return f"{app_lc(exponent_def, [n, m], context)}" 486 | 487 | functions_to_write.append(exponent_def) 488 | 489 | -------------------------------------------------------------------------------- /src/lc_defs/README.md: -------------------------------------------------------------------------------- 1 | To execute this monstrosity to update lc_constants, run `python src/lc_defs/lc_constants_macro.py` from the root directory -------------------------------------------------------------------------------- /src/lc_defs/lc_constants_macro.py: -------------------------------------------------------------------------------- 1 | from typing import List, Text, TextIO 2 | from io import TextIOWrapper 3 | from re import sub 4 | 5 | PRED_STRING = """ 6 | \"\"\" 7 | This is an auto-generated file, DO NOT TOUCH 8 | Definitions are made by lc_defs.py and generated with lc_constants_macro.py 9 | To update, see lc_defs/README.md 10 | \"\"\" 11 | 12 | from src.util import * 13 | import inspect 14 | from typing import Set, List 15 | 16 | scope_manager = \"\"\"class _Var: 17 | def __init__(self, val): 18 | self.val = val 19 | def __call__(self, other): # it's just a lambda ;) 20 | return self.val(other)\"\"\" 21 | 22 | lc_inspector = \"\"\" 23 | import inspect 24 | 25 | class LambdaInspect: 26 | def __init__(self, name : str = ""): 27 | self.name = name 28 | self.abstractions = [] 29 | self.callees = [] 30 | self.executing = False 31 | 32 | def __str__(self): 33 | return self.__repr__() 34 | 35 | def __repr__(self): 36 | return self.name 37 | 38 | def output(self, calls): 39 | if self in calls: 40 | return self.name 41 | calls.add(self) 42 | a = "" 43 | for layer in self.abstractions: 44 | for item in layer: 45 | a += f"lambda {item} : " 46 | if len(a) > 0: 47 | a += "(" 48 | clsp = ')' if len(a) > 0 else '' 49 | if not self.name: 50 | return f"{a}{' '.join(f'{x.output(calls)}' for x in self.callees)}{clsp}" 51 | 52 | spc = ' ' if len(self.callees) > 0 else '' 53 | cs = ' '.join(['({})'.format(x.output(calls)) for x in self.callees]) 54 | return f"{a}{self.name}{spc}{cs}{clsp}" 55 | 56 | def resolve(self, vars, calls): 57 | if self in calls: 58 | return self 59 | calls.add(self) 60 | for index in range(len(self.callees)): 61 | while inspect.isfunction(self.callees[index]): 62 | var = str(self.callees[index].__code__.co_varnames[0]) + "0" 63 | while var in vars: 64 | last_number = -1 65 | for i in range(len(var)-1,-1,-1): 66 | if not var[i].isdigit(): 67 | last_number = i 68 | break 69 | var = var[:last_number+1] + str(int(var[last_number+1:]) + 1) 70 | vars.add(var) 71 | self.abstractions[index].append(var) 72 | self.callees[index] = self.callees[index](LambdaInspect(var)) 73 | for callee in self.callees: 74 | if isinstance(callee, LambdaInspect): 75 | callee.resolve(vars, calls) 76 | return self 77 | 78 | def __call__(self, other): 79 | self.abstractions.append([]) 80 | self.callees.append(other) 81 | return self 82 | \"\"\" 83 | 84 | class CLI: 85 | def __init__(self, args : Set[str] = set()): 86 | self.set_args(args) 87 | 88 | def set_args(self, args : List[str]): 89 | for i in range(len(args)): 90 | if not args[i].startswith("--") or len(args[i]) < 2: 91 | raise Exception("Invalid argument " + str(args[i])) 92 | args[i] = args[i][2:] 93 | self.args = set(args) 94 | 95 | def contains(self, arg : str) -> bool: 96 | return arg in self.args 97 | 98 | def __repr__(self): 99 | return "CLI: " + str(self.args) 100 | 101 | class EmitContext(BaseClass): 102 | def __init__(self, cli : CLI): 103 | self.cli = cli 104 | self.interp = False 105 | self.fn_count = 0 106 | self.scope = 0 107 | self.vars = [] 108 | 109 | def raw(self): 110 | return self.cli.contains("raw") 111 | 112 | def debug(self): 113 | return self.cli.contains("debug") 114 | 115 | def no_input(self): 116 | return self.cli.contains("no-input") 117 | 118 | def copy(self): 119 | result = EmitContext(self.cli) 120 | result.interp = self.interp 121 | result.fn_count = 0 122 | result.scope = self.scope + 1 123 | result.vars = [var for var in self.vars] 124 | return result 125 | 126 | def __repr__(self): 127 | return "EMIT_CONTEXT" + str(self.cli) 128 | 129 | functions_to_write = [] 130 | 131 | def retrieve_name_constant(var : any) -> List[str]: 132 | callers_local_vars = inspect.currentframe().f_globals.items() 133 | return [var_name for var_name, var_val in callers_local_vars if var_val is var] 134 | 135 | # maybe_replace is too long to type 136 | def mr(val, context : EmitContext) -> str: 137 | if context.debug(): 138 | return f"{retrieve_name_constant(val)[0].upper().split('_')[0]}" 139 | def_name = val.__name__ 140 | if not def_name.endswith("_def"): 141 | def_name = f"{def_name}_def" 142 | return f"{globals()[def_name](context)}" 143 | 144 | def app_lc(f, args : List[str], context : EmitContext) -> str: 145 | return f"{mr(f, context)}{' ' if len(args) > 0 else ''}{' '.join(['({})'.format(x) for x in args])}" 146 | 147 | def emit_runtime_error_thunk(message : str, context : EmitContext) -> str: 148 | return f"lambda:_raise(RuntimeError(\\\"{message}\\\"))" 149 | """ 150 | 151 | def parse_defs(f : TextIOWrapper, data) -> List[List[str]]: 152 | expect_return = False 153 | for line in f: 154 | line = line.strip() 155 | if expect_return: 156 | if not line.startswith("return"): 157 | raise Exception("Invalid line, expected return, got " + line) 158 | data[-1].append(line) 159 | expect_return = False 160 | continue 161 | if line.startswith("def"): 162 | data.append([line]) 163 | expect_return = True 164 | 165 | def write_def(line0 : str, f : TextIOWrapper): 166 | name = line0.split("(")[0] 167 | f.write(f"{name}_def(context : EmitContext) -> str:") 168 | f.write("\n") 169 | 170 | def write_def_body(line0 : str, line1 : str, f : TextIOWrapper): 171 | args = line0.split("(")[1].split(")")[0].split(",") 172 | args = [arg.strip() for arg in args] 173 | if args[0] == '': 174 | args = [] 175 | f.write(f" return f\"(") 176 | if "_rec" in line1: 177 | f.write("{mr(z_def, context)}(lambda _rec : ") 178 | for arg in args: 179 | f.write(f"lambda _{arg} : ") 180 | if len(args) > 0: 181 | f.write("(") 182 | line1 = line1.split('"')[1] 183 | for arg in args: 184 | line1 = line1.replace(f"{{{arg}}}", f"_{arg}") 185 | inside_brackets = False 186 | marks = [] 187 | for i in range(len(line1)): 188 | if line1[i] == "{": 189 | inside_brackets = True 190 | if line1[i] == "}": 191 | inside_brackets = False 192 | if line1[i:i+len(arg)] == arg: 193 | if inside_brackets: 194 | if line1[i-2] == "," or line1[i-1] == "(": 195 | if line1[i+len(arg)] == ")" or line1[i+len(arg)] == ",": 196 | marks.append(i) 197 | for i in marks[::-1]: # go in reverse to avoid string update shenanigans 198 | line1 = line1[:i] + "'_" + line1[i:i+len(arg)] + "'" + line1[i+len(arg):] 199 | 200 | # manual regex-like crap cause matching is hard 201 | marks = [] 202 | in_raw = False 203 | str_start = -1 204 | in_quotes = False 205 | for i in range(len(line1)): 206 | if line1[i] == "{": 207 | in_raw = True 208 | if line1[i] == "}": 209 | in_raw = False 210 | if in_raw and line1[i] == "'": 211 | in_quotes = not in_quotes 212 | if in_raw and str_start == -1 and line1[i].isalpha() and not in_quotes: # ignoring variables cleverly 213 | str_start = i 214 | continue 215 | if str_start != -1 and not (line1[i].isalpha() or line1[i].isdigit() or line1[i] == "_"): 216 | if line1[i] != "(": 217 | marks.append((str_start, i)) 218 | str_start = -1 219 | for i in marks[::-1]: # go in reverse to avoid string update shenanigans 220 | line1 = f"{line1[:i[0]]}mr({line1[i[0]:i[1]]}){line1[i[1]:]}" 221 | valid_count = 0 222 | marks = [] 223 | in_quotes = False 224 | for item in range(len(line1)): 225 | if line1[item] == "'": 226 | in_quotes = not in_quotes 227 | if line1[item] == "(" and (line1[item-1].isalpha() or line1[item-1].isdigit()): 228 | valid_count += 1 229 | if line1[item] == ")" and valid_count > 0 and not in_quotes: 230 | valid_count -= 1 231 | marks.append(item) 232 | for i in marks[::-1]: # go in reverse to avoid string update shenanigans 233 | c = "context" 234 | if line1[i-1] != "(": 235 | c = ", " + c 236 | line1 = f"{line1[:i]}{c}{line1[i:]}" 237 | f.write(line1) 238 | if "_rec" in line1 and line0.split("(")[0].split(" ")[1] != "z": 239 | f.write(")") 240 | f.write((")" if len(args) > 0 else "") + ")\"") 241 | f.write("\n") 242 | 243 | def write_fn(line0 : str, f : TextIOWrapper): 244 | result = line0.split("(") 245 | result[1] = result[1].split(")")[0] 246 | result[1] = result[1].split(",") 247 | if result[1] == ['']: #stupid special case 248 | result[1] = [] 249 | for i in range(len(result[1])): 250 | result[1][i] = result[1][i].strip() + " : str" 251 | result[1].append("context : EmitContext") 252 | result = "(".join([result[0], ", ".join(result[1])]) 253 | result = result + ") -> str:" 254 | f.write(result) 255 | f.write("\n") 256 | 257 | def write_fn_body(line0 : str, f : TextIOWrapper): 258 | name = line0.split("(")[0].split(" ")[1] 259 | args = line0.split("(")[1].split(")")[0].split(",") 260 | args = f"[{', '.join([arg.strip() for arg in args])}]" 261 | f.write(f" return f\"{{app_lc({name}_def, {args}, context)}}\"") 262 | f.write("\n\n") 263 | 264 | def write_append(line0 : str, f : TextIOWrapper): 265 | name = line0.split("(")[0].split(" ")[1] 266 | f.write(f"functions_to_write.append({name}_def)") 267 | f.write("\n\n") 268 | 269 | def main(): 270 | data : List[List[str]] = [] 271 | with open("src/lc_defs/lc_defs.py", 'r') as f: 272 | parse_defs(f, data) 273 | 274 | with open("src/lc_constants.py", 'w') as f: 275 | f.write(PRED_STRING) 276 | f.write("\n") 277 | for line in data: 278 | # We're assuming a lot about line structure 279 | write_def(line[0], f) 280 | write_def_body(line[0], line[1], f) 281 | write_fn(line[0], f) 282 | write_fn_body(line[0], f) 283 | write_append(line[0], f) 284 | 285 | if __name__ == "__main__": 286 | main() 287 | -------------------------------------------------------------------------------- /src/lc_defs/lc_defs.py: -------------------------------------------------------------------------------- 1 | """ 2 | So basically this is a meta-file to be used by lc_constants_macro 3 | We make it a py file for linting, but this file alone will not do what we want 4 | """ 5 | 6 | # dummy class for typing -- we use a class to avoid being parsed 7 | class emit_runtime_error_thunk(): 8 | pass 9 | 10 | def app(x, y): 11 | return f"{x} ({y})" 12 | def app2(x, y, z): 13 | return f"{x} ({y}) ({z})" 14 | def thunk(x): 15 | return f"lambda:{x}" 16 | 17 | def false(): 18 | return "lambda _x : lambda _y : _y" 19 | def true(): 20 | return "lambda _x : lambda _y : _x" 21 | def lif(b, c, d): 22 | return f"{b} ({c}) ({d})" 23 | 24 | def lnot(b): 25 | return f"{lif(b, false(), true())}" 26 | def land(b1, b2): 27 | return f"{lif(b1, b2, false())}" 28 | def lor(b1, b2): 29 | return f"{lif(b1, true(), b2)}" 30 | def xor(b1, b2): 31 | return f"{lif(b1, lnot(b2), b2)}" 32 | 33 | def pair(x, y): 34 | return f"lambda _f : (_f ({x}) ({y}))" 35 | def first(x): 36 | return f"{x} ({true()})" 37 | def second(x): 38 | return f"{x} ({false()})" 39 | 40 | # c = church numeral 41 | def czero(): 42 | return "lambda _f : lambda _x : _x" 43 | def csucc(n): 44 | return f"lambda _f : lambda _x : (_f ({n} (_f) (_x)))" 45 | def cplus(n, m): 46 | return f"{app2(n, csucc, m)}" 47 | def cmult(n, m): 48 | return f"lambda _f : ({n} ({m} (_f)))" 49 | def cexp(b, e): 50 | return f"{e} ({b})" 51 | def ctwo(): 52 | return f"{csucc(csucc(czero()))}" 53 | 54 | def appfirst(f, p): 55 | return f"{pair(app(f, first(p)), second(p))}" 56 | def appsecond(f, p): 57 | return f"{pair(first(p), app(f, second(p)))}" 58 | 59 | # This is an _unusual_ representation 60 | def zero(): 61 | return f"{pair(czero(), czero())}" 62 | def succ(n): 63 | return f"{appfirst(csucc, n)}" 64 | def pred(n): 65 | return f"{appsecond(csucc, n)}" 66 | def one(): 67 | return f"{succ(zero())}" 68 | def two(): 69 | return f"{succ(one())}" 70 | 71 | def neg(n): 72 | return f"{pair(second(n), first(n))}" 73 | def plus(n, m): 74 | return f"{pair(app2(cplus, first(n), first(m)), app2(cplus, second(n), second(m)))}" 75 | def minus(n, m): 76 | return f"{pair(app2(cplus, first(n), second(m)), app2(cplus, second(n), first(m)))}" 77 | def mult(n, m): 78 | return f"{pair(app2(cplus, app2(cmult, first(n), first(m)), app2(cmult, second(n), second(m))), app2(cplus, app2(cmult, first(n), second(m)), app2(cmult, second(n), first(m))))}" 79 | 80 | def ciszero(n): 81 | return f"{n} (lambda _x : {false()}) ({true()})" 82 | # re: https://en.wikipedia.org/wiki/Lambda_calculus#Pairs 83 | def cphi(x): 84 | return f"{pair(second(x), csucc(second(x)))}" 85 | # be careful that cpred(0) = 0 86 | def cpred(n): 87 | return f"{first(app2(n, cphi, pair(czero(), czero())))}" 88 | def csub(n, m): 89 | return f"{app2(m, cpred, n)}" 90 | 91 | def cleq(n, m): 92 | return f"{ciszero(csub(n, m))}" 93 | def ceq(n, m): 94 | return f"{land(cleq(n, m), cleq(m, n))}" 95 | def clt(n, m): 96 | return f"{land(cleq(n, m), lnot(cleq(m, n)))}" 97 | 98 | def eq(n, m): #ALGEBRA 99 | return f"{ceq(cplus(first(n), second(m)), cplus(second(n), first(m)))}" 100 | def leq(n, m): 101 | return f"{cleq(cplus(first(n), second(m)), cplus(second(n), first(m)))}" 102 | def geq(n, m): 103 | return f"{leq(m, n)}" 104 | def lt(n, m): 105 | return f"{land(leq(n, m), lnot(eq(n, m)))}" 106 | def gt(n, m): 107 | return f"{lt(m, n)}" 108 | 109 | # useful utility function that collapses an integer into the minimal positive or negative result 110 | def collapse(n): 111 | return f"{lif(clt(second(n), first(n)), csub(first(n), second(n)), csub(second(n), first(n)))}" 112 | 113 | # We use the z combinator cause eager Python evaluation 114 | # Whenever you apply z, wrap the application in a 'lambda:', as usual 115 | def z(rec): 116 | return f"(lambda _x : {rec} (lambda _v : _x (_x) (_v))) (lambda _x : {rec} (lambda _v : _x (_x) (_v)))" 117 | # If you use an emit_runtime error, wrap the alternative in a thunk 118 | def cdiv(n, m): 119 | return f"({lif(ciszero(m), emit_runtime_error_thunk('Divide by zero'), thunk('('+lif(clt(n, m), thunk(czero()), '(lambda:'+csucc(app2('_rec', csub(n, m), m))+')'))+')()')})()" 120 | def cmod(n, m): 121 | return f"({lif(ciszero(m), emit_runtime_error_thunk('Mod by zero'), thunk('('+lif(clt(n, m), thunk(n), '(lambda:'+app2('_rec', csub(n, m), m)+')'))+')()')})()" 122 | 123 | def div(n, m): 124 | return f"{lif(xor(clt(second(n), first(n)), clt(second(m), first(m))), pair(czero(), cdiv(collapse(n), collapse(m))), pair(cdiv(collapse(n), collapse(m)), czero()))}" 125 | def mod(n, m): 126 | return f"({lif(lor(clt(first(n), second(n)), clt(first(m), second(m))), emit_runtime_error_thunk('Mod not permitted with negatives'), thunk(pair(cmod(collapse(n), collapse(m)), czero())))})()" 127 | # If exponent is negative, error out 128 | # otherwise compute exponent assuming positive base 129 | # finally, if `n` is negative and `m` is a mod of 2, swap the two` 130 | def exponent(n, m): 131 | return f"({lif(clt(first(m), second(m)), emit_runtime_error_thunk('Negative exponent not permitted'), thunk(lif(lor(cleq(second(n), first(n)), ciszero(cmod(collapse(m), ctwo()))), pair(cexp(collapse(n), collapse(m)), czero()), pair(czero(), cexp(collapse(n), collapse(m))))))})()" -------------------------------------------------------------------------------- /src/parser.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations # Recursion! 2 | import src.ast as ast 3 | from src.util import * 4 | from typing import List, Optional, Union, Callable 5 | 6 | reserved = ("true", "false", "print", "input", "output", "while", "if", "else") 7 | 8 | # This is actually so cool 9 | binop_precedence = [("and", "or"), ("==", "<", ">", "<=", ">="), ("+", "-"), ("%", "/", "*"), ("^")] 10 | 11 | def is_lower_precedence(op1 : ast.Op, op2 = ast.Op) -> bool: 12 | for prec in binop_precedence: 13 | if op1.op in prec: 14 | return True # right-associative 15 | if op2.op in prec: 16 | return False 17 | raise InternalException(str(op1) + " is not in the precedence list") 18 | 19 | # Dummy class for the expr stack 20 | class OpenParen(ast.Expr): 21 | def __init__(self): 22 | pass 23 | def __repr__(self): 24 | return "OpenParen" 25 | 26 | class ParsingException(Exception): 27 | def __init__(self, message : str, state : ParserState): 28 | super().__init__("Parsing error on line " + str(state.line_number) + ": " + str(message)) 29 | 30 | class ParsingExpectException(ParsingException): 31 | def __init__(self, expect : str, word : str, state : ParserState): 32 | super().__init__("expected " + str(expect) + ", got " + str(word), state) 33 | 34 | class ParsingState(BaseClass): 35 | pass 36 | 37 | class DefaultState(ParsingState): 38 | def __init__(self): 39 | pass 40 | def __repr__(self): 41 | return "DEFAULT STATE" 42 | 43 | class ConditionState(ParsingState): 44 | def __init__(self): 45 | pass 46 | def __repr__(self): 47 | return "DEFAULT STATE" 48 | 49 | class Lookahead(BaseClass): 50 | def __init__(self, lookahead : Callable[[str, PartialStatement, ParserState]]): 51 | if not (lookahead == lookahead_binop or lookahead == lookahead_expr): 52 | raise InternalException("expected lookahead, got " + str(lookahead)) 53 | self.lookahead = lookahead 54 | def __repr__(self): 55 | return "LOOKAHEAD " + str(self.unpack.__name__) 56 | 57 | class PartialASTElement(BaseClass): 58 | def __init__(self, cmd : ast.Statement, args : List[BaseClass]): 59 | self.cmd = cmd 60 | self.args = args 61 | self.index_update = -1 62 | self.index = -1 63 | 64 | def index_check(self, fun : str): 65 | if not isinstance(self.args[self.index], PartialASTElement): 66 | raise InternalException("Attempting to " + str(fun) + " on non-Partial element " + 67 | str(self.args[self.index]) + " at index " + str(self.index)) 68 | 69 | def set_index(self, arg : BaseClass): 70 | # Ok, this is _super_ janky, but control flow for Statement appending is mostly done here 71 | if self.cmd == ast.Seq: 72 | self.index = 2 73 | elif isinstance(arg, PartialExpression): 74 | self.index = len(self.args)-1 75 | elif self.index_update > -1: 76 | self.index += self.index_update 77 | 78 | def clear_index(self): 79 | if self.index == -1: 80 | if self.cmd == ast.Else: 81 | return # special casing the no-argument Else statement 82 | raise InternalException("Attempting to clear non-indexed Element " + str(self)) 83 | self.index_check("clear_index") 84 | if isinstance(self.args[self.index], PartialExpression): 85 | self.index = -1 86 | else: 87 | self.args[self.index].clear_index() 88 | 89 | def update_index(self, amount : int): 90 | self.index_update = amount 91 | 92 | def append(self, arg : BaseClass): 93 | if self.index == -1: 94 | self.args.append(arg) 95 | self.set_index(arg) 96 | else: 97 | self.index_check("append") 98 | self.args[self.index].append(arg) # Wow, that's a lotta recursion 99 | 100 | def overwrite(self, arg : BaseClass): 101 | if self.index == -1: 102 | if self.cmd == ast.Binop: 103 | self.args[-1] = arg 104 | self.args.pop(-2) 105 | else: 106 | self.args = [arg] 107 | self.set_index(arg) 108 | else: 109 | self.index_check("overwrite") 110 | self.args[self.index].overwrite(arg) 111 | 112 | def latest(self) -> PartialASTElement: 113 | if self.index == -1: 114 | if self.cmd == ast.Binop: 115 | return self.args[-2:] 116 | return self.args 117 | self.index_check("use latest") 118 | return self.args[self.index].latest() 119 | 120 | # Gets the last seq in sequence 121 | def latest_seq(self) -> PartialASTElement: 122 | if self.index == -1: 123 | raise InternalException("Attempting to get seq from " + str(self)) 124 | self.index_check("use latest_seq") 125 | arg = self.args[self.index] 126 | if arg.cmd != ast.Seq: 127 | return self 128 | return self.args[self.index].latest_seq() 129 | 130 | def pack(self, state): 131 | try: 132 | packed = [arg.pack(state) if isinstance(arg, PartialASTElement) else arg for arg in self.args] 133 | return self.cmd(*packed) 134 | except TypeError: 135 | raise ParsingException("wrong number of arguments to " + 136 | str(self.cmd.__name__) + " (" + str(len(self.args)) + " given)", state) 137 | 138 | def is_partial(self): 139 | return isinstance(self.cmd, PartialASTElement) 140 | 141 | class PartialStatement(PartialASTElement): 142 | def __repr__(self): 143 | return "PARTIAL_STATEMENT " + str(self.cmd) + " " + str(self.args) + " " + str(self.index) 144 | 145 | class PartialExpression(PartialASTElement): 146 | def __repr__(self): 147 | return "PARTIAL_EXPRESSION " + str(self.cmd) + " " + str(self.args) + " " + str(self.index) 148 | 149 | class ParserState(BaseClass): 150 | def __init__(self): 151 | self.next = None 152 | self.clear_state() 153 | self.line_number = 0 154 | self.ops : List[PartialExpression] = [] 155 | self.args : List[PartialASTElement] = [] 156 | self.scope : List[PartialASTElement] = [] 157 | def update(self, next): 158 | if not (isinstance(next, Lookahead) or callable(next)): 159 | raise InternalException("expected a function, got " + str(next)) 160 | self.next = next 161 | 162 | def set_state(self, state : ParsingState): 163 | typecheck(state, ParsingState) 164 | self.state = state 165 | 166 | def clear_state(self): 167 | self.state = DefaultState() 168 | 169 | def push_op(self, cmd : PartialExpression): 170 | typecheck(cmd, PartialExpression) 171 | if not ((cmd.cmd == ast.Binop) or (cmd.cmd == ast.Unop) or cmd.cmd == OpenParen): 172 | raise InternalException("Expected Binop or Unop, got " + str(cmd.cmd)) 173 | if cmd.cmd == ast.Unop or cmd.cmd == OpenParen: 174 | self.ops.append(cmd) 175 | return 176 | if len(self.ops) > 0 \ 177 | and self.ops[-1].cmd != OpenParen \ 178 | and is_lower_precedence(cmd.args[0], self.ops[-1].args[0]): 179 | self.pop_op() 180 | self.ops.append(cmd) 181 | 182 | def pop_op(self): 183 | op = self.ops.pop() 184 | if op.cmd == OpenParen: 185 | return 186 | arg1 = self.pop_arg() 187 | if op.cmd == ast.Binop: 188 | # Disentangle the stack 189 | op.args.append(self.pop_arg()) 190 | op.args.append(arg1) 191 | self.push_arg(op) 192 | 193 | def push_arg(self, arg : Union[PartialExpression, ast.Expr]): 194 | if not isinstance(arg, PartialExpression): 195 | typecheck(arg, ast.Expr) 196 | self.args.append(arg) 197 | if len(self.ops) > 0 and self.ops[-1].cmd == ast.Unop: 198 | self.pop_op() # unop arg comes after 199 | 200 | def pop_arg(self) -> Union[PartialExpression, ast.Expr]: 201 | return self.args.pop() 202 | 203 | def clean_ops(self, result : PartialASTElement, is_paren : bool): 204 | typecheck(result, PartialASTElement) 205 | if is_paren and not self.ops: 206 | raise ParsingException("Unmatched )", self) 207 | while (is_paren and not self.ops[-1].cmd == OpenParen) or (not is_paren and self.ops): 208 | if not is_paren and self.ops[-1].cmd == OpenParen: 209 | raise ParsingException("Unmatched (", self) 210 | self.pop_op() 211 | if is_paren and not self.ops: 212 | raise ParsingException("Unmatched )", self) 213 | if is_paren and self.ops[-1].cmd == OpenParen: 214 | self.pop_op() 215 | if not is_paren: 216 | while self.args: 217 | result.append(self.pop_arg()) 218 | 219 | def scope_in(self, cmd : PartialASTElement): 220 | typecheck(cmd, PartialASTElement) 221 | cmd.clear_index() 222 | cmd.update_index(3) 223 | self.scope.append(cmd) 224 | def scope_out(self) -> PartialASTElement: 225 | result = self.scope.pop() 226 | return result 227 | 228 | def __repr__(self): 229 | return "STATE: " + str(self.next) 230 | 231 | # Seq(Seq(A, B), .) --> Seq(A, Seq(B, .)) 232 | def untangle_seq(result : PartialStatement, state : ParserState) -> PartialStatement: 233 | if result.cmd == ast.Seq: 234 | seq = result.latest_seq() 235 | cmd = PartialStatement(ast.Seq, [state.line_number, seq.args[2]]) 236 | seq.args[2] = cmd 237 | return result 238 | return PartialStatement(ast.Seq, [state.line_number, result.pack(state)]) 239 | 240 | # So it's sorta weird, but these lookahead functions "manage" the control flow of the parser 241 | def lookahead_binop(word : str, result : PartialStatement, state : ParserState): 242 | if not isinstance(state.state, ConditionState) and word == ";": 243 | state.update(expect_semi) 244 | elif isinstance(state.state, ConditionState) and word == "{": 245 | state.update(expect_open_block) 246 | elif word == ")": 247 | state.update(expect_close_paren) 248 | else: 249 | state.update(expect_binop) 250 | 251 | def lookahead_expr(word : str, result : PartialStatement, state : ParserState): 252 | if word in ("-", "not"): 253 | state.update(expect_unop) 254 | elif word == "(": 255 | state.update(expect_open_paren) 256 | else: 257 | state.update(expect_const) 258 | 259 | def expect_semi(word : str, result : PartialStatement, state : ParserState) -> PartialASTElement: 260 | if not word == ";": 261 | raise InternalException("Expected ; got " + str(word)) 262 | state.update(expect_statement) 263 | state.clean_ops(result, False) 264 | return untangle_seq(result, state) 265 | 266 | def expect_open_block(word :str, result : PartialStatement, state : ParserState) -> PartialExpression: 267 | if not word == "{": 268 | raise ParsingExpectException("Expected { got ", word, state) 269 | state.update(expect_statement) 270 | state.clean_ops(result, False) 271 | state.clear_state() 272 | state.scope_in(result) 273 | return None 274 | 275 | def expect_close_paren(word :str, result : PartialStatement, state : ParserState) -> PartialExpression: 276 | if not word == ")": 277 | raise InternalException("Expected ) got " + str(word)) 278 | state.update(Lookahead(lookahead_binop)) 279 | state.clean_ops(result, True) 280 | return result 281 | 282 | def expect_binop(word : str, result : PartialStatement, state : ParserState) -> PartialExpression: 283 | if word in ("+", "-", "*", "/", "%", "^", "==", "<", ">", "<=", ">=", "and", "or"): 284 | state.update(Lookahead(lookahead_expr)) 285 | state.push_op(PartialExpression(ast.Binop, [ast.Op(word)])) 286 | return result 287 | raise ParsingExpectException("binary operation", word, state) 288 | 289 | def expect_unop(word : str, result : PartialStatement, state : ParserState) -> PartialExpression: 290 | if not (word == "-" or word == "not"): 291 | raise InternalException("Expected Unop got " + str(word)) 292 | state.update(Lookahead(lookahead_expr)) 293 | state.push_op(PartialExpression(ast.Unop, [(ast.Op(word))])) # we can do this cause we already did the lookahead 294 | return result 295 | 296 | def expect_open_paren(word :str, result : PartialStatement, state : ParserState) -> PartialExpression: 297 | if not word == "(": 298 | raise InternalException("Expected ( got " + str(word)) 299 | state.update(Lookahead(lookahead_expr)) 300 | state.push_op(PartialExpression(OpenParen, [])) 301 | return result 302 | 303 | def expect_const(word : str, result : PartialStatement, state : ParserState) -> PartialASTElement: 304 | state.update(Lookahead(lookahead_binop)) 305 | if word.isdigit(): 306 | state.push_arg(PartialExpression(ast.Const, [ast.Number(int(word))])) 307 | elif word == "true": 308 | state.push_arg(PartialExpression(ast.Const, [ast.Bool(True)])) 309 | elif word == "false": 310 | state.push_arg(PartialExpression(ast.Const, [ast.Bool(False)])) 311 | elif word in reserved: 312 | raise ParsingException("use of reserved keyword as variable " + word, state) 313 | elif word.isidentifier(): 314 | state.push_arg(PartialExpression(ast.Var, [word])) 315 | else: 316 | raise ParsingExpectException("constant, unary operation, or variable", word, state) 317 | return result 318 | 319 | def expect_assign(word : str, result : PartialStatement, state : ParserState) -> PartialASTElement: 320 | if word != "=": 321 | raise ParsingExpectException("=", word, state) 322 | state.update(Lookahead(lookahead_expr)) 323 | return result # No append, just looking stuff up 324 | 325 | def expect_var(word : str, result : PartialStatement, state : ParserState) -> PartialASTElement: 326 | if word in reserved: 327 | raise ParsingException("use of reserved keyword as variable " + word, state) 328 | if not word.isidentifier(): 329 | raise ParsingExpectException("variable", word, state) 330 | state.update(expect_semi) 331 | result.append(ast.Var(word)) 332 | return result 333 | 334 | def expect_statement(word : str, result : Optional[PartialStatement], state : ParserState) -> PartialStatement: 335 | if word == "skip": 336 | state.update(expect_semi) 337 | cmd = PartialStatement(ast.Skip, [state.line_number]) 338 | elif word == "print": 339 | state.update(Lookahead(lookahead_expr)) 340 | cmd = PartialStatement(ast.Print, [state.line_number]) 341 | elif word == "input": 342 | state.update(expect_var) 343 | cmd = PartialStatement(ast.Input, [state.line_number]) 344 | elif word == "if": 345 | state.update(Lookahead(lookahead_expr)) 346 | state.set_state(ConditionState()) 347 | cmd = PartialStatement(ast.If, [state.line_number]) 348 | elif word == "elif": 349 | state.update(Lookahead(lookahead_expr)) 350 | state.set_state(ConditionState()) 351 | cmd = PartialStatement(ast.Elif, [state.line_number]) 352 | elif word == "else": 353 | state.update(expect_open_block) 354 | cmd = PartialStatement(ast.Else, [state.line_number]) 355 | elif word == "while": 356 | state.update(Lookahead(lookahead_expr)) 357 | state.set_state(ConditionState()) 358 | cmd = PartialStatement(ast.While, [state.line_number]) 359 | elif word == "}": 360 | outer = state.scope_out() 361 | if result == None: 362 | outer.append(PartialStatement(ast.Skip, [state.line_number])) 363 | else: 364 | outer.append(result) 365 | cmd = untangle_seq(outer, state) 366 | return cmd # Don't append cmd to result and _then_ return 367 | elif word in reserved: 368 | raise ParsingException("attempting to assign to reserved keyword " + str(word), state) 369 | elif word.isidentifier(): 370 | state.update(expect_assign) 371 | cmd = PartialStatement(ast.Assign, [state.line_number, ast.Var(word)]) 372 | else: 373 | raise ParsingExpectException("a Statement", word, state) 374 | if result is None: 375 | return cmd 376 | result.append(cmd) 377 | return result 378 | 379 | def parse_line(line : List[str], 380 | result : Optional[PartialStatement], 381 | state : ParserState) -> Optional[PartialStatement]: 382 | while line: 383 | if line[0].startswith("//"): #Comments 384 | return result 385 | word = line[0] 386 | if result is None: 387 | result = expect_statement(word, None, state) 388 | line.pop(0) 389 | elif isinstance(state.next, Lookahead): 390 | state.next.lookahead(word, result, state) 391 | else: 392 | result = state.next(word, result, state) 393 | line.pop(0) 394 | return result 395 | 396 | def parse(line : str, 397 | result : Optional[PartialStatement], 398 | state : ParserState) -> Optional[PartialStatement]: 399 | line = line.strip() # who needs whitespace anyway 400 | # "Lex" the line -- yes, this is janky, yes I'm too lazy to fix it 401 | tokens = ";=+-*^()<>\{\}%/" 402 | for token in tokens: 403 | line = line.replace(token, f" {token} ") 404 | # Special 2-character symbols 405 | line = line.replace("= =", "==") # yes, this is dumb, but it works, ok? 406 | line = line.replace("> =", ">=") 407 | line = line.replace("< =", "<=") 408 | line = line.replace("/ /", "//") 409 | line = line.split() 410 | return parse_line(line, result, state) 411 | 412 | def parse_file(filename : str) -> ast.Program: 413 | result = None 414 | state = ParserState() 415 | with open(filename, 'r') as f: 416 | for line in f: 417 | result = parse(line, result, state) 418 | state.line_number += 1 419 | if len(state.scope) > 0: 420 | raise ParsingException("unclosed scope (did you forget a '}'?)", state) 421 | if result is None: 422 | return ast.Program(ast.Skip) # default program 423 | try: 424 | result = result.pack(state) 425 | except: 426 | raise ParsingException("incomplete final Statement " + str(result.cmd.__name__), state) 427 | return ast.Program(result) -------------------------------------------------------------------------------- /src/typechecker.py: -------------------------------------------------------------------------------- 1 | from src.util import * 2 | import src.ast as ast 3 | from src.typed_ast import * 4 | from typing import Dict, Tuple, Union, Type 5 | 6 | class TypeException(Exception): 7 | def __init__(self, message : str, context): 8 | super().__init__("Type Error on line " + str(context.line_number) + ": " + str(message)) 9 | 10 | class TypeExpectException(TypeException): 11 | def __init__(self, expect : BaseType, word : BaseType, context): 12 | super().__init__("expected " + str(expect) + ", got " + str(word), context) 13 | 14 | class TypeContext(BaseClass): 15 | def __init__(self): 16 | self.vars : Dict[str, BaseType] = dict() 17 | self.line_number = 0 18 | 19 | def get_var(self, x : str) -> BaseType: 20 | typecheck(x, str) 21 | if x not in self.vars: 22 | raise TypeException("undefined variable " + str(x), self) 23 | return self.vars[x] 24 | 25 | def add_var(self, x : str, t : BaseType): 26 | typecheck(x, str) 27 | typecheck(t, BaseType) 28 | self.vars[x] = t 29 | 30 | def copy(self): 31 | result = TypeContext() 32 | result.vars = self.vars.copy() 33 | return result 34 | 35 | def __repr__(self): 36 | return "TYPE_CONTEXT " + str(self.line_number) + "\n" + str(self.vars) 37 | 38 | def check_const(c : ast.Const, context : TypeContext) -> Typed[ast.Const]: 39 | if isinstance(c.v, ast.Bool): 40 | c.v = Typed(c.v, BoolType()) 41 | return Typed(c, BoolType()) 42 | if isinstance(c.v, ast.Number): 43 | c.v = Typed(c.v, IntType()) 44 | return Typed(c, IntType()) 45 | raise InternalException("Unknown matched const " + str(c)) 46 | 47 | def check_var(var : Union[ast.Var, Typed], context : TypeContext) -> Typed[str]: 48 | if isinstance(var, Typed): 49 | raise InternalException("Unexpected Typed var " + str(var)) 50 | if isinstance(var.v, Typed): 51 | raise InternalException("Unexpected Typed v in" + str(var)) 52 | return Typed(var, context.get_var(var.v)) 53 | 54 | def match_unop(to_check : Tuple[str, BaseType], expect : Tuple[str, Type[BaseType]], context) -> bool: 55 | if to_check[0] != expect[0]: 56 | return False 57 | if not isinstance(to_check[1], expect[1]): 58 | raise TypeExpectException(expect[1](), to_check[1], context) 59 | return True 60 | 61 | def match_binop(to_check : Tuple[str, BaseType, BaseType], expect : Tuple[List[str], Type[BaseType], Type[BaseType]], context) -> bool: 62 | if to_check[0] not in expect[0]: 63 | return False 64 | if not isinstance(to_check[1], expect[1]): 65 | raise TypeExpectException(expect[1](), to_check[1], context) 66 | if not isinstance(to_check[2], expect[2]): 67 | raise TypeExpectException(expect[2](), to_check[2], context) 68 | return True 69 | 70 | def match_binop_any(to_check : Tuple[str, BaseType, BaseType], expect : Tuple[List[str], List[BaseType], List[BaseType]], context) -> bool: 71 | if to_check[0] not in expect[0]: 72 | return False 73 | if len(expect[1]) != len(expect[2]): 74 | raise InternalException("Invalid pair of expect lists " + str(expect[1]) + " and " + str(expect[2])) 75 | for t1, t2 in zip(expect[1], expect[2]): 76 | if isinstance(to_check[1], t1) and isinstance(to_check[2], t2): 77 | return True 78 | raise TypeExpectException([(x[0](), x[1]()) for x in zip(expect[1], expect[2])], (to_check[1], to_check[2]), context) 79 | 80 | def check_unop(u : ast.Unop, context : TypeContext) -> Typed[ast.Unop]: 81 | u.exp = check_expr(u.exp, context) 82 | check = (u.op.op, u.exp.typ) 83 | if match_unop(check, ("-", IntType), context): 84 | return Typed(u, IntType()) 85 | if match_unop(check, ("not", BoolType), context): 86 | return Typed(u, BoolType()) 87 | raise InternalException("Unknown unary operation " + str(u.op.op)) 88 | 89 | def check_binop(b : ast.Binop, context : TypeContext) -> Typed[ast.Binop]: 90 | b.left = check_expr(b.left, context) 91 | b.right = check_expr(b.right, context) 92 | check = (b.op.op, b.left.typ, b.right.typ) 93 | if match_binop(check, (["+", "-", "*", "%", "/", "^"], IntType, IntType), context): 94 | return Typed(b, IntType()) 95 | if match_binop(check, (["<", ">", "<=", ">="], IntType, IntType), context): 96 | return Typed(b, BoolType()) 97 | if match_binop_any(check, (["=="], [BoolType, IntType], [BoolType, IntType]), context): 98 | return Typed(b, BoolType()) 99 | if match_binop(check, (["and", "or"], BoolType, BoolType), context): 100 | return Typed(b, BoolType()) 101 | raise InternalException("Unknown binary operation " + str(b.op.op)) 102 | 103 | def check_expr(exp : Union[ast.Expr, Typed], context : TypeContext) -> Typed[ast.Expr]: 104 | if isinstance(exp, ast.Const): 105 | return check_const(exp, context) 106 | if isinstance(exp, ast.Var): 107 | v = check_var(exp, context) 108 | return Typed(exp, v.typ) 109 | if isinstance(exp, ast.Unop): 110 | return check_unop(exp, context) 111 | if isinstance(exp, ast.Binop): 112 | return check_binop(exp, context) 113 | 114 | def check_assign(statement : ast.Assign, context : TypeContext) -> Typed[ast.Assign]: 115 | if isinstance(statement.var, Typed) or isinstance(statement.var.v, Typed): 116 | raise InternalException("Unexpected typed var in " + str(statement)) 117 | statement.exp = check_expr(statement.exp, context) 118 | context.add_var(statement.var.v, statement.exp.typ) 119 | statement.var = Typed(statement.var, statement.exp.typ) 120 | return Typed(statement, UnitType()) 121 | 122 | def check_seq(statement : ast.Seq, context : TypeContext) -> Typed[ast.Seq]: 123 | if (isinstance(statement.s2, ast.Seq) and (isinstance(statement.s2.s1, ast.Else) or isinstance(statement.s2.s1, ast.Elif))) \ 124 | or isinstance(statement.s2, ast.Else) or isinstance(statement.s2, ast.Elif): 125 | if not (isinstance(statement.s1, ast.If) or isinstance(statement.s1, ast.Elif)): 126 | raise TypeException("Elif or Else statement without preceding if or elif statement", context) 127 | statement.s1 = check_statement(statement.s1, context) 128 | statement.s2 = check_statement(statement.s2, context) 129 | return Typed(statement, UnitType()) 130 | 131 | def check_if(statement : Union[ast.If, ast.Elif, ast.While], context : TypeContext) -> Typed[Union[ast.If, ast.Elif, ast.While]]: 132 | # We can union this whole mess cause of similar names and duck typing... 133 | statement.b = check_expr(statement.b, context) # this is terrible stuff 134 | if not isinstance(statement.b.typ, BoolType): 135 | raise TypeExpectException(BoolType(), statement.b.typ, context) 136 | statement.s = check_statement(statement.s, context.copy()) # Scope! 137 | return Typed(statement, UnitType()) 138 | 139 | def check_else(statement : ast.Else, context : TypeContext) -> Typed[ast.Else]: 140 | statement.s = check_statement(statement.s, context.copy()) # Scope! 141 | return Typed(statement, UnitType()) 142 | 143 | def check_print(statement : ast.Print, context : TypeContext) -> Typed[ast.Print]: 144 | statement.exp = check_expr(statement.exp, context) 145 | return Typed(statement, UnitType()) 146 | 147 | def check_input(statement : ast.Input, context : TypeContext) -> Typed[ast.Input]: 148 | if isinstance(statement.var, Typed) or isinstance(statement.var.v, Typed): 149 | raise InternalException("Unexpected typed var in " + str(statement)) 150 | context.add_var(statement.var.v, IntType()) 151 | statement.var = Typed(statement.var, IntType()) 152 | return Typed(statement, UnitType()) 153 | 154 | def check_statement(statement : Union[ast.Statement, Typed], context : TypeContext) -> Typed[ast.Statement]: 155 | if isinstance(statement, Typed): 156 | raise InternalException("Unexpected typed statement", Typed) 157 | context.line_number = statement.ln # Typesafe cause ast.Statement is an abstract class 158 | if isinstance(statement, ast.Skip): 159 | return Typed(statement, UnitType()) 160 | if isinstance(statement, ast.Assign): 161 | return check_assign(statement, context) 162 | if isinstance(statement, ast.Seq): 163 | return check_seq(statement, context) 164 | if isinstance(statement, ast.If): 165 | return check_if(statement, context) 166 | if isinstance(statement, ast.Elif): 167 | return check_if(statement, context) 168 | if isinstance(statement, ast.Else): 169 | return check_else(statement, context) 170 | if isinstance(statement, ast.While): 171 | return check_if(statement, context) 172 | if isinstance(statement, ast.Print): 173 | return check_print(statement, context) 174 | if isinstance(statement, ast.Input): 175 | return check_input(statement, context) 176 | raise InternalException("Unknown matched statement " + str(statement)) 177 | 178 | def typecheck_program(program : ast.Program) -> Typed[ast.Program]: 179 | context = TypeContext() 180 | handle_seq = program.s.s1 if isinstance(program.s, ast.Seq) else program.s 181 | if isinstance(handle_seq, ast.Elif) or isinstance(handle_seq, ast.Else): 182 | raise TypeException("Cannot start program with Elif or If statement", context) 183 | return Typed(ast.Program(check_statement(program.s, context)), UnitType()) -------------------------------------------------------------------------------- /src/typed_ast.py: -------------------------------------------------------------------------------- 1 | import src.util as util 2 | from typing import Generic, TypeVar 3 | 4 | class BaseType(util.BaseClass): 5 | pass 6 | 7 | class BoolType(BaseType): 8 | def __init__(self): 9 | pass 10 | def __repr__(self): 11 | return "type bool" 12 | 13 | class IntType(BaseType): 14 | def __init__(self): 15 | pass 16 | def __repr__(self): 17 | return "type int" 18 | 19 | class UnitType(BaseType): 20 | def __init__(self): 21 | pass 22 | def __repr__(self): 23 | return "type unit" 24 | 25 | T = TypeVar('T') 26 | 27 | class Typed(Generic[T]): 28 | def __init__(self, element : T, typ : BaseType): 29 | util.typecheck(typ, BaseType) 30 | 31 | self.element = element 32 | self.typ = typ 33 | def __repr__(self): 34 | return str(self.typ) + " : " + str(self.element) -------------------------------------------------------------------------------- /src/util.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | class InternalException(Exception): 4 | def __init__(self, error : str): 5 | super().__init__("Internal error: " + str(error)) 6 | 7 | class UnimplementedException(InternalException): 8 | def __init__(self, value : any): 9 | super().__init__("Unimplemented " + str(value)) 10 | 11 | def typecheck(value : any, t : type) -> type: 12 | if not isinstance(value, t): 13 | raise InternalException("expected " + str(t.__name__) + " got " + str(value)) 14 | return value 15 | 16 | def typecheck_any(value : any, typs : List[type]): 17 | for t in typs: 18 | if isinstance(value, t): 19 | return 20 | raise InternalException("expected one of " + [str(t.__name__) for t in typs] + " got " + str(value)) 21 | 22 | class BaseClass: 23 | def __init__(self): 24 | raise Exception("Abstract class") 25 | def __repr__(self): 26 | raise InternalException("forgot to provide __repr__ to " + str(type(self))) -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | from tools.test_util import * 2 | from sys import argv 3 | import os 4 | 5 | def help(): 6 | print("Expected args: python test.py (name_of_file.pylambda | name_of_folder) [--err]") 7 | 8 | def main(): 9 | if len(argv) < 2: 10 | help() 11 | return 12 | if os.path.isdir(argv[1]): 13 | for filename in os.listdir(argv[1]): 14 | filepath = f"{argv[1]}/{filename}" 15 | if os.path.isdir(filepath): 16 | continue 17 | if not test_file(filepath): 18 | print(f"Failure in file {filename}") 19 | return 20 | print(f"{filepath} run successfully") 21 | print(f"Tests run successfully on directory {argv[1]}") 22 | return 23 | if not test_file(argv[1]): 24 | print(f"Failure in file {argv[1]}") 25 | else: 26 | print(f"{argv[1]} run successfully") 27 | 28 | if __name__=="__main__": 29 | main() -------------------------------------------------------------------------------- /tests/assignment.pylambda: -------------------------------------------------------------------------------- 1 | // Assignment tests 2 | x = 5; 3 | print x; 4 | y=3; 5 | print y; -------------------------------------------------------------------------------- /tests/binop.pylambda: -------------------------------------------------------------------------------- 1 | print (1+2); 2 | -------------------------------------------------------------------------------- /tests/bool_binop.pylambda: -------------------------------------------------------------------------------- 1 | print true; 2 | print not false; 3 | print true or false; -------------------------------------------------------------------------------- /tests/elif.pylambda: -------------------------------------------------------------------------------- 1 | if false and true { 2 | print false; 3 | } elif true { 4 | print true; 5 | } else { 6 | print false; 7 | } 8 | if false and (false or true) { 9 | print false; 10 | } elif true or not true { 11 | print true; 12 | } elif true { 13 | print false; 14 | } elif false { 15 | print false; 16 | } else { 17 | print false; 18 | } 19 | print 0; -------------------------------------------------------------------------------- /tests/else.pylambda: -------------------------------------------------------------------------------- 1 | if false { 2 | print false; 3 | } else { 4 | print true; 5 | } 6 | print 0; -------------------------------------------------------------------------------- /tests/if.pylambda: -------------------------------------------------------------------------------- 1 | if true or false { 2 | print 1; 3 | if false { 4 | print 2; 5 | } 6 | } 7 | print 3; -------------------------------------------------------------------------------- /tests/input.pylambda: -------------------------------------------------------------------------------- 1 | input x; 2 | print x; -------------------------------------------------------------------------------- /tests/parsing_errors/bad_assign.pylambda: -------------------------------------------------------------------------------- 1 | x 5; -------------------------------------------------------------------------------- /tests/parsing_errors/bad_assign2.pylambda: -------------------------------------------------------------------------------- 1 | hello = 1a; -------------------------------------------------------------------------------- /tests/parsing_errors/binop.pylambda: -------------------------------------------------------------------------------- 1 | print 1+; -------------------------------------------------------------------------------- /tests/parsing_errors/else.pylambda: -------------------------------------------------------------------------------- 1 | else true { 2 | print 5; 3 | } -------------------------------------------------------------------------------- /tests/parsing_errors/if.pylambda: -------------------------------------------------------------------------------- 1 | if true { 2 | skip; -------------------------------------------------------------------------------- /tests/parsing_errors/if2.pylambda: -------------------------------------------------------------------------------- 1 | if { 2 | skip; 3 | } -------------------------------------------------------------------------------- /tests/parsing_errors/if3.pylambda: -------------------------------------------------------------------------------- 1 | if true { 2 | if false { 3 | print 5; 4 | } -------------------------------------------------------------------------------- /tests/parsing_errors/reserved.pylambda: -------------------------------------------------------------------------------- 1 | true = 5; -------------------------------------------------------------------------------- /tests/parsing_errors/reserved2.pylambda: -------------------------------------------------------------------------------- 1 | print input; -------------------------------------------------------------------------------- /tests/parsing_errors/unmatched1.pylambda: -------------------------------------------------------------------------------- 1 | print (1+2; -------------------------------------------------------------------------------- /tests/parsing_errors/unmatched2.pylambda: -------------------------------------------------------------------------------- 1 | print 1+2); -------------------------------------------------------------------------------- /tests/parsing_errors/unop.pylambda: -------------------------------------------------------------------------------- 1 | print not; -------------------------------------------------------------------------------- /tests/parsing_errors/while.pylambda: -------------------------------------------------------------------------------- 1 | while { 2 | print 5; 3 | } -------------------------------------------------------------------------------- /tests/printing.pylambda: -------------------------------------------------------------------------------- 1 | print 0; 2 | print 1; 3 | print true; 4 | print false; -------------------------------------------------------------------------------- /tests/scope.pylambda: -------------------------------------------------------------------------------- 1 | x = 5; 2 | if true { 3 | y = 3; 4 | if true { 5 | y = 2; 6 | if true { 7 | x = 1; 8 | } 9 | } 10 | print y; 11 | } 12 | print x; -------------------------------------------------------------------------------- /tests/skip.pylambda: -------------------------------------------------------------------------------- 1 | skip -------------------------------------------------------------------------------- /tests/type_errors/binop.pylambda: -------------------------------------------------------------------------------- 1 | print 1 and 2; -------------------------------------------------------------------------------- /tests/type_errors/binop2.pylambda: -------------------------------------------------------------------------------- 1 | print true + false; -------------------------------------------------------------------------------- /tests/type_errors/binop3.pylambda: -------------------------------------------------------------------------------- 1 | print 1 + (1 < 2); -------------------------------------------------------------------------------- /tests/type_errors/binop4.pylambda: -------------------------------------------------------------------------------- 1 | print true or 1; -------------------------------------------------------------------------------- /tests/type_errors/binop5.pylambda: -------------------------------------------------------------------------------- 1 | print 1 == true; -------------------------------------------------------------------------------- /tests/type_errors/elif.pylambda: -------------------------------------------------------------------------------- 1 | if true or false { 2 | print true; 3 | } elif 1 + 1 { 4 | print false; 5 | } else { 6 | print false; 7 | } 8 | print 1; -------------------------------------------------------------------------------- /tests/type_errors/elif1.pylambda: -------------------------------------------------------------------------------- 1 | print 1; 2 | elif true{ 3 | print 1; 4 | } -------------------------------------------------------------------------------- /tests/type_errors/elif2.pylambda: -------------------------------------------------------------------------------- 1 | elif true { 2 | print 1; 3 | } -------------------------------------------------------------------------------- /tests/type_errors/else.pylambda: -------------------------------------------------------------------------------- 1 | else { 2 | print 1; 3 | } -------------------------------------------------------------------------------- /tests/type_errors/else2.pylambda: -------------------------------------------------------------------------------- 1 | print 1; 2 | else { 3 | print 1; 4 | } -------------------------------------------------------------------------------- /tests/type_errors/if.pylambda: -------------------------------------------------------------------------------- 1 | if 1 + 2 {} -------------------------------------------------------------------------------- /tests/type_errors/if2.pylambda: -------------------------------------------------------------------------------- 1 | input x; 2 | if true { 3 | if x { 4 | print 0; 5 | } 6 | } 7 | print 1; -------------------------------------------------------------------------------- /tests/type_errors/input.pylambda: -------------------------------------------------------------------------------- 1 | input x; 2 | print x or true; -------------------------------------------------------------------------------- /tests/type_errors/unop.pylambda: -------------------------------------------------------------------------------- 1 | print -true; -------------------------------------------------------------------------------- /tests/type_errors/unop2.pylambda: -------------------------------------------------------------------------------- 1 | print not 5; -------------------------------------------------------------------------------- /tests/type_errors/var.pylambda: -------------------------------------------------------------------------------- 1 | x = 5 + 3; 2 | print x or true; -------------------------------------------------------------------------------- /tests/type_errors/var2.pylambda: -------------------------------------------------------------------------------- 1 | x = 5 + 3; 2 | y = true == false; 3 | print x + y; -------------------------------------------------------------------------------- /tests/type_errors/while.pylambda: -------------------------------------------------------------------------------- 1 | input x; 2 | while x { 3 | x = x - 1; 4 | } 5 | print x; -------------------------------------------------------------------------------- /tests/unop.pylambda: -------------------------------------------------------------------------------- 1 | print not true; 2 | -------------------------------------------------------------------------------- /tests/while.pylambda: -------------------------------------------------------------------------------- 1 | x = 5; 2 | while x > 0 { 3 | print x; 4 | x = x - 1; 5 | } 6 | while x < 0 { 7 | print x; 8 | x = x - 1; 9 | } 10 | print -1; -------------------------------------------------------------------------------- /tools/README.md: -------------------------------------------------------------------------------- 1 | # Tools for Pylambda 2 | 3 | ## pylambda_to_python.py 4 | 5 | Given a `.pylambda` file, writes the file as raw Python for interpretation to stdout. The intention of this is for testing to make sure that both the pylambda compiler works and to check that the pylambda program works. The output of this can be put into a file or read directly by python with `python tools\pylambda_to_python.py > python -` (assuming you're in root) 6 | -------------------------------------------------------------------------------- /tools/pylambda_to_python.py: -------------------------------------------------------------------------------- 1 | """ 2 | Given a syntactically-valid pylambda program 3 | Produces a python code result 4 | Note that we assume valid pylambda code, things _will break_ if it's not 5 | Note also that any `input` will be replaced with 0 6 | """ 7 | 8 | from sys import argv 9 | from typing import List 10 | 11 | class ReadState: 12 | def __init__(self): 13 | self.index = 0 14 | self.scope = 0 15 | 16 | def __str__(self): 17 | return self.__repr__() 18 | def __repr__(self): 19 | return f"READSTATE: {self.index} {self.scope}" 20 | 21 | def help(): 22 | print("Expected args: python pylambda_to_python.py name_of_file.pylambda") 23 | 24 | # Literally the laziest lexing imaginable 25 | # Just ignore literally all external whitespace, and parse the internal stuff 26 | def lex_file(filename : str) -> List[str]: 27 | result = [] 28 | with open(filename, 'r') as f: 29 | for line in f: 30 | # we only care about `;{}` being parsed properly 31 | line = line.replace(";", " ; ").replace("{", " { ").replace("}", " } ") 32 | result += line.strip().split("//")[0].split() 33 | return result 34 | 35 | # Update state.index as we go, but parse a single command (until { or ;) 36 | def parse_command(inp : List[str], state : ReadState) -> str: 37 | token = inp[state.index] 38 | state.index += 1 39 | result = [] 40 | parsing_print = False 41 | parsing_input = False 42 | if token != "skip": 43 | result.append(token) 44 | if token == "print": # Special case for pylambda 45 | result.append("(") 46 | parsing_print = True 47 | if token == "input": 48 | parsing_input = True 49 | while token not in (';', '{', '}') and state.index < len(inp): # we actually don't care about the middle at all 50 | token = inp[state.index] 51 | state.index += 1 52 | if token == '^': # Special case for exponentiation 53 | result.append("**") 54 | elif token == '/': # Division stuff 55 | result.append("//") 56 | elif token in ("true", "false"): 57 | result.append(token.capitalize()) 58 | else: 59 | result.append(token) 60 | if parsing_print: 61 | result.insert(-1, ')') 62 | if parsing_input: 63 | # We use a dummy input for this 64 | result = result[1:-1] + ["= 0"] + [result[-1]] 65 | if token == '{': 66 | result[-1] = ':' 67 | state.scope += 1 68 | elif token == '}': 69 | result.pop() 70 | if state.scope <= 0: 71 | raise Exception("Unmatched }") 72 | state.scope -= 1 73 | elif token == ';': 74 | result.pop() 75 | return ' '.join(result) 76 | 77 | def to_python(inp : List[str]) -> str: 78 | state = ReadState() 79 | result = "" 80 | while state.index < len(inp): 81 | result += (" " * state.scope) + parse_command(inp, state) + "\n" 82 | return result 83 | 84 | def main(): 85 | if len(argv) < 2: 86 | help() 87 | exit() 88 | print(to_python(lex_file(argv[1]))) 89 | 90 | if __name__=="__main__": 91 | main() -------------------------------------------------------------------------------- /tools/test_util.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | from typing import Optional, List 3 | 4 | def interpret_file(command : str, filename : str) -> Optional[str]: 5 | interp = subprocess.Popen(["python", f"{command}.py", filename, "--no-input"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) 6 | interp_out = subprocess.Popen(["python", "-"], stdin=interp.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 7 | res1 = interp.stderr.read() 8 | if res1 != b"": 9 | print(res1.decode("UTF-8")) 10 | return None 11 | interp.stdout.close() 12 | res2 = interp_out.communicate() 13 | if res2[1] != b"": 14 | print(res2[1].decode("UTF-8")) 15 | return None 16 | return res2[0].decode("UTF-8") 17 | 18 | def clean(result : str) -> List[str]: 19 | return [x.strip() for x in result.split("\n")] 20 | 21 | def compare(result1 : str, result2 : str) -> bool: 22 | data1 = clean(result1) 23 | data2 = clean(result2) 24 | if len(data1) != len(data2): 25 | print (f"Non-matching number of results: expected {len(data2)}, got {len(data1)}") 26 | ln = 1 27 | for line1, line2 in zip(data1, data2): 28 | if line1 != line2: 29 | print (f"Unexpected result on line {ln}: expected {line2}, got {line1}") 30 | return False 31 | ln += 1 32 | return True 33 | 34 | def test_file(filename : str) -> bool: 35 | result1 = interpret_file("pylambda", filename) 36 | if result1 is None: 37 | return False 38 | result2 = interpret_file("tools/pylambda_to_python", filename) 39 | if result2 is None: 40 | return False 41 | return compare(result1, result2) --------------------------------------------------------------------------------