├── .github └── workflows │ └── tests.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── assets └── tic-tac-toe.gif ├── ast_tester.py ├── code_examples ├── advanced_ifs_and_loops.py ├── advanced_types.py ├── assignments.py ├── everything_else.py ├── for_loops.py ├── function_defs_and_calls.py ├── inheritance.py ├── loops_flow_control.py ├── multiple_assignment.py └── nested_function_calls.py ├── code_to_tac.py ├── demo ├── flatline_one_lined.py └── test_input.py ├── flatline.py ├── lexer.py ├── out └── .gitignore ├── python_parser.py ├── requirements.txt ├── tac_ast_examples ├── advanced_ifs_and_loops.py ├── advanced_types.py ├── assignments.py ├── basic_for_loops.py ├── for_loops.py ├── function_defs_and_calls.py ├── lists.py ├── loops_flow_control.py ├── nested_function_calls.py ├── unused_variables.py └── while_loops.py ├── tac_optimizer.py ├── tac_shorten.py ├── tac_to_code.py ├── test_compiler.py ├── test_input.py └── test_inputs ├── comprehensive.py ├── ifs_test.py └── loops.py /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: unit-tests 2 | 3 | on: 4 | push: 5 | schedule: 6 | - cron: '0 0 * * 0' 7 | 8 | jobs: 9 | unit_tests: 10 | runs-on: ubuntu-latest 11 | steps: 12 | # checks out repo and upgrades python version 13 | - uses: actions/checkout@v4 14 | - name: Configure Python version 15 | uses: actions/setup-python@v5 16 | with: 17 | python-version: '3.10.x' 18 | - name: Setup requirements and dependencies 19 | run: | 20 | pip3 install -r requirements.txt 21 | - name: Run tests 22 | run: python3 test_compiler.py 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_store 2 | /.idea 3 | /venv 4 | playground.py 5 | parser.out 6 | parsetab.py 7 | __pycache__/ 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Haocheng Hu 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: lexer run test 2 | lexer: 3 | for f in $$(ls code_examples/); do python3 lexer.py code_examples/$$f > out/$$f.out.txt; done 4 | 5 | run: 6 | python3 python_parser.py test_input.py 7 | 8 | test: 9 | python3 test_compiler.py 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flatliner 2 | 3 | [![unit-tests](https://github.com/hhc97/flatliner-src/actions/workflows/tests.yml/badge.svg)](https://github.com/hhc97/flatliner-src/actions/workflows/tests.yml) 4 | 5 | This repo contains the core code for Flatliner ([https://flatliner.herokuapp.com/](https://flatliner.herokuapp.com/)). (Give it a couple seconds if it doesn't load immediately.) 6 | 7 | ### Basic overview 8 | 9 | This project aims to convert Python 3 programs into a _single line_ of Python 3 code that is functionally equivalent 10 | -- meaning that the output code will do the same thing as the input code even though it is only one line long. 11 | 12 | For example, we can convert an entire interactive tic-tac-toe game into one line of code. 13 | 14 | Input: 15 | ```python 16 | """ 17 | A simple two player tic tac toe implementation. 18 | """ 19 | 20 | 21 | def get_line(line: list) -> str: 22 | """ 23 | Format a line of the board into a string. 24 | """ 25 | temp_string = ' ' 26 | spacer = ' | ' 27 | temp_string += line[0] 28 | temp_string += spacer 29 | temp_string += line[1] 30 | temp_string += spacer 31 | temp_string += line[2] 32 | return temp_string 33 | 34 | 35 | def print_board(state: list) -> None: 36 | """ 37 | Prints the board. 38 | """ 39 | horizontal_spacer = '---+---+---' 40 | print(get_line(state[0])) 41 | print(horizontal_spacer) 42 | print(get_line(state[1])) 43 | print(horizontal_spacer) 44 | print(get_line(state[2])) 45 | 46 | 47 | def check_won(state: list) -> str: 48 | """ 49 | Checks if a player has won the game and returns the winning symbol. 50 | '' 51 | 'X' 52 | 'O' 53 | """ 54 | if state[0][0] == state[1][1] == state[2][2] and state[0][0] != ' ': 55 | return state[0][0] 56 | if state[0][2] == state[1][1] == state[2][0] and state[0][2] != ' ': 57 | return state[0][2] 58 | for r in range(3): 59 | if state[r][0] == state[r][1] == state[r][2] and state[r][0] != ' ': 60 | return state[r][0] 61 | for c in range(3): 62 | if state[0][c] == state[1][c] == state[2][c] and state[0][c] != ' ': 63 | return state[0][c] 64 | return '' 65 | 66 | 67 | def have_space(state: list) -> bool: 68 | """ 69 | Return whether there is still an empty space on the board. 70 | """ 71 | if ' ' in state[0] or ' ' in state[1] or ' ' in state[2]: 72 | return True 73 | return False 74 | 75 | 76 | def translate_move_to_english(move: list) -> str: 77 | """ 78 | Translates move coordinates to English. 79 | [1, 2] 80 | """ 81 | if move[0] == 0 and move[1] == 0: 82 | return 'top-left' 83 | if move[0] == 0 and move[1] == 1: 84 | return 'top-center' 85 | if move[0] == 0 and move[1] == 2: 86 | return 'top-right' 87 | if move[0] == 1 and move[1] == 0: 88 | return 'middle-left' 89 | if move[0] == 1 and move[1] == 1: 90 | return 'middle-center' 91 | if move[0] == 1 and move[1] == 2: 92 | return 'middle-right' 93 | if move[0] == 2 and move[1] == 0: 94 | return 'bottom-left' 95 | if move[0] == 2 and move[1] == 1: 96 | return 'bottom-center' 97 | if move[0] == 2 and move[1] == 2: 98 | return 'bottom-right' 99 | 100 | 101 | def play_game() -> None: 102 | """ 103 | Starts a new game played via the command line. 104 | """ 105 | board = [[' ', ' ', ' '], 106 | [' ', ' ', ' '], 107 | [' ', ' ', ' ']] 108 | print_board(board) 109 | current_player = 'X' 110 | # while these two conditions are satisfied, the game has not ended 111 | while check_won(board) == '' and have_space(board): 112 | valid_moves = [] 113 | # get valid moves using a nested for loop 114 | for row in range(3): 115 | for col in range(3): 116 | if board[row][col] == ' ': 117 | valid_moves.append([row, col]) 118 | move_number = 1 119 | # print the prompt for the player 120 | for move in valid_moves: 121 | print(str(move_number) + '.', translate_move_to_english(move)) 122 | move_number += 1 123 | # obtain the move from the player. NOTE THAT THIS WILL CRASH IF THE PLAYER TROLLS 124 | player_selected_move = int(input('\nPlayer ' + current_player + ', select a move: ')) 125 | actual_move = valid_moves[player_selected_move - 1] 126 | board[actual_move[0]][actual_move[1]] = current_player 127 | print_board(board) 128 | if current_player == 'X': 129 | current_player = 'O' 130 | else: 131 | current_player = 'X' 132 | winner = check_won(board) 133 | if winner == '': 134 | print('It was a tie :)') 135 | else: 136 | print('Congratulations, player ' + winner + ' won!') 137 | 138 | 139 | if __name__ == '__main__': 140 | play_game() 141 | ``` 142 | Output: 143 | ```python 144 | (lambda _Y: (lambda get_line: (lambda print_board: (lambda check_won: (lambda have_space: (lambda translate_move_to_english: (lambda play_game: (play_game() if __name__ == '__main__' else None))(lambda: (lambda board: [print_board(board), (lambda current_player: (lambda actual_move, current_player, move_number, player_selected_move, valid_moves: (lambda _loop1: _loop1(actual_move, current_player, move_number, player_selected_move, valid_moves))(_Y(lambda _loop1: (lambda actual_move, current_player, move_number, player_selected_move, valid_moves: ((lambda valid_moves: (lambda _term3, _items3: (lambda _targ3: (lambda _targ3, row: (lambda _loop3: _loop3(_targ3, row))(_Y(lambda _loop3: (lambda _targ3, row: ((lambda row: (lambda _term4, _items4: (lambda _targ4: (lambda _targ4, col: (lambda _loop4: _loop4(_targ4, col))(_Y(lambda _loop4: (lambda _targ4, col: ((lambda col: ([valid_moves.append([row, col]), (lambda _targ4: _loop4(_targ4, col))(next(_items4, _term4))][-1] if board[row][col] == ' ' else (lambda _targ4: _loop4(_targ4, col))(next(_items4, _term4))))(_targ4)) if _targ4 is not _term4 else (lambda _targ3: _loop3(_targ3, row))(next(_items3, _term3))))))(_targ4 if "_targ4" in dir() else None, col if "col" in dir() else None))(next(_items4, _term4)))([], iter(range(3))))(_targ3)) if _targ3 is not _term3 else (lambda move_number: (lambda _term2, _items2: (lambda _targ2: (lambda _targ2, move, move_number: (lambda _loop2: _loop2(_targ2, move, move_number))(_Y(lambda _loop2: (lambda _targ2, move, move_number: ((lambda move: [print((str(move_number) + '.'), translate_move_to_english(move)), (lambda move_number: (lambda _targ2: _loop2(_targ2, move, move_number))(next(_items2, _term2)))((move_number + 1))][-1])(_targ2)) if _targ2 is not _term2 else (lambda player_selected_move: (lambda actual_move: [[print_board(board), ((lambda current_player: _loop1(actual_move, current_player, move_number, player_selected_move, valid_moves))('O') if current_player == 'X' else (lambda current_player: _loop1(actual_move, current_player, move_number, player_selected_move, valid_moves))('X'))][-1] for board[actual_move[0]][actual_move[1]] in [current_player]][0])(valid_moves[(player_selected_move - 1)]))(int(input((('\nPlayer ' + current_player) + ', select a move: '))))))))(_targ2 if "_targ2" in dir() else None, move if "move" in dir() else None, move_number if "move_number" in dir() else None))(next(_items2, _term2)))([], iter(valid_moves)))(1)))))(_targ3 if "_targ3" in dir() else None, row if "row" in dir() else None))(next(_items3, _term3)))([], iter(range(3))))([])) if check_won(board) == '' and have_space(board) else (lambda winner: (print('It was a tie :)') if winner == '' else print((('Congratulations, player ' + winner) + ' won!'))))(check_won(board))))))(actual_move if "actual_move" in dir() else None, current_player if "current_player" in dir() else None, move_number if "move_number" in dir() else None, player_selected_move if "player_selected_move" in dir() else None, valid_moves if "valid_moves" in dir() else None))('X')][-1])([[' ', ' ', ' '], [' ', ' ', ' '], [' ', ' ', ' ']])))(lambda move: ('top-left' if move[0] == 0 and move[1] == 0 else ('top-center' if move[0] == 0 and move[1] == 1 else ('top-right' if move[0] == 0 and move[1] == 2 else ('middle-left' if move[0] == 1 and move[1] == 0 else ('middle-center' if move[0] == 1 and move[1] == 1 else ('middle-right' if move[0] == 1 and move[1] == 2 else ('bottom-left' if move[0] == 2 and move[1] == 0 else ('bottom-center' if move[0] == 2 and move[1] == 1 else ('bottom-right' if move[0] == 2 and move[1] == 2 else None)))))))))))(lambda state: (True if ' ' in state[0] or ' ' in state[1] or ' ' in state[2] else False)))(lambda state: (state[0][0] if state[0][0] == state[1][1] == state[2][2] and state[0][0] != ' ' else (state[0][2] if state[0][2] == state[1][1] == state[2][0] and state[0][2] != ' ' else (lambda _term6, _items6: (lambda _targ6: (lambda _targ6, r: (lambda _loop6: _loop6(_targ6, r))(_Y(lambda _loop6: (lambda _targ6, r: ((lambda r: (state[r][0] if state[r][0] == state[r][1] == state[r][2] and state[r][0] != ' ' else (lambda _targ6: _loop6(_targ6, r))(next(_items6, _term6))))(_targ6)) if _targ6 is not _term6 else (lambda _term5, _items5: (lambda _targ5: (lambda _targ5, c: (lambda _loop5: _loop5(_targ5, c))(_Y(lambda _loop5: (lambda _targ5, c: ((lambda c: (state[0][c] if state[0][c] == state[1][c] == state[2][c] and state[0][c] != ' ' else (lambda _targ5: _loop5(_targ5, c))(next(_items5, _term5))))(_targ5)) if _targ5 is not _term5 else ''))))(_targ5 if "_targ5" in dir() else None, c if "c" in dir() else None))(next(_items5, _term5)))([], iter(range(3)))))))(_targ6 if "_targ6" in dir() else None, r if "r" in dir() else None))(next(_items6, _term6)))([], iter(range(3)))))))(lambda state: (lambda horizontal_spacer: [print(get_line(state[0])), [print(horizontal_spacer), [print(get_line(state[1])), [print(horizontal_spacer), print(get_line(state[2]))][-1]][-1]][-1]][-1])('---+---+---')))(lambda line: (lambda temp_string: (lambda spacer: (lambda temp_string: (lambda temp_string: (lambda temp_string: (lambda temp_string: (lambda temp_string: temp_string)((temp_string + line[2])))((temp_string + spacer)))((temp_string + line[1])))((temp_string + spacer)))((temp_string + line[0])))(' | '))(' ')))((lambda f: (lambda x: x(x))(lambda y: f(lambda *args: y(y)(*args))))) 145 | ``` 146 | 147 | Running this output shows us it works like the original code, how neat! 148 | ![](/assets/tic-tac-toe.gif) 149 | 150 | For more examples of what can be converted, see [code_examples](/code_examples). 151 | 152 | ### Misc 153 | 154 | For those interested in how this works, the conversion happens in [flatline.py](/flatline.py). This program 155 | can also convert itself into one line! See [flatline_one_lined.py](/demo/flatline_one_lined.py). 156 | 157 | 158 | ### Acknowledgement 159 | 160 | This project was made together with [Naaz](https://github.com/naazsibia) and [Ritvik](https://github.com/AipioxTechson) 161 | for a compilers course at the University of Toronto. 162 | 163 | While implementing our project, we also came across an excellent python 2 implementation of the same concept [here](https://github.com/csvoss/onelinerizer). 164 | 165 | 166 | ### License 167 | 168 | While this work is distributed under the [MIT license](/LICENSE), contributions back to source will be appreciated :) 169 | -------------------------------------------------------------------------------- /assets/tic-tac-toe.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhc97/flatliner-src/48ab0b5ed5078b5405f69720bdd6f8f328226e88/assets/tic-tac-toe.gif -------------------------------------------------------------------------------- /ast_tester.py: -------------------------------------------------------------------------------- 1 | import ast 2 | 3 | from flatline import Flatliner 4 | 5 | TO_TEST = """ 6 | a = 1 + 2 7 | b = 3 + 4 8 | c = a + b 9 | """ 10 | 11 | ast_rep = ast.parse(TO_TEST) 12 | print(ast.dump(ast_rep, indent=4)) 13 | print('-----') 14 | print(ast.unparse(ast_rep)) 15 | print('-----') 16 | flattener = Flatliner() 17 | flattener.ast = ast_rep 18 | flattened = flattener.unparse() 19 | print(flattened) 20 | print('===== lambda output =====') 21 | exec(flattened) 22 | print('===== original output =====') 23 | exec(TO_TEST) 24 | -------------------------------------------------------------------------------- /code_examples/advanced_ifs_and_loops.py: -------------------------------------------------------------------------------- 1 | def get_average_elevation(m: list[list[int]]) -> float: 2 | """ 3 | Returns the average elevation across the elevation map m. 4 | 5 | Examples 6 | >>> get_average_elevation([]) 7 | 0 8 | >>> m = [[1,2,3],[4,5,6],[7,8,9]] 9 | >>> get_average_elevation(m) 10 | 5.0 11 | >>> m = [[1,2,2,5],[4,5,4,8],[7,9,9,1],[1,2,1,4]] 12 | >>> get_average_elevation(m) 13 | 4.0625 14 | """ 15 | # Your code goes here 16 | total = 0 17 | n = 0 18 | for lst in m: 19 | for i in lst: 20 | total = total + i 21 | n += 1 22 | if n == 0: 23 | return 0 24 | return total / n 25 | 26 | 27 | def find_peak(m: list[list[int]]) -> list[int]: 28 | """ 29 | Given an non-empty elevation map m, returns the cell of the 30 | highest point in m. 31 | 32 | Examples (note some spacing has been added for human readablity) 33 | >>> m = [[1,2,3],[9,8,7],[5,4,6]] 34 | >>> find_peak(m) 35 | [1, 0] 36 | >>> m = [[6,2,3],[1,8,7],[5,4,9]] 37 | >>> find_peak(m) 38 | [2, 2] 39 | """ 40 | # Your code goes here 41 | largest = 0 42 | x = 0 43 | y = 0 44 | for data in m: 45 | for i in range(len(data)): 46 | if data[i] > largest: 47 | x = m.index(data) 48 | y = i 49 | largest = data[i] 50 | return [x, y] 51 | 52 | 53 | def is_sink(m: list[list[int]], c: list[int]) -> bool: 54 | """ 55 | Returns True if and only if c is a sink in m. 56 | 57 | Examples (note some spacing has been added for human readablity) 58 | >>> m = [[1,2,3],[2,3,3],[5,4,3]] 59 | >>> is_sink(m, [0,0]) 60 | True 61 | >>> is_sink(m, [2,2]) 62 | True 63 | >>> is_sink(m, [3,0]) 64 | False 65 | >>> m = [[1,2,3],[2,1,3],[5,4,3]] 66 | >>> is_sink(m, [1,1]) 67 | True 68 | """ 69 | # Your code goes here 70 | if c[0] == 0 and c[1] == 0: 71 | if (m[c[0]][c[1]] <= m[c[0] + 1][c[1]] and m[c[0]][c[1]] <= m[c[0]][c[1] + 1] and 72 | m[c[0]][c[1]] <= m[c[0] + 1][c[1] + 1]): 73 | return True 74 | if c[0] == 0 and c[1] > 0 and c[1] < (len(m[0]) - 1): 75 | if (m[c[0]][c[1]] <= m[c[0]][c[1] - 1] and m[c[0]][c[1]] <= m[c[0]][c[1] + 1] and 76 | m[c[0]][c[1]] <= m[c[0] + 1][c[1] - 1] and m[c[0]][c[1]] <= m[c[0] + 1][c[1]] and 77 | m[c[0]][c[1]] <= m[c[0] + 1][c[1] + 1]): 78 | return True 79 | if c[0] == 0 and c[1] == (len(m[0]) - 1): 80 | if (m[c[0]][c[1]] <= m[c[0]][c[1] - 1] and m[c[0]][c[1]] <= m[c[0] + 1][c[1] - 1] and 81 | m[c[0]][c[1]] <= m[c[0] + 1][c[1]]): 82 | return True 83 | if c[0] > 0 and c[0] < (len(m) - 1) and c[1] == 0: 84 | if (m[c[0]][c[1]] <= m[c[0] - 1][c[1]] and m[c[0]][c[1]] <= m[c[0] - 1][c[1] + 1] and 85 | m[c[0]][c[1]] <= m[c[0]][c[1] + 1] and m[c[0]][c[1]] <= m[c[0] + 1][c[1] + 1] and 86 | m[c[0]][c[1]] <= m[c[0] + 1][c[1]]): 87 | return True 88 | if c[0] > 0 and c[0] < (len(m) - 1) and c[1] > 0 and c[1] < (len(m[0]) - 1): 89 | if (m[c[0]][c[1]] <= m[c[0] - 1][c[1] - 1] and m[c[0]][c[1]] <= m[c[0] - 1][c[1]] and 90 | m[c[0]][c[1]] <= m[c[0] - 1][c[1] + 1] and m[c[0]][c[1]] <= m[c[0]][c[1] - 1] and 91 | m[c[0]][c[1]] <= m[c[0]][c[1] + 1] and m[c[0]][c[1]] <= m[c[0] + 1][c[1] - 1] and 92 | m[c[0]][c[1]] <= m[c[0] + 1][c[1]] and m[c[0]][c[1]] <= m[c[0] + 1][c[1] + 1]): 93 | return True 94 | if c[0] > 0 and c[0] < (len(m) - 1) and c[1] == (len(m[0]) - 1): 95 | if (m[c[0]][c[1]] <= m[c[0] - 1][c[1]] and m[c[0]][c[1]] <= m[c[0] - 1][c[1] - 1] and 96 | m[c[0]][c[1]] <= m[c[0]][c[1] - 1] and m[c[0]][c[1]] <= m[c[0] + 1][c[1] - 1] and 97 | m[c[0]][c[1]] <= m[c[0] + 1][c[1]]): 98 | return True 99 | if c[0] == (len(m) - 1) and c[1] == 0: 100 | if (m[c[0]][c[1]] <= m[c[0] - 1][c[1]] and m[c[0]][c[1]] <= m[c[0] - 1][c[1] + 1] and 101 | m[c[0]][c[1]] <= m[c[0]][c[1] + 1]): 102 | return True 103 | if c[0] == (len(m) - 1) and c[1] > 0 and c[1] < (len(m[0]) - 1): 104 | if (m[c[0]][c[1]] <= m[c[0]][c[1] - 1] and m[c[0]][c[1]] <= m[c[0] - 1][c[1] - 1] and 105 | m[c[0]][c[1]] <= m[c[0] - 1][c[1]] and m[c[0]][c[1]] <= m[c[0] - 1][c[1] + 1] and 106 | m[c[0]][c[1]] <= m[c[0]][c[1] + 1]): 107 | return True 108 | if c[0] == (len(m) - 1) and c[1] == (len(m[0]) - 1): 109 | if (m[c[0]][c[1]] <= m[c[0]][c[1] - 1] and m[c[0]][c[1]] <= m[c[0] - 1][c[1] - 1] and 110 | m[c[0]][c[1]] <= m[c[0] - 1][c[1]]): 111 | return True 112 | return False 113 | 114 | 115 | def can_hike_to(m: list[list[int]], s: list[int], d: list[int], supplies: int) -> bool: 116 | """ 117 | Given an elevation map m, a start cell s, a destination cell d, and 118 | the an amount of supplies returns True if and only if a hiker could reach 119 | d from s using the strategy dscribed in the assignment .pdf. Read the .pdf 120 | carefully. Assume d is always south, east, or south-east of s. The hiker 121 | never travels, north, west, nor backtracks. 122 | 123 | Examples (note some spacing has been added for human readablity) 124 | >>> m = [[1,4,3], 125 | [2,3,5], 126 | [5,4,3]] 127 | >>> can_hike_to(m, [0,0], [2,2], 4) 128 | True 129 | >>> can_hike_to(m, [0,0], [0,0], 0) 130 | True 131 | >>> can_hike_to(m, [0,0], [2,2], 3) 132 | False 133 | >>> m = [[1, 1,100], 134 | [1,100,100], 135 | [1, 1, 1]] 136 | >>> can_hike_to(m, [0,0], [2,2], 4) 137 | False 138 | >>> can_hike_to(m, [0,0], [2,2], 202) 139 | True 140 | """ 141 | # Your code goes here 142 | while s != d and s[0] < (len(m) - 1) and s[1] < (len(m[0]) - 1) and supplies >= 0 and s[0] < d[0] and s[1] < d[1]: 143 | if abs(m[s[0]][s[1]] - m[s[0]][s[1] + 1]) <= abs(m[s[0]][s[1]] - m[s[0] + 1][s[1]]): 144 | supplies = supplies - abs(m[s[0]][s[1]] - m[s[0]][s[1] + 1]) 145 | s[1] = s[1] + 1 146 | else: 147 | supplies = supplies - abs(m[s[0]][s[1]] - m[s[0] + 1][s[1]]) 148 | s[0] = s[0] + 1 149 | while s != d and s[0] == (len(m) - 1) and supplies >= 0: 150 | supplies = supplies - abs(m[s[0]][s[1]] - m[s[0]][s[1] + 1]) 151 | s[1] = s[1] + 1 152 | while s != d and s[1] == (len(m[0]) - 1) and supplies >= 0: 153 | supplies = supplies - abs(m[s[0]][s[1]] - m[s[0] + 1][s[1]]) 154 | s[0] = s[0] + 1 155 | while s != d and s[0] < d[0] and s[1] == d[1] and supplies > 0: 156 | supplies = supplies - abs(m[s[0]][s[1]] - m[s[0] + 1][s[1]]) 157 | s[0] = s[0] + 1 158 | while s != d and s[0] == d[0] and s[1] < d[1] and supplies > 0: 159 | supplies = supplies - abs(m[s[0]][s[1]] - m[s[0]][s[1] + 1]) 160 | s[1] = s[1] + 1 161 | if supplies < 0: 162 | return False 163 | if s == d and supplies >= 0: 164 | return True 165 | 166 | 167 | sample = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] 168 | print(get_average_elevation(sample)) 169 | print(find_peak(sample)) 170 | print(is_sink(sample, [1, 1])) 171 | print(is_sink(sample, [0, 0])) 172 | print(can_hike_to(sample, [0, 0], [2, 2], 8)) 173 | print(can_hike_to(sample, [0, 0], [2, 2], 4)) 174 | -------------------------------------------------------------------------------- /code_examples/advanced_types.py: -------------------------------------------------------------------------------- 1 | def test(a, b): 2 | new = [] 3 | for i in b: 4 | if i % 2 == 0: 5 | new.append(i) 6 | else: 7 | new.append(i + a) 8 | return new 9 | 10 | 11 | c = test(5, [1, 2, 3, 4, 5]) 12 | 13 | print(c) 14 | -------------------------------------------------------------------------------- /code_examples/assignments.py: -------------------------------------------------------------------------------- 1 | a = 1 + 2 2 | print(a) 3 | b = 3 + 4.0 4 | print(b) 5 | c = a + b 6 | print(c) 7 | d = "Hi there" 8 | print(d) 9 | 10 | e = 5 > 4 11 | print(e) 12 | f = 5 < 4 < 8 13 | print(f) 14 | g = 5 >= 4 15 | print(g) 16 | h = 4 != 5 17 | print(h) 18 | i = a or b 19 | j = e and d 20 | k = a or b and c 21 | 22 | l = a >= b and b != c 23 | a = 1 in range(5) 24 | print(a) 25 | -------------------------------------------------------------------------------- /code_examples/everything_else.py: -------------------------------------------------------------------------------- 1 | import os as renamed_import 2 | from sys import version as ver 3 | 4 | import math 5 | 6 | print(renamed_import.sep) 7 | print(ver) 8 | 9 | a = {0: 0} 10 | a[5] = 0 11 | a[6] = 2 12 | b = 9 13 | a[b] = 60 14 | print(a) 15 | 16 | c = {1, 2} 17 | c |= {3, 4} 18 | print(c) 19 | c &= {1, 4} 20 | print(c) 21 | 22 | 23 | def test(d): 24 | def another(one, two): 25 | return one + two 26 | 27 | d[78] = [1, 2, list([5]), not True] 28 | if d: 29 | return another(d[78][-1], 2) 30 | return 6 31 | 32 | 33 | x = 5 34 | y = 2 35 | while x < 20: 36 | if x > 5: 37 | x = x + y 38 | y = 2 * y 39 | print('in if') 40 | else: 41 | x = y + x 42 | y = x 43 | print('in else') 44 | print(x) 45 | 46 | x = 0 47 | while x < 5: 48 | if x % 2 == 0: 49 | print('even') 50 | else: 51 | print('odd') 52 | print('looped', x) 53 | x = x + 1 54 | print('done') 55 | 56 | running = True 57 | while running: 58 | brand_new_var = 2 + 6 59 | if brand_new_var > 5: 60 | running = False 61 | print(brand_new_var) 62 | print('loop done') 63 | 64 | 65 | def test_return_in_while(): 66 | while True: 67 | return 6 68 | 69 | 70 | print(test_return_in_while()) 71 | 72 | 73 | def test_while_stop(): 74 | running = True 75 | x = 5 76 | d = {} 77 | while running: 78 | if x > 50: 79 | return d 80 | d[x] = [1, 2, 3] 81 | x = x + 10 82 | 83 | 84 | print(test_while_stop()) 85 | 86 | x = 0 87 | y = 5 88 | while x < 10: 89 | y = x 90 | x = x + 5 91 | print(x, y) 92 | 93 | e = test(a) 94 | print(a, e) 95 | 96 | while a[0] < 10: 97 | a[0] = a[0] + 3 98 | print(a[0] + a[5]) 99 | print(a[0]) 100 | 101 | a = [[1, 2, 3], 2, 3] 102 | for item in a[0]: 103 | b = 5 104 | print(item + b) 105 | print('done for loop') 106 | 107 | 108 | def one(): 109 | return 6 110 | 111 | 112 | def two(): 113 | return 8 114 | 115 | 116 | x = 5 117 | while one() and x < 20 or False: 118 | test = None 119 | if x > 10: 120 | test = one 121 | else: 122 | test = two 123 | x = x + 8 124 | print(test()) 125 | print('here1') 126 | 127 | c = ('t', 'u', 'p', 'l', 'e') 128 | d = c[0] + ''.join(c[1:-1]) + c[-1] 129 | print(d) 130 | print(math.e) 131 | 132 | assert one() and two(), 'three' 133 | 134 | 135 | class TestClass: 136 | def __init__(self): 137 | self.var = 'value' 138 | self.x = 0 139 | 140 | def test_method(self, thing): 141 | return self.var + thing 142 | 143 | def test_recursive_method(self, n): 144 | if n > 5: 145 | assert n > 5, 'oops' 146 | return self.test_recursive_method(n / 2) 147 | return self.test_method(str(n)) 148 | 149 | def test_recursive_create(self): 150 | return TestClass() 151 | 152 | def test_default_param_method(self, p1, p2=5): 153 | return p1 + p2 154 | 155 | 156 | testclass = TestClass() 157 | testclass2 = testclass.test_recursive_create() 158 | print(testclass.test_method(' added!')) 159 | print(testclass.test_recursive_method(15)) 160 | 161 | print(testclass.x, testclass2.x) 162 | for thing in [testclass, testclass2]: 163 | thing.x = 2 164 | print(testclass.x, testclass2.x) 165 | testclass.x = 10 166 | print(testclass.x, testclass2.x) 167 | 168 | print(testclass.test_default_param_method(1, 2)) 169 | print(testclass.test_default_param_method(1)) 170 | 171 | 172 | def test_default_param_func(p1, p2='hello', p3='world'): 173 | return ' '.join([p1, p2, p3]) 174 | 175 | 176 | print(test_default_param_func('test1')) 177 | print(test_default_param_func('test2', p3='python!')) 178 | 179 | a = 0 180 | while a < 5: 181 | b = 0 182 | while b < 5: 183 | b = b + 1 184 | print(b) 185 | a = a + 1 186 | b = 0 187 | print(a) 188 | print('done') 189 | 190 | 191 | def rotate_matrix(matrix): 192 | new_matrix = [] 193 | for i in range(len(matrix)): 194 | new_matrix.append([0] * len(matrix[0])) 195 | for x in range(len(matrix)): 196 | for y in range(len(matrix[0])): 197 | new_matrix[y][len(matrix) - x - 1] = matrix[x][y] 198 | return new_matrix 199 | 200 | 201 | to_rotate = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] 202 | rotated = rotate_matrix(to_rotate) 203 | print(rotated) 204 | 205 | rotated += [1] 206 | print(rotated) 207 | rotated[0][0] += 2 208 | print(rotated) 209 | 210 | for i in range(5): 211 | while i < 6: 212 | i += 2 213 | print(i) 214 | for j in range(2): 215 | print('j') 216 | print('main') 217 | print('done') 218 | 219 | a = [1, 2, 3] 220 | for i in range(len(a)): 221 | a[i] += 2 222 | print(a) 223 | 224 | 225 | def sum_list_recursive(lst): 226 | if len(lst) == 1: 227 | return lst[0] 228 | else: 229 | pass 230 | lst[0] += lst[-1] 231 | return sum_list_recursive(lst[:-1]) 232 | 233 | 234 | print(sum_list_recursive(list(range(15)))) 235 | 236 | 237 | def do_twice(func): 238 | def wrapper_do_twice(): 239 | func() 240 | func() 241 | 242 | return wrapper_do_twice 243 | 244 | 245 | def extra_print(num_times): 246 | def repeat_print(func): 247 | def wrapper_repeat(): 248 | func() 249 | for i1 in range(num_times): 250 | print('extra print', i1) 251 | 252 | return wrapper_repeat 253 | 254 | return repeat_print 255 | 256 | 257 | @extra_print(num_times=3) 258 | @do_twice 259 | @extra_print(num_times=2) 260 | def double_print(): 261 | print('double printer') 262 | 263 | 264 | double_print() 265 | 266 | print(one() if False else two()) 267 | 268 | 269 | class BinarySearchTree: 270 | # test recursive init method of class 271 | def __init__(self, root) -> None: 272 | """Initialize a new BST containing only the given root value. 273 | 274 | If is None, initialize an empty tree. 275 | """ 276 | if root is None: 277 | self._root = None 278 | self._left = None 279 | self._right = None 280 | else: 281 | self._root = root 282 | self._left = BinarySearchTree(None) 283 | self._right = BinarySearchTree(None) 284 | 285 | def is_empty(self) -> bool: 286 | return self._root is None 287 | 288 | 289 | print(BinarySearchTree(10)._right is None) 290 | print(BinarySearchTree(10)._right.is_empty()) 291 | 292 | a = not (not True or not False) 293 | print(a) 294 | 295 | single_tuple = (1,) 296 | print(type(single_tuple)) 297 | 298 | [a, b] = [1, 2] 299 | print(a, b) 300 | [a,b] = {3:5, 4:6} 301 | print(a, b) 302 | -------------------------------------------------------------------------------- /code_examples/for_loops.py: -------------------------------------------------------------------------------- 1 | def test(loops): 2 | total = 0 # total is 0 at first 3 | # do the loop 4 | for i in range(loops): 5 | print('iteration', i, 'of loop') 6 | total = total + i 7 | return total 8 | 9 | 10 | print('total sum =', test(6)) 11 | -------------------------------------------------------------------------------- /code_examples/function_defs_and_calls.py: -------------------------------------------------------------------------------- 1 | def test(a, b): 2 | return a + b 3 | 4 | 5 | print(test(1, 2)) 6 | -------------------------------------------------------------------------------- /code_examples/inheritance.py: -------------------------------------------------------------------------------- 1 | class Parent1: 2 | def method1(self): 3 | print(1) 4 | 5 | 6 | class Parent2: 7 | def method2(self): 8 | print(2) 9 | 10 | 11 | def factory(): 12 | class Parent4: 13 | def method4(self): 14 | print(4) 15 | 16 | return Parent4 17 | 18 | 19 | class Child(Parent1, Parent2, factory()): 20 | def method3(self): 21 | print(3) 22 | 23 | 24 | child = Child() 25 | child.method1() 26 | child.method2() 27 | child.method3() 28 | child.method4() 29 | 30 | 31 | class Child2(Child): 32 | def method5(self): 33 | print(5) 34 | 35 | 36 | child2 = Child2() 37 | child2.method1() 38 | child2.method2() 39 | child2.method3() 40 | child2.method4() 41 | child2.method5() 42 | -------------------------------------------------------------------------------- /code_examples/loops_flow_control.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | sys.setrecursionlimit(8000) 4 | 5 | for i in range(22): 6 | if i < 5: 7 | print(i, 'continue i') 8 | continue 9 | print(i, 'i not continue') 10 | for j in range(i, 30): 11 | print(j) 12 | k = j 13 | print(k, 'k and j') 14 | while k > 15: 15 | k -= 1 16 | if k > 20: 17 | print(k, 'k continue') 18 | continue 19 | else: 20 | if k < 18: 21 | print(k, 'k break') 22 | break 23 | print(k, 'finished k loop') 24 | if j < i + 5: 25 | print(j, 'continue j') 26 | continue 27 | print(j, 'j breaking') 28 | break 29 | if i == 17: 30 | print('i 17 continue') 31 | continue 32 | if i == 17 or i == 18: 33 | print(i, 'i break') 34 | break 35 | 36 | a = 5 37 | while a < 15: 38 | a += 1 39 | print(a, 'a loop') 40 | if a > 7: 41 | for b in range(a, a + 5): 42 | if b < a + 1: 43 | print(b, 'b continue') 44 | continue 45 | else: 46 | while b < a + 15: 47 | b += 1 48 | if b - a > 3: 49 | print('b while break') 50 | break 51 | print(b, 'b for break') 52 | break 53 | if a < 13: 54 | print(a, 'a continue') 55 | continue 56 | print(a, 'a break') 57 | break 58 | 59 | print('done test') 60 | -------------------------------------------------------------------------------- /code_examples/multiple_assignment.py: -------------------------------------------------------------------------------- 1 | lst = [1, 2, 3, 4, 5] 2 | print(lst) 3 | 4 | # variable swapping 5 | lst[0], lst[1] = lst[1], lst[0] 6 | print(lst) 7 | lst[2], lst[3], lst[4] = lst[4], lst[2], lst[3] 8 | print(lst) 9 | 10 | # mixed assignment 11 | lst[1], b, c = 9, 10, 11 12 | print(lst, b, c) 13 | 14 | # multiple assignment 15 | lst[0] = d = 6 16 | print(lst, d) 17 | lst[0] = lst[1] = lst[2] = 33 18 | print(lst) 19 | 20 | 21 | def test(): 22 | t1, t2 = 9, 10 23 | t3, t4 = 11, 12 24 | return t1 + t4, t2 + t3 25 | 26 | 27 | # assign to return value 28 | e, f = test() 29 | print(e, f) 30 | 31 | g, h = lst[3] + lst[0], lst[4] 32 | print(g, h) 33 | 34 | temp = -5, -6, -7 35 | i, j, k = temp 36 | print(i, j, k) 37 | 38 | lst2 = [5, 4, 3, 2, 1] 39 | s1, s2, s3 = lst2[::-1][1:4] 40 | print(s1, s2, s3) 41 | for item in [[2, 5], [7, 8]]: 42 | lst2[0], lst2[3] = item 43 | l, m = item 44 | n, lst2[4] = item 45 | print(l, m, n) 46 | print(lst2) 47 | print(lst2) 48 | 49 | o, p = 10, 10 50 | for num in [1, 2, 3]: 51 | print(o, p) 52 | o, p = num, num + 1 53 | print(o, p) 54 | 55 | hold = None 56 | for thing in lst2: 57 | print(hold) 58 | hold = thing 59 | print(hold) 60 | 61 | 62 | class Tester: 63 | def __init__(self): 64 | self.x, self.y = 4, 5 65 | 66 | 67 | test_inst = Tester() 68 | for item in [[1, 2, 3], [6, 7, 8], [9, 10, 15]]: 69 | print(test_inst.x, test_inst.y) 70 | q, test_inst.x, test_inst.y = item 71 | print(q, test_inst.x, test_inst.y) 72 | 73 | print(test_inst.x, test_inst.y) 74 | print(q) 75 | 76 | for r, s in [[[(1, 2), (9, 6)], 3], [[(6, 7), (4, 3)], 8]]: 77 | print(r, s) 78 | for t, u in r: 79 | print(t, u) 80 | if r[0][0] <= r[0][1] <= u < t: 81 | print('chained comparison!') 82 | if r[0][0] <= r[0][1] <= u < t > 20: 83 | print('chained comparison failed') 84 | 85 | v, w = 11, 11 86 | for x, y in [[3, 4], [4, 5]]: 87 | print(v, w) 88 | v, w = x, y 89 | print(v, w) 90 | -------------------------------------------------------------------------------- /code_examples/nested_function_calls.py: -------------------------------------------------------------------------------- 1 | def test1(a, b): 2 | return a + b 3 | 4 | 5 | def test2(c, d): 6 | return c + d 7 | 8 | 9 | e = test1(test2(1, 2), test2(3, 4)) 10 | print(e) 11 | if e > 5: 12 | print('here') 13 | else: 14 | print('there') 15 | -------------------------------------------------------------------------------- /code_to_tac.py: -------------------------------------------------------------------------------- 1 | import ast 2 | 3 | from python_parser import PythonParser 4 | 5 | 6 | class ASTVisitor(ast.NodeVisitor): 7 | """ example recursive visitor """ 8 | 9 | # def recursive(func): 10 | # """ decorator to make visitor work recursive """ 11 | # def wrapper(self,node): 12 | # func(self,node) 13 | # for child in ast.iter_child_nodes(node): 14 | # self.visit(child) 15 | # return wrapper 16 | 17 | def __init__(self, use_smallest_name=False): 18 | super().__init__() 19 | self.tac = {"main": []} 20 | self.key = "main" 21 | self.L = 0 22 | self.exitL = None 23 | self.previousL = [] # anytime we go into a scope, add to this and 24 | self.var_count = 0 25 | self.mapping = {} 26 | self.use_smallest_name = use_smallest_name 27 | 28 | def fresh_variable(self): 29 | var = f't_{self.var_count}' 30 | self.var_count += 1 31 | return var 32 | 33 | def addToTac(self, value): 34 | if self.key in self.tac: 35 | self.tac[self.key].append(value) 36 | else: 37 | self.tac[self.key] = [value] 38 | 39 | def getL(self): 40 | self.L += 1 41 | return self.L - 1 42 | 43 | def visit_Assign(self, node): 44 | """ visit a Assign node and visits it recursively""" 45 | visitedNode = self.visit(node.value) 46 | s = f'{node.targets[0].id} = ' # assuming there's one target, I'm not supporting more 47 | s += f'{visitedNode}' 48 | 49 | self.addToTac(('=', visitedNode, None, node.targets[0].id)) 50 | 51 | def visit_Name(self, node): 52 | """ visit a Name node and visits it recursively""" 53 | return node.id 54 | 55 | def visit_BinOp(self, node): 56 | """ visit a BinOp node and visits it recursively""" 57 | op_map = { 58 | ast.Add: '+', 59 | ast.Sub: '-', 60 | ast.Mult: '*', 61 | ast.Div: '/', 62 | ast.Mod: '%' 63 | } 64 | left = self.visit(node.left) 65 | right = self.visit(node.right) 66 | operator = op_map[type(node.op)] 67 | var = self.fresh_variable() 68 | self.addToTac((operator, left, right, var)) 69 | return var 70 | 71 | def visit_BoolOp(self, node): 72 | s = [] 73 | op_map = { 74 | ast.And: 'AND', 75 | ast.Or: 'OR' 76 | } 77 | for curNode in node.values: 78 | s.append(self.visit(curNode)) 79 | 80 | operator = op_map[type(node.op)] 81 | var = self.fresh_variable() 82 | self.addToTac((operator, s[0], s[1], var)) 83 | return var 84 | 85 | def visit_Compare(self, node): 86 | """ visit a Compare node and visits it recursively""" 87 | op_map = { 88 | ast.Gt: '>', 89 | ast.Lt: '<', 90 | ast.GtE: '>=', 91 | ast.LtE: '<=', 92 | ast.Eq: '==', 93 | ast.NotEq: '!=', 94 | ast.In: 'in', 95 | } 96 | left = self.visit(node.left) # make new var if required 97 | curOpList = node.ops 98 | curCompList = node.comparators 99 | while len(curOpList) > 1: 100 | curOp = curOpList[0] 101 | operator = op_map[type(curOp)] 102 | 103 | curComp = self.visit(curCompList[0]) 104 | curComp2 = self.visit(curCompList[1]) 105 | var = self.fresh_variable() 106 | self.addToTac((operator, curComp, curComp2, var)) 107 | curOpList = curOpList[1:] 108 | curCompList = [var] + curCompList[3:] 109 | 110 | # temp variables 111 | var = self.fresh_variable() 112 | operator = op_map[type(curOpList[0])] 113 | right = curCompList[0] 114 | if (type(right) != str): 115 | right = self.visit(right) 116 | self.addToTac((operator, left, right, var)) 117 | return var 118 | 119 | def visit_Lambda(self, node): 120 | """ visit a Function node """ 121 | print(type(node).__name__) 122 | 123 | def visit_Break(self, node): 124 | self.addToTac(("BREAK", None, None, None)) 125 | 126 | def visit_Continue(self, node): 127 | self.addToTac(("CONTINUE", None, None, None)) 128 | 129 | def visit_FunctionDef(self, node): 130 | """ visit a Function node and visits it recursively""" 131 | self.addToTac(('DEFN', None, None, node.name)) 132 | oldKey = self.key 133 | self.key = node.name 134 | 135 | cur_end_segment = None 136 | for arg in node.args.args: 137 | self.addToTac(("ADD-PARAM", None, None, arg.arg)) 138 | for stmt in node.body: 139 | if type(stmt) in [ast.If, ast.While, ast.For]: 140 | cur_end_segment = f'_L{self.getL()}' 141 | 142 | self.visit(stmt, end_segment=cur_end_segment) 143 | 144 | if type(stmt) in [ast.If, ast.While, ast.For]: 145 | self.key = cur_end_segment 146 | self.key = oldKey 147 | 148 | def visit_Constant(self, node): 149 | """ visit a Module node and the visits recursively""" 150 | return node.value 151 | 152 | def visit_List(self, node): 153 | """ visit a List node""" 154 | var = self.fresh_variable() 155 | self.addToTac(("START-LIST", None, None, var)) 156 | for element in node.elts: 157 | self.addToTac(("PUSH-ELMT", None, None, self.visit(element))) 158 | self.addToTac(("END-LIST", None, None, var)) 159 | return var 160 | 161 | def visit_If(self, node, end_segment=None, go_to_end_segment=True): 162 | """ Visit an If node and the visits recursively""" 163 | # two paths to consider, if it satisfies or if it doesnt 164 | curNode = node 165 | ifStatements = [] 166 | while type(curNode) == ast.If: 167 | ifStatements.append((curNode, self.getL())) 168 | if len(curNode.orelse) >= 1: 169 | curNode = curNode.orelse[0] 170 | else: 171 | curNode = None 172 | break 173 | 174 | for ifstmt, L in ifStatements: 175 | tempVar = self.visit(ifstmt.test) 176 | self.addToTac(('IF', tempVar, None, f'_L{L}')) 177 | 178 | if curNode != None: 179 | i = 0 180 | while i < len(curNode): 181 | val = curNode[i] 182 | if type(val) in [ast.For, ast.If, ast.While] and i < len(curNode) - 1: 183 | new_segment = f'_L{self.getL()}' 184 | self.visit(val, end_segment=new_segment, 185 | go_to_end_segment=True) 186 | self.key = new_segment 187 | else: 188 | self.visit(val, end_segment=None, go_to_end_segment=False) 189 | i += 1 190 | if go_to_end_segment and end_segment: 191 | self.addToTac(('GOTO', None, None, end_segment if go_to_end_segment else None)) 192 | 193 | # visit each if else block 194 | keys = [] 195 | 196 | j = 0 197 | while j < len(ifStatements): 198 | ifStmt, L = ifStatements[j] 199 | self.key = f'_L{L}' 200 | for body in ifStmt.body: 201 | i = 0 202 | self.key = f'_L{L}' 203 | while i < len(body): 204 | element = body[i] 205 | if type(element) in [ast.For, ast.If, ast.While] and i < len(body) - 1: 206 | new_segment = f'_L{self.getL()}' 207 | self.visit(element, end_segment=(new_segment if go_to_end_segment else None), 208 | go_to_end_segment=go_to_end_segment) 209 | self.key = new_segment 210 | if go_to_end_segment and end_segment: 211 | if f'_L{L}' not in keys: 212 | keys.append(f'_L{L}') 213 | else: 214 | self.visit(element, end_segment=(end_segment if go_to_end_segment else None), 215 | go_to_end_segment=(go_to_end_segment or i < len(body) - 1)) 216 | if (go_to_end_segment or j < len(ifStatements) - 1) and end_segment: 217 | if f'_L{L}' not in keys: 218 | keys.append(f'_L{L}') 219 | i += 1 220 | j += 1 221 | self.key = f'_L{L}' 222 | # keys.append(f'_L{L}') 223 | for key in keys: 224 | self.tac[key].append(("GOTO", None, None, end_segment)) 225 | 226 | def visit_While(self, node, end_segment=None, go_to_end_segment=True): 227 | tempVar = self.visit(node.test) 228 | 229 | new_L = self.getL() 230 | self.addToTac(("WHILE", tempVar, None, f'_L{new_L}')) 231 | # self.addToTac(("GOTO-3", None, None, end_segment if go_to_end_segment else None)) 232 | self.key = f'_L{new_L}' 233 | 234 | i = 0 235 | while i < len(node.body[0]): 236 | expr = node.body[0][i] 237 | if type(expr) in [ast.For, ast.If, ast.While] and i < len(node.body[0]) - 1: 238 | new_segment = f'_L{self.getL()}' 239 | 240 | self.visit(expr, end_segment=(new_segment if go_to_end_segment else None), go_to_end_segment=True) 241 | self.key = new_segment 242 | else: 243 | self.visit(expr, end_segment=(end_segment if go_to_end_segment else None), go_to_end_segment=False) 244 | i += 1 245 | # tempVar = self.visit(node.test, end_segment=end_segment) 246 | # self.addToTac(("IFZ",tempVar,None, f'_L{new_L}')) 247 | self.key = f'_L{new_L}' 248 | if go_to_end_segment and end_segment: 249 | self.addToTac(("GOTO", None, None, end_segment)) 250 | 251 | def visit_Slice(self, node): 252 | lower = self.visit(node.lower) if node.lower else None 253 | upper = self.visit(node.upper) if node.upper else None 254 | step = self.visit(node.step) if node.step else None 255 | 256 | self.addToTac(("SLICE", lower, upper, step)) 257 | 258 | def visit_Subscript(self, node): 259 | var = self.visit(node.value) 260 | temp = self.fresh_variable() 261 | if type(node.slice) == ast.Slice: 262 | self.addToTac(('SLICE', var, None, temp)) 263 | self.visit(node.slice) 264 | else: 265 | self.addToTac(('INDEX', var, None, temp)) 266 | self.addToTac(("INDEX", var, self.visit(node.slice), None)) 267 | return temp 268 | 269 | def visit_For(self, node, end_segment=None, go_to_end_segment=True): 270 | identifier = self.visit(node.target) 271 | iterates = self.visit(node.iter) 272 | 273 | jumpL = self.getL() 274 | self.addToTac(('FOR', identifier, iterates, f'_L{jumpL}')) 275 | # self.addToTac(("GOTO-5", None, None, end_segment)) 276 | self.key = f'_L{jumpL}' 277 | 278 | i = 0 279 | while i < len(node.body): 280 | body = node.body[i] 281 | if type(body) in [ast.For, ast.If, ast.While] and i < len(node.body) - 1: 282 | new_segment = f'_L{self.getL()}' 283 | self.visit(body, end_segment=new_segment if go_to_end_segment else None, go_to_end_segment=True) 284 | self.key = new_segment 285 | else: 286 | self.visit(body, end_segment=(end_segment if go_to_end_segment else None), go_to_end_segment=False) 287 | i += 1 288 | self.key = f'_L{jumpL}' 289 | if go_to_end_segment: 290 | self.addToTac(("GOTO", None, None, end_segment)) 291 | 292 | def visit_Return(self, node): 293 | tempVar = self.visit(node.value) 294 | self.addToTac(("RETURN", None, None, tempVar)) 295 | 296 | def visit_Assert(self, node): 297 | var1 = self.visit(node.test) 298 | var2 = None 299 | if (node.msg): 300 | var2 = self.visit(node.msg) 301 | self.addToTac(("ASSERT", None, var2, var1)) 302 | 303 | def visit_Call(self, node): 304 | temp = self.fresh_variable() 305 | for arg in node.args: 306 | tempVar = self.visit(arg) 307 | self.addToTac(("PUSH-PARAM", None, None, tempVar)) 308 | if type(node.func) == ast.Attribute: 309 | objectName = self.visit(node.func.value) 310 | methodName = node.func.attr 311 | self.addToTac(("CALL", objectName, len(node.args), methodName)) 312 | else: 313 | self.addToTac(("CALL", None, len(node.args), self.visit(node.func))) 314 | 315 | self.addToTac(("=", None, "ret", temp)) 316 | return temp 317 | 318 | def visit_Expr(self, node): 319 | return self.visit(node.value) 320 | 321 | def visit_Module(self, node, go_to_end_segment=True): 322 | """ visit a Module node and the visits recursively""" 323 | for child in ast.iter_child_nodes(node): 324 | if type(child) in [ast.If, ast.While, ast.For]: 325 | # New flows added 326 | end_segment = f'_L{self.getL()}' 327 | self.visit(child, end_segment=end_segment, go_to_end_segment=go_to_end_segment) 328 | self.key = end_segment 329 | else: 330 | self.visit(child) 331 | 332 | def visit(self, node, end_segment=None, go_to_end_segment=True): 333 | op_map = { 334 | ast.If: self.visit_If, 335 | ast.While: self.visit_While, 336 | ast.For: self.visit_For 337 | } 338 | if end_segment and type(node) in op_map: 339 | return op_map[type(node)](node, end_segment=end_segment, go_to_end_segment=go_to_end_segment) 340 | else: 341 | return super().visit(node) 342 | 343 | def generic_visit(self, node): 344 | print('here') 345 | print(node) 346 | pass 347 | 348 | 349 | if __name__ == '__main__': 350 | visitor = ASTVisitor() 351 | infile = open('test_input.py') 352 | parser = PythonParser() 353 | parser.build() 354 | tree = parser.get_ast(infile.read()) 355 | visitor.visit(tree) 356 | print(visitor.tac) 357 | -------------------------------------------------------------------------------- /demo/flatline_one_lined.py: -------------------------------------------------------------------------------- 1 | # To use this, have a file called "test_input.py" in the same directory and run this program 2 | (lambda _Y: (lambda ast: (lambda sys: (lambda construct_lambda: (lambda provide_y: (lambda wrap_globals: (lambda Flatliner: ([sys.setrecursionlimit(20000), (lambda test: [test.set_ast('test_input.py'), (lambda result: print(result))(test.unparse())][-1])(Flatliner())][-1] if (__name__ == '__main__') else None))(type("Flatliner", (), {'__init__': lambda self: [[[[[None for self.node_handlers in [{ast.Assign: self.handle_assign, ast.AugAssign: self.handle_augassign, ast.Constant: self.handle_constant, ast.Expr: self.handle_expr, ast.keyword: self.handle_keyword, ast.Call: self.handle_call, ast.Name: self.handle_name, ast.Raise: self.handle_raise, ast.Assert: self.handle_assert, ast.Pass: self.handle_pass, ast.Break: self.handle_loop_flow, ast.Continue: self.handle_loop_flow, ast.While: self.handle_while, ast.For: self.handle_for, ast.List: self.handle_list, ast.Tuple: self.handle_tuple, ast.Set: self.handle_set, ast.Dict: self.handle_dict, ast.Subscript: self.handle_subscript, ast.Slice: self.handle_slice, ast.Attribute: self.handle_attribute, ast.BinOp: self.handle_binop, ast.If: self.handle_if, ast.IfExp: self.handle_if, list: self.unparse_list, ast.Compare: self.handle_compare, ast.BoolOp: self.handle_boolop, ast.UnaryOp: self.handle_unaryop, ast.FunctionDef: self.handle_functiondef, ast.ClassDef: self.handle_classdef, ast.Return: self.handle_return, ast.Import: self.handle_import, ast.ImportFrom: self.handle_importfrom}]][0] for self.loop_no in [1]][0] for self.needs_y in [False]][0] for self.ast in [None]][0], None][-1], 'set_ast': lambda self, infile: (lambda file: [file.close() for self.ast in [ast.parse(file.read())]][0])(open(infile, 'r')), 'apply_handler': lambda self, node, cont=None: self.node_handlers.get(type(node), self.handle_error)(node, cont), 'handle_constant': lambda self, node, cont: repr(node.value), 'handle_name': lambda self, node, cont: node.id, 'handle_pass': lambda self, node, cont: cont, 'handle_loop_flow': lambda self, node, cont: node.contents, 'handle_raise': lambda self, node, cont: f'(_ for _ in []).throw({self.apply_handler(node.exc)})', 'handle_assert': lambda self, node, cont: (lambda message: (lambda error: f'({cont} if {self.apply_handler(node.test)} else {self.apply_handler(error)})')(ast.parse(f'raise AssertionError{message}').body[0]))(('' if (node.msg is None) else f'({self.apply_handler(node.msg)})')), '_next_item': lambda self: f'next(_items{self.loop_no}, _term{self.loop_no})', 'handle_while': lambda self, node, cont: [(lambda assigned_in_loop: (lambda assigned_in_loop: (lambda assigned_in_loop: (lambda assigned_in_loop: (lambda args: (lambda loop_test: (lambda loop_id: (lambda loop_call: (lambda _term1, _items1: (lambda _targ1: (lambda _targ1, n: (lambda _loop1: _loop1(_targ1, n))(_Y(lambda _loop1: (lambda _targ1, n: ((lambda n: ([(([(lambda _targ1: _loop1(_targ1, n))(next(_items1, _term1)) for n.contents in [construct_lambda({node.target: self._next_item()}, loop_call)]][0] if hasattr(node, 'target') else [(lambda _targ1: _loop1(_targ1, n))(next(_items1, _term1)) for n.contents in [loop_call]][0]) if isinstance(n, ast.Continue) else (lambda _targ1: _loop1(_targ1, n))(next(_items1, _term1))) for n.contents in [cont]][0] if isinstance(n, ast.Break) else (([(lambda _targ1: _loop1(_targ1, n))(next(_items1, _term1)) for n.contents in [construct_lambda({node.target: self._next_item()}, loop_call)]][0] if hasattr(node, 'target') else [(lambda _targ1: _loop1(_targ1, n))(next(_items1, _term1)) for n.contents in [loop_call]][0]) if isinstance(n, ast.Continue) else (lambda _targ1: _loop1(_targ1, n))(next(_items1, _term1)))))(_targ1)) if (_targ1 is not _term1) else [(lambda loop_body: (lambda loop_repr: construct_lambda({v: f'{v} if "{v}" in dir() else None' for v in assigned_in_loop}, loop_repr))(construct_lambda({loop_id: f'_Y(lambda {loop_id}: (lambda {args}: ({loop_body}) if {loop_test} else {cont}))'}, loop_call)))(self.apply_handler(node.body, loop_call)) for self.loop_no in [(self.loop_no + 1)]][0]))))(_targ1 if "_targ1" in dir() else None, n if "n" in dir() else None))(next(_items1, _term1)))([], iter(ast.walk(node))))(f'{loop_id}({args})'))(f'_loop{self.loop_no}'))(self.apply_handler(node.test)))(', '.join(assigned_in_loop)))(sorted(assigned_in_loop)))((assigned_in_loop | {self.apply_handler(child) for n in ast.walk(node) if isinstance(n, ast.Assign) and isinstance(n.targets[0], ast.Tuple) for child in n.targets[0].elts if not any((isinstance(n2, (ast.Attribute, ast.Subscript)) for n2 in ast.walk(child)))})))((assigned_in_loop | {self.apply_handler(n.target) for n in ast.walk(node) if isinstance(n, ast.AugAssign) and (not isinstance(n.target, (ast.Attribute, ast.Subscript)))})))({self.apply_handler(n.targets[0]) for n in ast.walk(node) if isinstance(n, ast.Assign) and (not isinstance(n.targets[0], (ast.Attribute, ast.Subscript, ast.Tuple))) and (len(n.targets) == 1)}) for self.needs_y in [True]][0], 'handle_for': lambda self, node, cont: (lambda target_id: (lambda iter_id: (lambda term_id: (lambda pre: (lambda post: (lambda body_list: (lambda while_test: (lambda while_equivalent: [construct_lambda({term_id: '[]', iter_id: f'iter({self.apply_handler(node.iter)})'}, construct_lambda({target_id: self._next_item()}, self.apply_handler(while_equivalent, cont))) for while_equivalent.target in [target_id]][0])(ast.While(while_test, body_list)))(ast.parse(f'{target_id} is not {term_id}').body[0]))(((pre + node.body) + post)))(ast.parse(f'{target_id} = {self._next_item()}').body))(ast.parse(f'{self.apply_handler(node.target)} = {target_id}').body))(f'_term{self.loop_no}'))(f'_items{self.loop_no}'))(f'_targ{self.loop_no}'), '_handle_container': lambda self, node: ', '.join((self.apply_handler(child) for child in node.elts)), 'handle_list': lambda self, node, cont: f'[{self._handle_container(node)}]', 'handle_tuple': lambda self, node, cont: f"({self._handle_container(node)}{',' * (len(node.elts) == 1)})", 'handle_set': lambda self, node, cont: (('{' + self._handle_container(node)) + '}'), 'handle_dict': lambda self, node, cont: (('{' + ', '.join((f'{k}: {v}' for (k, v) in zip(map(self.apply_handler, node.keys), map(self.apply_handler, node.values))))) + '}'), 'handle_slice': lambda self, node, cont: (lambda parts: ':'.join(parts))([self.apply_handler(n) if n is not None else '' for n in [node.lower, node.upper, node.step]]), 'handle_subscript': lambda self, node, cont: (f'{self.apply_handler(node.value)}[{self.apply_handler(node.slice)[1:-1]}]' if isinstance(node.slice, ast.Tuple) else f'{self.apply_handler(node.value)}[{self.apply_handler(node.slice)}]'), 'handle_attribute': lambda self, node, cont: f'{self.apply_handler(node.value)}.{node.attr}', 'handle_assign': lambda self, node, cont: ((lambda targets: f"[{cont} for {', '.join(targets)} in [[{self.apply_handler(node.value)}] * {len(targets)}]][0]")([self.apply_handler(t) for t in node.targets]) if (len(node.targets) > 1) else (lambda target: (f'[{cont} for {self.apply_handler(target)} in [{self.apply_handler(node.value)}]][0]' if isinstance(target, (ast.Attribute, ast.Subscript, ast.Tuple, ast.List)) else construct_lambda({self.apply_handler(target): self.apply_handler(node.value)}, cont)))(node.targets[0])), 'handle_augassign': lambda self, node, cont: (lambda assign_equivalent: self.apply_handler(assign_equivalent, cont))(ast.Assign([node.target], ast.BinOp(node.target, node.op, node.value))), 'handle_expr': lambda self, node, cont: self.apply_handler(node.value, cont), 'handle_keyword': lambda self, node, cont: f'{node.arg}={self.apply_handler(node.value)}', 'handle_call': lambda self, node, cont: (lambda args: (lambda call: (call if not cont else f'[{call}, {cont}][-1]'))(f'{self.apply_handler(node.func)}({args})'))(', '.join((self.apply_handler(child) for child in node.args + node.keywords))), 'handle_binop': lambda self, node, cont: (lambda op_map: f'({self.apply_handler(node.left)} {op_map[type(node.op)]} {self.apply_handler(node.right)})')({ast.Add: '+', ast.Sub: '-', ast.Mult: '*', ast.Div: '/', ast.FloorDiv: '//', ast.Mod: '%', ast.Pow: '**', ast.LShift: '<<', ast.RShift: '>>', ast.BitOr: '|', ast.BitXor: '^', ast.BitAnd: '&'}), 'handle_boolop': lambda self, node, cont: (lambda op_map: f' {op_map[type(node.op)]} '.join((self.apply_handler(child) for child in node.values)))({ast.And: 'and', ast.Or: 'or'}), 'handle_unaryop': lambda self, node, cont: (lambda op_map: (f'{op_map[type(node.op)]}{self.apply_handler(node.operand)}' if isinstance(node.operand, (ast.Name, ast.Constant)) else f'{op_map[type(node.op)]}({self.apply_handler(node.operand)})'))({ast.UAdd: '+', ast.USub: '-', ast.Not: 'not ', ast.Invert: '~'}), 'handle_if': lambda self, node, cont: f'({self.apply_handler(node.body, cont)} if {self.apply_handler(node.test)} else {self.apply_handler(node.orelse, cont)})', 'handle_compare': lambda self, node, cont: (lambda op_map: (lambda comparisons: f'({self.apply_handler(node.left)}{comparisons})')(''.join((f' {op_map[type(op)]} ' + self.apply_handler(val) for (op, val) in zip(node.ops, node.comparators)))))({ast.Gt: '>', ast.Lt: '<', ast.GtE: '>=', ast.LtE: '<=', ast.Eq: '==', ast.NotEq: '!=', ast.In: 'in', ast.Is: 'is', ast.IsNot: 'is not', ast.NotIn: 'not in'}), 'handle_methoddef': lambda self, node, cont: (lambda all_args: (lambda defaults: (lambda padding: (lambda args: (lambda _term2, _items2: (lambda _targ2: (lambda _targ2, n: (lambda _loop2: _loop2(_targ2, n))(_Y(lambda _loop2: (lambda _targ2, n: ((lambda n: ([(lambda _targ2: _loop2(_targ2, n))(next(_items2, _term2)) for n.func.id in ['self.__class__']][0] if cont and isinstance(n, ast.Call) and (self.apply_handler(n.func) == cont.split('.')[0]) else (lambda _targ2: _loop2(_targ2, n))(next(_items2, _term2))))(_targ2)) if (_targ2 is not _term2) else (f"lambda{(' ' if args else '')}{args}: [{self.apply_handler(node.body)}, None][-1]" if cont.endswith('__init__') else ([f'_Y(lambda {node.name}: (lambda {args}: {self.apply_handler(node.body)}))' for self.needs_y in [True]][0] if any((isinstance(n, ast.Call) and self.apply_handler(n.func) == node.name for n in ast.walk(node))) else f"lambda{(' ' if args else '')}{args}: {self.apply_handler(node.body)}"))))))(_targ2 if "_targ2" in dir() else None, n if "n" in dir() else None))(next(_items2, _term2)))([], iter(ast.walk(node))))(', '.join((arg + val for (arg, val) in zip(all_args, padding + defaults)))))(([''] * (len(all_args) - len(defaults)))))([f'={self.apply_handler(val)}' for val in node.args.defaults]))([arg.arg for arg in node.args.args]), 'handle_functiondef': lambda self, node, cont: (lambda curr: (lambda _term3, _items3: (lambda _targ3: (lambda _targ3, curr, n: (lambda _loop3: _loop3(_targ3, curr, n))(_Y(lambda _loop3: (lambda _targ3, curr, n: ((lambda n: (lambda curr: (lambda _targ3: _loop3(_targ3, curr, n))(next(_items3, _term3)))(f'{self.apply_handler(n)}({curr})'))(_targ3)) if (_targ3 is not _term3) else construct_lambda({node.name: curr}, cont)))))(_targ3 if "_targ3" in dir() else None, curr if "curr" in dir() else None, n if "n" in dir() else None))(next(_items3, _term3)))([], iter(node.decorator_list[::-1])))(self.handle_methoddef(node, '')), 'handle_classdef': lambda self, node, cont: (lambda attr_dict: (lambda _term4, _items4: (lambda _targ4: (lambda _targ4, n: (lambda _loop4: _loop4(_targ4, n))(_Y(lambda _loop4: (lambda _targ4, n: ((lambda n: ([(lambda _targ4: _loop4(_targ4, n))(next(_items4, _term4)) for attr_dict[n.name] in [self.handle_methoddef(n, f'{node.name}.{n.name}')]][0] if isinstance(n, ast.FunctionDef) else (lambda _targ4: _loop4(_targ4, n))(next(_items4, _term4))))(_targ4)) if (_targ4 is not _term4) else (lambda bases: (lambda attr_repr: construct_lambda({node.name: f'type("{node.name}", ({bases}), {attr_repr})'}, cont))((('{' + ', '.join((f'{repr(k)}: {v}' for (k, v) in attr_dict.items()))) + '}')))(''.join((self.apply_handler(n) + ', ' for n in node.bases)))))))(_targ4 if "_targ4" in dir() else None, n if "n" in dir() else None))(next(_items4, _term4)))([], iter(node.body)))({}), 'handle_return': lambda self, node, cont: ('None' if (node.value is None) else self.apply_handler(node.value)), 'handle_import': lambda self, node, cont: (lambda imports: construct_lambda({name.split('.')[0]: f"__import__('{mod}')" for (name, mod) in imports}, cont))([(a.asname if a.asname else a.name, a.name) for a in node.names]), 'handle_importfrom': lambda self, node, cont: (lambda imports: construct_lambda({'_mod': f"__import__('{node.module}', {{}}, {{}}, {[i[1] for i in imports]})"}, construct_lambda({name: f'_mod.{submodule}' for (name, submodule) in imports}, cont)))([(a.asname if a.asname else a.name, a.name) for a in node.names]), 'handle_error': lambda self, node, cont: ast.unparse(node), 'unparse_list': lambda self, body, cont=None: (lambda temp: (lambda _term5, _items5: (lambda _targ5: (lambda _targ5, node, temp: (lambda _loop5: _loop5(_targ5, node, temp))(_Y(lambda _loop5: (lambda _targ5, node, temp: ((lambda node: ((lambda temp: (lambda _targ5: _loop5(_targ5, node, temp))(next(_items5, _term5)))(self.apply_handler(node, temp)) if not (isinstance(node, ast.Expr)) or isinstance(node.value, ast.Call) else (lambda _targ5: _loop5(_targ5, node, temp))(next(_items5, _term5))))(_targ5)) if (_targ5 is not _term5) else str(temp)))))(_targ5 if "_targ5" in dir() else None, node if "node" in dir() else None, temp if "temp" in dir() else None))(next(_items5, _term5)))([], iter(body[::-1])))(cont), 'unparse': lambda self: [[(lambda curr: ((lambda body: (provide_y(body) if self.needs_y else body))(self.apply_handler(curr.body)) if hasattr(curr, 'body') and isinstance(curr.body, list) else 'Unparse unsuccessful.'))(self.ast) for self.loop_no in [1]][0] for self.needs_y in [False]][0]})))(lambda body: construct_lambda({'__g': 'globals()'}, body)))(lambda body: construct_lambda({'_Y': '(lambda f: (lambda x: x(x))(lambda y: f(lambda *args: y(y)(*args))))'}, body)))(lambda vals, body='{}': f"(lambda {', '.join(vals.keys())}: {body})({', '.join(vals.values())})"))(__import__('sys')))(__import__('ast')))((lambda f: (lambda x: x(x))(lambda y: f(lambda *args: y(y)(*args))))) 3 | -------------------------------------------------------------------------------- /demo/test_input.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import sys 3 | 4 | 5 | def construct_lambda(vals: dict[str, str], body: str = '{}') -> str: 6 | """ 7 | Returns a lambda function string with the arguments provided. 8 | >>> construct_lambda({'a': '1', 'b': '2'}) 9 | '(lambda a, b: {})(1, 2)' 10 | >>> exec(construct_lambda({'a': '1', 'b': '2'}, 'print(a + b)')) 11 | 3 12 | """ 13 | return f'(lambda {", ".join(vals.keys())}: {body})({", ".join(vals.values())})' 14 | 15 | 16 | def provide_y(body: str) -> str: 17 | """ 18 | provides the Y combinator. 19 | """ 20 | return construct_lambda({'_Y': '(lambda f: (lambda x: x(x))(lambda y: f(lambda *args: y(y)(*args))))'}, body) 21 | 22 | 23 | def wrap_globals(body: str): 24 | """ 25 | Wraps a lambda body with the globals dict. 26 | >>> wrap_globals('print(a)') 27 | '(lambda __g: print(a))(globals())' 28 | """ 29 | return construct_lambda({'__g': 'globals()'}, body) 30 | 31 | 32 | class Flatliner: 33 | def __init__(self): 34 | self.ast = None 35 | self.needs_y = False 36 | self.loop_no = 1 37 | self.node_handlers = { 38 | ast.Assign: self.handle_assign, 39 | ast.AugAssign: self.handle_augassign, 40 | ast.Constant: self.handle_constant, 41 | ast.Expr: self.handle_expr, 42 | ast.keyword: self.handle_keyword, 43 | ast.Call: self.handle_call, 44 | ast.Name: self.handle_name, 45 | ast.Raise: self.handle_raise, 46 | ast.Assert: self.handle_assert, 47 | ast.Pass: self.handle_pass, 48 | ast.Break: self.handle_loop_flow, 49 | ast.Continue: self.handle_loop_flow, 50 | ast.While: self.handle_while, 51 | ast.For: self.handle_for, 52 | ast.List: self.handle_list, 53 | ast.Tuple: self.handle_tuple, 54 | ast.Set: self.handle_set, 55 | ast.Dict: self.handle_dict, 56 | ast.Subscript: self.handle_subscript, 57 | ast.Slice: self.handle_slice, 58 | ast.Attribute: self.handle_attribute, 59 | ast.BinOp: self.handle_binop, 60 | ast.If: self.handle_if, 61 | ast.IfExp: self.handle_if, 62 | list: self.unparse_list, 63 | ast.Compare: self.handle_compare, 64 | ast.BoolOp: self.handle_boolop, 65 | ast.UnaryOp: self.handle_unaryop, 66 | ast.FunctionDef: self.handle_functiondef, 67 | ast.ClassDef: self.handle_classdef, 68 | ast.Return: self.handle_return, 69 | ast.Import: self.handle_import, 70 | ast.ImportFrom: self.handle_importfrom, 71 | } 72 | 73 | def set_ast(self, infile: str): 74 | """ 75 | Turns the infile into an AST and sets the instance attribute accordingly. 76 | """ 77 | file = open(infile, 'r') 78 | self.ast = ast.parse(file.read()) 79 | file.close() 80 | 81 | def apply_handler(self, node, cont=None): 82 | return self.node_handlers.get(type(node), self.handle_error)(node, cont) 83 | 84 | def handle_constant(self, node, cont) -> str: 85 | return repr(node.value) 86 | 87 | def handle_name(self, node, cont) -> str: 88 | return node.id 89 | 90 | def handle_pass(self, node, cont) -> str: 91 | return cont 92 | 93 | def handle_loop_flow(self, node, cont) -> str: 94 | return node.contents 95 | 96 | def handle_raise(self, node, cont) -> str: 97 | return f'(_ for _ in []).throw({self.apply_handler(node.exc)})' 98 | 99 | def handle_assert(self, node, cont) -> str: 100 | message = '' if node.msg is None else f'({self.apply_handler(node.msg)})' 101 | error = ast.parse(f'raise AssertionError{message}').body[0] 102 | return f'({cont} if {self.apply_handler(node.test)} else {self.apply_handler(error)})' 103 | 104 | def _next_item(self) -> str: 105 | return f'next(_items{self.loop_no}, _term{self.loop_no})' 106 | 107 | def handle_while(self, node, cont) -> str: 108 | self.needs_y = True 109 | assigned_in_loop = {self.apply_handler(n.targets[0]) for n in ast.walk(node) if isinstance(n, ast.Assign) 110 | and not isinstance(n.targets[0], (ast.Attribute, ast.Subscript, ast.Tuple)) 111 | and len(n.targets) == 1} 112 | assigned_in_loop |= {self.apply_handler(n.target) for n in ast.walk(node) if isinstance(n, ast.AugAssign) 113 | and not isinstance(n.target, (ast.Attribute, ast.Subscript))} 114 | assigned_in_loop |= {self.apply_handler(child) for n in ast.walk(node) if isinstance(n, ast.Assign) 115 | and isinstance(n.targets[0], ast.Tuple) for child in n.targets[0].elts if 116 | not any(isinstance(n2, (ast.Attribute, ast.Subscript)) for n2 in ast.walk(child))} 117 | assigned_in_loop = sorted(assigned_in_loop) 118 | args = ', '.join(assigned_in_loop) 119 | loop_test = self.apply_handler(node.test) 120 | loop_id = f'_loop{self.loop_no}' 121 | loop_call = f'{loop_id}({args})' 122 | for n in ast.walk(node): 123 | if isinstance(n, ast.Break): 124 | n.contents = cont 125 | if isinstance(n, ast.Continue): 126 | if hasattr(node, 'target'): 127 | n.contents = construct_lambda({node.target: self._next_item()}, loop_call) 128 | else: 129 | n.contents = loop_call 130 | self.loop_no += 1 131 | loop_body = self.apply_handler(node.body, loop_call) 132 | loop_repr = construct_lambda( 133 | {loop_id: f'_Y(lambda {loop_id}: (lambda {args}: ({loop_body}) if {loop_test} else {cont}))'}, loop_call) 134 | return construct_lambda({v: f'{v} if "{v}" in dir() else None' for v in assigned_in_loop}, loop_repr) 135 | 136 | def handle_for(self, node, cont) -> str: 137 | target_id = f'_targ{self.loop_no}' 138 | iter_id = f'_items{self.loop_no}' 139 | term_id = f'_term{self.loop_no}' 140 | pre = ast.parse(f'{self.apply_handler(node.target)} = {target_id}').body 141 | post = ast.parse(f'{target_id} = {self._next_item()}').body 142 | body_list = pre + node.body + post 143 | while_test = ast.parse(f'{target_id} is not {term_id}').body[0] 144 | while_equivalent = ast.While(while_test, body_list) 145 | while_equivalent.target = target_id 146 | return construct_lambda({term_id: '[]', iter_id: f'iter({self.apply_handler(node.iter)})'}, 147 | construct_lambda({target_id: self._next_item()}, 148 | self.apply_handler(while_equivalent, cont))) 149 | 150 | def _handle_container(self, node) -> str: 151 | return ', '.join(self.apply_handler(child) for child in node.elts) 152 | 153 | def handle_list(self, node, cont) -> str: 154 | return f'[{self._handle_container(node)}]' 155 | 156 | def handle_tuple(self, node, cont) -> str: 157 | return f'({self._handle_container(node)}{"," * (len(node.elts) == 1)})' 158 | 159 | def handle_set(self, node, cont) -> str: 160 | return '{' + self._handle_container(node) + '}' 161 | 162 | def handle_dict(self, node, cont) -> str: 163 | return '{' + ', '.join(f'{k}: {v}' for k, v in zip(map(self.apply_handler, node.keys), 164 | map(self.apply_handler, node.values))) + '}' 165 | 166 | def handle_slice(self, node, cont) -> str: 167 | parts = [self.apply_handler(n) if n is not None else '' for n in [node.lower, node.upper, node.step]] 168 | return ':'.join(parts) 169 | 170 | def handle_subscript(self, node, cont) -> str: 171 | if isinstance(node.slice, ast.Tuple): 172 | return f'{self.apply_handler(node.value)}[{self.apply_handler(node.slice)[1:-1]}]' 173 | return f'{self.apply_handler(node.value)}[{self.apply_handler(node.slice)}]' 174 | 175 | def handle_attribute(self, node, cont) -> str: 176 | return f'{self.apply_handler(node.value)}.{node.attr}' 177 | 178 | def handle_assign(self, node, cont) -> str: 179 | if len(node.targets) > 1: 180 | targets = [self.apply_handler(t) for t in node.targets] 181 | return f'[{cont} for {", ".join(targets)} in [[{self.apply_handler(node.value)}] * {len(targets)}]][0]' 182 | target = node.targets[0] 183 | if isinstance(target, (ast.Attribute, ast.Subscript, ast.Tuple, ast.List)): 184 | return f'[{cont} for {self.apply_handler(target)} in [{self.apply_handler(node.value)}]][0]' 185 | return construct_lambda({self.apply_handler(target): self.apply_handler(node.value)}, cont) 186 | 187 | def handle_augassign(self, node, cont) -> str: 188 | assign_equivalent = ast.Assign([node.target], ast.BinOp(node.target, node.op, node.value)) 189 | return self.apply_handler(assign_equivalent, cont) 190 | 191 | def handle_expr(self, node, cont) -> str: 192 | return self.apply_handler(node.value, cont) 193 | 194 | def handle_keyword(self, node, cont) -> str: 195 | return f'{node.arg}={self.apply_handler(node.value)}' 196 | 197 | def handle_call(self, node, cont) -> str: 198 | args = ', '.join(self.apply_handler(child) for child in node.args + node.keywords) 199 | call = f'{self.apply_handler(node.func)}({args})' 200 | return call if not cont else f'[{call}, {cont}][-1]' 201 | 202 | def handle_binop(self, node, cont) -> str: 203 | op_map = { 204 | ast.Add: '+', 205 | ast.Sub: '-', 206 | ast.Mult: '*', 207 | ast.Div: '/', 208 | ast.FloorDiv: '//', 209 | ast.Mod: '%', 210 | ast.Pow: '**', 211 | ast.LShift: '<<', 212 | ast.RShift: '>>', 213 | ast.BitOr: '|', 214 | ast.BitXor: '^', 215 | ast.BitAnd: '&', 216 | } 217 | return f'({self.apply_handler(node.left)} {op_map[type(node.op)]} {self.apply_handler(node.right)})' 218 | 219 | def handle_boolop(self, node, cont) -> str: 220 | op_map = { 221 | ast.And: 'and', 222 | ast.Or: 'or', 223 | } 224 | return f' {op_map[type(node.op)]} '.join(self.apply_handler(child) for child in node.values) 225 | 226 | def handle_unaryop(self, node, cont) -> str: 227 | op_map = { 228 | ast.UAdd: '+', 229 | ast.USub: '-', 230 | ast.Not: 'not ', 231 | ast.Invert: '~', 232 | } 233 | if isinstance(node.operand, (ast.Name, ast.Constant)): 234 | return f'{op_map[type(node.op)]}{self.apply_handler(node.operand)}' 235 | return f'{op_map[type(node.op)]}({self.apply_handler(node.operand)})' 236 | 237 | def handle_if(self, node, cont) -> str: 238 | return f'({self.apply_handler(node.body, cont)} if {self.apply_handler(node.test)} else {self.apply_handler(node.orelse, cont)})' 239 | 240 | def handle_compare(self, node, cont) -> str: 241 | op_map = { 242 | ast.Gt: '>', 243 | ast.Lt: '<', 244 | ast.GtE: '>=', 245 | ast.LtE: '<=', 246 | ast.Eq: '==', 247 | ast.NotEq: '!=', 248 | ast.In: 'in', 249 | ast.Is: 'is', 250 | ast.IsNot: 'is not', 251 | ast.NotIn: 'not in', 252 | } 253 | comparisons = ''.join(f' {op_map[type(op)]} ' + self.apply_handler(val) 254 | for op, val in zip(node.ops, node.comparators)) 255 | return f'({self.apply_handler(node.left)}{comparisons})' 256 | 257 | def handle_methoddef(self, node, cont) -> str: 258 | all_args = [arg.arg for arg in node.args.args] 259 | defaults = [f'={self.apply_handler(val)}' for val in node.args.defaults] 260 | padding = [''] * (len(all_args) - len(defaults)) 261 | args = ', '.join(arg + val for arg, val in zip(all_args, padding + defaults)) 262 | for n in ast.walk(node): 263 | if cont and isinstance(n, ast.Call) and self.apply_handler(n.func) == cont.split('.')[0]: 264 | n.func.id = 'self.__class__' 265 | if cont.endswith('__init__'): 266 | return f'lambda{" " if args else ""}{args}: [{self.apply_handler(node.body)}, None][-1]' 267 | if any(isinstance(n, ast.Call) and self.apply_handler(n.func) == node.name for n in ast.walk(node)): 268 | self.needs_y = True 269 | return f'_Y(lambda {node.name}: (lambda {args}: {self.apply_handler(node.body)}))' 270 | return f'lambda{" " if args else ""}{args}: {self.apply_handler(node.body)}' 271 | 272 | def handle_functiondef(self, node, cont) -> str: 273 | curr = self.handle_methoddef(node, '') 274 | for n in node.decorator_list[::-1]: 275 | curr = f"{self.apply_handler(n)}({curr})" 276 | return construct_lambda({node.name: curr}, cont) 277 | 278 | def handle_classdef(self, node, cont) -> str: 279 | attr_dict = {} 280 | for n in node.body: 281 | if isinstance(n, ast.FunctionDef): 282 | attr_dict[n.name] = self.handle_methoddef(n, f'{node.name}.{n.name}') 283 | bases = ''.join(self.apply_handler(n) + ', ' for n in node.bases) 284 | attr_repr = '{' + ', '.join(f'{repr(k)}: {v}' for k, v in attr_dict.items()) + '}' 285 | return construct_lambda({node.name: f'type("{node.name}", ({bases}), {attr_repr})'}, cont) 286 | 287 | def handle_return(self, node, cont) -> str: 288 | if node.value is None: 289 | return 'None' 290 | return self.apply_handler(node.value) 291 | 292 | def handle_import(self, node, cont) -> str: 293 | imports = [(a.asname if a.asname else a.name, a.name) for a in node.names] 294 | return construct_lambda({name.split('.')[0]: f"__import__('{mod}')" for name, mod in imports}, cont) 295 | 296 | def handle_importfrom(self, node, cont) -> str: 297 | imports = [(a.asname if a.asname else a.name, a.name) for a in node.names] 298 | return construct_lambda({'_mod': f"__import__('{node.module}', {{}}, {{}}, {[i[1] for i in imports]})"}, 299 | construct_lambda({name: f'_mod.{submodule}' for name, submodule in imports}, cont)) 300 | 301 | def handle_error(self, node, cont) -> str: 302 | return ast.unparse(node) 303 | 304 | def unparse_list(self, body: list, cont=None) -> str: 305 | temp = cont 306 | for node in body[::-1]: 307 | if not isinstance(node, ast.Expr) or isinstance(node.value, ast.Call): 308 | temp = self.apply_handler(node, temp) 309 | return str(temp) 310 | 311 | def unparse(self) -> str: 312 | """ 313 | Unparses the ast. 314 | """ 315 | self.needs_y = False 316 | self.loop_no = 1 317 | curr = self.ast 318 | if hasattr(curr, 'body') and isinstance(curr.body, list): 319 | body = self.apply_handler(curr.body) 320 | return provide_y(body) if self.needs_y else body 321 | return 'Unparse unsuccessful.' 322 | 323 | 324 | if __name__ == '__main__': 325 | sys.setrecursionlimit(20000) 326 | test = Flatliner() 327 | test.set_ast('test_input.py') 328 | result = test.unparse() 329 | print(result) 330 | -------------------------------------------------------------------------------- /flatline.py: -------------------------------------------------------------------------------- 1 | import ast 2 | 3 | from python_parser import PythonParser 4 | 5 | 6 | def construct_lambda(vals: dict[str, str], body: str = '{}') -> str: 7 | """ 8 | Returns a lambda function string with the arguments provided. 9 | >>> construct_lambda({'a': '1', 'b': '2'}) 10 | '(lambda a, b: {})(1, 2)' 11 | >>> exec(construct_lambda({'a': '1', 'b': '2'}, 'print(a + b)')) 12 | 3 13 | """ 14 | return f'(lambda {", ".join(vals.keys())}: {body})({", ".join(vals.values())})' 15 | 16 | 17 | def provide_y(body: str) -> str: 18 | """ 19 | provides the Y combinator. 20 | """ 21 | return construct_lambda({'_Y': '(lambda f: (lambda x: x(x))(lambda y: f(lambda *args: y(y)(*args))))'}, body) 22 | 23 | 24 | def wrap_globals(body: str): 25 | """ 26 | Wraps a lambda body with the globals dict. 27 | >>> wrap_globals('print(a)') 28 | '(lambda __g: print(a))(globals())' 29 | """ 30 | return construct_lambda({'__g': 'globals()'}, body) 31 | 32 | 33 | class Flatliner: 34 | def __init__(self): 35 | self.ast = None 36 | self.needs_y = False 37 | self.loop_no = 1 38 | self.node_handlers = { 39 | ast.Assign: self.handle_assign, 40 | ast.AugAssign: self.handle_augassign, 41 | ast.Constant: self.handle_constant, 42 | ast.Expr: self.handle_expr, 43 | ast.keyword: self.handle_keyword, 44 | ast.Call: self.handle_call, 45 | ast.Name: self.handle_name, 46 | ast.Raise: self.handle_raise, 47 | ast.Assert: self.handle_assert, 48 | ast.Pass: self.handle_pass, 49 | ast.Break: self.handle_loop_flow, 50 | ast.Continue: self.handle_loop_flow, 51 | ast.While: self.handle_while, 52 | ast.For: self.handle_for, 53 | ast.List: self.handle_list, 54 | ast.Tuple: self.handle_tuple, 55 | ast.Set: self.handle_set, 56 | ast.Dict: self.handle_dict, 57 | ast.Subscript: self.handle_subscript, 58 | ast.Slice: self.handle_slice, 59 | ast.Attribute: self.handle_attribute, 60 | ast.BinOp: self.handle_binop, 61 | ast.If: self.handle_if, 62 | ast.IfExp: self.handle_if, 63 | list: self.unparse_list, 64 | ast.Compare: self.handle_compare, 65 | ast.BoolOp: self.handle_boolop, 66 | ast.UnaryOp: self.handle_unaryop, 67 | ast.FunctionDef: self.handle_functiondef, 68 | ast.ClassDef: self.handle_classdef, 69 | ast.Return: self.handle_return, 70 | ast.Import: self.handle_import, 71 | ast.ImportFrom: self.handle_importfrom, 72 | } 73 | # for testing to make sure all handlers are hit 74 | self.hit = {k: 0 for k in self.node_handlers} 75 | 76 | def all_hit(self) -> bool: 77 | """ 78 | returns if all handlers were tested at least once and prints 79 | out the handlers that were not tested. 80 | """ 81 | for k, v in self.hit.items(): 82 | if not v: 83 | print(f'not tested: {k}') 84 | return 0 not in self.hit.values() 85 | 86 | def set_ast(self, infile: str, read_from_file: bool = False): 87 | """ 88 | Turns the infile into an AST and sets the instance attribute accordingly. 89 | """ 90 | parser = PythonParser() 91 | parser.build() 92 | if read_from_file: 93 | with open(infile, 'r') as f: 94 | self.ast = parser.get_ast(f.read()) 95 | else: 96 | self.ast = parser.get_ast(infile) 97 | 98 | def apply_handler(self, node, cont=None): 99 | node_type = type(node) 100 | node_repr = self.node_handlers.get(node_type, self.handle_error)(node, cont) 101 | if node_type in self.hit: 102 | self.hit[node_type] += 1 103 | return node_repr 104 | 105 | def handle_constant(self, node, cont) -> str: 106 | return repr(node.value) 107 | 108 | def handle_name(self, node, cont) -> str: 109 | return node.id 110 | 111 | def handle_pass(self, node, cont) -> str: 112 | return cont 113 | 114 | def handle_loop_flow(self, node, cont) -> str: 115 | return node.contents 116 | 117 | def handle_raise(self, node, cont) -> str: 118 | return f'(_ for _ in []).throw({self.apply_handler(node.exc)})' 119 | 120 | def handle_assert(self, node, cont) -> str: 121 | message = '' if node.msg is None else f'({self.apply_handler(node.msg)})' 122 | error = ast.parse(f'raise AssertionError{message}').body[0] 123 | return f'({cont} if {self.apply_handler(node.test)} else {self.apply_handler(error)})' 124 | 125 | def _next_item(self) -> str: 126 | return f'next(_items{self.loop_no}, _term{self.loop_no})' 127 | 128 | def handle_while(self, node, cont) -> str: 129 | self.needs_y = True 130 | assigned_in_loop = {self.apply_handler(n.targets[0]) for n in ast.walk(node) if isinstance(n, ast.Assign) 131 | and not isinstance(n.targets[0], (ast.Attribute, ast.Subscript, ast.Tuple)) 132 | and len(n.targets) == 1} 133 | assigned_in_loop |= {self.apply_handler(n.target) for n in ast.walk(node) if isinstance(n, ast.AugAssign) 134 | and not isinstance(n.target, (ast.Attribute, ast.Subscript))} 135 | assigned_in_loop |= {self.apply_handler(child) for n in ast.walk(node) if isinstance(n, ast.Assign) 136 | and isinstance(n.targets[0], ast.Tuple) for child in n.targets[0].elts if 137 | not any(isinstance(n2, (ast.Attribute, ast.Subscript)) for n2 in ast.walk(child))} 138 | assigned_in_loop = sorted(assigned_in_loop) 139 | args = ', '.join(assigned_in_loop) 140 | loop_test = self.apply_handler(node.test) 141 | loop_id = f'_loop{self.loop_no}' 142 | loop_call = f'{loop_id}({args})' 143 | for n in ast.walk(node): 144 | if isinstance(n, ast.Break): 145 | n.contents = cont 146 | if isinstance(n, ast.Continue): 147 | if hasattr(node, 'target'): 148 | n.contents = construct_lambda({node.target: self._next_item()}, loop_call) 149 | else: 150 | n.contents = loop_call 151 | self.loop_no += 1 152 | loop_body = self.apply_handler(node.body, loop_call) 153 | loop_repr = construct_lambda( 154 | {loop_id: f'_Y(lambda {loop_id}: (lambda {args}: ({loop_body}) if {loop_test} else {cont}))'}, loop_call) 155 | return construct_lambda({v: f'{v} if "{v}" in dir() else None' for v in assigned_in_loop}, loop_repr) 156 | 157 | def handle_for(self, node, cont) -> str: 158 | target_id = f'_targ{self.loop_no}' 159 | iter_id = f'_items{self.loop_no}' 160 | term_id = f'_term{self.loop_no}' 161 | pre = ast.parse(f'{self.apply_handler(node.target)} = {target_id}').body 162 | post = ast.parse(f'{target_id} = {self._next_item()}').body 163 | body_list = pre + node.body + post 164 | while_test = ast.parse(f'{target_id} is not {term_id}').body[0] 165 | while_equivalent = ast.While(while_test, body_list) 166 | while_equivalent.target = target_id 167 | return construct_lambda({term_id: '[]', iter_id: f'iter({self.apply_handler(node.iter)})'}, 168 | construct_lambda({target_id: self._next_item()}, 169 | self.apply_handler(while_equivalent, cont))) 170 | 171 | def _handle_container(self, node) -> str: 172 | return ', '.join(self.apply_handler(child) for child in node.elts) 173 | 174 | def handle_list(self, node, cont) -> str: 175 | return f'[{self._handle_container(node)}]' 176 | 177 | def handle_tuple(self, node, cont) -> str: 178 | return f'({self._handle_container(node)}{"," * (len(node.elts) == 1)})' 179 | 180 | def handle_set(self, node, cont) -> str: 181 | return '{' + self._handle_container(node) + '}' 182 | 183 | def handle_dict(self, node, cont) -> str: 184 | return '{' + ', '.join(f'{k}: {v}' for k, v in zip(map(self.apply_handler, node.keys), 185 | map(self.apply_handler, node.values))) + '}' 186 | 187 | def handle_slice(self, node, cont) -> str: 188 | parts = [self.apply_handler(n) if n is not None else '' for n in [node.lower, node.upper, node.step]] 189 | return ':'.join(parts) 190 | 191 | def handle_subscript(self, node, cont) -> str: 192 | if isinstance(node.slice, ast.Tuple): 193 | return f'{self.apply_handler(node.value)}[{self.apply_handler(node.slice)[1:-1]}]' 194 | return f'{self.apply_handler(node.value)}[{self.apply_handler(node.slice)}]' 195 | 196 | def handle_attribute(self, node, cont) -> str: 197 | return f'{self.apply_handler(node.value)}.{node.attr}' 198 | 199 | def handle_assign(self, node, cont) -> str: 200 | if len(node.targets) > 1: 201 | targets = [self.apply_handler(t) for t in node.targets] 202 | return f'[{cont} for {", ".join(targets)} in [[{self.apply_handler(node.value)}] * {len(targets)}]][0]' 203 | target = node.targets[0] 204 | if isinstance(target, (ast.Attribute, ast.Subscript, ast.Tuple, ast.List)): 205 | return f'[{cont} for {self.apply_handler(target)} in [{self.apply_handler(node.value)}]][0]' 206 | return construct_lambda({self.apply_handler(target): self.apply_handler(node.value)}, cont) 207 | 208 | def handle_augassign(self, node, cont) -> str: 209 | assign_equivalent = ast.Assign([node.target], ast.BinOp(node.target, node.op, node.value)) 210 | return self.apply_handler(assign_equivalent, cont) 211 | 212 | def handle_expr(self, node, cont) -> str: 213 | return self.apply_handler(node.value, cont) 214 | 215 | def handle_keyword(self, node, cont) -> str: 216 | return f'{node.arg}={self.apply_handler(node.value)}' 217 | 218 | def handle_call(self, node, cont) -> str: 219 | args = ', '.join(self.apply_handler(child) for child in node.args + node.keywords) 220 | call = f'{self.apply_handler(node.func)}({args})' 221 | return call if not cont else f'[{call}, {cont}][-1]' 222 | 223 | def handle_binop(self, node, cont) -> str: 224 | op_map = { 225 | ast.Add: '+', 226 | ast.Sub: '-', 227 | ast.Mult: '*', 228 | ast.Div: '/', 229 | ast.FloorDiv: '//', 230 | ast.Mod: '%', 231 | ast.Pow: '**', 232 | ast.LShift: '<<', 233 | ast.RShift: '>>', 234 | ast.BitOr: '|', 235 | ast.BitXor: '^', 236 | ast.BitAnd: '&', 237 | } 238 | return f'({self.apply_handler(node.left)} {op_map[type(node.op)]} {self.apply_handler(node.right)})' 239 | 240 | def handle_boolop(self, node, cont) -> str: 241 | op_map = { 242 | ast.And: 'and', 243 | ast.Or: 'or', 244 | } 245 | return f' {op_map[type(node.op)]} '.join(self.apply_handler(child) for child in node.values) 246 | 247 | def handle_unaryop(self, node, cont) -> str: 248 | op_map = { 249 | ast.UAdd: '+', 250 | ast.USub: '-', 251 | ast.Not: 'not ', 252 | ast.Invert: '~', 253 | } 254 | if isinstance(node.operand, (ast.Name, ast.Constant)): 255 | return f'{op_map[type(node.op)]}{self.apply_handler(node.operand)}' 256 | return f'{op_map[type(node.op)]}({self.apply_handler(node.operand)})' 257 | 258 | def handle_if(self, node, cont) -> str: 259 | return f'({self.apply_handler(node.body, cont)} if {self.apply_handler(node.test)} else {self.apply_handler(node.orelse, cont)})' 260 | 261 | def handle_compare(self, node, cont) -> str: 262 | op_map = { 263 | ast.Gt: '>', 264 | ast.Lt: '<', 265 | ast.GtE: '>=', 266 | ast.LtE: '<=', 267 | ast.Eq: '==', 268 | ast.NotEq: '!=', 269 | ast.In: 'in', 270 | ast.Is: 'is', 271 | ast.IsNot: 'is not', 272 | ast.NotIn: 'not in', 273 | } 274 | comparisons = ''.join(f' {op_map[type(op)]} ' + self.apply_handler(val) 275 | for op, val in zip(node.ops, node.comparators)) 276 | return f'({self.apply_handler(node.left)}{comparisons})' 277 | 278 | def handle_methoddef(self, node, cont) -> str: 279 | all_args = [arg.arg for arg in node.args.args] 280 | defaults = [f'={self.apply_handler(val)}' for val in node.args.defaults] 281 | padding = [''] * (len(all_args) - len(defaults)) 282 | args = ', '.join(arg + val for arg, val in zip(all_args, padding + defaults)) 283 | for n in ast.walk(node): 284 | if cont and isinstance(n, ast.Call) and self.apply_handler(n.func) == cont.split('.')[0]: 285 | n.func.id = 'self.__class__' 286 | if cont.endswith('__init__'): 287 | return f'lambda{" " if args else ""}{args}: [{self.apply_handler(node.body)}, None][-1]' 288 | if any(isinstance(n, ast.Call) and self.apply_handler(n.func) == node.name for n in ast.walk(node)): 289 | self.needs_y = True 290 | return f'_Y(lambda {node.name}: (lambda {args}: {self.apply_handler(node.body)}))' 291 | return f'lambda{" " if args else ""}{args}: {self.apply_handler(node.body)}' 292 | 293 | def handle_functiondef(self, node, cont) -> str: 294 | curr = self.handle_methoddef(node, '') 295 | for n in node.decorator_list[::-1]: 296 | curr = f"{self.apply_handler(n)}({curr})" 297 | return construct_lambda({node.name: curr}, cont) 298 | 299 | def handle_classdef(self, node, cont) -> str: 300 | attr_dict = {} 301 | for n in node.body: 302 | if isinstance(n, ast.FunctionDef): 303 | attr_dict[n.name] = self.handle_methoddef(n, f'{node.name}.{n.name}') 304 | bases = ''.join(self.apply_handler(n) + ', ' for n in node.bases) 305 | attr_repr = '{' + ', '.join(f'{repr(k)}: {v}' for k, v in attr_dict.items()) + '}' 306 | return construct_lambda({node.name: f'type("{node.name}", ({bases}), {attr_repr})'}, cont) 307 | 308 | def handle_return(self, node, cont) -> str: 309 | if node.value is None: 310 | return 'None' 311 | return self.apply_handler(node.value) 312 | 313 | def handle_import(self, node, cont) -> str: 314 | imports = [(a.asname if a.asname else a.name, a.name) for a in node.names] 315 | return construct_lambda({name.split('.')[0]: f"__import__('{mod}')" for name, mod in imports}, cont) 316 | 317 | def handle_importfrom(self, node, cont) -> str: 318 | imports = [(a.asname if a.asname else a.name, a.name) for a in node.names] 319 | return construct_lambda({'_mod': f"__import__('{node.module}', {{}}, {{}}, {[i[1] for i in imports]})"}, 320 | construct_lambda({name: f'_mod.{submodule}' for name, submodule in imports}, cont)) 321 | 322 | def handle_error(self, node, cont) -> None: 323 | raise ValueError(f'Handler not found for {node}') 324 | 325 | def unparse_list(self, body: list, cont=None) -> str: 326 | temp = cont 327 | for node in body[::-1]: 328 | if not isinstance(node, ast.Expr) or isinstance(node.value, ast.Call): 329 | temp = self.apply_handler(node, temp) 330 | return str(temp) 331 | 332 | def unparse(self) -> str: 333 | """ 334 | Unparses the ast. 335 | """ 336 | self.needs_y = False 337 | self.loop_no = 1 338 | curr = self.ast 339 | if hasattr(curr, 'body') and isinstance(curr.body, list): 340 | body = self.apply_handler(curr.body) 341 | return provide_y(body) if self.needs_y else body 342 | return 'Unparse unsuccessful.' 343 | 344 | 345 | if __name__ == '__main__': 346 | import doctest 347 | 348 | doctest.testmod() 349 | test = Flatliner() 350 | test.set_ast('test_input.py', True) 351 | print(ast.dump(test.ast, indent=4)) 352 | result = test.unparse() 353 | print(result) 354 | -------------------------------------------------------------------------------- /lexer.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | 3 | from ply import lex 4 | 5 | NO_INDENT = 0 6 | MUST_INDENT = 2 7 | 8 | # Reserved words 9 | reserved = { 10 | 'if': 'IF', 11 | 'else': 'ELSE', 12 | 'while': 'WHILE', 13 | 'for': 'FOR', 14 | 'return': 'RETURN', 15 | ',': 'COMMA', 16 | 'or': 'OR', 17 | 'break': 'BREAK', 18 | 'continue': 'CONTINUE', 19 | 'assert': 'ASSERT', 20 | 'and': 'AND', 21 | 'def': 'DEF', 22 | 'None': 'NONE', 23 | 'elif': 'ELIF', 24 | 'in': 'IN', 25 | 'not': 'NOT', 26 | 'pass': 'PASS', 27 | } 28 | # List of token names. This is always required 29 | tokens = [ 30 | 'ID', 31 | 'BOOLEAN', 32 | 'FLOAT', 33 | 'NUMBER', 34 | 'STRING', 35 | 'ASSIGN', 36 | 'PLUS', 37 | 'MINUS', 38 | 'TIMES', 39 | 'DIVIDE', 40 | 'DOT', 41 | 'COLON', 42 | 43 | 'MODULO', 44 | 45 | 'GREATER', 46 | 'LESSER', 47 | 'GREATEREQ', 48 | 'LESSEREQ', 49 | 'EQ', 50 | 'NEQ', 51 | 52 | 'NEWLINE', 53 | 54 | 'LPAREN', 55 | 'RPAREN', 56 | 'LBRACE', 57 | 'RBRACE', 58 | 'WS', 59 | 'DEDENT', 60 | 'INDENT' 61 | ] + list(reserved.values()) 62 | 63 | 64 | class PythonLexer: 65 | states = ( 66 | ("COMMENT", "exclusive"), 67 | ) 68 | 69 | def t_start_comment(self, t): 70 | r'\"\"\"' 71 | t.lexer.push_state("COMMENT") 72 | 73 | def t_COMMENT_error(self, t): 74 | print("Illegal COMMENT character '%s'" % t.value[0]) 75 | t.lexer.skip(1) 76 | 77 | t_COMMENT_ignore = '' 78 | 79 | def t_COMMENT_contents(self, t): 80 | r'[^"][^"][^"]*' 81 | 82 | def t_COMMENT_end(self, t): 83 | r'\"\"\"' 84 | t.lexer.pop_state() 85 | 86 | def t_BOOLEAN(self, t): 87 | r'(?:True|False)' 88 | t.value = t.value == 'True' 89 | return t 90 | 91 | t_ignore_COMMENT = r'\#.*' 92 | t_COLON = r':' 93 | 94 | def t_STRING(self, t): 95 | r'[\'][^\']*[\']|[\"][^\"]*[\"]' 96 | t.value = t.value[1:-1] 97 | return t 98 | 99 | def t_ID(self, t): 100 | r'[a-zA-Z_,][a-zA-Z_0-9]*' 101 | t.type = reserved.get(t.value, 'ID') # Check for reserved words 102 | return t 103 | 104 | # Regular expression rule with some action code 105 | 106 | # math 107 | t_PLUS = r'\+' 108 | t_MINUS = r'-' 109 | t_TIMES = r'\*' 110 | t_DIVIDE = r'/' 111 | 112 | t_DOT = r'\.' 113 | t_MODULO = r'\%' 114 | 115 | # boolean algebra 116 | t_GREATER = r'>' 117 | t_LESSER = r'<' 118 | t_GREATEREQ = r'>=' 119 | t_EQ = r'==' 120 | t_ASSIGN = r'=' 121 | t_LESSEREQ = r'<=' 122 | t_NEQ = r'!=' 123 | 124 | # other 125 | 126 | t_LBRACE = r'\[' 127 | t_RBRACE = r'\]' 128 | 129 | def t_LPAREN(self, t): 130 | r'\(' 131 | return t 132 | 133 | def t_RPAREN(self, t): 134 | r'\)' 135 | return t 136 | 137 | def t_WS(self, t): 138 | r' [ ]+ ' 139 | if self.lexer.at_line_start: 140 | return t 141 | 142 | def t_FLOAT(self, t): 143 | '[-+]?[0-9]+(\.([0-9]+)?([eE][-+]?[0-9]+)?|[eE][-+]?[0-9]+)' 144 | t.value = float(t.value) 145 | return t 146 | 147 | def t_NUMBER(self, t): 148 | r'\d+' 149 | t.value = int(t.value) 150 | return t 151 | 152 | # Define a rule so we can track line numbers. DO NOT MODIFY 153 | def t_newline(self, t): 154 | r'\n+' 155 | t.lexer.lineno += len(t.value) 156 | t.type = "NEWLINE" 157 | return t 158 | 159 | # Error handling rule. DO NOT MODIFY 160 | def t_error(self, t): 161 | print("Illegal character '%s'" % t.value[0]) 162 | t.lexer.skip(1) 163 | 164 | # Lexer functions 165 | def build(self, **kwargs): 166 | self.tokens = tokens 167 | self.lexer = lex.lex(module=self, **kwargs) 168 | self.lexer.at_line_start = False 169 | 170 | def make_token(self, type, lineno, lexpos): 171 | tok = lex.LexToken() 172 | tok.type = type 173 | tok.value = None 174 | tok.lineno = lineno 175 | tok.lexpos = lexpos 176 | return tok 177 | 178 | def dedent(self, lineno, lexpos): 179 | return self.make_token('DEDENT', lineno, lexpos) 180 | 181 | def indent(self, lineno, lexpos): 182 | return self.make_token('INDENT', lineno, lexpos) 183 | 184 | def track_tokens_filter(self, lexer, tokens): 185 | ''' 186 | Given a stream of tokens, determine if it should be indented based on previous tokens 187 | ''' 188 | lexer.at_line_start = True 189 | at_line_start = True 190 | indent = NO_INDENT 191 | is_in_list = False 192 | for token in tokens: 193 | token.at_line_start = at_line_start 194 | 195 | if token.type == "LBRACE": 196 | is_in_list = True 197 | token.must_indent = False 198 | elif token.type == "RBRACE": 199 | is_in_list = False 200 | token.must_indent = False 201 | elif token.type == "COLON" and not is_in_list: 202 | at_line_start = False 203 | indent = MUST_INDENT 204 | token.must_indent = False 205 | 206 | elif token.type == "NEWLINE": 207 | at_line_start = True 208 | token.must_indent = False 209 | 210 | elif token.type == "WS": 211 | at_line_start = True 212 | token.must_indent = False 213 | 214 | else: 215 | if indent == MUST_INDENT: 216 | token.must_indent = True 217 | else: 218 | token.must_indent = False 219 | at_line_start = False 220 | indent = NO_INDENT 221 | 222 | yield token 223 | lexer.at_line_start = at_line_start 224 | 225 | def process_indentation(self, tokens): 226 | ''' 227 | Given a stream of tokens, calculate the necessary INDENT and DEDENT tokens needed 228 | ''' 229 | levels = [0] 230 | token = None 231 | depth = 0 232 | prev_was_ws = False 233 | for token in tokens: 234 | if token.type == "WS": 235 | assert depth == 0 236 | depth = len(token.value) 237 | prev_was_ws = True 238 | # WS tokens are never passed to the parser 239 | continue 240 | 241 | if token.type == "NEWLINE": 242 | depth = 0 243 | if prev_was_ws or token.at_line_start: 244 | continue 245 | yield token 246 | continue 247 | prev_was_ws = False 248 | if token.must_indent: 249 | # The current depth must be larger than the previous level 250 | if not (depth > levels[-1]): 251 | raise IndentationError("expected an indented block") 252 | 253 | levels.append(depth) 254 | yield self.indent(token.lineno, token.lexpos) 255 | 256 | elif token.at_line_start: 257 | # Must be on the same level or one of the previous levels 258 | if depth == levels[-1]: 259 | # At the same level 260 | pass 261 | elif depth > levels[-1]: 262 | raise IndentationError( 263 | "indentation increase but not in new block") 264 | else: 265 | # Back up; but only if it matches a previous level 266 | try: 267 | i = levels.index(depth) 268 | except ValueError: 269 | raise IndentationError("inconsistent indentation") 270 | for _ in range(i + 1, len(levels)): 271 | yield self.dedent(token.lineno, token.lexpos) 272 | levels.pop() 273 | 274 | yield token 275 | if len(levels) > 1: 276 | for _ in range(1, len(levels)): 277 | yield self.dedent(token.lineno, token.lexpos) 278 | 279 | def process(self, lexer): 280 | tokens = iter(lexer.token, None) 281 | tokens = self.track_tokens_filter(lexer, tokens) 282 | for token in self.process_indentation(tokens): 283 | yield token 284 | 285 | def get_token_external(self): 286 | if not hasattr(self, 'token_generator'): 287 | self.token_generator = self.process(self.lexer) 288 | return next(self.token_generator, None) 289 | 290 | def test(self, data): 291 | self.lexer.input(data) 292 | self.token_generator = self.process(self.lexer) 293 | for token in self.token_generator: 294 | print(token) 295 | 296 | 297 | # Main function. DO NOT MODIFY 298 | if __name__ == "__main__": 299 | parser = argparse.ArgumentParser(description='Take in the Python source code and perform lexical analysis.') 300 | parser.add_argument('FILE', help="Input file with Python source code") 301 | args = parser.parse_args() 302 | f = open(args.FILE, 'r') 303 | data = f.read() 304 | f.close() 305 | m = PythonLexer() 306 | m.build() 307 | m.test(data) 308 | -------------------------------------------------------------------------------- /out/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | */ 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /python_parser.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | import ast 5 | 6 | from ply import yacc 7 | 8 | from lexer import PythonLexer 9 | # Get the token map from the lexer. This is required. 10 | from lexer import tokens 11 | 12 | DEBUG = False 13 | 14 | 15 | def printd(*args, **kwargs): 16 | """Printing for debugging""" 17 | if DEBUG: 18 | print(*args, **kwargs) 19 | 20 | 21 | class PythonParser: 22 | """ 23 | A parser for a small subset of the Python programming language. 24 | """ 25 | 26 | precedence = ( 27 | ("left", "NOT"), 28 | ("left", "LBRACE"), 29 | ("left", "OR"), 30 | ("left", "AND"), 31 | ("left", "EQ", "GREATER", "LESSER", "NEQ", "GREATEREQ", "LESSEREQ",), 32 | ("left", "PLUS", "MINUS"), 33 | ("left", "MODULO"), 34 | ("left", "TIMES", "DIVIDE"), 35 | ("left", "IN") 36 | ) 37 | 38 | ################################ 39 | ## Statements 40 | ################################ 41 | def p_statements_or_empty(self, p): 42 | """ 43 | stmts_or_empty : stmt_lst 44 | | empty 45 | """ 46 | printd("statements or empty") 47 | p[0] = p[1] 48 | 49 | def p_statement_list(self, p): 50 | """ 51 | stmt_lst : stmt_lst stmt 52 | | stmt 53 | """ 54 | printd("statement list") 55 | if len(p) == 2: 56 | p[0] = [p[1]] 57 | else: 58 | p[0] = p[1] + [p[2]] 59 | 60 | def p_statement(self, p): 61 | """ 62 | stmt : assign_stmt 63 | | if_stmt 64 | | while_stmt 65 | | for_stmt 66 | | func_defn 67 | | return 68 | | expr_stmt 69 | | break 70 | | pass 71 | | continue 72 | | assert 73 | """ 74 | printd("statement") 75 | p[0] = p[1] 76 | 77 | def p_func_defn(self, p): 78 | """ 79 | func_defn : DEF ID params COLON NEWLINE INDENT stmt_lst DEDENT 80 | """ 81 | # p[2] = ast.Name(p[2], ast.Store()) 82 | p[0] = ast.FunctionDef(p[2], p[3], p[7], decorator_list=[], lineno=p.lineno) 83 | 84 | def p_for_stmt(self, p): 85 | """ 86 | for_stmt : FOR ID IN expr COLON NEWLINE INDENT stmt_lst DEDENT 87 | """ 88 | printd("For") 89 | p[2] = ast.Name(p[2], ast.Store()) 90 | p[0] = ast.For(p[2], p[4], p[8], [], lineno=p.lineno) 91 | 92 | def p_while_stmt(self, p): 93 | """ 94 | while_stmt : WHILE expr COLON NEWLINE INDENT stmt_lst DEDENT 95 | """ 96 | p[0] = ast.While(p[2], [p[6]], []) 97 | 98 | def p_if_statement(self, p): 99 | """ 100 | if_stmt : IF expr COLON NEWLINE INDENT stmt_lst DEDENT 101 | | IF expr COLON NEWLINE INDENT stmt_lst DEDENT elif_stmt 102 | | IF expr COLON NEWLINE INDENT stmt_lst DEDENT else_stmt 103 | """ 104 | printd("if statement") 105 | if len(p) == 9: 106 | p[0] = ast.If(p[2], [p[6]], [p[8]]) 107 | else: 108 | p[0] = ast.If(p[2], [p[6]], []) 109 | 110 | def p_return_statement(self, p): 111 | """ 112 | return : RETURN expr NEWLINE 113 | """ 114 | printd('return') 115 | p[0] = ast.Return(p[2]) 116 | 117 | def p_assert_statement(self, p): 118 | """ 119 | assert : ASSERT expr NEWLINE 120 | | ASSERT expr COMMA expr NEWLINE 121 | """ 122 | if len(p) == 4: 123 | p[0] = ast.Assert(p[2]) 124 | else: 125 | p[0] = ast.Assert(p[2], p[4]) 126 | 127 | def p_break_statement(self, p): 128 | """ 129 | break : BREAK NEWLINE 130 | """ 131 | printd('break') 132 | p[0] = ast.Break() 133 | 134 | def p_pass_statement(self, p): 135 | """ 136 | pass : PASS NEWLINE 137 | """ 138 | printd('pass') 139 | p[0] = ast.Pass() 140 | 141 | def p_continue_statement(self, p): 142 | """ 143 | continue : CONTINUE NEWLINE 144 | """ 145 | printd('continue') 146 | p[0] = ast.Continue() 147 | 148 | def p_elif(self, p): 149 | """ 150 | elif_stmt : ELIF expr COLON NEWLINE INDENT stmt_lst DEDENT elif_stmt 151 | | ELIF expr COLON NEWLINE INDENT stmt_lst DEDENT else_stmt 152 | | ELIF expr COLON NEWLINE INDENT stmt_lst DEDENT 153 | """ 154 | printd("or else") 155 | if len(p) == 9: 156 | p[0] = ast.If(p[2], [p[6]], [p[8]]) 157 | else: 158 | p[0] = ast.If(p[2], [p[6]], []) 159 | 160 | def p_else(self, p): 161 | """ 162 | else_stmt : ELSE COLON NEWLINE INDENT stmt_lst DEDENT 163 | """ 164 | printd("or else") 165 | p[0] = p[5] 166 | 167 | def p_assignment_statement(self, p): 168 | """ 169 | assign_stmt : ID ASSIGN expr NEWLINE 170 | """ 171 | printd("assign") 172 | p[0] = ast.Assign([ast.Name(p[1], ast.Store())], p[3], lineno=p.lineno) 173 | 174 | def p_expr_statement(self, p): 175 | """ 176 | expr_stmt : expr NEWLINE 177 | """ 178 | p[0] = ast.Expr(p[1]) 179 | 180 | ################################ 181 | ## Expressions 182 | ################################ 183 | def p_params(self, p): 184 | """ 185 | params : LPAREN params_or_empty RPAREN 186 | """ 187 | printd("this is p", p[2]) 188 | 189 | p[0] = ast.arguments([], p[2] if p[2] else [], [], [], [], [], []) 190 | 191 | def p_params_or_empty(self, p): 192 | """ 193 | params_or_empty : paramlst 194 | | empty 195 | """ 196 | if len(p) == 1: 197 | p[0] = [] 198 | else: 199 | p[0] = p[1] 200 | 201 | def p_paramlst(self, p): 202 | """ 203 | paramlst : paramlst COMMA ID 204 | | ID 205 | """ 206 | printd("param list") 207 | if len(p) == 2: 208 | p[0] = [ast.arg(p[1])] 209 | else: 210 | printd("p1", p[1]) 211 | p[0] = p[1] + [ast.arg(p[3])] 212 | 213 | def p_parenexpr(self, p): 214 | """ 215 | expr : LPAREN expr RPAREN 216 | """ 217 | p[0] = p[2] 218 | 219 | def p_method_call(self, p): 220 | """ 221 | expr : ID DOT ID args 222 | | LBRACE args_or_empty RBRACE DOT ID args 223 | 224 | """ 225 | if len(p) == 5: 226 | p[1] = ast.Name(p[1], ast.Load()) 227 | p[1] = ast.Attribute(p[1], p[3], ctx=ast.Load()) 228 | p[0] = ast.Call(p[1], p[4], []) 229 | else: 230 | p[1] = ast.List(p[2] if p[2] else []) 231 | p[1] = ast.Attribute(p[1], p[5], ctx=ast.Load()) 232 | p[0] = ast.Call(p[1], p[6], []) 233 | 234 | def p_func_call(self, p): 235 | """ 236 | expr : ID args 237 | """ 238 | p[1] = ast.Name(p[1], ast.Load()) 239 | p[0] = ast.Call(p[1], p[2], []) 240 | 241 | def p_expr_lst(self, p): 242 | """ 243 | expr : LBRACE args_or_empty RBRACE 244 | """ 245 | p[0] = ast.List(p[2] if p[2] else []) 246 | 247 | def p_args(self, p): 248 | """ 249 | args : LPAREN args_or_empty RPAREN 250 | """ 251 | printd("args", p) 252 | p[0] = p[2] if p[2] else [] 253 | 254 | def p_args_or_empty(self, p): 255 | """ 256 | args_or_empty : arg_lst 257 | | empty 258 | """ 259 | if len(p) == 1: 260 | p[0] = [] 261 | else: 262 | p[0] = p[1] 263 | 264 | def p_arg_lst(self, p): 265 | """ 266 | arg_lst : arg_lst COMMA expr 267 | | expr 268 | """ 269 | printd("arg list") 270 | if len(p) == 2: 271 | p[0] = [p[1]] 272 | else: 273 | p[0] = p[1] + [p[3]] 274 | 275 | def p_expr_index(self, p): 276 | """ 277 | expr : expr LBRACE expr RBRACE 278 | | expr slice 279 | """ 280 | if len(p) == 3: 281 | p[0] = ast.Subscript(p[1], p[2], ctx=ast.Load()) 282 | else: 283 | p[0] = ast.Subscript(p[1], p[3], ctx=ast.Load()) 284 | 285 | def p_slice(self, p): 286 | """ 287 | slice : LBRACE NUMBER COLON NUMBER RBRACE 288 | """ 289 | # we're not supporting tuple slices 290 | if len(p) == 3: 291 | if p[2] == ':': 292 | p[0] = ast.Slice(lower=p[1]) 293 | else: 294 | p[0] = ast.Slice(upper=p[2]) 295 | elif len(p) == 6: 296 | p[2] = ast.Constant(p[2], 'int') 297 | p[4] = ast.Constant(p[4], 'int') 298 | p[0] = ast.Slice(lower=p[2], upper=p[4]) 299 | 300 | def p_expr_boolop(self, p): 301 | """ 302 | expr : expr OR expr 303 | | expr AND expr 304 | """ 305 | printd("bool op") 306 | op_map = { 307 | 'and': ast.And(), 308 | 'or': ast.Or(), 309 | } 310 | p[0] = ast.BoolOp(op_map[p[2]], [p[1], p[3]]) 311 | 312 | def p_expr_compare(self, p): 313 | """ 314 | expr : expr GREATER expr 315 | | expr LESSER expr 316 | | expr GREATEREQ expr 317 | | expr LESSEREQ expr 318 | | expr EQ expr 319 | | expr NEQ expr 320 | | expr IN expr 321 | """ 322 | printd("compare op") 323 | op_map = { 324 | '>': ast.Gt(), 325 | '<': ast.Lt(), 326 | '>=': ast.GtE(), 327 | '<=': ast.LtE(), 328 | '==': ast.Eq(), 329 | '!=': ast.NotEq(), 330 | 'in': ast.In(), 331 | } 332 | p[0] = ast.Compare(p[1], [op_map[p[2]]], [p[3]]) 333 | 334 | def p_expr_binops(self, p): 335 | """ 336 | expr : expr PLUS expr 337 | | expr MINUS expr 338 | | expr TIMES expr 339 | | expr DIVIDE expr 340 | | expr MODULO expr 341 | """ 342 | printd("binary op") 343 | op_map = { 344 | '+': ast.Add(), 345 | '-': ast.Sub(), 346 | '*': ast.Mult(), 347 | '/': ast.Div(), 348 | '%': ast.Mod() 349 | } 350 | p[0] = ast.BinOp(p[1], op_map[p[2]], p[3]) 351 | 352 | def p_expr_not(self, p): 353 | """ 354 | expr : NOT expr 355 | """ 356 | printd("unary op") 357 | p[0] = ast.UnaryOp(ast.Not(), p[2]) 358 | 359 | def p_expr_number(self, p): 360 | """ 361 | expr : NUMBER 362 | """ 363 | printd("number") 364 | p[0] = ast.Constant(p[1], 'int') 365 | 366 | def p_expr_bool(self, p): 367 | """ 368 | expr : BOOLEAN 369 | """ 370 | printd('bool') 371 | p[0] = ast.Constant(p[1], 'bool') 372 | 373 | def p_expr_float(self, p): 374 | """ 375 | expr : FLOAT 376 | """ 377 | printd("float") 378 | p[0] = ast.Constant(p[1], 'float') 379 | 380 | def p_expr_string(self, p): 381 | """ 382 | expr : STRING 383 | """ 384 | printd("string") 385 | p[0] = ast.Constant(p[1], 'str') 386 | 387 | def p_expr_id(self, p): 388 | """ 389 | expr : ID 390 | """ 391 | p[0] = ast.Name(p[1], ast.Load()) 392 | 393 | def p_expr_none(self, p): 394 | """ 395 | expr : NONE 396 | """ 397 | p[0] = ast.Constant(value=None) 398 | 399 | ################################ 400 | ## if statements 401 | ################################ 402 | 403 | # def p_logic_if(self, p): 404 | # """ 405 | # 406 | # """ 407 | 408 | ################################ 409 | ## Misc 410 | ################################ 411 | 412 | # This can be used to handle the empty production, by using 'empty' 413 | # as a symbol. For example: 414 | # 415 | # optitem : item 416 | # | empty 417 | def p_empty(self, p): 418 | """empty :""" 419 | pass 420 | 421 | def p_error(self, p): 422 | if p: 423 | printd("Syntax error at token", repr(p.value), p) 424 | else: 425 | printd("None type:", p) 426 | 427 | def build(self, **kwargs): 428 | self.tokens = tokens 429 | self.lexer = PythonLexer() 430 | self.lexer.build() 431 | self.parser = yacc.yacc(module=self, **kwargs) 432 | 433 | def get_ast(self, data): 434 | """ 435 | returns the ast representation of 436 | """ 437 | return ast.Module(self.parser.parse(data, tokenfunc=self.lexer.get_token_external, debug=DEBUG), []) 438 | 439 | def test(self, data): 440 | result = self.get_ast(data) 441 | try: 442 | print(result) 443 | print(ast.dump(result, indent=4)) 444 | print(ast.unparse(result)) 445 | except Exception as e: 446 | print("Something went wrong lmao 😂") 447 | print(e) 448 | visitor = ast.NodeVisitor() 449 | visitor.visit(result) 450 | 451 | 452 | if __name__ == "__main__": 453 | argparser = argparse.ArgumentParser(description='Take in the python source code and parses it') 454 | argparser.add_argument('FILE', help='Input file with python source code') 455 | args = argparser.parse_args() 456 | 457 | f = open(args.FILE, 'r') 458 | data = f.read() 459 | f.close() 460 | 461 | m = PythonParser() 462 | m.build() 463 | m.test(data) 464 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | ply==3.11 2 | -------------------------------------------------------------------------------- /tac_ast_examples/advanced_ifs_and_loops.py: -------------------------------------------------------------------------------- 1 | def get_average_elevation(m): 2 | # Your code goes here 3 | total = 0 4 | n = 0 5 | for lst in m: 6 | for i in lst: 7 | total = total + i 8 | n = n + 1 9 | if n == 0: 10 | return 0 11 | return total / n 12 | 13 | 14 | def find_peak(m): 15 | # Your code goes here 16 | largest = 0 17 | x = 0 18 | y = 0 19 | for data in m: 20 | for i in range(len(data)): 21 | if data[i] > largest: 22 | x = m.index(data) 23 | y = i 24 | largest = data[i] 25 | return [x, y] 26 | 27 | 28 | sample = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] 29 | print(get_average_elevation(sample)) 30 | print(find_peak(sample)) 31 | -------------------------------------------------------------------------------- /tac_ast_examples/advanced_types.py: -------------------------------------------------------------------------------- 1 | def test(a, b): 2 | new = [] 3 | for i in b: 4 | if i == 2: 5 | new.append(i) 6 | else: 7 | new.append(i + a) 8 | return new 9 | 10 | 11 | c = test(5, [1, 2, 3, 4, 5]) 12 | 13 | print(c) 14 | -------------------------------------------------------------------------------- /tac_ast_examples/assignments.py: -------------------------------------------------------------------------------- 1 | a = 1 + 2 2 | print(a) 3 | b = 3 + 4.0 4 | print(b) 5 | c = a + b 6 | print(c) 7 | d = 5 8 | print(d) 9 | 10 | e = 5 > 4 11 | print(e) 12 | f = 5 < 4 < 8 13 | print(f) 14 | g = 5 >= 4 15 | print(g) 16 | h = 4 != 5 17 | print(h) 18 | i = a or b 19 | j = e and d 20 | k = a or b and c 21 | 22 | l = a >= b and b != c 23 | a = 1 in range(5) 24 | print(a) 25 | -------------------------------------------------------------------------------- /tac_ast_examples/basic_for_loops.py: -------------------------------------------------------------------------------- 1 | sample = [2, 3, 4] 2 | for i in range(len(sample)): 3 | i = i + 1 4 | print(i) 5 | -------------------------------------------------------------------------------- /tac_ast_examples/for_loops.py: -------------------------------------------------------------------------------- 1 | def test(loops): 2 | total = 0 # total is 0 at first 3 | # do the loop 4 | for i in range(loops): 5 | print('iteration', i, 'of loop') 6 | total = total + i 7 | return total 8 | 9 | 10 | print('total sum =', test(6)) 11 | -------------------------------------------------------------------------------- /tac_ast_examples/function_defs_and_calls.py: -------------------------------------------------------------------------------- 1 | def test(a, b): 2 | return a + b 3 | 4 | 5 | print(test(1, 2)) 6 | -------------------------------------------------------------------------------- /tac_ast_examples/lists.py: -------------------------------------------------------------------------------- 1 | sample = [2, 3, 4] 2 | sample.append(2) 3 | x = sample[0] 4 | y = sample[2] 5 | z = sample[1] 6 | 7 | j = sample[2:3] 8 | k = x + y + z 9 | 10 | sample2 = [[2, 3, 4], [3, 4, 5], [6, 7, 9]] 11 | sample2[0][1] 12 | -------------------------------------------------------------------------------- /tac_ast_examples/loops_flow_control.py: -------------------------------------------------------------------------------- 1 | for i in range(22): 2 | if i < 5: 3 | print(i, 'continue i') 4 | continue 5 | print(i, 'i not continue') 6 | for j in range(i, 30): 7 | print(j) 8 | k = j 9 | print(k, 'k and j') 10 | while k > 15: 11 | k = k - 1 12 | if k > 20: 13 | print(k, 'k continue') 14 | continue 15 | print(k, 'finished k loop') 16 | if j < i + 5: 17 | print(j, 'continue j') 18 | continue 19 | print(j, 'j breaking') 20 | break 21 | if i == 17: 22 | print('i 17 continue') 23 | continue 24 | if i == 17 or i == 18: 25 | print(i, 'i break') 26 | break 27 | 28 | a = 5 29 | while a < 15: 30 | a = a + 1 31 | print(a, 'a loop') 32 | if a > 7: 33 | for b in range(a, a + 5): 34 | if b < a + 1: 35 | print(b, 'b continue') 36 | continue 37 | else: 38 | while b < a + 15: 39 | b = b + 1 40 | if b - a > 3: 41 | print('b while break') 42 | break 43 | if a < 13: 44 | print(a, 'a continue') 45 | continue 46 | print(a, 'a break') 47 | break 48 | 49 | print('done test') 50 | -------------------------------------------------------------------------------- /tac_ast_examples/nested_function_calls.py: -------------------------------------------------------------------------------- 1 | def test1(a, b): 2 | return a + b 3 | 4 | 5 | def test2(c, d): 6 | return c + d 7 | 8 | 9 | e = test1(test2(1, 2), test2(3, 4)) 10 | print(e) 11 | if e > 5: 12 | print('here') 13 | else: 14 | print('there') 15 | -------------------------------------------------------------------------------- /tac_ast_examples/unused_variables.py: -------------------------------------------------------------------------------- 1 | x = 3 2 | y = x + 4 3 | 4 | z = 5 5 | print(z) 6 | 7 | a = z - 4 8 | b = z + 4 9 | lst = [a, b] 10 | print(lst[a]) 11 | 12 | for i in range(5): 13 | foo = i + 1 14 | print("hello") 15 | -------------------------------------------------------------------------------- /tac_ast_examples/while_loops.py: -------------------------------------------------------------------------------- 1 | i = 0 2 | j = 0 3 | k = 4 4 | while i < j: 5 | while j > i: 6 | i = i + 1 7 | while j < k: 8 | while j == 2: 9 | j = j + 1 10 | -------------------------------------------------------------------------------- /tac_optimizer.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import ast 3 | 4 | from code_to_tac import ASTVisitor 5 | from python_parser import PythonParser 6 | 7 | code = [] 8 | START = 'main' 9 | TEMP = 't_' 10 | BLOCK = '_L' 11 | DEBUG = False 12 | 13 | 14 | def printd(*args, **kwargs): 15 | """Printing for debugging""" 16 | if DEBUG: 17 | print(*args, **kwargs) 18 | 19 | 20 | class TACOptimizer: 21 | 22 | def __init__(self, tac): 23 | self.tac = tac 24 | self.lineno = 0 25 | 26 | def optimize_block(self, block, var_d=None): 27 | statements = self.tac[block] 28 | if var_d is None: var_d = {} # store variables here 29 | code = [] 30 | index = 0 31 | params = [] # stack for parameters to pass 32 | while index < len(statements): 33 | statement = statements[index] 34 | index += 1 35 | op = statement[0] 36 | var = statement[3] 37 | if op == 'IF': 38 | var = statement[1] 39 | if var in var_d: 40 | var_d[var].append('if') 41 | continue 42 | if op == 'WHILE': 43 | var = statement[1] 44 | if var in var_d: 45 | var_d[var].append('if') 46 | continue 47 | if op == 'FOR': 48 | continue 49 | if op == 'DEFN': 50 | continue 51 | if op == 'START-LIST': 52 | continue 53 | if op == 'SLICE': 54 | continue 55 | if op == 'INDEX': 56 | lst = statement[1] 57 | next_stmt = statements[index] 58 | index_var = next_stmt[2] 59 | if lst in var_d: 60 | var_d[lst].append('list') 61 | if index_var in var_d: 62 | var_d[index_var].append('index') 63 | index += 1 64 | continue 65 | if op == 'GOTO': 66 | continue 67 | if op == 'PUSH-ELMT' or op == 'PUSH-PARAM': 68 | if var in var_d: 69 | var_d[var].append('call') 70 | continue 71 | if op == 'CALL': 72 | continue 73 | self.handle_statement(statement, var_d, code) 74 | opt_statements = [] 75 | print(var_d) 76 | for statement in statements: 77 | var = statement[3] 78 | op = statement[0] 79 | removing = ['+', '-', '*', '/', 'OR', 80 | 'AND', '==', '>', '<', '<=', 81 | '>=', '!=', '=', 'in'] 82 | if op in removing and statement[2] != 'ret' and self._removed_var(var_d, var): 83 | var_d[var] = None 84 | continue 85 | opt_statements.append(statement) 86 | return opt_statements 87 | 88 | def handle_statement(self, statement, var_d, code): 89 | op, var = statement[0], statement[3] 90 | var1, var2 = statement[1], statement[2] 91 | if var: 92 | var_d[var] = [] # 0 occurences, we're counting 93 | 94 | if var1 in var_d: # is a variable and now used in an expression 95 | var_d[var1].append(var) 96 | 97 | if var2 in var_d: # is a variable and now used in an expression 98 | var_d[var2].append(var) 99 | 100 | def _removed_var(self, var_d, var): 101 | """ 102 | Return True if variable has been or will be removed. 103 | """ 104 | if not var in var_d: 105 | return False 106 | 107 | # has been removed 108 | if var_d[var] is None: 109 | return True 110 | 111 | # will be removed 112 | if not var.startswith(TEMP) and len(var_d[var]) == 0: 113 | return True 114 | 115 | for var2 in var_d[var]: 116 | if not self._removed_var(var_d, var2): 117 | return False 118 | return True 119 | 120 | def constant_handler(self, constant, var_d): 121 | pass 122 | 123 | def call_handler(self, statement, var_d, params): 124 | pass 125 | 126 | def return_handler(self, statement, var_d): 127 | pass 128 | 129 | def continue_handler(self, statement=None, var_d=None): 130 | pass 131 | 132 | def break_handler(self, statement=None, var_d=None): 133 | pass 134 | 135 | def binary_handler(self, statement, var_d): 136 | pass 137 | 138 | def assignment_handler(self, statement, var_d): 139 | pass 140 | 141 | def bool_handler(self, statement, var_d): 142 | pass 143 | 144 | def comp_handler(self, statement, var_d): 145 | pass 146 | 147 | def if_handler(self, statement, var_d, else_block, outer_code): 148 | pass 149 | 150 | def while_handler(self, statement, var_d, outer_code): 151 | pass 152 | 153 | def for_handler(self, statement, var_d, outer_code): 154 | pass 155 | 156 | def function_hander(self, statement, var_d, outer_code): 157 | pass 158 | 159 | def list_handler(self, statement, var_d, index, statements): 160 | pass 161 | 162 | def optimize_tac(self): 163 | optimized_tac = {} 164 | for scope in self.tac: 165 | optimized_tac[scope] = self.optimize_block(scope) 166 | return optimized_tac 167 | 168 | 169 | if __name__ == '__main__': 170 | argparser = argparse.ArgumentParser(description='Take in the python source code and parses it') 171 | argparser.add_argument('--FILE', help='Input file with python source code', required=False) 172 | args = argparser.parse_args() 173 | file = 'test_input.py' 174 | if args.FILE: 175 | file = args.FILE 176 | 177 | visitor = ASTVisitor() 178 | infile = open(file) 179 | parser = PythonParser() 180 | parser.build() 181 | tree = parser.get_ast(infile.read()) 182 | 183 | printd(ast.dump(tree, indent=4)) 184 | visitor.visit(tree) 185 | printd(visitor.tac) 186 | 187 | optimizer = TACOptimizer(visitor.tac) 188 | 189 | print(optimizer.optimize_tac()) 190 | -------------------------------------------------------------------------------- /tac_shorten.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import ast 3 | from copy import deepcopy 4 | 5 | from code_to_tac import ASTVisitor 6 | from python_parser import PythonParser 7 | from tac_to_code import TACConverter 8 | 9 | code = [] 10 | START = 'main' 11 | TEMP = 't_' 12 | BLOCK = '_L' 13 | DEBUG = False 14 | alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' 15 | 16 | 17 | def printd(*args, **kwargs): 18 | """Printing for debugging""" 19 | if DEBUG: 20 | print(*args, **kwargs) 21 | 22 | 23 | class TACShortener: 24 | 25 | def __init__(self, tac): 26 | self.tac = tac 27 | self.short_string_index = 0 28 | self.mapping = {} 29 | self.handlers = {'+': self.comparison_handler, '-': self.comparison_handler, '*': self.comparison_handler, 30 | '/': self.comparison_handler, 31 | 'OR': self.comparison_handler, 'AND': self.comparison_handler, 32 | '==': self.comparison_handler, '>': self.comparison_handler, 33 | '<': self.comparison_handler, '<=': self.comparison_handler, '>=': self.comparison_handler, 34 | '!=': self.comparison_handler, 35 | '=': self.assignment_handler, 36 | 'in': self.comparison_handler, 37 | 'DEFN': self.function_handler, 38 | 'ADD-PARAM': self.add_param_handler, 39 | 'PUSH-PARAM': self.push_param_handler, 40 | 'PUSH-ELMT': self.push_elmt_handler, 41 | 'CALL': self.call_handler, 42 | 'INDEX': self.index_handler, 43 | 'SLICE': self.slice_handler, 44 | 'GOTO': self.goto_handler, 45 | 'IF': self.control_flow_handler, 46 | 'WHILE': self.control_flow_handler, 47 | 'FOR': self.control_flow_handler, 48 | 'RETURN': self.return_handler 49 | } 50 | self.optimized_tac_statements = {} 51 | self.keys_done = set() 52 | 53 | def generate_small_string(self): 54 | index = self.short_string_index 55 | generated_string = '' 56 | amount = index // len(alphabet) 57 | 58 | # handles multiple 59 | while amount > len(alphabet): 60 | generated_string += alphabet[-1] 61 | amount -= len(alphabet) 62 | if amount > 0: 63 | generated_string = alphabet[amount - 1] 64 | index = index % len(alphabet) 65 | 66 | if index >= 0: 67 | generated_string += alphabet[index] 68 | self.short_string_index += 1 69 | return generated_string 70 | 71 | def get_new_name(self, name): 72 | if name in self.mapping: 73 | return self.mapping[name] 74 | else: 75 | new_var = self.generate_small_string() 76 | self.mapping[name] = new_var 77 | return new_var 78 | 79 | def call_handler(self, list_of_statements, statement): 80 | op, objName, length, var = statement 81 | 82 | if objName != None: 83 | objName = self.mapping[objName] 84 | 85 | if var in self.mapping: 86 | var = self.mapping[var] 87 | list_of_statements.append((op, objName, length, var)) 88 | 89 | def add_param_handler(self, list_of_statements, statement): 90 | list_of_statements.append((statement[0], None, None, self.get_new_name(statement[-1]))) 91 | 92 | def push_param_handler(self, list_of_statements, statement): 93 | op, _, _, var = statement 94 | 95 | if type(var) == str and var in self.mapping: 96 | var = self.mapping[var] 97 | list_of_statements.append((op, None, None, var)) 98 | 99 | def push_elmt_handler(self, list_of_statements, statement): 100 | 101 | op, arg1, arg2, value = statement 102 | if type(value) == str and value in self.mapping: 103 | value = self.mapping[value] 104 | 105 | list_of_statements.append((op, arg1, arg2, value)) 106 | 107 | def function_handler(self, list_of_statements, statement): 108 | # Add statement to old key 109 | statement_name = statement[-1] 110 | list_of_statements.append((statement[0], None, None, self.get_new_name(statement_name))) 111 | 112 | old_mapping = self.mapping 113 | self.mapping = deepcopy(self.mapping) 114 | self.optimize_tac(statement_name, self.get_new_name(statement_name)) 115 | self.mapping = old_mapping 116 | 117 | def comparison_handler(self, list_of_statements, statement): 118 | op, arg1, arg2, mainAssign = statement 119 | 120 | if type(arg1) == str and arg1 in self.mapping: 121 | arg1 = self.mapping[arg1] 122 | 123 | if type(arg2) == str and arg2 in self.mapping: 124 | arg2 = self.mapping[arg2] 125 | list_of_statements.append((op, arg1, arg2, mainAssign)) 126 | 127 | def return_handler(self, list_of_statements, statement): 128 | op, arg1, arg2, mainAssign = statement 129 | if type(mainAssign) == str and mainAssign in self.mapping: 130 | mainAssign = self.mapping[mainAssign] 131 | list_of_statements.append((op, arg1, arg2, mainAssign)) 132 | 133 | def assignment_handler(self, list_of_statements, statement): 134 | 135 | op, arg1, arg2, mainAssign = statement 136 | if type(arg1) == str and not arg1.startswith(TEMP): 137 | # variable 138 | arg1 = self.get_new_name(arg1) 139 | 140 | if not mainAssign.startswith(TEMP): 141 | mainAssign = self.get_new_name(mainAssign) 142 | list_of_statements.append((op, arg1, arg2, mainAssign)) 143 | 144 | def index_handler(self, list_of_statements, statement): 145 | op, arg1, arg2, var = statement 146 | 147 | if arg1 in self.mapping: 148 | arg1 = self.mapping[arg1] 149 | 150 | if arg2 in self.mapping and not arg2.startswith(TEMP): 151 | arg2 = self.mapping[arg2] 152 | 153 | list_of_statements.append((op, arg1, arg2, var)) 154 | 155 | def slice_handler(self, list_of_statements, statement): 156 | op, arg1, arg2, var = statement 157 | if arg1 in self.mapping: 158 | arg1 = self.mapping[arg1] 159 | list_of_statements.append((op, arg1, arg2, var)) 160 | 161 | def control_flow_handler(self, list_of_statements, statement): 162 | 163 | op, arg1, arg2, key = statement 164 | 165 | if type(arg1) == str and not arg1.startswith(TEMP): 166 | arg1 = self.get_new_name(arg1) 167 | 168 | if type(arg2) == str and not arg2.startswith(TEMP): 169 | arg2 = self.get_new_name(arg2) 170 | 171 | list_of_statements.append((op, arg1, arg2, key)) 172 | if not key in self.keys_done and key in self.tac: 173 | self.optimize_tac(key, key) 174 | self.keys_done.add(key) 175 | 176 | def goto_handler(self, list_of_statements, statement): 177 | list_of_statements.append(statement) 178 | key = statement[-1] 179 | if not key in self.keys_done and key in self.tac: 180 | self.optimize_tac(key, key) 181 | self.keys_done.add(key) 182 | 183 | def optimize_tac(self, block='main', new_key='main'): 184 | statements = self.tac[block] 185 | opt_statements = [] 186 | for statement in statements: 187 | 188 | # Basic statements 189 | if statement[0] in self.handlers: 190 | self.handlers[statement[0]](opt_statements, statement) 191 | else: 192 | opt_statements.append(statement) 193 | self.optimized_tac_statements[new_key] = opt_statements 194 | 195 | 196 | if __name__ == '__main__': 197 | argparser = argparse.ArgumentParser(description='Take in the python source code and parses it') 198 | argparser.add_argument('--FILE', help='Input file with python source code', required=False) 199 | args = argparser.parse_args() 200 | file = 'test_input.py' 201 | if args.FILE: 202 | file = args.FILE 203 | 204 | visitor = ASTVisitor() 205 | infile = open(file) 206 | parser = PythonParser() 207 | parser.build() 208 | tree = parser.get_ast(infile.read()) 209 | 210 | printd(ast.dump(tree, indent=4)) 211 | visitor.visit(tree) 212 | printd(visitor.tac) 213 | 214 | optimizer = TACShortener(visitor.tac) 215 | 216 | optimizer.optimize_tac() 217 | print('optimized_tac:') 218 | print(optimizer.optimized_tac_statements) 219 | 220 | converter = TACConverter(optimizer.optimized_tac_statements) 221 | wrap = converter.get_ast() 222 | 223 | printd(ast.dump(wrap, indent=4)) 224 | print(f'Code:\n{ast.unparse(wrap)}') 225 | -------------------------------------------------------------------------------- /tac_to_code.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import ast 3 | 4 | from code_to_tac import ASTVisitor 5 | from python_parser import PythonParser 6 | from tac_optimizer import TACOptimizer 7 | 8 | code = [] 9 | START = 'main' 10 | TEMP = 't_' 11 | BLOCK = '_L' 12 | DEBUG = False 13 | 14 | 15 | def printd(*args, **kwargs): 16 | """Printing for debugging""" 17 | if DEBUG: 18 | print(*args, **kwargs) 19 | 20 | 21 | class TACConverter: 22 | 23 | def __init__(self, tac): 24 | self.tac = tac 25 | self.lineno = 0 26 | 27 | def convert(self, statements=None, var_d=None): 28 | if statements is None: 29 | statements = self.tac[START] # start 30 | if var_d is None: var_d = {} # store variables here 31 | code = [] 32 | index = 0 33 | params = [] # stack for parameters to pass 34 | while index < len(statements): 35 | statement = statements[index] 36 | index += 1 37 | op = statement[0] 38 | var = statement[3] 39 | if op == 'IF': 40 | # everything till GOTO is else 41 | else_block = statements[index:] 42 | self.if_handler(statement, var_d, else_block, code) 43 | return code # it'll handle the rest of the main block too 44 | if op == 'WHILE': 45 | self.while_handler(statement, var_d.copy(), code) 46 | return code # it'll handle the rest of the main block too 47 | if op == 'FOR': 48 | self.for_handler(statement, var_d.copy(), code) 49 | return code # it'll handle the rest of the main block too 50 | if op == 'DEFN': 51 | self.function_hander(statement, var_d.copy(), code) 52 | continue 53 | if op == 'START-LIST': 54 | node, index = self.list_handler(statement, var_d, index, statements) 55 | self.lineno += 1 56 | if index < len(statements): 57 | next_stmt = statements[index] 58 | if var in next_stmt: 59 | continue 60 | expr_node = ast.Expr(node) 61 | code.append(expr_node) 62 | continue 63 | if op == 'SLICE': 64 | temp_var = var 65 | lst = self.constant_handler(statement[1], var_d) 66 | _, start, stop, _ = statements[index] # next line 67 | start = self.constant_handler(start, var_d) if start else start 68 | stop = self.constant_handler(stop, var_d) if stop else stop 69 | slice_node = ast.Slice(lower=start, upper=stop) 70 | node = ast.Subscript(lst, slice_node, ctx=ast.Load()) 71 | var_d[temp_var] = node 72 | self.lineno += 1 73 | index += 1 74 | if index < len(statements): 75 | next_stmt = statements[index] 76 | if temp_var in next_stmt: 77 | continue 78 | expr_node = ast.Expr(node) 79 | code.append(expr_node) 80 | continue 81 | if op == 'INDEX': 82 | temp_var = var 83 | lst = self.constant_handler(statement[1], var_d) 84 | index_num = statements[index][2] # next line 85 | index_node = self.constant_handler(index_num, var_d) 86 | node = ast.Subscript(lst, index_node, ctx=ast.Load()) 87 | var_d[temp_var] = node 88 | self.lineno += 1 89 | index += 1 90 | if index < len(statements): 91 | next_stmt = statements[index] 92 | if temp_var in next_stmt: 93 | continue 94 | expr_node = ast.Expr(node) 95 | code.append(expr_node) 96 | continue 97 | if op == 'GOTO': 98 | return code 99 | if op == 'PUSH-PARAM': 100 | params.append(var) 101 | continue 102 | if op == 'CALL': 103 | node = self.call_handler(statement, var_d, params) 104 | self.lineno += 1 105 | temp_var = statements[index][3] 106 | var_d[temp_var] = node 107 | index += 1 108 | if index < len(statements): 109 | next_stmt = statements[index] 110 | if temp_var in next_stmt: 111 | continue 112 | expr_node = ast.Expr(node) 113 | code.append(expr_node) 114 | continue 115 | self.handle_statement(statement, var_d, code) 116 | 117 | return code 118 | 119 | def handle_statement(self, statement, var_d, code): 120 | op, var = statement[0], statement[3] 121 | handlers = {'+': self.binary_handler, '-': self.binary_handler, '*': self.binary_handler, 122 | '/': self.binary_handler, 123 | 'OR': self.bool_handler, 'AND': self.bool_handler, '==': self.comp_handler, '>': self.comp_handler, 124 | '<': self.comp_handler, '<=': self.comp_handler, '>=': self.comp_handler, '!=': self.comp_handler, 125 | '=': self.assignment_handler, 126 | 'in': self.comp_handler, 127 | 'RETURN': self.return_handler, 128 | 'CONTINUE': self.continue_handler, 129 | 'BREAK': self.break_handler 130 | } 131 | node = handlers[op](statement, var_d) 132 | if var: 133 | var_d[var] = node 134 | 135 | if op != 'RETURN' and var and var.startswith(TEMP): 136 | # don't count temp vars 137 | return 138 | code.append(node) 139 | self.lineno += 1 140 | 141 | def constant_handler(self, constant, var_d): 142 | type_map = { 143 | int: 'int', 144 | str: 'str', 145 | bool: 'bool', 146 | float: 'float' 147 | } 148 | if constant in var_d: 149 | if not (constant.startswith(TEMP) or constant == 'ret'): 150 | return ast.Name(constant, ast.Load()) 151 | return var_d[constant] 152 | if DEBUG and isinstance(constant, str): 153 | print(f'const {constant} not found in {var_d}') 154 | return ast.Constant(constant, type_map[type(constant)]) 155 | 156 | def call_handler(self, statement, var_d, params): 157 | printd('CALL') 158 | num_params = statement[2] 159 | call_params = params[-num_params:] 160 | for _ in range(num_params): 161 | if params: params.pop() 162 | # method call 163 | if statement[1]: 164 | obj = self.constant_handler(statement[1], var_d) 165 | name = ast.Attribute(obj, statement[3], ast.Load()) 166 | else: 167 | name = ast.Name(statement[3], ast.Load()) 168 | for i, p in enumerate(call_params): 169 | call_params[i] = self.constant_handler(p, var_d) 170 | return ast.Call(name, call_params, [], lineno=self.lineno) 171 | 172 | def return_handler(self, statement, var_d): 173 | printd('RETURN') 174 | right_side = self.constant_handler(statement[3], var_d) 175 | return ast.Return(right_side) 176 | 177 | def continue_handler(self, statement=None, var_d=None): 178 | return ast.Continue() 179 | 180 | def break_handler(self, statement=None, var_d=None): 181 | return ast.Break() 182 | 183 | def binary_handler(self, statement, var_d): 184 | printd('BIN') 185 | op, left, right, var = statement 186 | op_map = { 187 | '+': ast.Add(), 188 | '-': ast.Sub(), 189 | '*': ast.Mult(), 190 | '/': ast.Div(), 191 | '%': ast.Mod() 192 | } 193 | left = self.constant_handler(left, var_d) 194 | right = self.constant_handler(right, var_d) 195 | return ast.BinOp(left, op_map[op], right) 196 | 197 | def assignment_handler(self, statement, var_d): 198 | _, expr, _, var = statement 199 | expr = self.constant_handler(expr, var_d) 200 | return ast.Assign([ast.Name(var, ast.Store())], expr, lineno=self.lineno) 201 | 202 | def bool_handler(self, statement, var_d): 203 | printd('BOOL') 204 | op, left, right, var = statement 205 | op_map = { 206 | 'AND': ast.And(), 207 | 'OR': ast.Or(), 208 | } 209 | left = self.constant_handler(left, var_d) 210 | right = self.constant_handler(right, var_d) 211 | return ast.BoolOp(op_map[op], [left, right]) 212 | 213 | def comp_handler(self, statement, var_d): 214 | op, left, right, var = statement 215 | op_map = { 216 | '>': ast.Gt(), 217 | '<': ast.Lt(), 218 | '>=': ast.GtE(), 219 | '<=': ast.LtE(), 220 | '==': ast.Eq(), 221 | '!=': ast.NotEq(), 222 | 'in': ast.In(), 223 | } 224 | left = self.constant_handler(left, var_d) 225 | right = self.constant_handler(right, var_d) 226 | return ast.Compare(left, [op_map[op]], [right]) 227 | 228 | def if_handler(self, statement, var_d, else_block, outer_code): 229 | printd('IF') 230 | cond = self.constant_handler(statement[1], var_d) 231 | if_block = statement[3] 232 | index = 0 233 | temp_variable_loader = [] 234 | while index < len(else_block): 235 | statement = else_block[index] 236 | op, var = statement[0], statement[3] 237 | if var and var.startswith(TEMP) and op != 'PUSH-PARAM': 238 | # need to load temp variables 239 | temp_variable_loader.append(statement) 240 | else: 241 | break 242 | index += 1 243 | self.convert(temp_variable_loader, var_d) 244 | body = self.convert(self.tac[if_block], var_d.copy()) 245 | else_block = self.convert(else_block[index:], var_d.copy()) 246 | 247 | # handles elif to prevent duplication 248 | if statement[0] == 'IF': 249 | else_block = else_block[:1] 250 | 251 | outer_code.append(ast.If(cond, body, else_block)) 252 | goto_block = self.tac[if_block][-1] 253 | goto_block = goto_block[3] if goto_block[0] == 'GOTO' else None 254 | # add goto code to outer code 255 | outer_code.extend(self.convert(self.tac.get(goto_block, []), var_d)) 256 | 257 | def while_handler(self, statement, var_d, outer_code): 258 | printd('WHILE') 259 | cond = self.constant_handler(statement[1], var_d) 260 | block = statement[3] 261 | body = self.convert(self.tac[block], var_d.copy()) 262 | 263 | outer_code.append(ast.While(cond, body, [])) 264 | goto_block = self.tac[block][-1] 265 | goto_block = goto_block[3] if goto_block[0] == 'GOTO' else None 266 | # add goto code to outer code 267 | outer_code.extend(self.convert(self.tac.get(goto_block, []), var_d)) 268 | 269 | def for_handler(self, statement, var_d, outer_code): 270 | printd('FOR') 271 | var, iterator, block = statement[1:] 272 | iterator = self.constant_handler(iterator, var_d) 273 | var_d[var] = var 274 | var = ast.Name(var, ast.Store()) 275 | body = self.convert(self.tac[block], var_d.copy()) 276 | outer_code.append(ast.For(var, iterator, body, [], lineno=self.lineno)) 277 | goto_block = self.tac[block][-1] 278 | goto_block = goto_block[3] if goto_block[0] == 'GOTO' else None 279 | # add goto code to outer code 280 | outer_code.extend(self.convert(self.tac.get(goto_block, []), var_d)) 281 | 282 | def function_hander(self, statement, var_d, outer_code): 283 | printd("FUNC") 284 | func_block = statement[3] 285 | func_code = self.tac.get(func_block, []) 286 | params = [] 287 | index = 0 288 | while index < len(func_code): 289 | statement = func_code[index] 290 | if statement[0] != 'ADD-PARAM': 291 | break 292 | param = statement[3] 293 | params.append(ast.arg(param)) 294 | var_d[param] = param 295 | index += 1 296 | printd(func_code[index:]) 297 | func_code = self.convert(func_code[index:], var_d) 298 | param_lst = ast.arguments([], params, [], [], [], [], []) 299 | node = ast.FunctionDef(func_block, param_lst, func_code, decorator_list=[], lineno=self.lineno) 300 | outer_code.append(node) 301 | 302 | def list_handler(self, statement, var_d, index, statements): 303 | lst = [] 304 | lst_name = statement[3] 305 | while index < len(statements): 306 | statement = statements[index] 307 | op, _, _, var = statement 308 | index += 1 309 | if op == 'END-LIST' and var == lst_name: 310 | break 311 | # nested list 312 | if op == 'START-LIST': 313 | _, index = self.list_handler(statement, var_d, index, statements) 314 | 315 | elif op == 'PUSH-ELMT': 316 | element = self.constant_handler(var, var_d) 317 | lst.append(element) 318 | else: 319 | next_index = index 320 | while statements[next_index][0] != 'PUSH-ELMT' and \ 321 | not (statements[next_index][0] == ['END-LIST'] and \ 322 | statements[next_index][3] == lst_name): 323 | next_index += 1 324 | self.convert(statements[index - 1:next_index], var_d) 325 | index = next_index 326 | var_d[lst_name] = ast.List(lst) 327 | return var_d[lst_name], index 328 | 329 | def get_ast(self): 330 | body = self.convert() 331 | return ast.Module(body, []) 332 | 333 | 334 | if __name__ == '__main__': 335 | argparser = argparse.ArgumentParser(description='Take in the python source code and parses it') 336 | argparser.add_argument('--FILE', help='Input file with python source code', required=False) 337 | args = argparser.parse_args() 338 | file = 'test_input.py' 339 | if args.FILE: 340 | file = args.FILE 341 | 342 | visitor = ASTVisitor() 343 | infile = open(file) 344 | parser = PythonParser() 345 | parser.build() 346 | tree = parser.get_ast(infile.read()) 347 | 348 | printd(ast.dump(tree, indent=4)) 349 | visitor.visit(tree) 350 | print(visitor.tac) 351 | 352 | optimizer = TACOptimizer(visitor.tac) 353 | opt_tac = optimizer.optimize_tac() 354 | 355 | converter = TACConverter(opt_tac) 356 | wrap = converter.get_ast() 357 | 358 | printd(ast.dump(wrap, indent=4)) 359 | print(f'Code:\n{ast.unparse(wrap)}') 360 | -------------------------------------------------------------------------------- /test_compiler.py: -------------------------------------------------------------------------------- 1 | """ 2 | Test suite for the python compiler. 3 | """ 4 | import ast 5 | import os 6 | import re 7 | from contextlib import redirect_stdout 8 | from io import StringIO 9 | 10 | # from code_to_tac import ASTVisitor 11 | from flatline import Flatliner 12 | # from python_parser import PythonParser 13 | # from tac_shorten import TACShortener 14 | # from tac_to_code import TACConverter 15 | 16 | 17 | def clean_contents(s: str) -> str: 18 | return re.sub(r'[()]', '', s) 19 | 20 | 21 | def compare_parser(test_input_filepath: str) -> None: 22 | """ 23 | Checks that the parser can parse the input file properly by ensuring that it will be able to 24 | reconstruct the source code. 25 | """ 26 | with open(test_input_filepath, 'r') as infile: 27 | file_contents = infile.read() 28 | 29 | parser = PythonParser() 30 | parser.build() 31 | ast_rep = parser.get_ast(file_contents) 32 | dump = ast.unparse(ast_rep) 33 | 34 | with open(f'./out/{test_input_filepath.split("/")[-1][:-3]}.ast.txt', 'w') as outfile: 35 | outfile.write(file_contents) 36 | outfile.write('\n----------------AST representation below-----------------\n') 37 | outfile.write(ast.dump(ast_rep, indent=4)) 38 | 39 | true_value = ast.unparse(ast.parse(file_contents)) 40 | 41 | # if dump != true_value: 42 | # print('Input:') 43 | # print(true_value) 44 | # print('Output:') 45 | # print(dump) 46 | assert clean_contents(dump) == clean_contents(true_value), f'parsing error in {test_input_filepath}' 47 | 48 | 49 | def get_exec_output(code_string: str) -> str: 50 | """ 51 | returns the exec output of the code string 52 | """ 53 | capture = StringIO() 54 | with redirect_stdout(capture): 55 | exec(code_string, {}) 56 | return capture.getvalue() 57 | 58 | 59 | FLATLINER = Flatliner() 60 | 61 | 62 | def check_unparse_result(test_input_filepath: str) -> None: 63 | with open(test_input_filepath, 'r') as infile: 64 | file_contents = infile.read() 65 | 66 | FLATLINER.ast = ast.parse(file_contents) 67 | 68 | result = FLATLINER.unparse() 69 | assert result.count('\n') == 0, 'output is not one line' # check that its one line 70 | assert result.count(';') == 0, 'output contains semicolons' # check that no semicolons are used 71 | 72 | original_output = get_exec_output(file_contents) 73 | new_output = get_exec_output(result) 74 | assert original_output == new_output, f'outputs different for {test_input_filepath}' 75 | 76 | with open(f'./out/{test_input_filepath.split("/")[-1][:-3]}.flattened.py', 'w') as outfile: 77 | outfile.write(f'# below is the 1 line version of the file "{test_input_filepath}"\n') 78 | outfile.write(result + '\n' * 3) 79 | outfile.write('# if you execute this file, the output will be the same as the original file,' 80 | ' which is as follows:') 81 | outfile.write('\n"""\n') 82 | outfile.write(new_output) 83 | outfile.write('"""\n') 84 | 85 | 86 | def tac_shortener(test_input_filepath: str) -> None: 87 | with open(test_input_filepath, 'r') as infile: 88 | file_contents = infile.read() 89 | parser = PythonParser() 90 | parser.build() 91 | # get ast from parser 92 | ast_rep = parser.get_ast(file_contents) 93 | # convert ast to tac 94 | visitor = ASTVisitor() 95 | visitor.visit(ast_rep) 96 | 97 | # Run shortener 98 | tac_shortener_optimizer = TACShortener(visitor.tac) 99 | tac_shortener_optimizer.optimize_tac() 100 | 101 | # write tac to file 102 | with open(f'./out/{test_input_filepath.split("/")[-1][:-3]}.optimized_tac.py', 'w') as outfile: 103 | outfile.write(f'tac = {repr(tac_shortener_optimizer.optimized_tac_statements)}') 104 | 105 | # convert tac back into ast 106 | tac_converter = TACConverter(tac_shortener_optimizer.optimized_tac_statements) 107 | final_ast = tac_converter.get_ast() 108 | # try to unparse the final ast to get to our target 109 | flatliner = Flatliner() 110 | flatliner.ast = final_ast 111 | result = flatliner.unparse() 112 | assert result.count('\n') == 0, 'output is not one line' # check that its one line 113 | assert result.count(';') == 0, 'output contains semicolons' # check that no semicolons are used 114 | 115 | original_output = get_exec_output(file_contents) 116 | new_output = get_exec_output(result) 117 | assert original_output == new_output, f'outputs different for {test_input_filepath} - optimizer' 118 | 119 | 120 | def end_to_end(test_input_filepath: str) -> None: 121 | with open(test_input_filepath, 'r') as infile: 122 | file_contents = infile.read() 123 | parser = PythonParser() 124 | parser.build() 125 | # get ast from parser 126 | ast_rep = parser.get_ast(file_contents) 127 | # convert ast to tac 128 | visitor = ASTVisitor() 129 | visitor.visit(ast_rep) 130 | # write tac to file 131 | with open(f'./out/{test_input_filepath.split("/")[-1][:-3]}.tac.py', 'w') as outfile: 132 | outfile.write(f'tac = {repr(visitor.tac)}') 133 | # convert tac back into ast 134 | tac_converter = TACConverter(visitor.tac) 135 | final_ast = tac_converter.get_ast() 136 | # try to unparse the final ast to get to our target 137 | flatliner = Flatliner() 138 | flatliner.ast = final_ast 139 | result = flatliner.unparse() 140 | assert result.count('\n') == 0, 'output is not one line' # check that its one line 141 | assert result.count(';') == 0, 'output contains semicolons' # check that no semicolons are used 142 | 143 | original_output = get_exec_output(file_contents) 144 | new_output = get_exec_output(result) 145 | assert original_output == new_output, f'outputs different for {test_input_filepath}' 146 | 147 | 148 | # def test_assignments(): 149 | # compare_parser('code_examples/assignments.py') 150 | 151 | 152 | # def test_loops(): 153 | # compare_parser('test_inputs/loops.py') 154 | 155 | 156 | # def test_ifs(): 157 | # compare_parser('test_inputs/ifs_test.py') 158 | 159 | 160 | # def test_comprehensive(): 161 | # compare_parser('test_inputs/comprehensive.py') 162 | 163 | 164 | # def test_parse_files(): 165 | # base = './code_examples' 166 | # ignored = ['everything_else.py', 'advanced_ifs_and_loops.py', 'loops_flow_control.py', 'inheritance.py', 167 | # 'multiple_assignment.py'] 168 | # for file in os.listdir(base): 169 | # if not any(file.endswith(ending) for ending in ignored): 170 | # compare_parser(base + '/' + file) 171 | 172 | 173 | def test_unparse_files_multiple(): 174 | base = './code_examples' 175 | ignored = [] 176 | for file in os.listdir(base): 177 | if not any(file.endswith(ending) for ending in ignored): 178 | check_unparse_result(base + '/' + file) 179 | print(f'> {file} {"-" * (33 - len(file))} Passed') 180 | assert FLATLINER.all_hit() 181 | 182 | 183 | # def test_tac_conversion(): 184 | # base = './tac_ast_examples' 185 | # ignored = ['everything_else.py'] 186 | # for file in os.listdir(base): 187 | # if not any(file.endswith(ending) for ending in ignored): 188 | # end_to_end(base + '/' + file) 189 | 190 | 191 | # def test_tac_shortener(): 192 | # base = './tac_ast_examples' 193 | # ignored = ['everything_else.py', 'unused_variables.py'] 194 | # for file in os.listdir(base): 195 | # if not any(file.endswith(ending) for ending in ignored): 196 | # tac_shortener(base + '/' + file) 197 | 198 | 199 | def run_tests() -> None: 200 | """ 201 | Runs all tests in this file if run as main. 202 | """ 203 | passed, failed = [], [] 204 | for name, func in globals().items(): 205 | if name.startswith('test') and callable(func): 206 | try: 207 | func() 208 | print(f'{name} {"-" * (35 - len(name))} Passed') 209 | except Exception as err: 210 | failed.append(f'{name}: {str(err)}') 211 | continue 212 | passed.append(name) 213 | if not failed: 214 | print(f'All {len(passed)} tests passed.') 215 | print('\nPlease see the "/out" directory for produced artifacts.') 216 | else: 217 | print(f'Total {len(passed) + len(failed)} tests, failed {len(failed)}:') 218 | for test in failed: 219 | print(test) 220 | exit(1) 221 | 222 | 223 | if __name__ == '__main__': 224 | run_tests() 225 | -------------------------------------------------------------------------------- /test_input.py: -------------------------------------------------------------------------------- 1 | if 5 < 4: 2 | a = 3 3 | -------------------------------------------------------------------------------- /test_inputs/comprehensive.py: -------------------------------------------------------------------------------- 1 | a = 5 2 | b = 6.0 3 | c = b + 5.4 4 | 5 | for thing in 5: 6 | other = 3 7 | for other in 6: 8 | me = 1 9 | 10 | while True: 11 | for thing in me: 12 | number = 5 13 | while False: 14 | while 5 < 7: 15 | other = 88 16 | 17 | if thing: 18 | x = 6 19 | 20 | if other: 21 | x = 60 22 | else: 23 | y = 7 24 | 25 | if me: 26 | x = 66 27 | elif you: 28 | thing = 89 29 | elif them: 30 | other = yes 31 | else: 32 | no = yes 33 | 34 | thing = a or b 35 | compilers = suffering and torture 36 | 37 | string = 'string' 38 | 39 | 40 | def test(a, b, c, d, e, f, g): 41 | thing = a 42 | for item in thing: 43 | do = 8 44 | while True: 45 | loop_stuff = 6 46 | return 9 47 | 48 | 49 | def func_no_return(a, b): 50 | b = 6 51 | 52 | 53 | def func(): 54 | return 6 55 | 56 | 57 | def harder_func(a, b, c): 58 | if a: 59 | return 4 60 | elif b: 61 | return 9 62 | return 0 63 | 64 | 65 | def return_in_loops(a, b): 66 | for i in a: 67 | return b 68 | for b in c: 69 | return a 70 | while 0: 71 | return 8 72 | while 1: 73 | while 2: 74 | return b 75 | return 9 76 | 77 | 78 | a = return_in_loops(1, 2) 79 | b = print() 80 | print() 81 | 82 | 83 | def test_func_calls_in_func(): 84 | if True: 85 | print(x) 86 | else: 87 | other_func(thing, 1 + 1) 88 | 89 | 90 | print(a + b, other(), one, return_in_loops(5 + 6, 2)) 91 | -------------------------------------------------------------------------------- /test_inputs/ifs_test.py: -------------------------------------------------------------------------------- 1 | if x: 2 | a = 6 3 | elif y: 4 | c = 5 5 | elif z: 6 | s = 6 7 | else: 8 | e = 9 9 | -------------------------------------------------------------------------------- /test_inputs/loops.py: -------------------------------------------------------------------------------- 1 | for thing in a: 2 | for other in b: 3 | a = 6 4 | 5 | while True: 6 | while False: 7 | c = 6 8 | for item in 5: 9 | t = 9 10 | --------------------------------------------------------------------------------