├── .github └── workflows │ └── build.yml ├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── main.py ├── mini_lisp_interpreter ├── __init__.py ├── grammar.lark └── interpreter.py ├── poetry.lock ├── poetry.toml ├── pylintrc ├── pyproject.toml └── tests ├── __init__.py ├── test_data ├── 01_1.lsp ├── 01_2.lsp ├── 02_1.lsp ├── 02_2.lsp ├── 03_1.lsp ├── 03_2.lsp ├── 04_1.lsp ├── 04_2.lsp ├── 05_1.lsp ├── 05_2.lsp ├── 06_1.lsp ├── 06_2.lsp ├── 07_1.lsp ├── 07_2.lsp ├── 08_1.lsp ├── 08_2.lsp ├── b1_1.lsp ├── b1_2.lsp ├── b2_1.lsp ├── b2_2.lsp ├── b3_1.lsp ├── b3_2.lsp ├── b4_1.lsp └── b4_2.lsp └── test_mini_lisp_interpreter.py /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | 11 | - name: Set up Python 12 | uses: actions/setup-python@v2 13 | with: 14 | python-version: 3.8 15 | 16 | - name: Set up Poetry 17 | run: | 18 | curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python 19 | echo "PATH=$HOME/.poetry/bin:$PATH" >> $GITHUB_ENV 20 | 21 | - name: Install dependencies 22 | run: poetry install --no-root 23 | 24 | - name: Lint 25 | run: | 26 | poetry run python -m pylint mini_lisp_interpreter/ 27 | poetry run python -m mypy --ignore-missing-import mini_lisp_interpreter/ 28 | 29 | - name: Test 30 | run: poetry run python -m pytest -v --cov-report xml --cov=mini_lisp_interpreter tests/ 31 | 32 | - name: Upload coverage report to Codacy 33 | uses: codacy/codacy-coverage-reporter-action@master 34 | with: 35 | project-token: ${{ secrets.CODACY_PROJECT_TOKEN }} 36 | coverage-reports: coverage.xml -------------------------------------------------------------------------------- /.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 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .nox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *.cover 48 | .hypothesis/ 49 | .pytest_cache/ 50 | 51 | # Translations 52 | *.mo 53 | *.pot 54 | 55 | # Django stuff: 56 | *.log 57 | local_settings.py 58 | db.sqlite3 59 | 60 | # Flask stuff: 61 | instance/ 62 | .webassets-cache 63 | 64 | # Scrapy stuff: 65 | .scrapy 66 | 67 | # Sphinx documentation 68 | docs/_build/ 69 | 70 | # PyBuilder 71 | target/ 72 | 73 | # Jupyter Notebook 74 | .ipynb_checkpoints 75 | 76 | # IPython 77 | profile_default/ 78 | ipython_config.py 79 | 80 | # pyenv 81 | .python-version 82 | 83 | # celery beat schedule file 84 | celerybeat-schedule 85 | 86 | # SageMath parsed files 87 | *.sage.py 88 | 89 | # Environments 90 | .env 91 | .venv 92 | env/ 93 | venv/ 94 | ENV/ 95 | env.bak/ 96 | venv.bak/ 97 | 98 | # Spyder project settings 99 | .spyderproject 100 | .spyproject 101 | 102 | # Rope project settings 103 | .ropeproject 104 | 105 | # mkdocs documentation 106 | /site 107 | 108 | # mypy 109 | .mypy_cache/ 110 | .dmypy.json 111 | dmypy.json 112 | 113 | # vscode 114 | .vscode/* 115 | !.vscode/settings.json 116 | !.vscode/tasks.json 117 | !.vscode/launch.json 118 | !.vscode/extensions.json -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "python.linting.enabled": true, 4 | "python.linting.pylintEnabled": true, 5 | "python.linting.mypyEnabled": true, 6 | "python.linting.pylintUseMinimalCheckers": false, 7 | "python.pythonPath": ".venv/bin/python" 8 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Sean Wu 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mini-Lisp Interpreter 2 | 3 | [![build](https://github.com/seanwu1105/mini-lisp-interpreter/workflows/build/badge.svg)](https://github.com/seanwu1105/mini-lisp-interpreter/actions?query=workflow%3Abuild) 4 | 5 | Mini-LISP is the subset of LISP. You can find the grammar rules in the README.md file. This is the final project of Compiler in NCU, Taiwan. 6 | 7 | ## Overview 8 | 9 | LISP is an ancient programming language based on S-expressions and lambda calculus. All operations in Mini-LISP are written in parenthesized prefix notation. For example, a simple mathematical formula `(1 + 2) * 3` written in Mini-LISP is: 10 | 11 | ``` lisp 12 | (* (+ 1 2) 3) 13 | ``` 14 | 15 | As a simplified language, Mini-LISP has only three types (Boolean, number and function) and a few operations. 16 | 17 | ## Features 18 | 19 | ### Basic Features 20 | 21 | * [x] Syntax Validation 22 | * [x] Print 23 | * [x] Numerical Operations 24 | * [x] Logical Operations 25 | * [x] if Expression 26 | * [x] Variable Definition 27 | * [x] Function 28 | * [x] Named Function 29 | 30 | ### Bonus Features 31 | 32 | * [x] Recursion 33 | * [x] Type Checking 34 | * [x] Nested Function 35 | * [x] First-class Function 36 | 37 | ## Usage 38 | 39 | Clone the project. 40 | 41 | ``` bash 42 | git clone https://github.com/seanwu1105/mini-lisp-interpreter 43 | ``` 44 | 45 | Change directory to project folder. 46 | 47 | ``` bash 48 | cd mini-lisp-interpreter/ 49 | ``` 50 | 51 | Install the dependencies with Poetry. 52 | 53 | ``` bash 54 | poetry install --no-root 55 | ``` 56 | 57 | Feed the Mini-LISP source codes into the interpreter as standard input file. 58 | 59 | ``` bash 60 | python main.py < filename.lsp 61 | ``` 62 | 63 | Or import the `Interpreter` class in `mini_lisp_interpreter` folder and call the `Interpreter().interpret(your_mini_lisp_code)`. 64 | 65 | For example: 66 | 67 | ``` python 68 | mini_lisp_interpreter.Interpreter().interpret(r'(print-num (mod 10 4))') 69 | ``` 70 | 71 | ## Type Definition 72 | 73 | * Boolean: Boolean type includes two values, `#t` for true and `#f` for false. 74 | * Number: Signed integer from `-(231)` to `231 - 1`, behavior out of this range is not defined. 75 | * Function: See [Function](#function). 76 | 77 | > Casting: Not allowed, but type checking is a bonus feature. 78 | 79 | ## Operation Overview 80 | 81 | ### Numerical Operators 82 | 83 | | Name | Symbol | Example | Example Output | 84 | |:--------:|:------:|:-----------:|:--------------:| 85 | | Plus | `+` | `(+ 1 2)` | `3` | 86 | | Minus | `-` | `(- 1 2)` | `-1` | 87 | | Multiply | `*` | `(* 2 3)` | `6` | 88 | | Divide | `/` | `(/ 10 3)` | `3` | 89 | | Modulus | `mod` | `(mod 8 3)` | `2` | 90 | | Greater | `>` | `(> 1 2)` | `#f` | 91 | | Smaller | `<` | `(< 1 2)` | `#t` | 92 | | Equal | `=` | `(= 1 2)` | `#f` | 93 | 94 | ### Logical Operators 95 | 96 | | Name | Symbol | Example | Example Output | 97 | |:----:|:------:|:-------------:|:--------------:| 98 | | And | `and` | `(and #t #f)` | `#f` | 99 | | Or | `or` | `(or #t #f)` | `#t` | 100 | | Not | `not` | `(not #t)` | `#f` | 101 | 102 | ### Other Operators 103 | 104 | * `define` 105 | * `fun` 106 | * `if` 107 | 108 | > All operators are reserved words, you cannot use any of these words as ID. 109 | 110 | ## Lexical Details 111 | 112 | ### Preliminary Definitions 113 | 114 | ``` ebnf 115 | separator ::= ‘\t’ | ‘\n’ | ‘\r’ | ‘ ’ 116 | letter ::= [a-z] 117 | digit ::= [0-9] 118 | ``` 119 | 120 | ### Token Definitions 121 | 122 | ``` ebnf 123 | number ::= 0 | [1-9]digit* | -[1-9]digit* 124 | ``` 125 | 126 | Examples: `0`, `1`, `-23`, `123456` 127 | 128 | ``` ebnf 129 | ID ::= letter (letter | digit | ‘-’)* 130 | ``` 131 | 132 | Examples: `x`, `y`, `john`, `cat-food` 133 | 134 | ``` ebnf 135 | bool-val ::= #t | #f 136 | ``` 137 | 138 | ## Grammar Overview 139 | 140 | ``` ebnf 141 | PROGRAM ::= STMT+ 142 | STMT ::= EXP | DEF-STMT | PRINT-STMT 143 | PRINT-STMT ::= (print-num EXP) | (print-bool EXP) 144 | EXP ::= bool-val | number | VARIABLE | NUM-OP | LOGICAL-OP 145 | 146 | | FUN-EXP | FUN-CALL | IF-EXP 147 | 148 | NUM-OP ::= PLUS | MINUS | MULTIPLY | DIVIDE | MODULUS | GREATER 149 | 150 | | SMALLER | EQUAL 151 | PLUS ::= (+ EXP EXP+) 152 | MINUS ::= (- EXP EXP) 153 | MULTIPLY ::= (* EXP EXP+) 154 | DIVIDE ::= (/ EXP EXP) 155 | MODULUS ::= (mod EXP EXP) 156 | GREATER ::= (> EXP EXP) 157 | SMALLER ::= (< EXP EXP) 158 | EQUAL ::= (= EXP EXP+) 159 | LOGICAL-OP ::= AND-OP | OR-OP | NOT-OP 160 | AND-OP ::= (and EXP EXP+) 161 | OR-OP ::= (or EXP EXP+) 162 | NOT-OP ::= (not EXP) 163 | DEF-STMT ::= (define VARIABLE EXP) 164 | VARIABLE ::= id 165 | FUN-EXP ::= (fun FUN_IDs FUN-BODY) 166 | FUN-IDs ::= (id*) 167 | FUN-BODY ::= EXP 168 | FUN-CALL ::= (FUN-EXP PARAM*) | (FUN-NAME PARAM*) 169 | PARAM ::= EXP 170 | FUN-NAME ::= id 171 | IF-EXP ::= (if TEST-EXP THAN-EXP ELSE-EXP) 172 | TEST-EXP ::= EXP 173 | THEN-EXP ::= EXP 174 | ELSE-EXP ::= EXP 175 | ``` 176 | 177 | ## Grammar and Behavior Definition 178 | 179 | ### Program 180 | 181 | ``` ebnf 182 | PROGRAM :: = STMT+ 183 | STMT ::= EXP | DEF-STMT | PRINT-STMT 184 | ``` 185 | 186 | ### Print 187 | 188 | ``` ebnf 189 | PRINT-STMT ::= (print-num EXP) 190 | ``` 191 | 192 | Behavior: Print `exp` in decimal. 193 | 194 | ``` ebnf 195 | | (print-bool EXP) 196 | ``` 197 | 198 | Behavior: Print `#t` if `EXP` is true. Print `#f`, otherwise. 199 | 200 | ### Expression (`EXP`) 201 | 202 | ``` ebnf 203 | EXP ::= bool-val | number | VARIABLE 204 | | NUM-OP | LOGICAL-OP | FUN-EXP | FUN-CALL | IF-EXP 205 | ``` 206 | 207 | ### Numerical Operations (`NUM-OP`) 208 | 209 | ``` ebnf 210 | NUM-OP ::= PLUS | MINUS | MULTIPLY | DIVIDE | MODULUS | 211 | | GREATER | SMALLER | EQUAL 212 | ``` 213 | 214 | #### `PLUS ::= (+ EXP EXP+)` 215 | 216 | Behavior: return sum of all `EXP` inside. 217 | 218 | Example: 219 | 220 | ``` lisp 221 | (+ 1 2 3 4) ; → 10 222 | ``` 223 | 224 | #### `MINUS ::= (- EXP EXP)` 225 | 226 | Behavior: return the result that the 1st `EXP` minus the 2nd `EXP`. 227 | 228 | Example: 229 | 230 | ``` lisp 231 | (- 2 1) ; → 1 232 | ``` 233 | 234 | #### `MULTIPLY ::= (* EXP EXP+)` 235 | 236 | Behavior: return the product of all `EXP` inside. 237 | 238 | Example: 239 | 240 | ``` lisp 241 | (* 1 2 3 4) ; → 24 242 | ``` 243 | 244 | #### `DIVIDE ::= (/ EXP EXP)` 245 | 246 | Behavior: return the result that 1st `EXP` divided by 2nd `EXP`. 247 | 248 | Example: 249 | 250 | ``` lisp 251 | (/ 10 5) ; → 2 252 | (/ 3 2) ; → 1 (just like C++) 253 | ``` 254 | 255 | #### `MODULUS ::= (mod EXP EXP)` 256 | 257 | Behavior: return the modulus that 1st `EXP` divided by 2nd `EXP`. 258 | 259 | Example: 260 | 261 | ``` lisp 262 | (mod 8 5) ; → 3 263 | ``` 264 | 265 | #### `GREATER ::= (> EXP EXP)` 266 | 267 | Behavior: return `#t` if 1st `EXP` greater than 2nd `EXP`. `#f` otherwise. 268 | 269 | Example: 270 | 271 | ``` lisp 272 | (> 1 2) ; → #f 273 | ``` 274 | 275 | #### `SMALLER ::= (< EXP EXP)` 276 | 277 | Behavior: return `#t` if 1st `EXP` smaller than 2nd `EXP`. `#f` otherwise. 278 | 279 | Example: 280 | 281 | ``` lisp 282 | (< 1 2) ; → #t 283 | ``` 284 | 285 | #### `EQUAL ::= (= EXP EXP+)` 286 | 287 | Behavior: return `#t` if all `EXP`s are equal. `#f` otherwise. 288 | 289 | Example: 290 | 291 | ``` lisp 292 | (= (+ 1 1) 2 (/6 3)) ; → #t 293 | ``` 294 | 295 | ### Logical Operations (`LOGICAL-OP`) 296 | 297 | ``` ebnf 298 | LOGICAL-OP ::= AND-OP | OR-OP | NOT-OP 299 | ``` 300 | 301 | #### `AND-OP ::= (and EXP EXP+)` 302 | 303 | Behavior: return `#t` if all `EXP`s are true. `#f` otherwise. 304 | 305 | Example: 306 | 307 | ``` lisp 308 | (and #t (> 2 1)) ; → #t 309 | ``` 310 | 311 | #### `OR-OP ::= (or EXP EXP+)` 312 | 313 | Behavior: return `#t` if at least one `EXP` is true. `#f` otherwise. 314 | 315 | Example: 316 | 317 | ``` lisp 318 | (or (> 1 2) #f) ; → #f 319 | ``` 320 | 321 | #### `NOT-OP ::= (not EXP)` 322 | 323 | Behavior: return `#t` if `EXP` is false. `#f` otherwise. 324 | 325 | Example: 326 | 327 | ``` lisp 328 | (not (> 1 2)) ; → #t 329 | ``` 330 | 331 | ### define Statement (`DEF-STMT`) 332 | 333 | ``` ebnf 334 | DEF-STMT ::= (define id EXP) 335 | ``` 336 | 337 | ``` ebnf 338 | VARIABLE ::= id 339 | ``` 340 | 341 | Behavior: Define a variable named `id` whose value is `EXP`. 342 | 343 | Example: 344 | 345 | ``` lisp 346 | (define x 5) 347 | (+ x 1) ; → 6 348 | ``` 349 | 350 | > Note: Redefining is not allowed. 351 | 352 | ### Function 353 | 354 | ``` ebnf 355 | FUN-EXP ::= (fun FUN-IDs FUN-BODY) 356 | FUN-IDs ::= (id*) 357 | 358 | FUN-BODY ::= EXP 359 | FUN-CALL ::= (FUN-EXP PARAM*) 360 | | (FUN-NAME PARAM*) 361 | PARAM ::= EXP 362 | FUN-NAME ::= id 363 | ``` 364 | 365 | Behavior: 366 | 367 | `FUN-EXP` defines a function. When a function is called, bind `FUN-ID`s to `PARAM`s, just like the define statement. If an id has been defined outside this function, prefer the definition inside the `FUN-EXP`. The variable definitions inside a function should not affect the outer scope. A `FUN-CALL` returns the evaluated result of `FUN-BODY`. Note that variables used in `FUN-BODY` should be bound to 368 | `PARAM`s. 369 | 370 | Examples: 371 | 372 | ``` lisp 373 | ((fun (x) (+ x 1)) 2) ; → 3 374 | (define foo (fun () 0)) 375 | (foo) ; → 0 376 | (define x 1) 377 | (define bar (fun (x y) (+ x y))) 378 | (bar 2 3) ; → 5 379 | x ; → 1 380 | ``` 381 | 382 | ### `if` Expression 383 | 384 | ``` ebnf 385 | IF-EXP ::= (if TEST-EXP THEN-EXP ELSE-EXP) 386 | TEST-EXP ::= EXP 387 | THEN-EXP ::= EXP 388 | ELSE-EXP ::= EXP 389 | ``` 390 | 391 | Behavior: When `TEST-EXP` is true, returns `THEN-EXP`. Otherwise, returns `ELSE-EXP`. 392 | 393 | Example: 394 | 395 | ``` lisp 396 | (if (= 1 0) 1 2) ; → 2 397 | (if #t 1 2) ; → 1 398 | ``` 399 | 400 | ## Bonus Features Details 401 | 402 | ### Recursion 403 | 404 | The interpreter is able to handle recursive function call. 405 | 406 | ``` lisp 407 | (define f 408 | (fun (x) (if (= x 1) 409 | 1 410 | (* x (f (- x 1)))))) 411 | (f 4) ; → 24 412 | ``` 413 | 414 | ### Type Checking 415 | 416 | For type specifications of operations, please check out the table below: 417 | 418 | | Op | Parameter Type | Output Type | 419 | |:-------------------------:|:-------------------------:|:-----------------------------------:| 420 | | `+`, `-`, `*`, `/`, `mod` | Number(s) | Number | 421 | | `<`, `>`, `=` | Number(s) | Boolean | 422 | | `and`, `or`, `not` | Boolean(s) | Boolean | 423 | | `if` | Boolean(s) for `test-exp` | Depend on `then-exp` and `else-exp` | 424 | | `fun` | Any | Function | 425 | | Function call | Any | Depend on `fun-body` and parameters | 426 | 427 | ``` lisp 428 | (> 1 #t) ; Type Error: Expect 'number' but got 'boolean'. 429 | ``` 430 | 431 | ### Nested Function 432 | 433 | There could be a function inside another function. The inner one is able to access the local variables of the outer function. 434 | 435 | > The syntax rule of `fun-body` should be redefined to 436 | > ``` ebnf 437 | > fun-body ::= def-stmt* exp 438 | > ``` 439 | 440 | ``` lisp 441 | (define dist-square 442 | (fun (x y) 443 | (define square 444 | (fun (x) (* x x))) 445 | (+ (square x) (square y)))) 446 | ``` 447 | 448 | ### First-Class Function 449 | 450 | Functions can be passed like other variables. Furthermore, it can keep its environment. 451 | 452 | ``` lisp 453 | (define chose 454 | (fun (chose-fun x y) 455 | (if (chose-fun x y) x y))) 456 | (chose (fun (x y) (> x y)) 2 1) ; → 2 457 | 458 | (define add-x 459 | (fun (x) 460 | (fun (y) (+ x y)))) 461 | (define f (add-x 5)) 462 | (f 3) ; → 8 463 | ``` 464 | 465 | ## References 466 | 467 | * [Ruslan's Blog - Let’s Build A Simple Interpreter](https://ruslanspivak.com/) 468 | * [Peter@Norvig.com - lispy](http://norvig.com/lispy.html) 469 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import mini_lisp_interpreter 3 | 4 | 5 | def main(): 6 | mini_lisp_interpreter.Interpreter().interpret(sys.stdin.read()) 7 | 8 | 9 | if __name__ == '__main__': 10 | main() 11 | -------------------------------------------------------------------------------- /mini_lisp_interpreter/__init__.py: -------------------------------------------------------------------------------- 1 | from mini_lisp_interpreter.interpreter import Interpreter 2 | 3 | __version__ = '0.1.0' 4 | -------------------------------------------------------------------------------- /mini_lisp_interpreter/grammar.lark: -------------------------------------------------------------------------------- 1 | program : stmt+ 2 | 3 | ?stmt : exp 4 | | def_stmt 5 | | print_stmt 6 | 7 | ?print_stmt : "(" "print-num" exp ")" -> print_num 8 | | "(" "print-bool" exp ")" -> print_bool 9 | 10 | ?exp : BOOL_VAL 11 | | SIGNED_INT 12 | | variable 13 | | num_op 14 | | logical_op 15 | | fun_exp 16 | | fun_call 17 | | if_exp 18 | 19 | ?num_op : plus | minus | multiply | divide | modulus | greater | smaller | equal 20 | ?plus : "(" "+" exp exp+ ")" 21 | ?minus : "(" "-" exp exp ")" 22 | ?multiply : "(" "*" exp exp+ ")" 23 | ?divide : "(" "/" exp exp ")" 24 | ?modulus : "(" "mod" exp exp ")" 25 | ?greater : "(" ">" exp exp ")" 26 | ?smaller : "(" "<" exp exp ")" 27 | ?equal : "(" "=" exp exp+ ")" 28 | 29 | ?logical_op : and_op | or_op | not_op 30 | ?and_op : "(" "and" exp exp+ ")" 31 | ?or_op : "(" "or" exp exp+ ")" 32 | not_op : "(" "not" exp ")" 33 | 34 | ?def_stmt : "(" "define" variable exp ")" 35 | ?variable : ID 36 | 37 | ?fun_exp : "(" "fun" fun_ids fun_body ")" 38 | fun_ids : "(" ID* ")" 39 | fun_body : def_stmt* exp 40 | fun_call : "(" fun_exp param* ")" 41 | | "(" fun_name param* ")" 42 | ?param : exp 43 | ?fun_name : ID 44 | 45 | ?if_exp : "(" "if" test_exp than_exp else_exp ")" 46 | ?test_exp : exp 47 | ?than_exp : exp 48 | ?else_exp : exp 49 | 50 | ID : LETTER (LETTER | DIGIT | "-")* 51 | BOOL_VAL : "#t" | "#f" 52 | 53 | %import common.SIGNED_INT 54 | %import common.LETTER 55 | %import common.DIGIT 56 | %import common.WS 57 | %ignore WS -------------------------------------------------------------------------------- /mini_lisp_interpreter/interpreter.py: -------------------------------------------------------------------------------- 1 | import functools 2 | import logging 3 | import operator 4 | 5 | from lark import Lark, UnexpectedInput, UnexpectedToken, UnexpectedCharacters 6 | 7 | 8 | logging.basicConfig(level=logging.INFO) 9 | 10 | 11 | class Interpreter: 12 | def __init__(self): 13 | self.tree = None 14 | with open('mini_lisp_interpreter/grammar.lark') as larkfile: 15 | self.parser = Lark(larkfile, start='program', 16 | parser='lalr', lexer='contextual') 17 | 18 | def interpret(self, code): 19 | try: 20 | self.tree = self.parser.parse(code) 21 | except (UnexpectedInput, UnexpectedToken, UnexpectedCharacters) as exception: 22 | raise SyntaxError('Mini-lisp syntax error.') from exception 23 | else: 24 | return interpret_ast(self.tree) 25 | 26 | 27 | class Environment(dict): 28 | """ The symbol table with the link if outer environment. """ 29 | 30 | def __init__(self, symbol_names=None, symbol_values=None, outer=None): 31 | """Create the symbol table with local variables and functions. 32 | 33 | Keyword Arguments: 34 | symbol_names {iterable} -- The name of symbols (default: {None}) 35 | symbol_values {iterable} -- The content of symbols (default: {None}) 36 | outer {Environment instance} -- The outer environment (default: {None}) 37 | """ 38 | super().__init__(self) 39 | 40 | if symbol_names is None: 41 | symbol_names = tuple() 42 | symbol_values = tuple() 43 | self.update(zip(symbol_names, symbol_values)) 44 | self.outer = outer 45 | 46 | def find(self, name): 47 | """ Get the innermost environment where the symbol name appears. """ 48 | if name not in self and self.outer is None: 49 | raise NameError("%s is not defined" % name) 50 | return self if name in self else self.outer.find(name) 51 | 52 | 53 | class GlobalEnvironment(Environment): 54 | """ The default symbol table. """ 55 | 56 | def __init__(self): 57 | super().__init__() 58 | self.update({ 59 | 'print_num': print, 60 | 'print_bool': lambda x: print('#t' if x else '#f'), 61 | 'plus': self.plus, 62 | 'minus': self.minus, 63 | 'multiply': self.multiply, 64 | 'divide': self.divide, 65 | 'modulus': self.modulus, 66 | 'greater': self.greater, 67 | 'smaller': self.smaller, 68 | 'equal': self.equal, 69 | 'and_op': self.and_op, 70 | 'or_op': self.or_op, 71 | 'not_op': self.not_op 72 | }) 73 | 74 | def plus(self, *args): 75 | self.number_type_checker(args) 76 | return sum(args) 77 | 78 | def minus(self, *args): 79 | self.number_type_checker(args) 80 | return args[0] - args[1] 81 | 82 | def multiply(self, *args): 83 | self.number_type_checker(args) 84 | return functools.reduce(operator.mul, args, 1) 85 | 86 | def divide(self, *args): 87 | self.number_type_checker(args) 88 | return int(operator.truediv(args[0], args[1])) 89 | 90 | def modulus(self, *args): 91 | self.number_type_checker(args) 92 | return args[0] % args[1] 93 | 94 | def greater(self, *args): 95 | self.number_type_checker(args) 96 | return args[0] > args[1] 97 | 98 | def smaller(self, *args): 99 | self.number_type_checker(args) 100 | return args[0] < args[1] 101 | 102 | def equal(self, *args): 103 | self.number_type_checker(args) 104 | return all(args[0] == arg for arg in args) 105 | 106 | def and_op(self, *args): 107 | self.boolean_type_checker(args) 108 | return all(args) 109 | 110 | def or_op(self, *args): 111 | self.boolean_type_checker(args) 112 | return any(args) 113 | 114 | def not_op(self, arg): 115 | self.boolean_type_checker([arg]) 116 | return not arg 117 | 118 | @staticmethod 119 | def number_type_checker(args): 120 | if not all(type(arg) is int for arg in args): # pylint: disable=unidiomatic-typecheck 121 | raise TypeError("Expect 'number' but got 'boolean'.") 122 | 123 | @staticmethod 124 | def boolean_type_checker(args): 125 | if not all(type(arg) is bool for arg in args): # pylint: disable=unidiomatic-typecheck 126 | raise TypeError("Expect 'boolean' but got 'number'.") 127 | 128 | 129 | class Function: 130 | """ A user-defined scheme function. """ 131 | 132 | def __init__(self, args, body, environment=GlobalEnvironment()): 133 | self.args, self.body, self.environment = args, body, environment 134 | 135 | def __call__(self, *params): 136 | return interpret_ast(self.body, Environment(self.args, params, self.environment)) 137 | 138 | 139 | def interpret_ast(node, environment=GlobalEnvironment()): 140 | """ Interpret the AST of mini-lisp. 141 | 142 | Arguments: 143 | node {Tree or Token} -- The current dealing object. 144 | 145 | Keyword Arguments: 146 | environment {Environment} -- The symbol table (default: {GlobalEnvironment()}) 147 | """ 148 | 149 | try: # convert SIGNED_INT to int 150 | return int(node) 151 | except (TypeError, ValueError): 152 | # convert '#t' to Python True 153 | if node == '#t': 154 | return True 155 | 156 | # convert '#f' to Python False 157 | if node == '#f': 158 | return False 159 | 160 | # symbol reference 161 | if isinstance(node, str): 162 | return environment.find(node)[node] 163 | 164 | # program : stmt+ 165 | if node.data == 'program': 166 | ret = list() 167 | for child in node.children: 168 | result = interpret_ast(child, environment) 169 | logging.debug(result) 170 | if result is not None: 171 | ret.append(result) 172 | return ret 173 | 174 | # if_exp : test_exp then_exp else_exp 175 | if node.data == 'if_exp': 176 | (test, then, els) = node.children 177 | test_res = interpret_ast(test, environment) 178 | # type checking -> test_exp should be boolean 179 | try: 180 | assert isinstance(test_res, bool) 181 | except AssertionError as exception: 182 | raise TypeError( 183 | "Expect 'boolean' but got 'number'." 184 | ) from exception 185 | expr = then if test_res else els 186 | return interpret_ast(expr, environment) 187 | 188 | # def_stmt : ( variable exp ) 189 | if node.data == 'def_stmt': 190 | (var, expr) = node.children 191 | environment[var] = interpret_ast(expr, environment) 192 | 193 | # fun_exp : ( fun_ids fun_body ) 194 | elif node.data == 'fun_exp': 195 | args = interpret_ast(node.children[0]) 196 | body = interpret_ast(node.children[-1]) 197 | return Function(args, body, environment) 198 | 199 | # simply return all arguments (ids) 200 | elif node.data == 'fun_ids': 201 | return node.children 202 | 203 | # fun_body : def_stmt* exp 204 | elif node.data == 'fun_body': 205 | # deal with define statements 206 | for def_stmt in node.children[:-1]: 207 | interpret_ast(def_stmt) 208 | # return the expresion (the actual function body) 209 | return node.children[-1] 210 | 211 | # get the user-defined function with arguments and then execute it 212 | elif node.data == 'fun_call': 213 | proc = interpret_ast(node.children[0], environment) 214 | args = tuple(interpret_ast(expr, environment) 215 | for expr in node.children[1:]) 216 | return proc(*args) 217 | 218 | # execute the function got from environment dict 219 | else: 220 | proc = interpret_ast(node.data, environment) 221 | args = tuple(interpret_ast(expr, environment) 222 | for expr in node.children) 223 | return proc(*args) 224 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "astroid" 3 | version = "2.4.2" 4 | description = "An abstract syntax tree for Python with inference support." 5 | category = "dev" 6 | optional = false 7 | python-versions = ">=3.5" 8 | 9 | [package.dependencies] 10 | lazy-object-proxy = ">=1.4.0,<1.5.0" 11 | six = ">=1.12,<2.0" 12 | wrapt = ">=1.11,<2.0" 13 | 14 | [[package]] 15 | name = "atomicwrites" 16 | version = "1.4.0" 17 | description = "Atomic file writes." 18 | category = "dev" 19 | optional = false 20 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 21 | 22 | [[package]] 23 | name = "attrs" 24 | version = "20.2.0" 25 | description = "Classes Without Boilerplate" 26 | category = "dev" 27 | optional = false 28 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 29 | 30 | [package.extras] 31 | dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "sphinx-rtd-theme", "pre-commit"] 32 | docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"] 33 | tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] 34 | tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] 35 | 36 | [[package]] 37 | name = "autopep8" 38 | version = "1.5.4" 39 | description = "A tool that automatically formats Python code to conform to the PEP 8 style guide" 40 | category = "dev" 41 | optional = false 42 | python-versions = "*" 43 | 44 | [package.dependencies] 45 | pycodestyle = ">=2.6.0" 46 | toml = "*" 47 | 48 | [[package]] 49 | name = "colorama" 50 | version = "0.4.4" 51 | description = "Cross-platform colored terminal text." 52 | category = "dev" 53 | optional = false 54 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 55 | 56 | [[package]] 57 | name = "coverage" 58 | version = "5.3" 59 | description = "Code coverage measurement for Python" 60 | category = "dev" 61 | optional = false 62 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" 63 | 64 | [package.extras] 65 | toml = ["toml"] 66 | 67 | [[package]] 68 | name = "iniconfig" 69 | version = "1.1.1" 70 | description = "iniconfig: brain-dead simple config-ini parsing" 71 | category = "dev" 72 | optional = false 73 | python-versions = "*" 74 | 75 | [[package]] 76 | name = "isort" 77 | version = "5.6.4" 78 | description = "A Python utility / library to sort Python imports." 79 | category = "dev" 80 | optional = false 81 | python-versions = ">=3.6,<4.0" 82 | 83 | [package.extras] 84 | pipfile_deprecated_finder = ["pipreqs", "requirementslib"] 85 | requirements_deprecated_finder = ["pipreqs", "pip-api"] 86 | colors = ["colorama (>=0.4.3,<0.5.0)"] 87 | 88 | [[package]] 89 | name = "lark-parser" 90 | version = "0.10.1" 91 | description = "a modern parsing library" 92 | category = "main" 93 | optional = false 94 | python-versions = "*" 95 | 96 | [package.extras] 97 | nearley = ["js2py"] 98 | regex = ["regex"] 99 | 100 | [[package]] 101 | name = "lazy-object-proxy" 102 | version = "1.4.3" 103 | description = "A fast and thorough lazy object proxy." 104 | category = "dev" 105 | optional = false 106 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 107 | 108 | [[package]] 109 | name = "mccabe" 110 | version = "0.6.1" 111 | description = "McCabe checker, plugin for flake8" 112 | category = "dev" 113 | optional = false 114 | python-versions = "*" 115 | 116 | [[package]] 117 | name = "mypy" 118 | version = "0.790" 119 | description = "Optional static typing for Python" 120 | category = "dev" 121 | optional = false 122 | python-versions = ">=3.5" 123 | 124 | [package.dependencies] 125 | mypy-extensions = ">=0.4.3,<0.5.0" 126 | typed-ast = ">=1.4.0,<1.5.0" 127 | typing-extensions = ">=3.7.4" 128 | 129 | [package.extras] 130 | dmypy = ["psutil (>=4.0)"] 131 | 132 | [[package]] 133 | name = "mypy-extensions" 134 | version = "0.4.3" 135 | description = "Experimental type system extensions for programs checked with the mypy typechecker." 136 | category = "dev" 137 | optional = false 138 | python-versions = "*" 139 | 140 | [[package]] 141 | name = "packaging" 142 | version = "20.4" 143 | description = "Core utilities for Python packages" 144 | category = "dev" 145 | optional = false 146 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 147 | 148 | [package.dependencies] 149 | pyparsing = ">=2.0.2" 150 | six = "*" 151 | 152 | [[package]] 153 | name = "pluggy" 154 | version = "0.13.1" 155 | description = "plugin and hook calling mechanisms for python" 156 | category = "dev" 157 | optional = false 158 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 159 | 160 | [package.extras] 161 | dev = ["pre-commit", "tox"] 162 | 163 | [[package]] 164 | name = "py" 165 | version = "1.9.0" 166 | description = "library with cross-python path, ini-parsing, io, code, log facilities" 167 | category = "dev" 168 | optional = false 169 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 170 | 171 | [[package]] 172 | name = "pycodestyle" 173 | version = "2.6.0" 174 | description = "Python style guide checker" 175 | category = "dev" 176 | optional = false 177 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 178 | 179 | [[package]] 180 | name = "pylint" 181 | version = "2.6.0" 182 | description = "python code static checker" 183 | category = "dev" 184 | optional = false 185 | python-versions = ">=3.5.*" 186 | 187 | [package.dependencies] 188 | astroid = ">=2.4.0,<=2.5" 189 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 190 | isort = ">=4.2.5,<6" 191 | mccabe = ">=0.6,<0.7" 192 | toml = ">=0.7.1" 193 | 194 | [[package]] 195 | name = "pyparsing" 196 | version = "2.4.7" 197 | description = "Python parsing module" 198 | category = "dev" 199 | optional = false 200 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 201 | 202 | [[package]] 203 | name = "pytest" 204 | version = "6.1.1" 205 | description = "pytest: simple powerful testing with Python" 206 | category = "dev" 207 | optional = false 208 | python-versions = ">=3.5" 209 | 210 | [package.dependencies] 211 | atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} 212 | attrs = ">=17.4.0" 213 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 214 | iniconfig = "*" 215 | packaging = "*" 216 | pluggy = ">=0.12,<1.0" 217 | py = ">=1.8.2" 218 | toml = "*" 219 | 220 | [package.extras] 221 | checkqa_mypy = ["mypy (0.780)"] 222 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] 223 | 224 | [[package]] 225 | name = "pytest-cov" 226 | version = "2.10.1" 227 | description = "Pytest plugin for measuring coverage." 228 | category = "dev" 229 | optional = false 230 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 231 | 232 | [package.dependencies] 233 | coverage = ">=4.4" 234 | pytest = ">=4.6" 235 | 236 | [package.extras] 237 | testing = ["fields", "hunter", "process-tests (2.0.2)", "six", "pytest-xdist", "virtualenv"] 238 | 239 | [[package]] 240 | name = "six" 241 | version = "1.15.0" 242 | description = "Python 2 and 3 compatibility utilities" 243 | category = "dev" 244 | optional = false 245 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 246 | 247 | [[package]] 248 | name = "toml" 249 | version = "0.10.1" 250 | description = "Python Library for Tom's Obvious, Minimal Language" 251 | category = "dev" 252 | optional = false 253 | python-versions = "*" 254 | 255 | [[package]] 256 | name = "typed-ast" 257 | version = "1.4.1" 258 | description = "a fork of Python 2 and 3 ast modules with type comment support" 259 | category = "dev" 260 | optional = false 261 | python-versions = "*" 262 | 263 | [[package]] 264 | name = "typing-extensions" 265 | version = "3.7.4.3" 266 | description = "Backported and Experimental Type Hints for Python 3.5+" 267 | category = "dev" 268 | optional = false 269 | python-versions = "*" 270 | 271 | [[package]] 272 | name = "wrapt" 273 | version = "1.12.1" 274 | description = "Module for decorators, wrappers and monkey patching." 275 | category = "dev" 276 | optional = false 277 | python-versions = "*" 278 | 279 | [metadata] 280 | lock-version = "1.1" 281 | python-versions = "^3.8" 282 | content-hash = "e0c78c973ff2b30faeaca51d7b57b5e792c79b1832235b03b6c2c1cebeb04fd8" 283 | 284 | [metadata.files] 285 | astroid = [ 286 | {file = "astroid-2.4.2-py3-none-any.whl", hash = "sha256:bc58d83eb610252fd8de6363e39d4f1d0619c894b0ed24603b881c02e64c7386"}, 287 | {file = "astroid-2.4.2.tar.gz", hash = "sha256:2f4078c2a41bf377eea06d71c9d2ba4eb8f6b1af2135bec27bbbb7d8f12bb703"}, 288 | ] 289 | atomicwrites = [ 290 | {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, 291 | {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, 292 | ] 293 | attrs = [ 294 | {file = "attrs-20.2.0-py2.py3-none-any.whl", hash = "sha256:fce7fc47dfc976152e82d53ff92fa0407700c21acd20886a13777a0d20e655dc"}, 295 | {file = "attrs-20.2.0.tar.gz", hash = "sha256:26b54ddbbb9ee1d34d5d3668dd37d6cf74990ab23c828c2888dccdceee395594"}, 296 | ] 297 | autopep8 = [ 298 | {file = "autopep8-1.5.4.tar.gz", hash = "sha256:d21d3901cb0da6ebd1e83fc9b0dfbde8b46afc2ede4fe32fbda0c7c6118ca094"}, 299 | ] 300 | colorama = [ 301 | {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, 302 | {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, 303 | ] 304 | coverage = [ 305 | {file = "coverage-5.3-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:bd3166bb3b111e76a4f8e2980fa1addf2920a4ca9b2b8ca36a3bc3dedc618270"}, 306 | {file = "coverage-5.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:9342dd70a1e151684727c9c91ea003b2fb33523bf19385d4554f7897ca0141d4"}, 307 | {file = "coverage-5.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:63808c30b41f3bbf65e29f7280bf793c79f54fb807057de7e5238ffc7cc4d7b9"}, 308 | {file = "coverage-5.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:4d6a42744139a7fa5b46a264874a781e8694bb32f1d76d8137b68138686f1729"}, 309 | {file = "coverage-5.3-cp27-cp27m-win32.whl", hash = "sha256:86e9f8cd4b0cdd57b4ae71a9c186717daa4c5a99f3238a8723f416256e0b064d"}, 310 | {file = "coverage-5.3-cp27-cp27m-win_amd64.whl", hash = "sha256:7858847f2d84bf6e64c7f66498e851c54de8ea06a6f96a32a1d192d846734418"}, 311 | {file = "coverage-5.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:530cc8aaf11cc2ac7430f3614b04645662ef20c348dce4167c22d99bec3480e9"}, 312 | {file = "coverage-5.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:381ead10b9b9af5f64646cd27107fb27b614ee7040bb1226f9c07ba96625cbb5"}, 313 | {file = "coverage-5.3-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:71b69bd716698fa62cd97137d6f2fdf49f534decb23a2c6fc80813e8b7be6822"}, 314 | {file = "coverage-5.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:1d44bb3a652fed01f1f2c10d5477956116e9b391320c94d36c6bf13b088a1097"}, 315 | {file = "coverage-5.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:1c6703094c81fa55b816f5ae542c6ffc625fec769f22b053adb42ad712d086c9"}, 316 | {file = "coverage-5.3-cp35-cp35m-win32.whl", hash = "sha256:cedb2f9e1f990918ea061f28a0f0077a07702e3819602d3507e2ff98c8d20636"}, 317 | {file = "coverage-5.3-cp35-cp35m-win_amd64.whl", hash = "sha256:7f43286f13d91a34fadf61ae252a51a130223c52bfefb50310d5b2deb062cf0f"}, 318 | {file = "coverage-5.3-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:c851b35fc078389bc16b915a0a7c1d5923e12e2c5aeec58c52f4aa8085ac8237"}, 319 | {file = "coverage-5.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:aac1ba0a253e17889550ddb1b60a2063f7474155465577caa2a3b131224cfd54"}, 320 | {file = "coverage-5.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2b31f46bf7b31e6aa690d4c7a3d51bb262438c6dcb0d528adde446531d0d3bb7"}, 321 | {file = "coverage-5.3-cp36-cp36m-win32.whl", hash = "sha256:c5f17ad25d2c1286436761b462e22b5020d83316f8e8fcb5deb2b3151f8f1d3a"}, 322 | {file = "coverage-5.3-cp36-cp36m-win_amd64.whl", hash = "sha256:aef72eae10b5e3116bac6957de1df4d75909fc76d1499a53fb6387434b6bcd8d"}, 323 | {file = "coverage-5.3-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:e8caf961e1b1a945db76f1b5fa9c91498d15f545ac0ababbe575cfab185d3bd8"}, 324 | {file = "coverage-5.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:29a6272fec10623fcbe158fdf9abc7a5fa032048ac1d8631f14b50fbfc10d17f"}, 325 | {file = "coverage-5.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:2d43af2be93ffbad25dd959899b5b809618a496926146ce98ee0b23683f8c51c"}, 326 | {file = "coverage-5.3-cp37-cp37m-win32.whl", hash = "sha256:c3888a051226e676e383de03bf49eb633cd39fc829516e5334e69b8d81aae751"}, 327 | {file = "coverage-5.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9669179786254a2e7e57f0ecf224e978471491d660aaca833f845b72a2df3709"}, 328 | {file = "coverage-5.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0203acd33d2298e19b57451ebb0bed0ab0c602e5cf5a818591b4918b1f97d516"}, 329 | {file = "coverage-5.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:582ddfbe712025448206a5bc45855d16c2e491c2dd102ee9a2841418ac1c629f"}, 330 | {file = "coverage-5.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:0f313707cdecd5cd3e217fc68c78a960b616604b559e9ea60cc16795c4304259"}, 331 | {file = "coverage-5.3-cp38-cp38-win32.whl", hash = "sha256:78e93cc3571fd928a39c0b26767c986188a4118edc67bc0695bc7a284da22e82"}, 332 | {file = "coverage-5.3-cp38-cp38-win_amd64.whl", hash = "sha256:8f264ba2701b8c9f815b272ad568d555ef98dfe1576802ab3149c3629a9f2221"}, 333 | {file = "coverage-5.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:50691e744714856f03a86df3e2bff847c2acede4c191f9a1da38f088df342978"}, 334 | {file = "coverage-5.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9361de40701666b034c59ad9e317bae95c973b9ff92513dd0eced11c6adf2e21"}, 335 | {file = "coverage-5.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:c1b78fb9700fc961f53386ad2fd86d87091e06ede5d118b8a50dea285a071c24"}, 336 | {file = "coverage-5.3-cp39-cp39-win32.whl", hash = "sha256:cb7df71de0af56000115eafd000b867d1261f786b5eebd88a0ca6360cccfaca7"}, 337 | {file = "coverage-5.3-cp39-cp39-win_amd64.whl", hash = "sha256:47a11bdbd8ada9b7ee628596f9d97fbd3851bd9999d398e9436bd67376dbece7"}, 338 | {file = "coverage-5.3.tar.gz", hash = "sha256:280baa8ec489c4f542f8940f9c4c2181f0306a8ee1a54eceba071a449fb870a0"}, 339 | ] 340 | iniconfig = [ 341 | {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, 342 | {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, 343 | ] 344 | isort = [ 345 | {file = "isort-5.6.4-py3-none-any.whl", hash = "sha256:dcab1d98b469a12a1a624ead220584391648790275560e1a43e54c5dceae65e7"}, 346 | {file = "isort-5.6.4.tar.gz", hash = "sha256:dcaeec1b5f0eca77faea2a35ab790b4f3680ff75590bfcb7145986905aab2f58"}, 347 | ] 348 | lark-parser = [ 349 | {file = "lark-parser-0.10.1.tar.gz", hash = "sha256:42f367612a1bbc4cf9d8c8eb1b209d8a9b397d55af75620c9e6f53e502235996"}, 350 | ] 351 | lazy-object-proxy = [ 352 | {file = "lazy-object-proxy-1.4.3.tar.gz", hash = "sha256:f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0"}, 353 | {file = "lazy_object_proxy-1.4.3-cp27-cp27m-macosx_10_13_x86_64.whl", hash = "sha256:a2238e9d1bb71a56cd710611a1614d1194dc10a175c1e08d75e1a7bcc250d442"}, 354 | {file = "lazy_object_proxy-1.4.3-cp27-cp27m-win32.whl", hash = "sha256:efa1909120ce98bbb3777e8b6f92237f5d5c8ea6758efea36a473e1d38f7d3e4"}, 355 | {file = "lazy_object_proxy-1.4.3-cp27-cp27m-win_amd64.whl", hash = "sha256:4677f594e474c91da97f489fea5b7daa17b5517190899cf213697e48d3902f5a"}, 356 | {file = "lazy_object_proxy-1.4.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:0c4b206227a8097f05c4dbdd323c50edf81f15db3b8dc064d08c62d37e1a504d"}, 357 | {file = "lazy_object_proxy-1.4.3-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:d945239a5639b3ff35b70a88c5f2f491913eb94871780ebfabb2568bd58afc5a"}, 358 | {file = "lazy_object_proxy-1.4.3-cp34-cp34m-win32.whl", hash = "sha256:9651375199045a358eb6741df3e02a651e0330be090b3bc79f6d0de31a80ec3e"}, 359 | {file = "lazy_object_proxy-1.4.3-cp34-cp34m-win_amd64.whl", hash = "sha256:eba7011090323c1dadf18b3b689845fd96a61ba0a1dfbd7f24b921398affc357"}, 360 | {file = "lazy_object_proxy-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:48dab84ebd4831077b150572aec802f303117c8cc5c871e182447281ebf3ac50"}, 361 | {file = "lazy_object_proxy-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:ca0a928a3ddbc5725be2dd1cf895ec0a254798915fb3a36af0964a0a4149e3db"}, 362 | {file = "lazy_object_proxy-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:194d092e6f246b906e8f70884e620e459fc54db3259e60cf69a4d66c3fda3449"}, 363 | {file = "lazy_object_proxy-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:97bb5884f6f1cdce0099f86b907aa41c970c3c672ac8b9c8352789e103cf3156"}, 364 | {file = "lazy_object_proxy-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:cb2c7c57005a6804ab66f106ceb8482da55f5314b7fcb06551db1edae4ad1531"}, 365 | {file = "lazy_object_proxy-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:8d859b89baf8ef7f8bc6b00aa20316483d67f0b1cbf422f5b4dc56701c8f2ffb"}, 366 | {file = "lazy_object_proxy-1.4.3-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:1be7e4c9f96948003609aa6c974ae59830a6baecc5376c25c92d7d697e684c08"}, 367 | {file = "lazy_object_proxy-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d74bb8693bf9cf75ac3b47a54d716bbb1a92648d5f781fc799347cfc95952383"}, 368 | {file = "lazy_object_proxy-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:9b15f3f4c0f35727d3a0fba4b770b3c4ebbb1fa907dbcc046a1d2799f3edd142"}, 369 | {file = "lazy_object_proxy-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9254f4358b9b541e3441b007a0ea0764b9d056afdeafc1a5569eee1cc6c1b9ea"}, 370 | {file = "lazy_object_proxy-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:a6ae12d08c0bf9909ce12385803a543bfe99b95fe01e752536a60af2b7797c62"}, 371 | {file = "lazy_object_proxy-1.4.3-cp38-cp38-win32.whl", hash = "sha256:5541cada25cd173702dbd99f8e22434105456314462326f06dba3e180f203dfd"}, 372 | {file = "lazy_object_proxy-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:59f79fef100b09564bc2df42ea2d8d21a64fdcda64979c0fa3db7bdaabaf6239"}, 373 | ] 374 | mccabe = [ 375 | {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, 376 | {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, 377 | ] 378 | mypy = [ 379 | {file = "mypy-0.790-cp35-cp35m-macosx_10_6_x86_64.whl", hash = "sha256:bd03b3cf666bff8d710d633d1c56ab7facbdc204d567715cb3b9f85c6e94f669"}, 380 | {file = "mypy-0.790-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:2170492030f6faa537647d29945786d297e4862765f0b4ac5930ff62e300d802"}, 381 | {file = "mypy-0.790-cp35-cp35m-win_amd64.whl", hash = "sha256:e86bdace26c5fe9cf8cb735e7cedfe7850ad92b327ac5d797c656717d2ca66de"}, 382 | {file = "mypy-0.790-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e97e9c13d67fbe524be17e4d8025d51a7dca38f90de2e462243ab8ed8a9178d1"}, 383 | {file = "mypy-0.790-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0d34d6b122597d48a36d6c59e35341f410d4abfa771d96d04ae2c468dd201abc"}, 384 | {file = "mypy-0.790-cp36-cp36m-win_amd64.whl", hash = "sha256:72060bf64f290fb629bd4a67c707a66fd88ca26e413a91384b18db3876e57ed7"}, 385 | {file = "mypy-0.790-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:eea260feb1830a627fb526d22fbb426b750d9f5a47b624e8d5e7e004359b219c"}, 386 | {file = "mypy-0.790-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:c614194e01c85bb2e551c421397e49afb2872c88b5830e3554f0519f9fb1c178"}, 387 | {file = "mypy-0.790-cp37-cp37m-win_amd64.whl", hash = "sha256:0a0d102247c16ce93c97066443d11e2d36e6cc2a32d8ccc1f705268970479324"}, 388 | {file = "mypy-0.790-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cf4e7bf7f1214826cf7333627cb2547c0db7e3078723227820d0a2490f117a01"}, 389 | {file = "mypy-0.790-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:af4e9ff1834e565f1baa74ccf7ae2564ae38c8df2a85b057af1dbbc958eb6666"}, 390 | {file = "mypy-0.790-cp38-cp38-win_amd64.whl", hash = "sha256:da56dedcd7cd502ccd3c5dddc656cb36113dd793ad466e894574125945653cea"}, 391 | {file = "mypy-0.790-py3-none-any.whl", hash = "sha256:2842d4fbd1b12ab422346376aad03ff5d0805b706102e475e962370f874a5122"}, 392 | {file = "mypy-0.790.tar.gz", hash = "sha256:2b21ba45ad9ef2e2eb88ce4aeadd0112d0f5026418324176fd494a6824b74975"}, 393 | ] 394 | mypy-extensions = [ 395 | {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, 396 | {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, 397 | ] 398 | packaging = [ 399 | {file = "packaging-20.4-py2.py3-none-any.whl", hash = "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"}, 400 | {file = "packaging-20.4.tar.gz", hash = "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8"}, 401 | ] 402 | pluggy = [ 403 | {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, 404 | {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, 405 | ] 406 | py = [ 407 | {file = "py-1.9.0-py2.py3-none-any.whl", hash = "sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2"}, 408 | {file = "py-1.9.0.tar.gz", hash = "sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342"}, 409 | ] 410 | pycodestyle = [ 411 | {file = "pycodestyle-2.6.0-py2.py3-none-any.whl", hash = "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367"}, 412 | {file = "pycodestyle-2.6.0.tar.gz", hash = "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"}, 413 | ] 414 | pylint = [ 415 | {file = "pylint-2.6.0-py3-none-any.whl", hash = "sha256:bfe68f020f8a0fece830a22dd4d5dddb4ecc6137db04face4c3420a46a52239f"}, 416 | {file = "pylint-2.6.0.tar.gz", hash = "sha256:bb4a908c9dadbc3aac18860550e870f58e1a02c9f2c204fdf5693d73be061210"}, 417 | ] 418 | pyparsing = [ 419 | {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, 420 | {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, 421 | ] 422 | pytest = [ 423 | {file = "pytest-6.1.1-py3-none-any.whl", hash = "sha256:7a8190790c17d79a11f847fba0b004ee9a8122582ebff4729a082c109e81a4c9"}, 424 | {file = "pytest-6.1.1.tar.gz", hash = "sha256:8f593023c1a0f916110285b6efd7f99db07d59546e3d8c36fc60e2ab05d3be92"}, 425 | ] 426 | pytest-cov = [ 427 | {file = "pytest-cov-2.10.1.tar.gz", hash = "sha256:47bd0ce14056fdd79f93e1713f88fad7bdcc583dcd7783da86ef2f085a0bb88e"}, 428 | {file = "pytest_cov-2.10.1-py2.py3-none-any.whl", hash = "sha256:45ec2d5182f89a81fc3eb29e3d1ed3113b9e9a873bcddb2a71faaab066110191"}, 429 | ] 430 | six = [ 431 | {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, 432 | {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, 433 | ] 434 | toml = [ 435 | {file = "toml-0.10.1-py2.py3-none-any.whl", hash = "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88"}, 436 | {file = "toml-0.10.1.tar.gz", hash = "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f"}, 437 | ] 438 | typed-ast = [ 439 | {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"}, 440 | {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb"}, 441 | {file = "typed_ast-1.4.1-cp35-cp35m-win32.whl", hash = "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919"}, 442 | {file = "typed_ast-1.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01"}, 443 | {file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"}, 444 | {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"}, 445 | {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"}, 446 | {file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"}, 447 | {file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"}, 448 | {file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"}, 449 | {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"}, 450 | {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"}, 451 | {file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"}, 452 | {file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"}, 453 | {file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"}, 454 | {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"}, 455 | {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"}, 456 | {file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"}, 457 | {file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"}, 458 | {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"}, 459 | {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"}, 460 | ] 461 | typing-extensions = [ 462 | {file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"}, 463 | {file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"}, 464 | {file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"}, 465 | ] 466 | wrapt = [ 467 | {file = "wrapt-1.12.1.tar.gz", hash = "sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7"}, 468 | ] 469 | -------------------------------------------------------------------------------- /poetry.toml: -------------------------------------------------------------------------------- 1 | [virtualenvs] 2 | in-project = true -------------------------------------------------------------------------------- /pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | 3 | # A comma-separated list of package or module names from where C extensions may 4 | # be loaded. Extensions are loading into the active Python interpreter and may 5 | # run arbitrary code. 6 | extension-pkg-whitelist= 7 | 8 | # Specify a score threshold to be exceeded before program exits with error. 9 | fail-under=10 10 | 11 | # Add files or directories to the blacklist. They should be base names, not 12 | # paths. 13 | ignore=CVS 14 | 15 | # Add files or directories matching the regex patterns to the blacklist. The 16 | # regex matches against base names, not paths. 17 | ignore-patterns= 18 | 19 | # Python code to execute, usually for sys.path manipulation such as 20 | # pygtk.require(). 21 | #init-hook= 22 | 23 | # Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the 24 | # number of processors available to use. 25 | jobs=1 26 | 27 | # Control the amount of potential inferred values when inferring a single 28 | # object. This can help the performance when dealing with large functions or 29 | # complex, nested conditions. 30 | limit-inference-results=100 31 | 32 | # List of plugins (as comma separated values of python module names) to load, 33 | # usually to register additional checkers. 34 | load-plugins= 35 | 36 | # Pickle collected data for later comparisons. 37 | persistent=yes 38 | 39 | # When enabled, pylint would attempt to guess common misconfiguration and emit 40 | # user-friendly hints instead of false-positive error messages. 41 | suggestion-mode=yes 42 | 43 | # Allow loading of arbitrary C extensions. Extensions are imported into the 44 | # active Python interpreter and may run arbitrary code. 45 | unsafe-load-any-extension=no 46 | 47 | 48 | [MESSAGES CONTROL] 49 | 50 | # Only show warnings with the listed confidence levels. Leave empty to show 51 | # all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. 52 | confidence= 53 | 54 | # Disable the message, report, category or checker with the given id(s). You 55 | # can either give multiple identifiers separated by comma (,) or put this 56 | # option multiple times (only on the command line, not in the configuration 57 | # file where it should appear only once). You can also use "--disable=all" to 58 | # disable everything first and then reenable specific checks. For example, if 59 | # you want to run only the similarities checker, you can use "--disable=all 60 | # --enable=similarities". If you want to run only the classes checker, but have 61 | # no Warning level messages displayed, use "--disable=all --enable=classes 62 | # --disable=W". 63 | disable=print-statement, 64 | parameter-unpacking, 65 | unpacking-in-except, 66 | old-raise-syntax, 67 | backtick, 68 | long-suffix, 69 | old-ne-operator, 70 | old-octal-literal, 71 | import-star-module-level, 72 | non-ascii-bytes-literal, 73 | raw-checker-failed, 74 | bad-inline-option, 75 | locally-disabled, 76 | file-ignored, 77 | suppressed-message, 78 | useless-suppression, 79 | deprecated-pragma, 80 | use-symbolic-message-instead, 81 | apply-builtin, 82 | basestring-builtin, 83 | buffer-builtin, 84 | cmp-builtin, 85 | coerce-builtin, 86 | execfile-builtin, 87 | file-builtin, 88 | long-builtin, 89 | raw_input-builtin, 90 | reduce-builtin, 91 | standarderror-builtin, 92 | unicode-builtin, 93 | xrange-builtin, 94 | coerce-method, 95 | delslice-method, 96 | getslice-method, 97 | setslice-method, 98 | no-absolute-import, 99 | old-division, 100 | dict-iter-method, 101 | dict-view-method, 102 | next-method-called, 103 | metaclass-assignment, 104 | indexing-exception, 105 | raising-string, 106 | reload-builtin, 107 | oct-method, 108 | hex-method, 109 | nonzero-method, 110 | cmp-method, 111 | input-builtin, 112 | round-builtin, 113 | intern-builtin, 114 | unichr-builtin, 115 | map-builtin-not-iterating, 116 | zip-builtin-not-iterating, 117 | range-builtin-not-iterating, 118 | filter-builtin-not-iterating, 119 | using-cmp-argument, 120 | eq-without-hash, 121 | div-method, 122 | idiv-method, 123 | rdiv-method, 124 | exception-message-attribute, 125 | invalid-str-codec, 126 | sys-max-int, 127 | bad-python3-import, 128 | deprecated-string-function, 129 | deprecated-str-translate-call, 130 | deprecated-itertools-function, 131 | deprecated-types-field, 132 | next-method-defined, 133 | dict-items-not-iterating, 134 | dict-keys-not-iterating, 135 | dict-values-not-iterating, 136 | deprecated-operator-function, 137 | deprecated-urllib-function, 138 | xreadlines-attribute, 139 | deprecated-sys-function, 140 | exception-escape, 141 | comprehension-escape, 142 | missing-module-docstring, 143 | missing-class-docstring, 144 | missing-function-docstring, 145 | too-few-public-methods, 146 | too-many-locals, 147 | too-many-return-statements, 148 | too-many-branches 149 | 150 | # Enable the message, report, category or checker with the given id(s). You can 151 | # either give multiple identifier separated by comma (,) or put this option 152 | # multiple time (only on the command line, not in the configuration file where 153 | # it should appear only once). See also the "--disable" option for examples. 154 | enable=c-extension-no-member 155 | 156 | 157 | [REPORTS] 158 | 159 | # Python expression which should return a score less than or equal to 10. You 160 | # have access to the variables 'error', 'warning', 'refactor', and 'convention' 161 | # which contain the number of messages in each category, as well as 'statement' 162 | # which is the total number of statements analyzed. This score is used by the 163 | # global evaluation report (RP0004). 164 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) 165 | 166 | # Template used to display messages. This is a python new-style format string 167 | # used to format the message information. See doc for all details. 168 | #msg-template= 169 | 170 | # Set the output format. Available formats are text, parseable, colorized, json 171 | # and msvs (visual studio). You can also give a reporter class, e.g. 172 | # mypackage.mymodule.MyReporterClass. 173 | output-format=text 174 | 175 | # Tells whether to display a full report or only the messages. 176 | reports=no 177 | 178 | # Activate the evaluation score. 179 | score=yes 180 | 181 | 182 | [REFACTORING] 183 | 184 | # Maximum number of nested blocks for function / method body 185 | max-nested-blocks=5 186 | 187 | # Complete name of functions that never returns. When checking for 188 | # inconsistent-return-statements if a never returning function is called then 189 | # it will be considered as an explicit return statement and no message will be 190 | # printed. 191 | never-returning-functions=sys.exit 192 | 193 | 194 | [LOGGING] 195 | 196 | # The type of string formatting that logging methods do. `old` means using % 197 | # formatting, `new` is for `{}` formatting. 198 | logging-format-style=old 199 | 200 | # Logging modules to check that the string format arguments are in logging 201 | # function parameter format. 202 | logging-modules=logging 203 | 204 | 205 | [BASIC] 206 | 207 | # Naming style matching correct argument names. 208 | argument-naming-style=snake_case 209 | 210 | # Regular expression matching correct argument names. Overrides argument- 211 | # naming-style. 212 | #argument-rgx= 213 | 214 | # Naming style matching correct attribute names. 215 | attr-naming-style=snake_case 216 | 217 | # Regular expression matching correct attribute names. Overrides attr-naming- 218 | # style. 219 | #attr-rgx= 220 | 221 | # Bad variable names which should always be refused, separated by a comma. 222 | bad-names=foo, 223 | bar, 224 | baz, 225 | toto, 226 | tutu, 227 | tata 228 | 229 | # Bad variable names regexes, separated by a comma. If names match any regex, 230 | # they will always be refused 231 | bad-names-rgxs= 232 | 233 | # Naming style matching correct class attribute names. 234 | class-attribute-naming-style=any 235 | 236 | # Regular expression matching correct class attribute names. Overrides class- 237 | # attribute-naming-style. 238 | #class-attribute-rgx= 239 | 240 | # Naming style matching correct class names. 241 | class-naming-style=PascalCase 242 | 243 | # Regular expression matching correct class names. Overrides class-naming- 244 | # style. 245 | #class-rgx= 246 | 247 | # Naming style matching correct constant names. 248 | const-naming-style=UPPER_CASE 249 | 250 | # Regular expression matching correct constant names. Overrides const-naming- 251 | # style. 252 | #const-rgx= 253 | 254 | # Minimum line length for functions/classes that require docstrings, shorter 255 | # ones are exempt. 256 | docstring-min-length=-1 257 | 258 | # Naming style matching correct function names. 259 | function-naming-style=snake_case 260 | 261 | # Regular expression matching correct function names. Overrides function- 262 | # naming-style. 263 | #function-rgx= 264 | 265 | # Good variable names which should always be accepted, separated by a comma. 266 | good-names=i, 267 | j, 268 | k, 269 | ex, 270 | Run, 271 | _ 272 | 273 | # Good variable names regexes, separated by a comma. If names match any regex, 274 | # they will always be accepted 275 | good-names-rgxs= 276 | 277 | # Include a hint for the correct naming format with invalid-name. 278 | include-naming-hint=no 279 | 280 | # Naming style matching correct inline iteration names. 281 | inlinevar-naming-style=any 282 | 283 | # Regular expression matching correct inline iteration names. Overrides 284 | # inlinevar-naming-style. 285 | #inlinevar-rgx= 286 | 287 | # Naming style matching correct method names. 288 | method-naming-style=snake_case 289 | 290 | # Regular expression matching correct method names. Overrides method-naming- 291 | # style. 292 | #method-rgx= 293 | 294 | # Naming style matching correct module names. 295 | module-naming-style=snake_case 296 | 297 | # Regular expression matching correct module names. Overrides module-naming- 298 | # style. 299 | #module-rgx= 300 | 301 | # Colon-delimited sets of names that determine each other's naming style when 302 | # the name regexes allow several styles. 303 | name-group= 304 | 305 | # Regular expression which should only match function or class names that do 306 | # not require a docstring. 307 | no-docstring-rgx=^_ 308 | 309 | # List of decorators that produce properties, such as abc.abstractproperty. Add 310 | # to this list to register other decorators that produce valid properties. 311 | # These decorators are taken in consideration only for invalid-name. 312 | property-classes=abc.abstractproperty 313 | 314 | # Naming style matching correct variable names. 315 | variable-naming-style=snake_case 316 | 317 | # Regular expression matching correct variable names. Overrides variable- 318 | # naming-style. 319 | #variable-rgx= 320 | 321 | 322 | [SPELLING] 323 | 324 | # Limits count of emitted suggestions for spelling mistakes. 325 | max-spelling-suggestions=4 326 | 327 | # Spelling dictionary name. Available dictionaries: none. To make it work, 328 | # install the python-enchant package. 329 | spelling-dict= 330 | 331 | # List of comma separated words that should not be checked. 332 | spelling-ignore-words= 333 | 334 | # A path to a file that contains the private dictionary; one word per line. 335 | spelling-private-dict-file= 336 | 337 | # Tells whether to store unknown words to the private dictionary (see the 338 | # --spelling-private-dict-file option) instead of raising a message. 339 | spelling-store-unknown-words=no 340 | 341 | 342 | [TYPECHECK] 343 | 344 | # List of decorators that produce context managers, such as 345 | # contextlib.contextmanager. Add to this list to register other decorators that 346 | # produce valid context managers. 347 | contextmanager-decorators=contextlib.contextmanager 348 | 349 | # List of members which are set dynamically and missed by pylint inference 350 | # system, and so shouldn't trigger E1101 when accessed. Python regular 351 | # expressions are accepted. 352 | generated-members= 353 | 354 | # Tells whether missing members accessed in mixin class should be ignored. A 355 | # mixin class is detected if its name ends with "mixin" (case insensitive). 356 | ignore-mixin-members=yes 357 | 358 | # Tells whether to warn about missing members when the owner of the attribute 359 | # is inferred to be None. 360 | ignore-none=yes 361 | 362 | # This flag controls whether pylint should warn about no-member and similar 363 | # checks whenever an opaque object is returned when inferring. The inference 364 | # can return multiple potential results while evaluating a Python object, but 365 | # some branches might not be evaluated, which results in partial inference. In 366 | # that case, it might be useful to still emit no-member and other checks for 367 | # the rest of the inferred objects. 368 | ignore-on-opaque-inference=yes 369 | 370 | # List of class names for which member attributes should not be checked (useful 371 | # for classes with dynamically set attributes). This supports the use of 372 | # qualified names. 373 | ignored-classes=optparse.Values,thread._local,_thread._local 374 | 375 | # List of module names for which member attributes should not be checked 376 | # (useful for modules/projects where namespaces are manipulated during runtime 377 | # and thus existing member attributes cannot be deduced by static analysis). It 378 | # supports qualified module names, as well as Unix pattern matching. 379 | ignored-modules= 380 | 381 | # Show a hint with possible names when a member name was not found. The aspect 382 | # of finding the hint is based on edit distance. 383 | missing-member-hint=yes 384 | 385 | # The minimum edit distance a name should have in order to be considered a 386 | # similar match for a missing member name. 387 | missing-member-hint-distance=1 388 | 389 | # The total number of similar names that should be taken in consideration when 390 | # showing a hint for a missing member. 391 | missing-member-max-choices=1 392 | 393 | # List of decorators that change the signature of a decorated function. 394 | signature-mutators= 395 | 396 | 397 | [VARIABLES] 398 | 399 | # List of additional names supposed to be defined in builtins. Remember that 400 | # you should avoid defining new builtins when possible. 401 | additional-builtins= 402 | 403 | # Tells whether unused global variables should be treated as a violation. 404 | allow-global-unused-variables=yes 405 | 406 | # List of strings which can identify a callback function by name. A callback 407 | # name must start or end with one of those strings. 408 | callbacks=cb_, 409 | _cb 410 | 411 | # A regular expression matching the name of dummy variables (i.e. expected to 412 | # not be used). 413 | dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ 414 | 415 | # Argument names that match this expression will be ignored. Default to name 416 | # with leading underscore. 417 | ignored-argument-names=_.*|^ignored_|^unused_ 418 | 419 | # Tells whether we should check for unused import in __init__ files. 420 | init-import=no 421 | 422 | # List of qualified module names which can have objects that can redefine 423 | # builtins. 424 | redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io 425 | 426 | 427 | [FORMAT] 428 | 429 | # Expected format of line ending, e.g. empty (any line ending), LF or CRLF. 430 | expected-line-ending-format= 431 | 432 | # Regexp for a line that is allowed to be longer than the limit. 433 | ignore-long-lines=^\s*(# )??$ 434 | 435 | # Number of spaces of indent required inside a hanging or continued line. 436 | indent-after-paren=4 437 | 438 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 439 | # tab). 440 | indent-string=' ' 441 | 442 | # Maximum number of characters on a single line. 443 | max-line-length=100 444 | 445 | # Maximum number of lines in a module. 446 | max-module-lines=1000 447 | 448 | # List of optional constructs for which whitespace checking is disabled. `dict- 449 | # separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. 450 | # `trailing-comma` allows a space between comma and closing bracket: (a, ). 451 | # `empty-line` allows space-only lines. 452 | no-space-check=trailing-comma, 453 | dict-separator 454 | 455 | # Allow the body of a class to be on the same line as the declaration if body 456 | # contains single statement. 457 | single-line-class-stmt=no 458 | 459 | # Allow the body of an if to be on the same line as the test if there is no 460 | # else. 461 | single-line-if-stmt=no 462 | 463 | 464 | [SIMILARITIES] 465 | 466 | # Ignore comments when computing similarities. 467 | ignore-comments=yes 468 | 469 | # Ignore docstrings when computing similarities. 470 | ignore-docstrings=yes 471 | 472 | # Ignore imports when computing similarities. 473 | ignore-imports=no 474 | 475 | # Minimum lines number of a similarity. 476 | min-similarity-lines=4 477 | 478 | 479 | [STRING] 480 | 481 | # This flag controls whether inconsistent-quotes generates a warning when the 482 | # character used as a quote delimiter is used inconsistently within a module. 483 | check-quote-consistency=no 484 | 485 | # This flag controls whether the implicit-str-concat should generate a warning 486 | # on implicit string concatenation in sequences defined over several lines. 487 | check-str-concat-over-line-jumps=no 488 | 489 | 490 | [MISCELLANEOUS] 491 | 492 | # List of note tags to take in consideration, separated by a comma. 493 | notes=FIXME, 494 | XXX, 495 | TODO 496 | 497 | # Regular expression of note tags to take in consideration. 498 | #notes-rgx= 499 | 500 | 501 | [IMPORTS] 502 | 503 | # List of modules that can be imported at any level, not just the top level 504 | # one. 505 | allow-any-import-level= 506 | 507 | # Allow wildcard imports from modules that define __all__. 508 | allow-wildcard-with-all=no 509 | 510 | # Analyse import fallback blocks. This can be used to support both Python 2 and 511 | # 3 compatible code, which means that the block might have code that exists 512 | # only in one or another interpreter, leading to false positives when analysed. 513 | analyse-fallback-blocks=no 514 | 515 | # Deprecated modules which should not be used, separated by a comma. 516 | deprecated-modules=optparse,tkinter.tix 517 | 518 | # Create a graph of external dependencies in the given file (report RP0402 must 519 | # not be disabled). 520 | ext-import-graph= 521 | 522 | # Create a graph of every (i.e. internal and external) dependencies in the 523 | # given file (report RP0402 must not be disabled). 524 | import-graph= 525 | 526 | # Create a graph of internal dependencies in the given file (report RP0402 must 527 | # not be disabled). 528 | int-import-graph= 529 | 530 | # Force import order to recognize a module as part of the standard 531 | # compatibility libraries. 532 | known-standard-library= 533 | 534 | # Force import order to recognize a module as part of a third party library. 535 | known-third-party=enchant 536 | 537 | # Couples of modules and preferred modules, separated by a comma. 538 | preferred-modules= 539 | 540 | 541 | [CLASSES] 542 | 543 | # List of method names used to declare (i.e. assign) instance attributes. 544 | defining-attr-methods=__init__, 545 | __new__, 546 | setUp, 547 | __post_init__ 548 | 549 | # List of member names, which should be excluded from the protected access 550 | # warning. 551 | exclude-protected=_asdict, 552 | _fields, 553 | _replace, 554 | _source, 555 | _make 556 | 557 | # List of valid names for the first argument in a class method. 558 | valid-classmethod-first-arg=cls 559 | 560 | # List of valid names for the first argument in a metaclass class method. 561 | valid-metaclass-classmethod-first-arg=cls 562 | 563 | 564 | [DESIGN] 565 | 566 | # Maximum number of arguments for function / method. 567 | max-args=5 568 | 569 | # Maximum number of attributes for a class (see R0902). 570 | max-attributes=7 571 | 572 | # Maximum number of boolean expressions in an if statement (see R0916). 573 | max-bool-expr=5 574 | 575 | # Maximum number of branch for function / method body. 576 | max-branches=12 577 | 578 | # Maximum number of locals for function / method body. 579 | max-locals=15 580 | 581 | # Maximum number of parents for a class (see R0901). 582 | max-parents=7 583 | 584 | # Maximum number of public methods for a class (see R0904). 585 | max-public-methods=20 586 | 587 | # Maximum number of return / yield for function / method body. 588 | max-returns=6 589 | 590 | # Maximum number of statements in function / method body. 591 | max-statements=50 592 | 593 | # Minimum number of public methods for a class (see R0903). 594 | min-public-methods=2 595 | 596 | 597 | [EXCEPTIONS] 598 | 599 | # Exceptions that will emit a warning when being caught. Defaults to 600 | # "BaseException, Exception". 601 | overgeneral-exceptions=BaseException, 602 | Exception -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "mini-lisp-interpreter" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Sean Wu "] 6 | 7 | [tool.poetry.dependencies] 8 | python = "^3.8" 9 | lark-parser = "^0.10.1" 10 | 11 | [tool.poetry.dev-dependencies] 12 | pytest = "^6.1" 13 | pylint = "^2.6.0" 14 | mypy = "^0.790" 15 | autopep8 = "^1.5.4" 16 | pytest-cov = "^2.10.1" 17 | 18 | [build-system] 19 | requires = ["poetry>=0.12"] 20 | build-backend = "poetry.masonry.api" 21 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seanwu1105/mini-lisp-interpreter/42b1d3d573fad4970f51ed249674ae45f3be934d/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_data/01_1.lsp: -------------------------------------------------------------------------------- 1 | (+) 2 | 3 | -------------------------------------------------------------------------------- /tests/test_data/01_2.lsp: -------------------------------------------------------------------------------- 1 | (+ (* 5 2) -) 2 | 3 | -------------------------------------------------------------------------------- /tests/test_data/02_1.lsp: -------------------------------------------------------------------------------- 1 | (print-num 1) 2 | (print-num 2) 3 | (print-num 3) 4 | (print-num 4) 5 | 6 | -------------------------------------------------------------------------------- /tests/test_data/02_2.lsp: -------------------------------------------------------------------------------- 1 | (print-num 0) 2 | (print-num -123) 3 | (print-num 456) 4 | 5 | -------------------------------------------------------------------------------- /tests/test_data/03_1.lsp: -------------------------------------------------------------------------------- 1 | (+ 1 2 3) 2 | (* 4 5 6) 3 | 4 | (print-num (+ 1 (+ 2 3 4) (* 4 5 6) (/ 8 3) (mod 10 3))) 5 | 6 | (print-num (mod 10 4)) 7 | 8 | (print-num (- (+ 1 2) 4)) 9 | 10 | (print-num -256) 11 | 12 | -------------------------------------------------------------------------------- /tests/test_data/03_2.lsp: -------------------------------------------------------------------------------- 1 | (print-num (mod 10 (+ 1 2))) 2 | 3 | (print-num (* (/ 1 2) 4)) 4 | 5 | (print-num (- (+ 1 2 3 (- 4 5) 6 (/ 7 8) (mod 9 10)) 6 | 11)) 7 | 8 | 9 | -------------------------------------------------------------------------------- /tests/test_data/04_1.lsp: -------------------------------------------------------------------------------- 1 | (print-bool #t) 2 | (print-bool #f) 3 | 4 | (print-bool (and #t #f)) 5 | (print-bool (and #t #t)) 6 | 7 | (print-bool (or #t #f)) 8 | (print-bool (or #f #f)) 9 | 10 | (print-bool (not #t)) 11 | (print-bool (not #f)) 12 | 13 | -------------------------------------------------------------------------------- /tests/test_data/04_2.lsp: -------------------------------------------------------------------------------- 1 | (print-bool (or #t #t #f)) 2 | (print-bool (or #f (and #f #t) (not #f))) 3 | (print-bool (and #t (not #f) (or #f #t) (and #t (not #t)))) 4 | 5 | 6 | -------------------------------------------------------------------------------- /tests/test_data/05_1.lsp: -------------------------------------------------------------------------------- 1 | (print-num (if #t 1 2)) 2 | 3 | (print-num (if #f 1 2)) 4 | 5 | -------------------------------------------------------------------------------- /tests/test_data/05_2.lsp: -------------------------------------------------------------------------------- 1 | (print-num (if (< 1 2) (+ 1 2 3) (* 1 2 3 4 5))) 2 | 3 | (print-num (if (= 9 (* 2 5)) 4 | 0 5 | (if #t 1 2))) 6 | 7 | -------------------------------------------------------------------------------- /tests/test_data/06_1.lsp: -------------------------------------------------------------------------------- 1 | (define x 1) 2 | 3 | (print-num x) 4 | 5 | (define y (+ 1 2 3)) 6 | 7 | (print-num y) 8 | 9 | -------------------------------------------------------------------------------- /tests/test_data/06_2.lsp: -------------------------------------------------------------------------------- 1 | (define a (* 1 2 3 4)) 2 | 3 | (define b (+ 10 -5 -2 -1)) 4 | 5 | (print-num (+ a b)) 6 | 7 | -------------------------------------------------------------------------------- /tests/test_data/07_1.lsp: -------------------------------------------------------------------------------- 1 | (print-num 2 | ((fun (x) (+ x 1)) 3)) 3 | 4 | (print-num 5 | ((fun (a b) (+ a b)) 4 5)) 6 | 7 | -------------------------------------------------------------------------------- /tests/test_data/07_2.lsp: -------------------------------------------------------------------------------- 1 | (define x 0) 2 | 3 | (print-num 4 | ((fun (x y z) (+ x (* y z))) 10 20 30)) 5 | 6 | 7 | (print-num x) 8 | 9 | -------------------------------------------------------------------------------- /tests/test_data/08_1.lsp: -------------------------------------------------------------------------------- 1 | (define foo 2 | (fun (a b c) (+ a b (* b c)))) 3 | 4 | (print-num (foo 10 9 8)) 5 | 6 | -------------------------------------------------------------------------------- /tests/test_data/08_2.lsp: -------------------------------------------------------------------------------- 1 | (define bar (fun (x) (+ x 1))) 2 | 3 | (define bar-z (fun () 2)) 4 | 5 | (print-num (bar (bar-z))) 6 | 7 | -------------------------------------------------------------------------------- /tests/test_data/b1_1.lsp: -------------------------------------------------------------------------------- 1 | (define fact 2 | (fun (n) (if (< n 3) n 3 | (* n (fact (- n 1)))))) 4 | 5 | (print-num (fact 2)) 6 | (print-num (fact 3)) 7 | (print-num (fact 4)) 8 | (print-num (fact 10)) 9 | 10 | (define fib (fun (x) 11 | (if (< x 2) x (+ 12 | (fib (- x 1)) 13 | (fib (- x 2)))))) 14 | 15 | (print-num (fib 1)) 16 | (print-num (fib 3)) 17 | (print-num (fib 5)) 18 | (print-num (fib 10)) 19 | (print-num (fib 20)) 20 | 21 | -------------------------------------------------------------------------------- /tests/test_data/b1_2.lsp: -------------------------------------------------------------------------------- 1 | (define min 2 | (fun (a b) 3 | (if (< a b) a b))) 4 | 5 | (define max 6 | (fun (a b) 7 | (if (> a b) a b))) 8 | 9 | (define gcd 10 | (fun (a b) 11 | (if (= 0 (mod (max a b) (min a b))) 12 | (min a b) 13 | (gcd (min a b) (mod (max a b) (min a b)))))) 14 | 15 | (print-num (gcd 100 88)) 16 | 17 | (print-num (gcd 1234 5678)) 18 | 19 | (print-num (gcd 81 54)) 20 | 21 | -------------------------------------------------------------------------------- /tests/test_data/b2_1.lsp: -------------------------------------------------------------------------------- 1 | (+ 1 2 3 (or #t #f)) 2 | 3 | -------------------------------------------------------------------------------- /tests/test_data/b2_2.lsp: -------------------------------------------------------------------------------- 1 | (define f 2 | (fun (x) 3 | (if (> x 10) 10 (= x 5)))) 4 | 5 | (print-num (* 2 (f 4))) 6 | 7 | -------------------------------------------------------------------------------- /tests/test_data/b3_1.lsp: -------------------------------------------------------------------------------- 1 | (define dist-square 2 | (fun (x y) 3 | (define square (fun (x) (* x x))) 4 | (+ (square x) (square y)))) 5 | 6 | (print-num (dist-square 3 4)) 7 | 8 | -------------------------------------------------------------------------------- /tests/test_data/b3_2.lsp: -------------------------------------------------------------------------------- 1 | (define diff 2 | (fun (a b) 3 | (define abs 4 | (fun (a) 5 | (if (< a 0) (- 0 a) a))) 6 | (abs (- a b)))) 7 | 8 | (print-num (diff 1 10)) 9 | (print-num (diff 10 2)) 10 | 11 | -------------------------------------------------------------------------------- /tests/test_data/b4_1.lsp: -------------------------------------------------------------------------------- 1 | (define add-x 2 | (fun (x) (fun (y) (+ x y)))) 3 | 4 | (define z (add-x 10)) 5 | 6 | (print-num (z 1)) 7 | 8 | -------------------------------------------------------------------------------- /tests/test_data/b4_2.lsp: -------------------------------------------------------------------------------- 1 | (define foo 2 | (fun (f x) (f x))) 3 | 4 | (print-num 5 | (foo (fun (x) (- x 1)) 10)) 6 | 7 | -------------------------------------------------------------------------------- /tests/test_mini_lisp_interpreter.py: -------------------------------------------------------------------------------- 1 | import io 2 | import unittest 3 | from unittest import mock 4 | from mini_lisp_interpreter import __version__, Interpreter 5 | 6 | 7 | def test_version(): 8 | assert __version__ == '0.1.0' 9 | 10 | 11 | class TestInterpreter(unittest.TestCase): 12 | def test_syntax_error(self): 13 | code = '(+)' 14 | self.assertRaises(SyntaxError, Interpreter().interpret, code) 15 | code = ''' 16 | (+ (* 5 2) -) 17 | 18 | 19 | ''' 20 | self.assertRaises(SyntaxError, Interpreter().interpret, code) 21 | 22 | @mock.patch('sys.stdout', new_callable=io.StringIO) 23 | def test_print_num(self, mock_stdout): 24 | code = ''' 25 | (print-num 1) 26 | (print-num 2) 27 | (print-num 3) 28 | (print-num 4) 29 | (print-num 0) 30 | (print-num -123) 31 | (print-num 456) 32 | ''' 33 | Interpreter().interpret(code) 34 | self.assertEqual(mock_stdout.getvalue(), '1\n2\n3\n4\n0\n-123\n456\n') 35 | 36 | def test_calculation(self): 37 | code = ''' 38 | (+ 1 2 3) 39 | (* 4 5 6) 40 | (+ 1 (+ 2 3 4) (* 4 5 6) (/ 8 3) (mod 10 3)) 41 | (mod 10 4) 42 | (- (+ 1 2) 4) 43 | -256 44 | (mod 10 (+ 1 2)) 45 | (* (/ 1 2) 4) 46 | (- (+ 1 2 3 (- 4 5) 6 (/ 7 8) (mod 9 10)) 47 | 11) 48 | ''' 49 | self.assertEqual(Interpreter().interpret(code), 50 | [6, 120, 133, 2, -1, -256, 1, 0, 9]) 51 | 52 | def test_bool(self): 53 | code = ''' 54 | #t 55 | #f 56 | (and #t #f) 57 | (and #t #t) 58 | (or #t #f) 59 | (or #f #f) 60 | (not #t) 61 | (not #f) 62 | (or #t #t #f) 63 | (or #f (and #f #t) (not #f)) 64 | (and #t (not #f) (or #f #t) (and #t (not #t))) 65 | ''' 66 | self.assertEqual(Interpreter().interpret(code), 67 | [True, False, False, True, True, False, 68 | False, True, True, True, False]) 69 | 70 | def test_if(self): 71 | code = ''' 72 | (if #t 1 2) 73 | (if #f 1 2) 74 | (if (< 1 2) (+ 1 2 3) (* 1 2 3 4 5)) 75 | (if (= 9 (* 2 5)) 76 | 0 77 | (if #t 1 2)) 78 | ''' 79 | self.assertEqual(Interpreter().interpret(code), [1, 2, 6, 1]) 80 | 81 | def test_define(self): 82 | code = ''' 83 | (define x 1) 84 | x 85 | (define y (+ 1 2 3)) 86 | y 87 | (define a (* 1 2 3 4)) 88 | (define b (+ 10 -5 -2 -1)) 89 | (+ a b) 90 | ''' 91 | self.assertEqual(Interpreter().interpret(code), [1, 6, 26]) 92 | 93 | def test_anony_func(self): 94 | code = ''' 95 | ((fun (x) (+ x 1)) 3) 96 | ((fun (a b) (+ a b)) 4 5) 97 | (define x 0) 98 | ((fun (x y z) (+ x (* y z))) 10 20 30) 99 | x 100 | ''' 101 | self.assertEqual(Interpreter().interpret(code), [4, 9, 610, 0]) 102 | 103 | def test_named_func(self): 104 | code = ''' 105 | (define foo 106 | (fun (a b c) (+ a b (* b c)))) 107 | (foo 10 9 8) 108 | (define bar (fun (x) (+ x 1))) 109 | (define bar-z (fun () 2)) 110 | (bar (bar-z)) 111 | ''' 112 | self.assertEqual(Interpreter().interpret(code), [91, 3]) 113 | 114 | def test_recursion(self): 115 | code = ''' 116 | (define fact 117 | (fun (n) (if (< n 3) n 118 | (* n (fact (- n 1)))))) 119 | (fact 2) 120 | (fact 3) 121 | (fact 4) 122 | (fact 10) 123 | (define fib (fun (x) 124 | (if (< x 2) x (+ 125 | (fib (- x 1)) 126 | (fib (- x 2)))))) 127 | (fib 1) 128 | (fib 3) 129 | (fib 5) 130 | (fib 10) 131 | (fib 20) 132 | (define min 133 | (fun (a b) 134 | (if (< a b) a b))) 135 | (define max 136 | (fun (a b) 137 | (if (> a b) a b))) 138 | (define gcd 139 | (fun (a b) 140 | (if (= 0 (mod (max a b) (min a b))) 141 | (min a b) 142 | (gcd (min a b) (mod (max a b) (min a b)))))) 143 | (gcd 100 88) 144 | (gcd 1234 5678) 145 | (gcd 81 54) 146 | ''' 147 | self.assertEqual(Interpreter().interpret(code), 148 | [2, 6, 24, 3628800, 1, 2, 5, 55, 6765, 4, 2, 27]) 149 | 150 | def test_type_error(self): 151 | code = '(+ 1 2 3 (or #t #f))' 152 | self.assertRaises(TypeError, Interpreter().interpret, code) 153 | code = ''' 154 | (define f 155 | (fun (x) 156 | (if (> x 10) 10 (= x 5)))) 157 | (* 2 (f 4)) 158 | ''' 159 | self.assertRaises(TypeError, Interpreter().interpret, code) 160 | 161 | def test_nested_func(self): 162 | code = ''' 163 | (define dist-square 164 | (fun (x y) 165 | (define square (fun (x) (* x x))) 166 | (+ (square x) (square y)))) 167 | (dist-square 3 4) 168 | (define diff 169 | (fun (a b) 170 | (define abs 171 | (fun (a) 172 | (if (< a 0) (- 0 a) a))) 173 | (abs (- a b)))) 174 | (diff 1 10) 175 | (diff 10 2) 176 | ''' 177 | self.assertEqual(Interpreter().interpret(code), [25, 9, 8]) 178 | 179 | def test_first_class_func(self): 180 | code = ''' 181 | (define add-x 182 | (fun (x) (fun (y) (+ x y)))) 183 | (define z (add-x 10)) 184 | (z 1) 185 | (define foo 186 | (fun (f x) (f x))) 187 | (foo (fun (x) (- x 1)) 10) 188 | ''' 189 | self.assertEqual(Interpreter().interpret(code), [11, 9]) 190 | --------------------------------------------------------------------------------