├── .coveragerc ├── .travis.yml ├── LICENSE ├── README.md ├── appveyor.yml ├── requirements.txt ├── setup.py ├── test_entryp.py └── tinypy ├── .gitignore ├── AST ├── __init__.py ├── ast.py ├── builder │ ├── Builder.py │ ├── ExprVisitor.py │ ├── StmtVisitor.py │ └── __init__.py ├── expr.py └── stmt.py ├── __init__.py ├── parser ├── CST.py ├── CustomLexer.py ├── Errors.py ├── TinyPy.g4 ├── Utils.py ├── __init__.py └── __pycache__ │ ├── CST.cpython-34.pyc │ ├── CST.cpython-35.pyc │ ├── CustomLexer.cpython-34.pyc │ ├── CustomLexer.cpython-35.pyc │ ├── CustomLexer.pypy3-23.pyc │ ├── CustomListener.cpython-34.pyc │ ├── CustomListener.cpython-35.pyc │ ├── Errors.cpython-34.pyc │ ├── Errors.cpython-35.pyc │ ├── Errors.pypy3-23.pyc │ ├── TinyPyLexer.cpython-35.pyc │ ├── TinyPyListener.cpython-35.pyc │ ├── TinyPyParser.cpython-35.pyc │ ├── TinyPyVisitor.cpython-35.pyc │ ├── Utils.cpython-34.pyc │ ├── Utils.cpython-35.pyc │ ├── __init__.cpython-34.pyc │ ├── __init__.cpython-35.pyc │ └── __init__.pypy3-23.pyc ├── run_tests.py ├── runtime ├── Errors.py ├── Memory.py └── __init__.py ├── shell ├── __init__.py └── shell.py ├── tests ├── 1.txt ├── 10.txt ├── 11.txt ├── 12.txt ├── 13.txt ├── 14.txt ├── 15.txt ├── 16.txt ├── 17.txt ├── 18.txt ├── 19.txt ├── 2.txt ├── 20.txt ├── 21.txt ├── 22.txt ├── 23.txt ├── 3.txt ├── 4.txt ├── 5.txt ├── 6.txt ├── 7.txt ├── 8.txt ├── 9.txt ├── binarysearch.py ├── ethiopian.py ├── euler04.py ├── euler38.py ├── factorial.py ├── fail │ ├── 1.txt │ ├── 2.txt │ ├── 3.txt │ ├── 4.txt │ ├── 5.txt │ ├── 6.txt │ ├── 7.txt │ ├── 8.txt │ └── 9.txt ├── fibo1.py ├── fibo2.py ├── fibo3.py ├── file.py ├── fizzbuzz.py ├── fizzbuzz2.py ├── flow1.py ├── gcd.py ├── logic.py ├── mergesort1.py ├── numbers1.py ├── parenbalance.py ├── parens.py ├── quicksort.py ├── scope1.py ├── scope2.py ├── shell │ ├── assignment.py │ ├── control_flow.py │ ├── debug.py │ ├── dedents.py │ ├── lcm.py │ └── statement_lists.py ├── trailing_dedents.py └── unicode1.py └── tinypyapp.py /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | parallel = True 3 | 4 | omit = 5 | # used only for debugs purposes 6 | tinypy/parser/CST.py 7 | tinypy/parser/Utils.py 8 | # auto-generated files 9 | tinypy/parser/TinyPy*.py 10 | 11 | source = 12 | tinypy 13 | 14 | [report] 15 | exclude_lines = 16 | # Don't complain if tests don't hit defensive assertion code: 17 | raise AssertionError 18 | raise NotImplementedError 19 | raise ValueError 20 | 21 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | matrix: 4 | allow_failures: 5 | - os: osx 6 | include: 7 | - python: 3.5 8 | os: linux 9 | - python: nightly 10 | os: linux 11 | - language: generic 12 | os: osx 13 | 14 | 15 | before_install: 16 | - curl -O http://www.antlr.org/download/antlr-4.5.1-complete.jar 17 | - antlr4='java -jar antlr-4.5.1-complete.jar' 18 | - if [ "$TRAVIS_OS_NAME" == "osx" ]; then brew update ; fi 19 | - if [ "$TRAVIS_OS_NAME" == "osx" ]; then brew install python3; fi 20 | - if [ "$TRAVIS_OS_NAME" == "osx" ]; then virtualenv --python=python3 env; fi 21 | - if [ "$TRAVIS_OS_NAME" == "osx" ]; then source env/bin/activate; fi 22 | - python --version; pip --version 23 | 24 | # command to install dependencies 25 | install: 26 | - "pip install coverage" 27 | - "pip install coveralls" 28 | - "pip install -r requirements.txt" 29 | - "$antlr4 -visitor tinypy/parser/TinyPy.g4" 30 | - "pip install ." 31 | # Add subprocess support for coverage 32 | - SPACK=$(python -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())") 33 | - printf "\nimport coverage; coverage.process_startup()\n" >> "$SPACK/sitecustomize.py" 34 | 35 | # command to run tests 36 | script: 37 | - "coverage run setup.py test" 38 | - "coverage combine" 39 | - "coverage report" 40 | 41 | after_success: 42 | coveralls 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Max Malysh 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Unix Build Status](https://travis-ci.org/maxmalysh/tiny-py-interpreter.svg)](https://travis-ci.org/maxmalysh/tiny-py-interpreter) 2 | [![Windows Build status](https://ci.appveyor.com/api/projects/status/github/maxmalysh/tiny-py-interpreter?svg=true)](https://ci.appveyor.com/project/maxmalysh/tiny-py-interpreter) 3 | [![Coverage Status](https://coveralls.io/repos/maxmalysh/tiny-py-interpreter/badge.svg?branch=master&service=github)](https://coveralls.io/github/maxmalysh/tiny-py-interpreter?branch=master) 4 | 5 | # TinyPy Interpreter 6 | 7 | ## About 8 | TinyPy is an interpreter of a small Python subset I have written as a coursework. 9 | 10 | ## Installation 11 | This project uses ANTLR4 as a parser generator. To run the interpreter, you will need to install ANTLR4 Python3 runtime and ANTLR itself. 12 | 13 | Please note, that 4.5.2 runtime has [a bug which results in a dramatic performance dropdown][3]. 14 | At the moment this text was written, pypi had an older version, so it's recommended to install ANTLR4 runtime manually. 15 | 16 | Step-by-step instruction: 17 | 18 | 1. Install [ANTLR4][1] 19 | 2. Install ANTLR4 Python3 runtime: 20 | 1. `git clone https://github.com/antlr/antlr4` 21 | 2. `cd antlr4/runtime/Python3` 22 | 3. `python3 setup.py install` 23 | 24 | It's also possible to use pip, package name is `antlr4-python3-runtime`. Be aware of the bug described above. 25 | 3. Generate parser 26 | 1. cd `tiny-py-interpreter/tinypy` 27 | 2. `antlr4 -visitor parser/TinyPy.g4` 28 | 4. Install tinypy: `pip3 install .` 29 | 5. Try to launch some tests: `python3 setup.py test` 30 | 31 | 32 | [1]: http://www.antlr.org 33 | [2]: https://github.com/antlr/antlr4 34 | [3]: http://stackoverflow.com/questions/31455500/slow-antlr4-generated-parser-in-python-but-fast-in-java -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | os: Windows Server 2012 3 | 4 | matrix: 5 | - PYTHON: C:\\Python35 6 | - PYTHON: C:\\Python35-x64 7 | 8 | install: 9 | - ps: wget 'http://www.antlr.org/download/antlr-4.5.1-complete.jar' -OutFile antlr4.jar 10 | - "java -jar antlr4.jar -visitor tinypy\\parser\\TinyPy.g4" 11 | - "dir tinypy\\parser" 12 | - "%PYTHON%\\python.exe -m pip install -r requirements.txt" 13 | - "%PYTHON%\\python.exe setup.py install" 14 | 15 | build: off 16 | 17 | test_script: 18 | - "%PYTHON%\\python.exe setup.py test" 19 | 20 | # docs here: http://www.appveyor.com/docs/appveyor-yml 21 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | antlr4-python3-runtime>=4.5.2 2 | coverage>=4.0.3 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup( 4 | name='TinyPy Interpreter', 5 | version='0.4', 6 | author='Max Malysh', 7 | author_email='iam@maxmalysh.com', 8 | description='Interpreter of a small Python subset I have made as a coursework. ', 9 | long_description=open('README.md').read(), 10 | classifiers=[ 11 | "Development Status :: 3 - Alpha", 12 | 'Intended Audience :: Developers', 13 | 'Programming Language :: Python :: 3.5', 14 | ], 15 | install_requires = [ 'setuptools-git' ], 16 | include_package_data = True, 17 | packages=find_packages(), 18 | entry_points = { 19 | 'console_scripts' : [ 'tinypy = tinypy.tinypyapp:main'] 20 | }, 21 | test_suite = 'tinypy.run_tests.get_suite', 22 | 23 | ) 24 | -------------------------------------------------------------------------------- /test_entryp.py: -------------------------------------------------------------------------------- 1 | # This script is needed for subprocess launched during tests; 2 | # this way subprocess will not use tinypy package from the site-packages directory 3 | from tinypy.tinypyapp import main 4 | if __name__ == '__main__': 5 | main() 6 | -------------------------------------------------------------------------------- /tinypy/.gitignore: -------------------------------------------------------------------------------- 1 | parser_notes.md 2 | .DS_Store 3 | /parser/TinyPy.tokens 4 | /parser/TinyPyLexer.py 5 | /parser/TinyPyLexer.tokens 6 | /parser/TinyPyListener.py 7 | /parser/TinyPyParser.py 8 | /parser/TinyPyVisitor.py 9 | -------------------------------------------------------------------------------- /tinypy/AST/__init__.py: -------------------------------------------------------------------------------- 1 | from . import expr 2 | from . import stmt 3 | -------------------------------------------------------------------------------- /tinypy/AST/ast.py: -------------------------------------------------------------------------------- 1 | # 2 | # Some useful stuff here: 3 | # http://greentreesnakes.readthedocs.org/en/latest/index.html 4 | # https://docs.python.org/3/reference/expressions.html#calls 5 | # https://docs.python.org/3/reference/executionmodel.html#naming 6 | # 7 | from enum import Enum 8 | 9 | 10 | class AST(object): 11 | def eval(self): 12 | raise NotImplementedError() 13 | 14 | 15 | """ Input types """ 16 | 17 | class Module(AST): 18 | def __init__(self, body:[]): 19 | super().__init__() 20 | self.body = body 21 | 22 | def eval(self): 23 | if type(self.body) is not list: 24 | self.body.eval() 25 | for stmt in self.body: 26 | stmt.eval() 27 | 28 | 29 | class Interactive(AST): 30 | def __init__(self, body:[]): 31 | super().__init__() 32 | self.body = body 33 | 34 | def eval(self): 35 | if type(self.body) is not list: 36 | return self.body.eval() 37 | else: 38 | return [stmt.eval() for stmt in self.body] 39 | 40 | 41 | class EvalExpression(AST): 42 | def __init__(self, body): 43 | super().__init__() 44 | self.body = body 45 | 46 | def eval(self): 47 | return self.body.eval() 48 | 49 | 50 | """ Base node types """ 51 | 52 | class Expression(AST): 53 | def __init__(self): 54 | super().__init__() 55 | 56 | 57 | class Statement(AST): 58 | def __init__(self): 59 | super().__init__() 60 | 61 | 62 | """ Memory context for names, attributes, indexes, et.c. """ 63 | class MemoryContext(Enum): 64 | Load = 1 65 | Store = 2 66 | Del = 3 67 | -------------------------------------------------------------------------------- /tinypy/AST/builder/Builder.py: -------------------------------------------------------------------------------- 1 | from tinypy.AST.builder.ExprVisitor import ExprVisitorMixin 2 | from tinypy.AST.builder.StmtVisitor import StmtVisitorMixin 3 | 4 | from tinypy.parser.TinyPyParser import TinyPyParser 5 | from tinypy.parser.TinyPyVisitor import TinyPyVisitor 6 | 7 | from tinypy.AST import ast 8 | 9 | class CustomVisitor(StmtVisitorMixin, ExprVisitorMixin, TinyPyVisitor): 10 | 11 | # 12 | # Visit parse tree produced from a file 13 | # 14 | def visitFile_input(self, ctx:TinyPyParser.File_inputContext): 15 | statements = [] 16 | 17 | for stmt in ctx.stmt(): 18 | statement = self.visit(stmt) 19 | if statement != None: 20 | if type(statement) is list: 21 | statements += statement 22 | else: 23 | statements.append(statement) 24 | 25 | return ast.Module(body=statements) 26 | 27 | 28 | # 29 | # Single input is used both in interpreter mode and with strings passes as a parameter 30 | # 31 | def visitSingle_input(self, ctx:TinyPyParser.Single_inputContext): 32 | if ctx.compound_stmt() != None: 33 | return ast.Interactive(self.visit(ctx.compound_stmt())) 34 | 35 | elif ctx.simple_stmt() != None: 36 | return ast.Interactive(self.visit(ctx.simple_stmt())) 37 | 38 | return None 39 | 40 | # 41 | # Visit single expression (call to the eval() function) 42 | # 43 | def visitEval_input(self, ctx:TinyPyParser.Eval_inputContext): 44 | return ast.EvalExpression(self.visit(ctx.test())) 45 | 46 | 47 | -------------------------------------------------------------------------------- /tinypy/AST/builder/ExprVisitor.py: -------------------------------------------------------------------------------- 1 | from tinypy.parser.TinyPyParser import TinyPyParser 2 | from tinypy.parser.TinyPyVisitor import TinyPyVisitor 3 | 4 | from tinypy import AST 5 | from tinypy.AST.ast import MemoryContext 6 | 7 | class ExprVisitorMixin(TinyPyVisitor): 8 | 9 | # 10 | # Tests (comparisons) 11 | # 12 | 13 | def visitComparison(self, ctx:TinyPyParser.ComparisonContext): 14 | left = self.visit(ctx.test(0)) 15 | right = self.visit(ctx.test(1)) 16 | op = ctx.comp_op().getText() 17 | 18 | firstSymbolType = ctx.comp_op().children[0].symbol.type 19 | 20 | if firstSymbolType == TinyPyParser.IN: 21 | op = AST.expr.Compare.Op.IN 22 | elif firstSymbolType == TinyPyParser.IS: 23 | op = AST.expr.Compare.Op.IS 24 | 25 | if len(ctx.comp_op().children) == 2: 26 | secondSymbolType = ctx.comp_op().children[1].symbol.type 27 | 28 | if firstSymbolType == TinyPyParser.NOT and secondSymbolType == TinyPyParser.IN: 29 | op = AST.expr.Compare.Op.NOT_IN 30 | elif firstSymbolType == TinyPyParser.IS and secondSymbolType == TinyPyParser.NOT: 31 | op = AST.expr.Compare.Op.IS_NOT 32 | else: 33 | raise ValueError("Unexpected binary comparison operation") 34 | 35 | return AST.expr.BinaryComp(left=left, right=right, op=op) 36 | 37 | def visitNotTest(self, ctx:TinyPyParser.NotTestContext): 38 | test = self.visit(ctx.test()) 39 | return AST.expr.UnaryComp(operand=test, op=AST.expr.Compare.Op.NOT) 40 | 41 | def visitAndTest(self, ctx:TinyPyParser.AndTestContext): 42 | left = self.visit(ctx.test(0)) 43 | right = self.visit(ctx.test(1)) 44 | return AST.expr.BinaryComp(left=left, right=right, op=AST.expr.Compare.Op.AND) 45 | 46 | def visitOrTest(self, ctx:TinyPyParser.AndTestContext): 47 | left = self.visit(ctx.test(0)) 48 | right = self.visit(ctx.test(1)) 49 | return AST.expr.BinaryComp(left=left, right=right, op=AST.expr.Compare.Op.OR) 50 | 51 | # 52 | # Arithmetic (@expr rule) 53 | # 54 | 55 | binaryExprTable = { 56 | TinyPyParser.ADD : AST.expr.AddOp, 57 | TinyPyParser.MINUS : AST.expr.SubOp, 58 | TinyPyParser.STAR : AST.expr.MultOp, 59 | TinyPyParser.DIV : AST.expr.DivOp, 60 | TinyPyParser.MOD : AST.expr.ModOp, 61 | TinyPyParser.LEFT_SHIFT : AST.expr.LshiftOp, 62 | TinyPyParser.RIGHT_SHIFT : AST.expr.RshiftOp, 63 | TinyPyParser.AND_OP : AST.expr.BitAndOp, 64 | TinyPyParser.XOR : AST.expr.BitXorOp, 65 | TinyPyParser.OR_OP : AST.expr.BitOrOp, 66 | } 67 | 68 | def visitGenericExpr(self, ctx): 69 | left = self.visit(ctx.expr(0)) 70 | right = self.visit(ctx.expr(1)) 71 | 72 | try: 73 | return ExprVisitorMixin.binaryExprTable[ctx.op.type](left, right) 74 | except KeyError: 75 | raise ValueError("Unexpected op type") 76 | 77 | def visitMulDivMod(self, ctx:TinyPyParser.MulDivModContext): 78 | return self.visitGenericExpr(ctx) 79 | 80 | def visitAddSub(self, ctx:TinyPyParser.AddSubContext): 81 | return self.visitGenericExpr(ctx) 82 | 83 | def visitShifts(self, ctx:TinyPyParser.ShiftsContext): 84 | return self.visitGenericExpr(ctx) 85 | 86 | def visitBitAnd(self, ctx:TinyPyParser.BitAndContext): 87 | return self.visitGenericExpr(ctx) 88 | 89 | def visitBitXor(self, ctx:TinyPyParser.BitXorContext): 90 | return self.visitGenericExpr(ctx) 91 | 92 | def visitBitOr(self, ctx:TinyPyParser.BitOrContext): 93 | return self.visitGenericExpr(ctx) 94 | 95 | # 96 | # Factor rule 97 | # 98 | 99 | def visitUnaryExpr(self, ctx:TinyPyParser.UnaryExprContext): 100 | operand = ctx.factor().accept(self) 101 | return AST.expr.UnaryOp(op=ctx.op.text, operand=operand) 102 | 103 | 104 | def visitParenExpr(self, ctx:TinyPyParser.ParenExprContext): 105 | return self.visit(ctx.test()) 106 | 107 | 108 | def visitAtom(self, ctx:TinyPyParser.AtomContext): 109 | if ctx.NONE() != None: 110 | return AST.expr.NameConstant('None') 111 | elif ctx.TRUE() != None: 112 | return AST.expr.NameConstant('True') 113 | elif ctx.FALSE() != None: 114 | return AST.expr.NameConstant('False') 115 | 116 | # Visit other nodes 117 | return self.visitChildren(ctx) 118 | 119 | 120 | # 121 | # Name access: PlainName, FuncInvoke, SubName 122 | # 123 | 124 | def nameContextFor(self, ctx): 125 | if type(ctx.parentCtx) is TinyPyParser.ExprStmtAssignContext or type(ctx.parentCtx) is TinyPyParser.ExprStmtAugmentedContext: 126 | return MemoryContext.Store 127 | else: 128 | return MemoryContext.Load 129 | 130 | 131 | def visitPlainName(self, ctx:TinyPyParser.PlainNameContext): 132 | context = self.nameContextFor(ctx) 133 | return AST.expr.Name(id=ctx.NAME().getText(), ctx=context) 134 | 135 | 136 | def visitFuncInvoke(self, ctx:TinyPyParser.FuncInvokeContext): 137 | funcName = self.visit(ctx.nameaccess()) 138 | args = [] 139 | 140 | if ctx.arglist() != None: 141 | for argStmt in ctx.arglist().test(): 142 | arg = self.visit(argStmt) 143 | if arg != None: 144 | args.append(arg) 145 | 146 | return AST.expr.CallExpr(func=funcName, args=args) 147 | 148 | 149 | def visitDottedName(self, ctx:TinyPyParser.DottedNameContext): 150 | left = self.visit(ctx.nameaccess()) 151 | attrName = ctx.NAME().getText() 152 | return AST.stmt.Attribute(value=left, attr=attrName, ctx=MemoryContext.Load) 153 | 154 | 155 | def visitSubName(self, ctx:TinyPyParser.SubNameContext): 156 | leftNode = self.visit(ctx.nameaccess()) 157 | subscript = self.visit(ctx.subscript()) 158 | 159 | context = self.nameContextFor(ctx) 160 | 161 | return AST.stmt.Subscript(value=leftNode, slice=subscript, ctx=context) 162 | 163 | 164 | # 165 | # Index and slice operations 166 | # 167 | 168 | def visitSubscriptIndex(self, ctx:TinyPyParser.SubscriptIndexContext): 169 | test = self.visit(ctx.test()) 170 | return AST.stmt.Index(value=test) 171 | 172 | def visitSubscriptSlice(self, ctx:TinyPyParser.SubscriptSliceContext): 173 | lower = upper = None 174 | 175 | if ctx.lower != None: 176 | lower = self.visit(ctx.lower) 177 | 178 | if ctx.upper != None: 179 | upper = self.visit(ctx.upper) 180 | 181 | return AST.stmt.Slice(lower=lower, upper=upper, step=None) 182 | # 183 | # Collection definitions 184 | # 185 | 186 | def visitDictorsetmaker(self, ctx:TinyPyParser.DictorsetmakerContext): 187 | if ctx.dictormaker() != None: 188 | return self.visit(ctx.dictormaker()) 189 | 190 | if ctx.setmaker() != None: 191 | return self.visit(ctx.setmaker()) 192 | 193 | def visitDictMaker(self, ctx:TinyPyParser.DictMakerContext): 194 | if ctx.dictorsetmaker() != None: 195 | return self.visit(ctx.dictorsetmaker()) 196 | 197 | return AST.expr.DictContainer({}) 198 | 199 | def visitSetmaker(self, ctx:TinyPyParser.SetmakerContext): 200 | result = set({}) 201 | for test in ctx.test(): 202 | result.add(self.visit(test)) 203 | return AST.expr.SetContainer(result) 204 | 205 | 206 | def visitDictormaker(self, ctx:TinyPyParser.DictormakerContext): 207 | if ctx.test(0) != None: 208 | left = self.visit(ctx.test(0)) 209 | right = self.visit(ctx.test(1)) 210 | return AST.expr.DictContainer({left : right}) 211 | 212 | if ctx.dictormaker(0) != None: 213 | left = self.visit(ctx.dictormaker(0)) 214 | right = self.visit(ctx.dictormaker(1)) 215 | 216 | result = left.copy() 217 | result.update(right) 218 | 219 | if type(result) is not AST.expr.DictContainer: 220 | return AST.expr.DictContainer(result) 221 | else: 222 | return result 223 | 224 | def visitListMaker(self, ctx:TinyPyParser.ListMakerContext): 225 | if ctx.testlist_comp() == None: 226 | return AST.expr.ListContainer([]) 227 | 228 | return AST.expr.ListContainer(self.visit(ctx.testlist_comp())) 229 | 230 | def visitTupleMaker(self, ctx:TinyPyParser.TupleMakerContext): 231 | if ctx.testlist_comp() == None: 232 | return AST.expr.TupleContainer(()) 233 | 234 | return AST.expr.TupleContainer(tuple(self.visit(ctx.testlist_comp()))) 235 | 236 | def visitTestlist_comp(self, ctx:TinyPyParser.Testlist_compContext): 237 | if ctx.test() != None: 238 | return [self.visit(ctx.test())] 239 | 240 | if ctx.testlist_comp(1) == None: 241 | return self.visit(ctx.testlist_comp(0)) 242 | 243 | left = self.visit(ctx.testlist_comp(0)) 244 | right = self.visit(ctx.testlist_comp(1)) 245 | result = [] 246 | 247 | if type(left) is list: 248 | result += left 249 | else: 250 | result.append(left) 251 | 252 | if type(right) is list: 253 | result += right 254 | else: 255 | result.append(right) 256 | 257 | return result 258 | 259 | # 260 | # Strings and numbers 261 | # 262 | 263 | def visitNumber(self, ctx:TinyPyParser.NumberContext): 264 | if ctx.integer() != None: 265 | return self.visit(ctx.integer()) 266 | 267 | elif ctx.FLOAT_NUMBER() != None: 268 | number = float(ctx.FLOAT_NUMBER().getText()) 269 | return AST.expr.Num(number) 270 | 271 | raise ValueError() 272 | 273 | def visitInteger(self, ctx:TinyPyParser.IntegerContext): 274 | if ctx.DECIMAL_INTEGER() != None: 275 | decimal = int(ctx.DECIMAL_INTEGER().getText()) 276 | return AST.expr.Num(decimal) 277 | 278 | elif ctx.HEX_INTEGER() != None: 279 | hex = int(ctx.HEX_INTEGER().getText(), 16) 280 | return AST.expr.Num(hex) 281 | 282 | elif ctx.BIN_INTEGER() != None: 283 | bin = int(ctx.BIN_INTEGER().getText(), 2) 284 | return AST.expr.Num(bin) 285 | 286 | elif ctx.OCT_INTEGER() != None: 287 | oct = int(ctx.OCT_INTEGER().getText(), 8) 288 | return AST.expr.Num(oct) 289 | 290 | raise ValueError() 291 | 292 | def visitString(self, ctx:TinyPyParser.StringContext): 293 | node = ctx.STRING_LITERAL() 294 | if node != None: 295 | text = node.getText()[1:-1] 296 | return AST.expr.Str(text) 297 | 298 | raise ValueError() 299 | -------------------------------------------------------------------------------- /tinypy/AST/builder/StmtVisitor.py: -------------------------------------------------------------------------------- 1 | from tinypy.parser.TinyPyParser import TinyPyParser 2 | from tinypy.parser.TinyPyVisitor import TinyPyVisitor 3 | 4 | from tinypy import AST 5 | from tinypy import runtime 6 | 7 | class StmtVisitorMixin(TinyPyVisitor): 8 | 9 | # 10 | # Base statements 11 | # 12 | def visitSimple_stmt(self, ctx:TinyPyParser.Simple_stmtContext): 13 | statements = [] 14 | 15 | for smallStmt in ctx.small_stmt(): 16 | statement = self.visit(smallStmt) 17 | if statement != None: 18 | statements.append(statement) 19 | 20 | return statements 21 | 22 | # 23 | # Compound statements 24 | # 25 | def visitSuite(self, ctx:TinyPyParser.SuiteContext): 26 | if ctx.simple_stmt() != None: 27 | return self.visit(ctx.simple_stmt()) 28 | 29 | statements = [] 30 | 31 | for stmt in ctx.stmt(): 32 | if stmt.simple_stmt() != None: 33 | statements += self.visit(stmt.simple_stmt()) 34 | else: 35 | statements.append(self.visit(stmt)) 36 | 37 | return statements 38 | 39 | 40 | def visitIf_stmt(self, ctx:TinyPyParser.If_stmtContext): 41 | test = self.visit(ctx.test()) 42 | suite = self.visit(ctx.suite()) 43 | orelse = [] 44 | 45 | if ctx.if_else() != None: 46 | orelse = self.visit(ctx.if_else().suite()) 47 | 48 | if ctx.if_elif() != None and len(ctx.if_elif()) >= 1: 49 | elifNodes = ctx.if_elif().copy() 50 | elifNodes.reverse() 51 | 52 | for node in elifNodes: 53 | nodeTest = self.visit(node.test()) 54 | nodeSuite = self.visit(node.suite()) 55 | 56 | orelse = [AST.stmt.IfStmt(test=nodeTest, body=nodeSuite, orelse=orelse)] 57 | 58 | return AST.stmt.IfStmt(test=test, body=suite, orelse=orelse) 59 | 60 | 61 | def visitWhile_stmt(self, ctx:TinyPyParser.While_stmtContext): 62 | test = self.visit(ctx.test()) 63 | suite = self.visit(ctx.suite()) 64 | 65 | return AST.stmt.WhileStmt(test=test, body=suite, orelse=[]) 66 | 67 | 68 | def visitFor_stmt(self, ctx:TinyPyParser.For_stmtContext): 69 | expr = self.visit(ctx.nameaccess()) 70 | test = self.visit(ctx.test()) 71 | suite = self.visit(ctx.suite()) 72 | 73 | return AST.stmt.ForStmt(target=expr, iter=test, body=suite) 74 | 75 | 76 | def visitFuncdef(self, ctx:TinyPyParser.FuncdefContext): 77 | name = ctx.NAME().getText() 78 | suite = self.visit(ctx.suite()) 79 | 80 | param_ctx = ctx.parameters().param_argslist() 81 | params = [] 82 | 83 | if param_ctx != None: 84 | for argName in param_ctx.NAME(): 85 | params.append(argName.getText()) 86 | 87 | return AST.stmt.FunctionDef(name=name, args=params, body=suite) 88 | 89 | # 90 | # Small statements 91 | # 92 | def visitExprStmtAssign(self, ctx:TinyPyParser.ExprStmtAssignContext): 93 | name = self.visit(ctx.nameaccess()) 94 | expr = self.visit(ctx.test()) 95 | 96 | return AST.stmt.AssignStmt(target=name, value=expr) 97 | 98 | 99 | def visitExprStmtAugmented(self, ctx:TinyPyParser.ExprStmtAugmentedContext): 100 | name = self.visit(ctx.nameaccess()) 101 | value = self.visit(ctx.test()) 102 | op = ctx.augassign().getText() 103 | 104 | return AST.stmt.AugAssignStmt(name=name, value=value, op=op) 105 | 106 | # 107 | # Control flow statements 108 | # 109 | def visitReturn_stmt(self, ctx:TinyPyParser.Return_stmtContext): 110 | test = None 111 | 112 | validParents = (TinyPyParser.FuncdefContext, ) 113 | 114 | if not self.validContextParents(ctx, validParents): 115 | raise runtime.Errors.SyntaxError("'return' outside function") 116 | 117 | if ctx.test() != None: 118 | test = self.visit(ctx.test()) 119 | 120 | return AST.stmt.ReturnStmt(expr=test) 121 | 122 | 123 | def visitPass_stmt(self, ctx:TinyPyParser.Pass_stmtContext): 124 | return AST.stmt.PassStmt() 125 | 126 | 127 | def visitBreak_stmt(self, ctx:TinyPyParser.Break_stmtContext): 128 | validParents = TinyPyParser.For_stmtContext, TinyPyParser.While_stmtContext 129 | 130 | if not self.validContextParents(ctx, validParents): 131 | raise runtime.Errors.SyntaxError("'break' outside loop") 132 | 133 | return AST.stmt.BreakStmt() 134 | 135 | 136 | def visitContinue_stmt(self, ctx:TinyPyParser.Continue_stmtContext): 137 | validParents = TinyPyParser.For_stmtContext, TinyPyParser.While_stmtContext 138 | 139 | if not self.validContextParents(ctx, validParents): 140 | raise runtime.Errors.SyntaxError("'continue' outside loop") 141 | 142 | return AST.stmt.ContinueStmt() 143 | 144 | # 145 | # Check whether context has one of the specified proper parents 146 | # 147 | def validContextParents(self, context, properParents:tuple): 148 | context = context.parentCtx 149 | 150 | while context != None: 151 | context = context.parentCtx 152 | if isinstance(context, properParents): 153 | return True 154 | 155 | return False -------------------------------------------------------------------------------- /tinypy/AST/builder/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tinypy/AST/expr.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | import operator 3 | 4 | from tinypy.AST.ast import Expression, MemoryContext 5 | from tinypy import runtime 6 | 7 | 8 | """ 9 | # Binary arithmetic, bitwise and logic operations 10 | """ 11 | class BinOp(Expression): 12 | def __init__(self, left:Expression, right:Expression): 13 | super().__init__() 14 | self.left = left 15 | self.right = right 16 | 17 | class AddOp(BinOp): 18 | def eval(self): 19 | return self.left.eval() + self.right.eval() 20 | 21 | class SubOp(BinOp): 22 | def eval(self): 23 | return self.left.eval() - self.right.eval() 24 | 25 | class MultOp(BinOp): 26 | def eval(self): 27 | return self.left.eval() * self.right.eval() 28 | 29 | class DivOp(BinOp): 30 | def eval(self): 31 | left = self.left.eval() 32 | right = self.right.eval() 33 | 34 | if right == 0: 35 | raise runtime.Errors.ZeroDivisionError() 36 | 37 | return left / right 38 | 39 | class ModOp(BinOp): 40 | def eval(self): 41 | left = self.left.eval() 42 | right = self.right.eval() 43 | 44 | if right == 0: 45 | raise runtime.Errors.ZeroDivisionError() 46 | 47 | return left % right 48 | 49 | class LshiftOp(BinOp): 50 | def eval(self): 51 | return self.left.eval() << self.right.eval() 52 | 53 | class RshiftOp(BinOp): 54 | def eval(self): 55 | return self.left.eval() >> self.right.eval() 56 | 57 | class BitAndOp(BinOp): 58 | def eval(self): 59 | return self.left.eval() & self.right.eval() 60 | 61 | class BitXorOp(BinOp): 62 | def eval(self): 63 | return self.left.eval() ^ self.right.eval() 64 | 65 | class BitOrOp(BinOp): 66 | def eval(self): 67 | return self.left.eval() | self.right.eval() 68 | 69 | 70 | """ 71 | # Unary arithmetic operations 72 | """ 73 | class UnaryOp(Expression): 74 | def __init__(self, op, operand:Expression): 75 | super().__init__() 76 | self.op = op 77 | self.operand = operand 78 | 79 | def eval(self): 80 | if self.op == '+': 81 | return self.operand.eval() 82 | elif self.op == '-': 83 | return -(self.operand.eval()) 84 | else: 85 | raise ValueError('Unsupported unary operation!') 86 | 87 | 88 | """ 89 | # Base class for comparisons. 90 | """ 91 | class Compare(Expression): 92 | 93 | class Op(Enum): 94 | AND = 1 95 | OR = 2 96 | NOT = 3 97 | IN = 4 98 | IS = 5 99 | NOT_IN = 6 100 | IS_NOT = 7 101 | 102 | opTable = { 103 | '<' : operator.lt, 104 | '>' : operator.gt, 105 | '==' : operator.eq, 106 | '>=' : operator.ge, 107 | '<=' : operator.le, 108 | '!=' : operator.ne, 109 | Op.AND : operator.__and__, 110 | Op.OR : operator.__or__, 111 | Op.NOT : operator.__not__, 112 | Op.IS : operator.is_, 113 | Op.IS_NOT : operator.is_not, 114 | } 115 | 116 | def __init__(self, op): 117 | super().__init__() 118 | self.op = op 119 | 120 | class BinaryComp(Compare): 121 | def __init__(self, left, right, op): 122 | super().__init__(op=op) 123 | self.left = left 124 | self.right = right 125 | 126 | def eval(self): 127 | left = self.left.eval() 128 | right = self.right.eval() 129 | 130 | if self.op == Compare.Op.IN: 131 | return left in right 132 | elif self.op == Compare.Op.NOT_IN: 133 | return left not in right 134 | 135 | return Compare.opTable[self.op](left, right) 136 | 137 | class UnaryComp(Compare): 138 | def __init__(self, operand, op): 139 | super().__init__(op=op) 140 | self.operand = operand 141 | 142 | def eval(self): 143 | operand = self.operand.eval() 144 | return Compare.opTable[self.op](operand) 145 | 146 | 147 | """ 148 | # Represents None, False and True literals. 149 | """ 150 | class NameConstant(Expression): 151 | nameTable = { 'None' : None, 'True': True, 'False': False } 152 | 153 | def __init__(self, name): 154 | super().__init__() 155 | self.name = name 156 | 157 | def eval(self): 158 | try: 159 | return NameConstant.nameTable[self.name] 160 | except KeyError: 161 | raise ValueError("Wrong name constant") 162 | 163 | """ 164 | # A variable name. 165 | # @id holds the name as a string 166 | # @ctx is one of the following types: @Load / @Store / @Del 167 | """ 168 | class Name(Expression): 169 | 170 | def __init__(self, id, ctx:MemoryContext): 171 | super().__init__() 172 | self.id = id 173 | self.ctx = ctx 174 | 175 | def eval(self): 176 | if self.ctx == MemoryContext.Load: 177 | return self.getNamespace().get(name=self.id) 178 | elif self.ctx == MemoryContext.Store: 179 | return self.id 180 | else: 181 | raise NotImplementedError() 182 | 183 | def getNamespace(self): 184 | # Problem: we're very loosely coupled. 185 | return runtime.Memory.CurrentNamespace 186 | 187 | 188 | """ 189 | # Function call 190 | # @param func is the function, which will often be a Name object. 191 | # @args holds a list of the arguments passed by position. 192 | """ 193 | class CallExpr(Expression): 194 | def __init__(self, func, args): 195 | super().__init__() 196 | self.func = func # name 197 | self.args = args 198 | 199 | def eval(self): 200 | func = self.func.eval() 201 | evalArgs = [ arg.eval() for arg in self.args ] 202 | return func(*evalArgs) 203 | 204 | 205 | """ 206 | # Base class for collections. 207 | # @value holds a collection, such as a list or a dict. 208 | # 209 | # This class delegates common collection methods to the contained value. 210 | """ 211 | class CollectionContainer(Expression): 212 | def __init__(self, value): 213 | super().__init__() 214 | self.value = value 215 | 216 | def __repr__(self): 217 | return self.value.__repr__() 218 | 219 | def __iter__(self): 220 | return iter(self.value) 221 | 222 | def __getitem__(self, item): 223 | return self.value.__getitem__(item) 224 | 225 | def __setitem__(self, key, value): 226 | return self.value.__setitem__(key, value) 227 | 228 | def __len__(self): 229 | return self.value.__len__() 230 | 231 | 232 | class ListContainer(CollectionContainer): 233 | def __init__(self, value:list): 234 | super().__init__(value) 235 | 236 | def eval(self): 237 | return ListContainer([value.eval() for value in self.value]) 238 | 239 | def __add__(self, other): 240 | if type(other) is not ListContainer: 241 | msg = 'can only concatenate list to list' 242 | raise runtime.Errors.TypeError(msg) 243 | return ListContainer(self.value + other.value) 244 | 245 | def __mul__(self, other): 246 | return ListContainer(self.value.__mul__(other)) 247 | 248 | def append(self, what): 249 | return self.value.append(what) 250 | 251 | 252 | class TupleContainer(CollectionContainer): 253 | def __init__(self, value): 254 | super().__init__(value) 255 | 256 | def eval(self): 257 | return TupleContainer(tuple(value.eval() for value in self.value)) 258 | 259 | def __add__(self, other): 260 | if type(other) is not TupleContainer: 261 | msg = 'can only concatenate tuple to tuple' 262 | raise runtime.Errors.TypeError(msg) 263 | return TupleContainer(self.value + other.value) 264 | 265 | def __mul__(self, other): 266 | return TupleContainer(self.value.__mul__(other)) 267 | 268 | class DictContainer(CollectionContainer): 269 | def __init__(self, value:dict): 270 | super().__init__(value) 271 | 272 | def copy(self): 273 | return DictContainer(self.value.copy()) 274 | 275 | def update(self, right): 276 | return DictContainer(self.value.update(right.value)) 277 | 278 | def eval(self): 279 | result = {} 280 | 281 | for key in self.value.keys(): 282 | newKey = key.eval() 283 | newVal = self.value[key].eval() 284 | result[newKey] = newVal 285 | 286 | return result 287 | 288 | 289 | class SetContainer(CollectionContainer): 290 | def __init__(self, value:set): 291 | super().__init__(value) 292 | 293 | def eval(self): 294 | result = set({}) 295 | for item in self.value: 296 | result.add(item.eval()) 297 | return SetContainer(result) 298 | 299 | def update(self, right): 300 | SetContainer(self.value.update(right)) 301 | 302 | """ 303 | # Number literal 304 | """ 305 | class Num(Expression): 306 | 307 | def __init__(self, value): 308 | super().__init__() 309 | self.value = value 310 | 311 | def eval(self): 312 | return self.value 313 | 314 | """ 315 | # String literal 316 | """ 317 | class Str(Expression): 318 | def __init__(self, value): 319 | super().__init__() 320 | self.value = value 321 | 322 | def eval(self): 323 | return self.value -------------------------------------------------------------------------------- /tinypy/AST/stmt.py: -------------------------------------------------------------------------------- 1 | import copy 2 | from enum import Enum 3 | from tinypy.AST.ast import Statement, Expression, MemoryContext 4 | from tinypy.AST.expr import AddOp, SubOp, MultOp, DivOp, ModOp, LshiftOp, RshiftOp, BinOp, UnaryOp, Compare 5 | from tinypy.AST.expr import BitAndOp, BitOrOp, BitXorOp, Name, CallExpr 6 | 7 | from tinypy import AST 8 | from tinypy import runtime 9 | 10 | """ 11 | # Function definition. 12 | # @name - text name of the function 13 | # @args - list of arguments (just names) 14 | # @body - list of statements, which form functions body 15 | # 16 | # Every function has name which is written to the outer namespace. 17 | # For the top-level function definitions, the outer namespace is the global namespace. 18 | # For nested functions its the namespace of the outer function. 19 | # 20 | # Our way to implement name scoping is to set current namespace during the evaluation of ANY *STATEMENT* 21 | # Actually, we'll need to set the new (and then back the old one) when evaluating only functions, 22 | # as there are no scoping rules for other statements; thus, @Name expression will need to check 23 | # only single global variable - current namespace, and function calls will switch scopes. 24 | # 25 | # This solution is far from perfect. However, it just works as there is no need for modules. 26 | # Implementing modules will require providing each @Name node an ability to get a proper namespace. 27 | """ 28 | class FunctionDef(Statement): 29 | def __init__(self, name:str, args:list, body:list): 30 | super().__init__() 31 | self.name = name 32 | self.args = args 33 | self.body = body 34 | 35 | def getNamespace(self) -> runtime.Memory.Namespace: 36 | return runtime.Memory.CurrentNamespace 37 | 38 | def eval(self) -> None: 39 | 40 | declarationNamespace = self.getNamespace() 41 | 42 | def container(*args): 43 | namespace = runtime.Memory.Namespace(outerScope=declarationNamespace) 44 | previousNamespace = runtime.Memory.CurrentNamespace 45 | runtime.Memory.CurrentNamespace = namespace 46 | 47 | if len(args) != len(self.args): 48 | message = "%s() takes %d positional arguments but %d were given" % \ 49 | (self.name, len(self.args), len(args)) 50 | raise runtime.Errors.TypeError(message) 51 | 52 | for pair in zip (self.args, args): 53 | namespace.set(name=pair[0], value=pair[1]) 54 | 55 | returnValue = None 56 | 57 | for stmt in self.body: 58 | res = stmt.eval() 59 | if isinstance(res, ControlFlowMark): 60 | if res.type == ControlFlowMark.Type.Return: 61 | if res.toEval != None: 62 | returnValue = res.toEval.eval() 63 | break 64 | 65 | runtime.Memory.CurrentNamespace = previousNamespace 66 | return returnValue 67 | 68 | # Finally, write the function container to the memory. 69 | # Call to the container will trigger eval of function body 70 | declarationNamespace.set(self.name, container) 71 | return None 72 | 73 | 74 | """ 75 | # An if statement. 76 | # @test holds a single node, such as a Compare node. 77 | # @body and orelse each hold a list of nodes. 78 | # 79 | # @elif clauses don’t have a special representation in the AST, but rather 80 | # appear as extra If nodes within the orelse section of the previous one. 81 | # 82 | # Optional clauses such as @else are stored as an empty list if they’re not present. 83 | """ 84 | class IfStmt(Statement): 85 | def __init__(self, test, body:[], orelse:[]): 86 | super().__init__() 87 | self.test = test 88 | self.body = body 89 | self.orelse = orelse 90 | 91 | def eval(self): 92 | test = self.test.eval() 93 | result = [] 94 | 95 | for stmt in self.body if (test) else self.orelse: 96 | evalResult = stmt.eval() 97 | 98 | if isinstance(evalResult, ControlFlowMark): 99 | if evalResult.type != ControlFlowMark.Type.Pass: 100 | return evalResult 101 | 102 | if type(evalResult) is list: 103 | result += evalResult 104 | else: 105 | result.append(evalResult) 106 | 107 | return result 108 | 109 | 110 | """ 111 | # An while statement. 112 | # @test holds a single node, such as a @Compare node. 113 | # @body and @orelse each hold a list of nodes. 114 | # 115 | # @orelse is not used as it is not present in grammar. 116 | """ 117 | class WhileStmt(Statement): 118 | def __init__(self, test, body:[], orelse:[]): 119 | super().__init__() 120 | self.test = test 121 | self.body = body 122 | 123 | def eval(self): 124 | result = [] 125 | 126 | while self.test.eval(): 127 | shouldBreak = False 128 | for stmt in self.body: 129 | evalResult = stmt.eval() 130 | 131 | if isinstance(evalResult, ControlFlowMark): 132 | if evalResult.type == ControlFlowMark.Type.Break: 133 | shouldBreak = True 134 | break 135 | elif evalResult.type == ControlFlowMark.Type.Continue: 136 | break 137 | elif evalResult.type == ControlFlowMark.Type.Pass: 138 | pass 139 | elif evalResult.type == ControlFlowMark.Type.Return: 140 | return evalResult 141 | 142 | if type(evalResult) is list: 143 | result += evalResult 144 | else: 145 | result.append(evalResult) 146 | if shouldBreak: 147 | break 148 | 149 | return result 150 | 151 | """ 152 | # A for loop. 153 | # @target holds the variable(s) the loop assigns to, as a single Name, Tuple or List node. 154 | # @iter holds the item to be looped over, again as a single node. 155 | # @body and orelse contain lists of nodes to execute. 156 | # 157 | # @orelse is not used as it is not present in grammar. 158 | """ 159 | class ForStmt(Statement): 160 | def __init__(self, target, iter, body, orelse=None): 161 | super().__init__() 162 | self.target = target 163 | self.iter = iter 164 | self.body = body 165 | 166 | if not isinstance(target, Name): 167 | raise runtime.Errors.SyntaxError("can't assign to literal") 168 | 169 | if orelse is not None: 170 | raise NotImplementedError("You should implement orelse in grammar first!") 171 | 172 | def eval(self): 173 | result = [] 174 | 175 | # Check if target name exists. If no - create it. 176 | #runtime.Memory.CurrentNamespace.get(self) 177 | 178 | for x in self.iter.eval(): 179 | # Set target to the current value 180 | runtime.Memory.CurrentNamespace.set(self.target.id, x) 181 | 182 | shouldBreak = False 183 | for stmt in self.body: 184 | evalResult = stmt.eval() 185 | 186 | if isinstance(evalResult, ControlFlowMark): 187 | if evalResult.type == ControlFlowMark.Type.Break: 188 | shouldBreak = True 189 | break 190 | elif evalResult.type == ControlFlowMark.Type.Continue: 191 | break 192 | elif evalResult.type == ControlFlowMark.Type.Pass: 193 | pass 194 | elif evalResult.type == ControlFlowMark.Type.Return: 195 | return evalResult 196 | 197 | if type(evalResult) is list: 198 | result += evalResult 199 | else: 200 | result.append(evalResult) 201 | if shouldBreak: 202 | break 203 | 204 | return result 205 | 206 | """ 207 | # An assignment. 208 | # @targets is a list of nodes, 209 | # @value is a single node. 210 | # 211 | # Multiple nodes in targets represents assigning the same value to each. 212 | # Unpacking is represented by putting a Tuple or List within targets. 213 | # 214 | # Notice, that grammar I've implemented doesn't allow to assign to operators/keywords/literals; 215 | # Because of this we don't perform check for the type of a target value here. 216 | """ 217 | class AssignStmt(Statement): 218 | def __init__(self, target, value:Expression): 219 | super().__init__() 220 | self.target = target 221 | self.value = value 222 | 223 | def eval(self) -> None: 224 | if isinstance(self.target, AST.expr.CallExpr): 225 | raise runtime.Errors.SyntaxError("can't assign to function call") 226 | 227 | lValue = self.target.eval() 228 | rValue = self.value.eval() 229 | 230 | if isinstance(lValue, Subscript.AssignWrapper): 231 | lValue.collection[lValue.index] = rValue 232 | return 233 | 234 | runtime.Memory.CurrentNamespace.set(name=lValue, value=rValue) 235 | 236 | class AugAssignStmt(AssignStmt): 237 | opTable = { 238 | '+=' : AddOp, 239 | '-=' : SubOp, 240 | '*=' : MultOp, 241 | '/=' : DivOp, 242 | '%=' : ModOp, 243 | '&=' : BitAndOp, 244 | '|=' : BitOrOp, 245 | '^=' : BitXorOp, 246 | '<<=' : LshiftOp, 247 | '>>=' : RshiftOp, 248 | } 249 | 250 | def __init__(self, name, value, op): 251 | nameNodeLoad = copy.copy(name) 252 | nameNodeStore = copy.copy(name) 253 | 254 | nameNodeLoad.ctx = MemoryContext.Load 255 | nameNodeStore.ctx = MemoryContext.Store 256 | 257 | binOp = AugAssignStmt.opTable[op](left=nameNodeLoad, right=value) 258 | super().__init__(target=nameNodeStore, value=binOp) 259 | 260 | 261 | 262 | """ 263 | # Attribute access (e.g., name.attribute) 264 | # @value is a node, typically a Name. 265 | # @attr is a bare string giving the name of the attribute 266 | # @ctx is Load, Store or Del according to how the attribute is acted on. 267 | """ 268 | class Attribute(Statement): 269 | 270 | class Wrapper(): 271 | def __init__(self, name, attr): 272 | self.name = name 273 | self.attr = attr 274 | 275 | def __init__(self, value, attr, ctx): 276 | super().__init__() 277 | self.value = value 278 | self.attr = attr 279 | self.ctx = ctx 280 | 281 | def eval(self): 282 | value = self.value.eval() 283 | 284 | if self.ctx == MemoryContext.Load: 285 | if hasattr(value, self.attr): 286 | return getattr(value, self.attr) 287 | else: 288 | msg = "object has no attribute %s" % self.attr 289 | raise runtime.Errors.AttributeError(msg) 290 | elif self.ctx == MemoryContext.Store: 291 | raise NotImplementedError("Assigning to attributes is not supported!") 292 | # 293 | # if isinstance(value, object): 294 | # if value.__class__.__module__ == 'builtins': 295 | # raise runtime.Errors.ArithmeticError("writing to attributes of built-in objects is not supported") 296 | # elif callable(value): 297 | # return Attribute.Wrapper(self.value, self.attr) 298 | 299 | 300 | """ 301 | A subscript, such as l[1]. 302 | @value is the object, often a Name. 303 | @slice is one of @Index or @Slice. 304 | @ctx is Load, Store or Del according to what it does with the subscript. 305 | """ 306 | class Subscript(Statement): 307 | 308 | class AssignWrapper: 309 | def __init__(self, collection, index): 310 | self.collection = collection 311 | self.index = index 312 | 313 | def __init__(self, value, slice, ctx): 314 | super().__init__() 315 | self.value = value 316 | self.slice = slice 317 | self.ctx = ctx 318 | 319 | def eval(self): 320 | lValue = self.value.eval() 321 | 322 | try: 323 | if isinstance(self.slice, Index): 324 | index = self.slice.eval() 325 | 326 | if self.ctx == MemoryContext.Load: 327 | return lValue[index] 328 | elif self.ctx == MemoryContext.Store: 329 | return Subscript.AssignWrapper(lValue, index) 330 | else: 331 | raise NotImplementedError 332 | 333 | elif isinstance(self.slice, Slice): 334 | lower, upper = self.slice.eval() 335 | 336 | if self.ctx == MemoryContext.Load: 337 | return lValue[lower:upper] 338 | else: 339 | raise NotImplementedError("Writing to slices & deleting elements is not supported") 340 | 341 | else: 342 | raise ValueError("Unexpected slice type") 343 | except IndexError as e: 344 | raise runtime.Errors.IndexError(e) 345 | except KeyError as e: 346 | raise runtime.Errors.KeyError(e) 347 | except TypeError as e: 348 | raise runtime.Errors.TypeError(e) 349 | 350 | """ 351 | Simple subscripting with a single value: l[1] 352 | """ 353 | class Index(Statement): 354 | def __init__(self, value): 355 | super().__init__() 356 | self.value = value 357 | 358 | def eval(self): 359 | return self.value.eval() 360 | 361 | """ 362 | Regular slicing: l[1:2] 363 | """ 364 | class Slice(Statement): 365 | def __init__(self, lower, upper, step): 366 | super().__init__() 367 | self.lower = lower 368 | self.upper = upper 369 | self.step = step 370 | 371 | if self.step != None: 372 | raise NotImplementedError() 373 | 374 | def eval(self): 375 | lower = upper = None 376 | if self.lower != None: 377 | lower = self.lower.eval() 378 | if self.upper != None: 379 | upper = self.upper.eval() 380 | return lower, upper 381 | 382 | 383 | """ 384 | # Control flow statements. 385 | # Each statement returns corresponding @ControlFlowMark as a result of evaluation. 386 | # Compound statements are checking whether evaluation result is a such mark, and react accordingly. 387 | """ 388 | class ControlFlowStmt(Statement): 389 | pass 390 | 391 | 392 | class ReturnStmt(ControlFlowStmt): 393 | def __init__(self, expr): 394 | super().__init__() 395 | self.expr = expr 396 | 397 | def eval(self): 398 | return ControlFlowMark(ControlFlowMark.Type.Return, self.expr) 399 | 400 | 401 | class PassStmt(ControlFlowStmt): 402 | def eval(self): 403 | return ControlFlowMark(ControlFlowMark.Type.Pass) 404 | 405 | 406 | class ContinueStmt(ControlFlowStmt): 407 | def eval(self): 408 | return ControlFlowMark(ControlFlowMark.Type.Continue) 409 | 410 | 411 | class BreakStmt(ControlFlowStmt): 412 | def eval(self): 413 | return ControlFlowMark(ControlFlowMark.Type.Break) 414 | 415 | 416 | class ControlFlowMark: 417 | 418 | class Type(Enum): 419 | Return = 1 420 | Break = 2 421 | Continue = 3 422 | Pass = 4 423 | 424 | def __init__(self, type, toEval=None): 425 | self.type = type 426 | self.toEval = toEval 427 | 428 | 429 | 430 | -------------------------------------------------------------------------------- /tinypy/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxmalysh/tiny-py-interpreter/d636b613ef7a156b6d225e345a56c7502a193324/tinypy/__init__.py -------------------------------------------------------------------------------- /tinypy/parser/CST.py: -------------------------------------------------------------------------------- 1 | import antlr4 2 | from antlr4.tree.Tree import ParseTree 3 | 4 | from tinypy.parser.TinyPyParser import TinyPyParser 5 | from tinypy.parser.Utils import nameFor 6 | 7 | useless_tokens = [ 8 | TinyPyParser.NEWLINE, 9 | TinyPyParser.EOF, 10 | TinyPyParser.COLON, 11 | TinyPyParser.COLON, 12 | TinyPyParser.OPEN_PAREN, 13 | TinyPyParser.CLOSE_PAREN, 14 | TinyPyParser.IF, 15 | TinyPyParser.ELIF, 16 | TinyPyParser.ELSE, 17 | ] 18 | 19 | useless_tokens = [] 20 | 21 | class CstFlattened: 22 | def __init__(self, parent=None, tree:ParseTree=None, children=None): 23 | self.payload = self.getPayload(tree) 24 | 25 | if children is None: 26 | children = [] 27 | 28 | self.children = children 29 | 30 | if parent is None: 31 | self.walk(tree, self) 32 | else: 33 | parent.addToChildren(self) 34 | 35 | def addToChildren(self, what): 36 | self.children.append(what) 37 | 38 | # Determines the payload of this CST: a string in case it's an inner node (which 39 | # is the name of the parser rule), or a Token in case it is a leaf node. 40 | def getPayload(self, tree:ParseTree): 41 | if tree.getChildCount() == 0: 42 | # A leaf node: return the tree's payload, which is a Token. 43 | return tree.getPayload() 44 | else: 45 | # The name for parser rule `foo` will be `FooContext`. Strip `Context` and 46 | # lower case the first character. 47 | ruleNume = tree.__class__.__name__.replace('Context', '') 48 | return ruleNume 49 | 50 | # Fills this AST based on the parse tree. 51 | def walk(self, tree:ParseTree, ast): 52 | if tree.getChildCount() == 0: 53 | # We've reached a leaf. We must create a new instance of an AST because 54 | # the constructor will make sure this new instance is added to its parent's 55 | # child nodes. 56 | CstFlattened(ast, tree) 57 | elif tree.getChildCount() == 1: 58 | # We've reached an inner node with a single child: we don't include this in 59 | # our AST. 60 | self.walk(tree.getChild(0), ast) 61 | elif tree.getChildCount() > 1: 62 | for child in tree.getChildren(): 63 | temp = CstFlattened(ast, child) 64 | if not isinstance(temp.payload, antlr4.Token): 65 | # Only traverse down if the payload is not a Token. 66 | self.walk(child, temp) 67 | 68 | # 69 | # Look for box-drawing characters here: 70 | # https://en.wikipedia.org/wiki/Box-drawing_character 71 | # 72 | def __str__(self, print_pos=False): 73 | result = "" 74 | firstStack = [self] 75 | childListStack = [firstStack] 76 | 77 | while len(childListStack) != 0: 78 | childStack = childListStack[-1] 79 | 80 | if len(childStack) == 0: 81 | childListStack.pop(-1) 82 | else: 83 | ast = childStack.pop(0) 84 | 85 | if isinstance(ast.payload, antlr4.Token): 86 | token = ast.payload 87 | 88 | position = ", at (%d, %d) " % (token.line, token.column) if print_pos else "" 89 | caption = 'TOKEN[type: %s, text: %s%s]' % ( 90 | nameFor(token.type), token.text.replace('\n', '\\n'), position 91 | ) 92 | else: 93 | caption = str(ast.payload) 94 | 95 | indent = '' 96 | 97 | for i in range(0, len(childListStack) - 1): 98 | indent += '│ ' if len(childListStack[i]) > 0 else ' ' 99 | 100 | result += indent 101 | result += '└──── ' if len(childStack) == 0 else '├──── ' 102 | result += caption 103 | result += '\n' 104 | 105 | if len(ast.children) > 0: 106 | children = [] 107 | for i in range(0, len(ast.children)): 108 | children.append(ast.children[i]) 109 | childListStack.append(children) 110 | 111 | return result 112 | 113 | class CstFiltered(CstFlattened): 114 | 115 | # Don't include NEWLINEs and EOFs in the Tree 116 | def walk(self, tree:ParseTree, ast): 117 | if tree.getChildCount() > 1: 118 | for child in tree.getChildren(): 119 | # Don't add useless token to tree 120 | if isinstance(child.getPayload(), antlr4.Token) and child.symbol.type in useless_tokens: 121 | continue 122 | temp = CstFlattened(ast, child) 123 | if not isinstance(temp.payload, antlr4.Token): 124 | # Only traverse down if the payload is not a Token. 125 | self.walk(child, temp) 126 | else: 127 | super().walk(tree, ast) 128 | -------------------------------------------------------------------------------- /tinypy/parser/CustomLexer.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from antlr4.Token import CommonToken 4 | from antlr4 import * 5 | 6 | from tinypy.parser.Errors import IndentationErr 7 | from tinypy.parser.TinyPyLexer import TinyPyLexer 8 | from tinypy.parser.TinyPyParser import TinyPyParser 9 | 10 | # 11 | # https://docs.python.org/3/reference/lexical_analysis.html#indentation 12 | # The indentation levels of consecutive lines are used to generate INDENT and DEDENT tokens, using a stack, as follows. 13 | # 14 | # Before the first line of the file is read, a single zero is pushed on the stack; 15 | # this will never be popped off again. The numbers pushed on the stack will always be 16 | # strictly increasing from bottom to top. At the beginning of each logical line, the line’s 17 | # indentation level is compared to the top of the stack. If it is equal, nothing happens. 18 | # 19 | # If it is larger, it is pushed on the stack, and one INDENT token is generated. 20 | # 21 | # If it is smaller, it must be one of the numbers occurring on the stack; all numbers on the stack 22 | # that are larger are popped off, and for each number popped off a DEDENT token is generated. 23 | # 24 | # At the end of the file, a DEDENT token is generated for each number remaining on the stack 25 | # that is larger than zero. 26 | # 27 | # The idea is the following: 28 | # 29 | # Whenever you match a line break in your lexer, optionally match one or more spaces. 30 | # If there are spaces after the line break, compare the length of these spaces with the current indent-size. 31 | # If it's more than the current indent size, emit an Indent token, if it's less than the current indent-size, 32 | # emit a Dedent token and if it's the same, don't do anything. 33 | # 34 | # You'll also want to emit a number of Dedent tokens at the end of the file 35 | # to let every Indent have a matching Dedent token. 36 | 37 | class CustomLexer(TinyPyLexer): 38 | def __init__(self, input=None): 39 | super().__init__(input) 40 | self.tokens = [] # A queue where extra tokens are pushed on (see the NEWLINE lexer rule). 41 | self.indents = [] # The stack that keeps track of the indentation level. 42 | self.opened = 0 # The amount of opened braces, brackets and parenthesis. 43 | self.lastToken = None # The most recently produced token. 44 | 45 | def emitToken(self, token:Token): 46 | self._token = token 47 | self.tokens.append(token) 48 | self.lastToken = token 49 | # import AST; print("%s at [%d, %d]" % (AST.nameFor(token.type), token.line, token.column)) 50 | # super().emitToken(token) 51 | 52 | def nextToken(self): 53 | # return super().nextToken() 54 | # Check if the end-of-file is ahead and there are still some DEDENTS expected 55 | if self._input.LA(1) == Token.EOF and len(self.indents) != 0: 56 | # Remove any trailing EOF tokens from our buffer 57 | i = len(self.tokens) - 1 58 | while i >= 0: 59 | if self.tokens[i].type == TinyPyParser.EOF: 60 | del self.tokens[i] 61 | i -= 1 62 | 63 | # First emit an extra line break that serves as the end of the statement 64 | self.emitToken(self.commonToken(TinyPyParser.NEWLINE, '\n')) 65 | 66 | # Now emit as much DEDENT tokens as needed 67 | while len(self.indents) != 0: 68 | self.emitToken(self.createDedent()) 69 | self.indents.pop() 70 | 71 | # Put the EOF back on the token stream 72 | self.emitToken(self.commonToken(TinyPyParser.EOF, "")) 73 | 74 | nextToken = super().nextToken() 75 | 76 | if nextToken.channel == Token.DEFAULT_CHANNEL: 77 | # Keep track of the last token on the default channel 78 | self.lastToken = nextToken 79 | 80 | return nextToken if len(self.tokens) == 0 else self.tokens.pop(0) 81 | 82 | def createDedent(self): 83 | dedent = self.commonToken(TinyPyParser.DEDENT, "") 84 | dedent.line = self.lastToken.line 85 | return dedent 86 | 87 | def commonToken(self, _type, text): 88 | stop = self.getCharIndex() - 1 89 | start = stop if text == "" else stop - len(text) + 1 90 | return CommonToken(self._tokenFactorySourcePair, _type, start=start, stop=stop) 91 | 92 | # Calculates the indentation of the provided spaces, taking the 93 | # following rules into account: 94 | # 95 | # "Tabs are replaced (from left to right) by one to eight spaces 96 | # such that the total number of characters up to and including 97 | # the replacement is a multiple of eight [...]" 98 | # 99 | # -- https://docs.python.org/3.1/reference/lexical_analysis.html#indentation 100 | def getIndentationCount(self, spaces): 101 | count = 0 102 | for ch in spaces: 103 | if ch == '\t': 104 | count += 8 - (count % 8) 105 | else: 106 | count += 1 107 | return count 108 | 109 | 110 | def newLineAction(self): 111 | newLine = re.sub("[^\r\n]+", "", self.text) 112 | spaces = re.sub("[\r\n]+", "", self.text) 113 | 114 | next_codepoint = self._input.LA(1) 115 | _next = chr(next_codepoint if next_codepoint >= 0 else 0) 116 | 117 | if self.opened > 0 or _next == '#' or _next == '\n' or _next == '\r': 118 | # If we're inside a list or an a blank line, ignore all indents, 119 | # dedents and line breaks. 120 | self.skip() 121 | return 122 | 123 | self.emitToken(self.commonToken(self.NEWLINE, newLine)) 124 | 125 | indent = self.getIndentationCount(spaces) 126 | previous = self.indents[-1] if len(self.indents) != 0 else 0 127 | 128 | if indent == previous: 129 | # Skip indents of the same size as the present indent-size 130 | self.skip() 131 | elif indent > previous: 132 | self.indents.append(indent) 133 | self.emitToken(self.commonToken(TinyPyParser.INDENT, spaces)) 134 | else: 135 | # Possibly emit more than 1 DEDENT token. 136 | while len(self.indents) != 0 and self.indents[-1] > indent: 137 | if indent not in self.indents and indent != 0: 138 | raise IndentationErr(line=self.line) 139 | self.emitToken(self.createDedent()) 140 | self.indents.pop() 141 | 142 | # Emit an additional newline (which we could have skipped) just before the EOF, 143 | # as it is needed for the shell mode (see single_input rule definition) 144 | prev_codepoint = self._input.LA(-1) 145 | pre_prev_codepoint = self._input.LA(-2) 146 | 147 | prev = chr(prev_codepoint if prev_codepoint >= 0 else 0) 148 | pre_prev = chr(pre_prev_codepoint if pre_prev_codepoint >= 0 else 0) 149 | 150 | if next_codepoint == Token.EOF: 151 | if (prev == '\n' or prev == '\r') and (pre_prev == '\n' or pre_prev =='\r'): 152 | self.emitToken(self.commonToken(self.NEWLINE, newLine)) 153 | 154 | def atStartOfInput(self): 155 | return self.column == 0 and self.line == 1 156 | 157 | -------------------------------------------------------------------------------- /tinypy/parser/Errors.py: -------------------------------------------------------------------------------- 1 | from antlr4.error.ErrorListener import ErrorListener, ProxyErrorListener 2 | from antlr4.error.Errors import RecognitionException, ParseCancellationException, InputMismatchException 3 | from antlr4.error.ErrorStrategy import DefaultErrorStrategy 4 | from antlr4.Parser import Parser 5 | from antlr4.Token import Token 6 | 7 | from tinypy.parser.TinyPyParser import TinyPyParser 8 | 9 | 10 | class IndentationErr(RecognitionException): 11 | def __init__(self, line): 12 | super().__init__(message="unindent does not match any outer indentation level") 13 | self.line = line 14 | 15 | 16 | class SingleInputUnfinished(RecognitionException): 17 | def __init__(self): 18 | super().__init__() 19 | 20 | 21 | class CustomErrorStrategy(DefaultErrorStrategy): 22 | 23 | def __init__(self): 24 | super().__init__() 25 | 26 | def reportError(self, recognizer:Parser, e:RecognitionException): 27 | if isinstance(e, IndentationErr): 28 | self.reportIndendationError(recognizer, e) 29 | elif isinstance(e, SingleInputUnfinished): 30 | super().reportError(recognizer, e) 31 | else: 32 | super().reportError(recognizer, e) 33 | 34 | def reportIndendationError(self, recognizer:Parser, e:IndentationErr): 35 | offendingToken = Token() 36 | offendingToken.line = e.line 37 | offendingToken.column = 0 38 | recognizer.notifyErrorListeners(e.message, offendingToken, e) 39 | 40 | 41 | class CustomErrorListener(ProxyErrorListener): 42 | 43 | def __init__(self): 44 | super().__init__(delegates=[]) 45 | self.errors_encountered = 0 46 | self.input_unfinished = False 47 | self.eof_received = False 48 | 49 | def syntaxError(self, recognizer, offendingSymbol, line, column, msg, e): 50 | self.errors_encountered += 1 51 | 52 | # 53 | # User just started compound statement (e.g. "if True:\n") 54 | # 55 | if offendingSymbol.type == Token.EOF: 56 | if isinstance(e, InputMismatchException): 57 | if isinstance(e.ctx, TinyPyParser.SuiteContext): 58 | self.input_unfinished = True 59 | 60 | # 61 | # This happens when user have not entered the second newline in compound statement 62 | # TODO: handle this by using parser state (based on a rule context) 63 | # 64 | if offendingSymbol.type == Token.EOF and msg == "missing at ''": 65 | self.input_unfinished = True 66 | 67 | if offendingSymbol.type == Token.EOF and line == 1 and column == 0: 68 | if isinstance(e.ctx , TinyPyParser.Single_inputContext): 69 | self.eof_received = True 70 | 71 | super().syntaxError(recognizer, offendingSymbol, line, column, msg, e) 72 | 73 | def addDelegatee(self, delegatee): 74 | self.delegates.append(delegatee) 75 | 76 | 77 | class BufferedErrorListener(ErrorListener): 78 | 79 | def __init__(self): 80 | self.buffer = "" 81 | 82 | def syntaxError(self, recognizer, offendingSymbol, line, column, msg, e): 83 | self.buffer += ("line " + str(line) + ":" + str(column) + " " + msg) + '\n' 84 | 85 | def printBuffer(self): 86 | print(self.buffer) 87 | 88 | -------------------------------------------------------------------------------- /tinypy/parser/TinyPy.g4: -------------------------------------------------------------------------------- 1 | grammar TinyPy; 2 | 3 | options 4 | { 5 | language=Python3; 6 | } 7 | 8 | tokens { INDENT, DEDENT } 9 | 10 | // See @CustomLexer.py 11 | @lexer::members { } 12 | 13 | // https://docs.python.org/2/reference/grammar.html 14 | // https://docs.python.org/3/reference/grammar.html 15 | // https://docs.python.org/devguide/grammar.html 16 | 17 | //******************************************************// 18 | // Parser Rules // 19 | //******************************************************// 20 | 21 | /** 22 | * 23 | * Different input types 24 | * 25 | # Start symbols for the grammar: 26 | # single_input is a single interactive statement; 27 | # file_input is a module or sequence of commands read from an input file; 28 | # eval_input is the input for the eval() functions. 29 | */ 30 | file_input 31 | : ( NEWLINE | stmt )* EOF 32 | ; 33 | 34 | single_input 35 | : NEWLINE 36 | | simple_stmt 37 | | compound_stmt NEWLINE 38 | ; 39 | 40 | eval_input 41 | : test NEWLINE* EOF 42 | ; 43 | 44 | /** 45 | * 46 | * Base statements 47 | * 48 | */ 49 | stmt 50 | : simple_stmt 51 | | compound_stmt 52 | ; 53 | 54 | simple_stmt 55 | : small_stmt ( ';' small_stmt )* ';'? NEWLINE 56 | ; 57 | 58 | small_stmt 59 | : expr_stmt 60 | // | import_stmt 61 | // | del_stmt 62 | | flow_stmt 63 | | pass_stmt; 64 | 65 | compound_stmt 66 | : if_stmt 67 | | while_stmt 68 | | for_stmt 69 | | funcdef; 70 | 71 | /** 72 | * 73 | * Compound statements 74 | * 75 | */ 76 | 77 | if_stmt: 78 | IF test ':' suite if_elif* if_else?; 79 | 80 | if_elif: 81 | ELIF test ':' suite; 82 | 83 | if_else: 84 | ELSE ':' suite; 85 | 86 | while_stmt 87 | : WHILE test ':' suite //( ELSE ':' suite )? 88 | ; 89 | 90 | for_stmt 91 | : FOR nameaccess IN test ':' suite 92 | ; 93 | 94 | funcdef 95 | : DEF NAME parameters ':' suite; 96 | 97 | parameters 98 | : '(' param_argslist? ')' 99 | ; 100 | 101 | param_argslist 102 | : (NAME ',')* NAME ; 103 | 104 | suite 105 | : simple_stmt 106 | | NEWLINE INDENT stmt+ DEDENT 107 | ; 108 | 109 | /** 110 | * 111 | * Small statements 112 | * 113 | */ 114 | 115 | expr_stmt 116 | : test # ExprStmtExpr 117 | | nameaccess '=' test # ExprStmtAssign 118 | | nameaccess augassign test # ExprStmtAugmented 119 | ; 120 | 121 | augassign : '+=' | '-=' | '*=' | '/=' | '%=' 122 | | '<<=' | '>>=' | '&=' | '|=' | '^=' 123 | ; 124 | //expr_stmt 125 | // : testlist_expr ( '=' ( testlist_expr ) )* 126 | // ; 127 | 128 | 129 | flow_stmt 130 | : return_stmt 131 | | break_stmt 132 | | continue_stmt 133 | ; 134 | 135 | return_stmt: RETURN test?; 136 | pass_stmt: PASS; 137 | break_stmt: BREAK; 138 | continue_stmt: CONTINUE; 139 | 140 | /** 141 | * 142 | * Common stuff: comparisons, arithmetic and logic expressions 143 | * 144 | * Rules "tests" and "expr" from the official grammar were re-written in order 145 | * to make AST construction easier; ANTLR handles left recursion for us 146 | * 147 | */ 148 | 149 | test : expr # TestExpr 150 | | test comp_op test # Comparison 151 | | NOT test # NotTest 152 | | test AND test # AndTest 153 | | test OR test # OrTest 154 | ; 155 | 156 | comp_op : '<' | '>' | '==' | '>=' | '<=' | '<>' | '!=' 157 | | IN | NOT IN | IS NOT | IS 158 | ; 159 | 160 | 161 | expr : factor # FactorExpr 162 | | expr op=( '*' | '/' | '%' ) expr # MulDivMod 163 | | expr op=( '+' | '-' ) expr # AddSub 164 | | expr op=( '<<' | '>>' ) expr # Shifts 165 | | expr op='&' expr # BitAnd 166 | | expr op='^' expr # BitXor 167 | | expr op='|' expr # BitOr 168 | ; 169 | 170 | factor 171 | : op='+' factor # unaryExpr 172 | | op='-' factor # unaryExpr 173 | | '(' test ')' # parenExpr 174 | | atom # atomExpr 175 | ; 176 | 177 | atom 178 | : collectiondefs 179 | | nameaccess 180 | | number 181 | | string+ 182 | | NONE 183 | | TRUE 184 | | FALSE 185 | ; 186 | 187 | nameaccess 188 | : NAME # PlainName 189 | | nameaccess '(' arglist? ')' # FuncInvoke 190 | | nameaccess '.' NAME # DottedName // This won't work for a.attr.attr! 191 | | nameaccess '[' subscript ']' # SubName 192 | ; 193 | 194 | collectiondefs 195 | : '{' dictorsetmaker? '}' # DictMaker 196 | | '[' testlist_comp? ']' # ListMaker 197 | | '(' testlist_comp? ')' # TupleMaker 198 | ; 199 | 200 | testlist_comp 201 | : test 202 | | testlist_comp ',' testlist_comp 203 | | testlist_comp ',' 204 | ; 205 | 206 | funcinvoke 207 | : NAME '(' arglist? ')' 208 | ; 209 | 210 | arglist 211 | : (test ',')* test 212 | ; 213 | 214 | subscript 215 | : test #SubscriptIndex 216 | | lower=test? ':' upper=test? #SubscriptSlice 217 | ; 218 | 219 | dictorsetmaker 220 | : dictormaker ','? 221 | | setmaker ','? 222 | ; 223 | 224 | dictormaker 225 | : test ':' test 226 | | dictormaker ',' dictormaker 227 | ; 228 | 229 | setmaker 230 | : test ( ',' test )* 231 | ; 232 | 233 | /** 234 | * 235 | * Strings and numbers 236 | * 237 | */ 238 | 239 | number 240 | : integer 241 | | FLOAT_NUMBER 242 | ; 243 | 244 | integer 245 | : DECIMAL_INTEGER 246 | | OCT_INTEGER 247 | | HEX_INTEGER 248 | | BIN_INTEGER 249 | ; 250 | 251 | FLOAT_NUMBER 252 | : POINT_FLOAT 253 | | EXPONENT_FLOAT 254 | ; 255 | 256 | string 257 | : STRING_LITERAL 258 | ; 259 | 260 | //*******************************************************// 261 | // Lexer Rules // 262 | //*******************************************************// 263 | 264 | DEF : 'def'; 265 | RETURN : 'return'; 266 | FROM : 'from'; 267 | IMPORT : 'import'; 268 | AS : 'as'; 269 | DEL : 'del'; 270 | PASS : 'pass'; 271 | BREAK : 'break'; 272 | CONTINUE : 'continue'; 273 | 274 | IF : 'if'; 275 | ELIF : 'elif'; 276 | ELSE : 'else'; 277 | 278 | WHILE: 'while'; 279 | FOR : 'for'; 280 | IN : 'in'; 281 | 282 | OR : 'or'; 283 | AND : 'and'; 284 | NOT : 'not'; 285 | IS : 'is'; 286 | 287 | NONE : 'None'; 288 | TRUE : 'True'; 289 | FALSE : 'False'; 290 | 291 | NEWLINE 292 | : ( {self.atStartOfInput()}? SPACES 293 | | ( '\r'? '\n' | '\r' ) SPACES? 294 | ) 295 | { self.newLineAction() } 296 | ; 297 | 298 | COMMA : ','; 299 | COLON : ':'; 300 | 301 | OPEN_PAREN : '(' { self.opened += 1 }; 302 | CLOSE_PAREN : ')' { self.opened -= 1 }; 303 | OPEN_BRACK : '[' { self.opened += 1 }; 304 | CLOSE_BRACK : ']' { self.opened -= 1 }; 305 | OPEN_BRACE : '{' { self.opened += 1 }; 306 | CLOSE_BRACE : '}' { self.opened -= 1 }; 307 | 308 | LEFT_SHIFT : '<<'; 309 | RIGHT_SHIFT : '>>'; 310 | 311 | STAR : '*'; 312 | POWER : '**'; 313 | ASSIGN : '='; 314 | ADD : '+'; 315 | MINUS : '-'; 316 | DIV : '/'; 317 | MOD : '%'; 318 | OR_OP : '|'; 319 | XOR : '^'; 320 | AND_OP : '&'; 321 | 322 | LESS_THAN : '<'; 323 | GREATER_THAN : '>'; 324 | EQUALS : '=='; 325 | GT_EQ : '>='; 326 | LT_EQ : '<='; 327 | NOT_EQ_1 : '<>'; 328 | NOT_EQ_2 : '!='; 329 | 330 | 331 | ADD_ASSIGN : '+='; 332 | SUB_ASSIGN : '-='; 333 | MULT_ASSIGN : '*='; 334 | AT_ASSIGN : '@='; 335 | DIV_ASSIGN : '/='; 336 | MOD_ASSIGN : '%='; 337 | AND_ASSIGN : '&='; 338 | OR_ASSIGN : '|='; 339 | XOR_ASSIGN : '^='; 340 | LEFT_SHIFT_ASSIGN : '<<='; 341 | RIGHT_SHIFT_ASSIGN : '>>='; 342 | POWER_ASSIGN : '**='; 343 | IDIV_ASSIGN : '//='; 344 | 345 | NAME 346 | : ID_START ID_CONTINUE*; 347 | 348 | STRING_LITERAL 349 | : [uU]? [rR]? ( SHORT_STRING ); 350 | 351 | DECIMAL_INTEGER 352 | : NON_ZERO_DIGIT DIGIT* | '0'+; 353 | 354 | HEX_INTEGER 355 | : '0' [xX] HEX_DIGIT+; 356 | 357 | OCT_INTEGER 358 | : '0' [oO] OCT_DIGIT+; 359 | 360 | BIN_INTEGER 361 | : '0' [bB] BIN_DIGIT+; 362 | 363 | SKIP 364 | : ( SPACES | COMMENT ) -> skip 365 | ; 366 | 367 | UNKNOWN_CHAR 368 | : . 369 | ; 370 | 371 | CYRILLIC_RANGE : [\u0400-\u04FF] ; 372 | 373 | fragment SPACES: [ \t]+; 374 | fragment COMMENT: '#' ~[\r\n]*; 375 | fragment ID_START: '_' | [A-Z] | [a-z] | CYRILLIC_RANGE; 376 | fragment ID_CONTINUE: ID_START | [0-9]; 377 | 378 | fragment NON_ZERO_DIGIT : [1-9]; 379 | fragment DIGIT : [0-9]; 380 | fragment OCT_DIGIT : [0-7]; 381 | fragment HEX_DIGIT : [0-9a-fA-F]; 382 | fragment BIN_DIGIT : [01]; 383 | 384 | fragment SHORT_STRING 385 | : '\'' ( STRING_ESCAPE_SEQ | ~[\\\r\n'] )* '\'' 386 | | '"' ( STRING_ESCAPE_SEQ | ~[\\\r\n"] )* '"' 387 | ; 388 | 389 | fragment STRING_ESCAPE_SEQ 390 | : '\\' . 391 | ; 392 | 393 | fragment POINT_FLOAT 394 | : INT_PART? FRACTION 395 | | INT_PART '.' 396 | ; 397 | 398 | 399 | fragment EXPONENT_FLOAT 400 | : ( INT_PART | POINT_FLOAT ) EXPONENT 401 | ; 402 | 403 | fragment INT_PART: DIGIT+; 404 | fragment FRACTION: '.' DIGIT+; 405 | fragment EXPONENT: [eE] [+-]? DIGIT+; 406 | 407 | -------------------------------------------------------------------------------- /tinypy/parser/Utils.py: -------------------------------------------------------------------------------- 1 | from tinypy.parser.TinyPyParser import TinyPyParser 2 | 3 | # 4 | # Converts Lisp-style s-expression string to python dictionary. 5 | # Pretty-printing this helps to get a more meaningful representation of a parse tree 6 | # 7 | def sExprToDict(string): 8 | sexp = [[]] 9 | word = '' 10 | in_str = False 11 | for c in string: 12 | if c == '(' and not in_str: 13 | sexp.append([]) 14 | elif c == ')' and not in_str: 15 | if(len(word) > 0): 16 | sexp[-1].append(word) 17 | word = '' 18 | temp = sexp.pop() 19 | sexp[-1].append(temp) 20 | elif c in (' ', '\n', '\t') and not in_str: 21 | sexp[-1].append(word) 22 | word = '' 23 | elif c == '\"': 24 | in_str = not in_str 25 | else: 26 | word = word + c 27 | return sexp[0] 28 | 29 | 30 | def nameFor(tokenType:int): 31 | if tokenType == -1: 32 | return 'EOF' 33 | return TinyPyParser.symbolicNames[tokenType] 34 | -------------------------------------------------------------------------------- /tinypy/parser/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Max Malysh' 2 | -------------------------------------------------------------------------------- /tinypy/parser/__pycache__/CST.cpython-34.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxmalysh/tiny-py-interpreter/d636b613ef7a156b6d225e345a56c7502a193324/tinypy/parser/__pycache__/CST.cpython-34.pyc -------------------------------------------------------------------------------- /tinypy/parser/__pycache__/CST.cpython-35.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxmalysh/tiny-py-interpreter/d636b613ef7a156b6d225e345a56c7502a193324/tinypy/parser/__pycache__/CST.cpython-35.pyc -------------------------------------------------------------------------------- /tinypy/parser/__pycache__/CustomLexer.cpython-34.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxmalysh/tiny-py-interpreter/d636b613ef7a156b6d225e345a56c7502a193324/tinypy/parser/__pycache__/CustomLexer.cpython-34.pyc -------------------------------------------------------------------------------- /tinypy/parser/__pycache__/CustomLexer.cpython-35.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxmalysh/tiny-py-interpreter/d636b613ef7a156b6d225e345a56c7502a193324/tinypy/parser/__pycache__/CustomLexer.cpython-35.pyc -------------------------------------------------------------------------------- /tinypy/parser/__pycache__/CustomLexer.pypy3-23.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxmalysh/tiny-py-interpreter/d636b613ef7a156b6d225e345a56c7502a193324/tinypy/parser/__pycache__/CustomLexer.pypy3-23.pyc -------------------------------------------------------------------------------- /tinypy/parser/__pycache__/CustomListener.cpython-34.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxmalysh/tiny-py-interpreter/d636b613ef7a156b6d225e345a56c7502a193324/tinypy/parser/__pycache__/CustomListener.cpython-34.pyc -------------------------------------------------------------------------------- /tinypy/parser/__pycache__/CustomListener.cpython-35.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxmalysh/tiny-py-interpreter/d636b613ef7a156b6d225e345a56c7502a193324/tinypy/parser/__pycache__/CustomListener.cpython-35.pyc -------------------------------------------------------------------------------- /tinypy/parser/__pycache__/Errors.cpython-34.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxmalysh/tiny-py-interpreter/d636b613ef7a156b6d225e345a56c7502a193324/tinypy/parser/__pycache__/Errors.cpython-34.pyc -------------------------------------------------------------------------------- /tinypy/parser/__pycache__/Errors.cpython-35.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxmalysh/tiny-py-interpreter/d636b613ef7a156b6d225e345a56c7502a193324/tinypy/parser/__pycache__/Errors.cpython-35.pyc -------------------------------------------------------------------------------- /tinypy/parser/__pycache__/Errors.pypy3-23.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxmalysh/tiny-py-interpreter/d636b613ef7a156b6d225e345a56c7502a193324/tinypy/parser/__pycache__/Errors.pypy3-23.pyc -------------------------------------------------------------------------------- /tinypy/parser/__pycache__/TinyPyLexer.cpython-35.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxmalysh/tiny-py-interpreter/d636b613ef7a156b6d225e345a56c7502a193324/tinypy/parser/__pycache__/TinyPyLexer.cpython-35.pyc -------------------------------------------------------------------------------- /tinypy/parser/__pycache__/TinyPyListener.cpython-35.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxmalysh/tiny-py-interpreter/d636b613ef7a156b6d225e345a56c7502a193324/tinypy/parser/__pycache__/TinyPyListener.cpython-35.pyc -------------------------------------------------------------------------------- /tinypy/parser/__pycache__/TinyPyParser.cpython-35.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxmalysh/tiny-py-interpreter/d636b613ef7a156b6d225e345a56c7502a193324/tinypy/parser/__pycache__/TinyPyParser.cpython-35.pyc -------------------------------------------------------------------------------- /tinypy/parser/__pycache__/TinyPyVisitor.cpython-35.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxmalysh/tiny-py-interpreter/d636b613ef7a156b6d225e345a56c7502a193324/tinypy/parser/__pycache__/TinyPyVisitor.cpython-35.pyc -------------------------------------------------------------------------------- /tinypy/parser/__pycache__/Utils.cpython-34.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxmalysh/tiny-py-interpreter/d636b613ef7a156b6d225e345a56c7502a193324/tinypy/parser/__pycache__/Utils.cpython-34.pyc -------------------------------------------------------------------------------- /tinypy/parser/__pycache__/Utils.cpython-35.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxmalysh/tiny-py-interpreter/d636b613ef7a156b6d225e345a56c7502a193324/tinypy/parser/__pycache__/Utils.cpython-35.pyc -------------------------------------------------------------------------------- /tinypy/parser/__pycache__/__init__.cpython-34.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxmalysh/tiny-py-interpreter/d636b613ef7a156b6d225e345a56c7502a193324/tinypy/parser/__pycache__/__init__.cpython-34.pyc -------------------------------------------------------------------------------- /tinypy/parser/__pycache__/__init__.cpython-35.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxmalysh/tiny-py-interpreter/d636b613ef7a156b6d225e345a56c7502a193324/tinypy/parser/__pycache__/__init__.cpython-35.pyc -------------------------------------------------------------------------------- /tinypy/parser/__pycache__/__init__.pypy3-23.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxmalysh/tiny-py-interpreter/d636b613ef7a156b6d225e345a56c7502a193324/tinypy/parser/__pycache__/__init__.pypy3-23.pyc -------------------------------------------------------------------------------- /tinypy/run_tests.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import unittest 4 | import subprocess 5 | from subprocess import PIPE, STDOUT 6 | 7 | in_directory = os.path.join(os.getcwd(), 'tinypy/tests') 8 | proper_tests_dir = in_directory 9 | failing_tests_dir = os.path.join(in_directory, 'fail') 10 | shell_tests_dir = os.path.join(in_directory, 'shell') 11 | 12 | python_binary = sys.executable 13 | tinypy_binary = sys.executable + ' ' + os.path.join(os.getcwd(), 'test_entryp.py') 14 | 15 | envs = dict(os.environ) 16 | envs['COVERAGE_PROCESS_START'] = '.coveragerc' 17 | 18 | 19 | class FileTestCase(unittest.TestCase): 20 | 21 | def __init__(self, file_path): 22 | super().__init__() 23 | self.file_path = file_path 24 | 25 | def __str__(self): 26 | return self.__class__.__name__ + ":" + os.path.basename(self.file_path) 27 | 28 | 29 | class ProperParserTest(FileTestCase): 30 | 31 | def runTest(self): 32 | result = subprocess.run( 33 | tinypy_binary + " --parse " + self.file_path, stdout=PIPE, stderr=STDOUT, 34 | shell=True, universal_newlines=True, env=envs, 35 | ) 36 | 37 | self.assertEqual(result.returncode, 0) 38 | self.assertTrue(not result.stdout) 39 | self.assertTrue(not result.stderr) 40 | 41 | 42 | class FailingParserTest(FileTestCase): 43 | 44 | def runTest(self): 45 | result = subprocess.run( 46 | tinypy_binary + " --parse " + self.file_path, stdout=PIPE, stderr=STDOUT, 47 | shell=True, universal_newlines=True, env=envs, 48 | ) 49 | 50 | self.assertNotEqual(result.returncode, 0) 51 | self.assertTrue(result.stdout != '') 52 | 53 | 54 | class SemanticTest(FileTestCase): 55 | 56 | def runTest(self): 57 | tinypy_result = subprocess.run( 58 | tinypy_binary + ' -q ' + self.file_path, stdout=PIPE, stderr=STDOUT, 59 | shell=True, universal_newlines=True, env=envs 60 | ) 61 | python_result = subprocess.run( 62 | python_binary + ' -q ' + self.file_path, stdout=PIPE, stderr=STDOUT, 63 | shell=True, universal_newlines=True 64 | ) 65 | 66 | self.assertEqual(tinypy_result.returncode, python_result.returncode) 67 | self.assertEqual(tinypy_result.stdout, python_result.stdout) 68 | 69 | 70 | class ShellTest(FileTestCase): 71 | 72 | def runTest(self): 73 | # Note that each process should have its own file handle 74 | with open(self.file_path) as fstdin: 75 | tinypy_result = subprocess.run( 76 | tinypy_binary + ' -q -i ', stdin=fstdin, stdout=PIPE, stderr=STDOUT, 77 | shell=True, env=envs, 78 | ) 79 | 80 | with open(self.file_path) as fstdin: 81 | python_result = subprocess.run( 82 | python_binary + ' -q -i ', stdin=fstdin, stdout=PIPE, stderr=STDOUT, 83 | shell=True, env=envs, 84 | ) 85 | 86 | self.assertEqual(tinypy_result.returncode, python_result.returncode) 87 | self.assertEqual(tinypy_result.stdout, python_result.stdout) 88 | 89 | 90 | class EvalTest(unittest.TestCase): 91 | 92 | def __init__(self, expression): 93 | super().__init__() 94 | self.expression = expression 95 | 96 | def runTest(self): 97 | from tinypy.tinypyapp import tinypy_eval 98 | expected = eval(self.expression) 99 | actual = tinypy_eval(self.expression) 100 | self.assertEqual(expected, actual, msg=self.expression) 101 | 102 | def __str__(self): 103 | return self.__class__.__name__ + ":" + self.expression 104 | 105 | 106 | class RedirectTest(SemanticTest): 107 | def __init__(self, filepath): 108 | super().__init__(' < ' + filepath) 109 | 110 | 111 | def get_suite(): 112 | suite = unittest.TestSuite() 113 | 114 | for file in os.listdir(proper_tests_dir): 115 | file_path = os.path.join(proper_tests_dir, file) 116 | 117 | if file.startswith("unicode") and os.name == 'nt': 118 | continue 119 | 120 | if file.endswith(".txt"): 121 | suite.addTest(ProperParserTest(file_path)) 122 | 123 | elif file.endswith(".py"): 124 | suite.addTest(SemanticTest(file_path)) 125 | 126 | for file in os.listdir(failing_tests_dir): 127 | if file.endswith(".txt"): 128 | file_path = os.path.join(failing_tests_dir, file) 129 | suite.addTest(FailingParserTest(file_path)) 130 | 131 | for file in os.listdir(shell_tests_dir): 132 | file_path = os.path.join(shell_tests_dir, file) 133 | suite.addTest(ShellTest(file_path)) 134 | 135 | suite.addTest(RedirectTest(os.path.join(proper_tests_dir, 'factorial.py'))) 136 | 137 | evalTests = [ 138 | EvalTest("0xDEADC0DE"), 139 | EvalTest("float( 1+3*(7-2) / min(3.0, 1.5) )"), 140 | EvalTest("'hey'"), 141 | EvalTest("print"), 142 | EvalTest("'hello' + 'world'"), 143 | EvalTest('max(1,10,3.0,4)'), 144 | ] 145 | suite.addTests(evalTests) 146 | 147 | return suite 148 | 149 | if __name__ == '__main__': 150 | unittest.TextTestRunner().run(get_suite()) 151 | -------------------------------------------------------------------------------- /tinypy/runtime/Errors.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | class BaseRuntimeException(BaseException): 4 | def __init__(self, message): 5 | super().__init__(message) 6 | 7 | class MemoryError(BaseRuntimeException): 8 | pass 9 | 10 | class NameError(MemoryError): 11 | pass 12 | 13 | class TypeError(BaseRuntimeException): 14 | pass 15 | 16 | class ArithmeticError(BaseRuntimeException): 17 | pass 18 | 19 | class ZeroDivisionError(ArithmeticError): 20 | def __init__(self): 21 | super().__init__("division by zero") 22 | 23 | class SyntaxError(BaseRuntimeException): 24 | pass 25 | 26 | 27 | class AttributeError(BaseRuntimeException): 28 | pass 29 | 30 | 31 | class KeyError(BaseRuntimeException): 32 | pass 33 | 34 | 35 | class IndexError(BaseRuntimeException): 36 | pass 37 | 38 | 39 | class NotImplementedError(BaseRuntimeException): 40 | pass 41 | -------------------------------------------------------------------------------- /tinypy/runtime/Memory.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | # 4 | # Looks like we have to pass namespace instance to every @Name node; 5 | # Alternatively, we can make every node being able to retreive namespace by its own. 6 | # How? Probably, enclosing statement should provide such API, so we'll pass enclosing statements 7 | # as arguments for expressions, and @Expression will have a method "retrieve namespace" 8 | # 9 | from tinypy import runtime 10 | 11 | class Namespace: 12 | 13 | INSTANCE = None 14 | 15 | builtInFunctions = { 16 | 'print' : print, 17 | 'input' : input, 18 | 'exit' : exit, 19 | 'len' : len, 20 | 'str' : str, 21 | 'int' : int, 22 | 'hex' : hex, 23 | 'oct' : oct, 24 | 'bin' : bin, 25 | 'float' : float, 26 | 'type' : type, 27 | 'range' : range, 28 | 'chr' : chr, 29 | 'ascii' : ascii, 30 | 'abs' : abs, 31 | 'max' : max, 32 | 'min' : min, 33 | 'sum' : sum, 34 | 'open' : open, 35 | 'reversed' : reversed, 36 | } 37 | 38 | def __init__(self, outerScope): 39 | self.outerScope = outerScope 40 | self.content = {} 41 | if self.outerScope == None: 42 | self.content.update(Namespace.builtInFunctions) 43 | 44 | def get(self, name): 45 | try: 46 | # Search in the current scope first 47 | return self.content[name] 48 | except KeyError: 49 | if self.outerScope != None: 50 | return self.outerScope.get(name) 51 | 52 | raise runtime.Errors.NameError("name %s is not defined" % name) 53 | 54 | def set(self, name, value): 55 | self.content[name] = value 56 | 57 | 58 | 59 | Namespace.INSTANCE = Namespace(None) 60 | CurrentNamespace = Namespace.INSTANCE 61 | -------------------------------------------------------------------------------- /tinypy/runtime/__init__.py: -------------------------------------------------------------------------------- 1 | from . import Errors 2 | from . import Memory -------------------------------------------------------------------------------- /tinypy/shell/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Max Malysh' 2 | -------------------------------------------------------------------------------- /tinypy/shell/shell.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | import antlr4 4 | from antlr4.tree.Trees import Trees 5 | 6 | from tinypy.AST.builder.Builder import CustomVisitor 7 | from tinypy.AST.stmt import ControlFlowMark 8 | from tinypy.parser.CustomLexer import CustomLexer 9 | from tinypy.parser.TinyPyParser import TinyPyParser 10 | from tinypy.parser.Errors import CustomErrorStrategy, CustomErrorListener, BufferedErrorListener 11 | from tinypy.parser import CST 12 | from tinypy import runtime 13 | 14 | class InteractiveShell: 15 | greeting = "Call exit() to quit\n" 16 | 17 | def __init__(self, args): 18 | self.args = args 19 | self.single_input = "" 20 | self.readMore = False 21 | pass 22 | 23 | def loop(self): 24 | while True: 25 | try: 26 | if self.readMore: 27 | sys.stdout.write("... ") 28 | sys.stdout.flush() 29 | self.single_input += sys.stdin.readline() 30 | else: 31 | sys.stdout.write(">>> ") 32 | sys.stdout.flush() 33 | self.single_input = sys.stdin.readline() 34 | 35 | input_stream = antlr4.InputStream(self.single_input) 36 | 37 | # Instantiate and run generated lexer 38 | self.lexer = CustomLexer(input_stream) 39 | self.tokens = antlr4.CommonTokenStream(self.lexer) 40 | 41 | # Setting up error handling stuff 42 | error_handler = CustomErrorStrategy() 43 | error_listener = CustomErrorListener() 44 | buffered_errors = BufferedErrorListener() 45 | error_listener.addDelegatee(buffered_errors) 46 | 47 | # Run parser and set error handler 48 | self.parser = TinyPyParser(self.tokens) 49 | self.parser._errHandler = error_handler 50 | 51 | # Remove default terminal error listener & and our own 52 | self.parser.removeErrorListeners() 53 | self.parser.addErrorListener(error_listener) 54 | 55 | # Parse input 56 | parse_tree = self.parser.single_input() 57 | 58 | # Determine what to do next 59 | if error_listener.eof_received: 60 | print() 61 | exit(0) 62 | elif error_listener.input_unfinished or self.lexer.opened > 0: 63 | # User has not finished his input yet, read the next line and repeat 64 | self.readMore = True 65 | continue 66 | elif error_listener.errors_encountered > 0: 67 | # Errors encountered, start over 68 | print(buffered_errors.buffer) 69 | self.readMore = False 70 | continue 71 | else: 72 | # Successfully parsed the input, next time start over 73 | self.readMore = False 74 | 75 | # Build a flattened syntax tree 76 | cst = CST.CstFiltered(tree=parse_tree) 77 | 78 | # Print some stuff... (if needed) 79 | if self.args.cst: 80 | print(cst) 81 | 82 | if self.args.parse_tree: 83 | parseTreeString = Trees.toStringTree(parse_tree, recog=self.parser) 84 | print(parseTreeString) 85 | 86 | # Evaluate it... 87 | visitor = CustomVisitor() 88 | ast = visitor.visitSingle_input(parse_tree) 89 | if ast == None: 90 | continue 91 | 92 | if self.args.parse_only: 93 | continue 94 | 95 | results = ast.eval() 96 | 97 | # 98 | # ast.eval() returns list of statements; loop through them and print 99 | # 100 | if results != None: 101 | for statement in results: 102 | if statement != None and not isinstance(statement, ControlFlowMark): 103 | sys.displayhook(statement) 104 | 105 | #if results != None: 106 | # sys.displayhook(results) 107 | 108 | except KeyboardInterrupt as e: 109 | print("") 110 | exit(0) 111 | except antlr4.RecognitionException as e: 112 | print("Caught" + str(e) ) 113 | except runtime.Errors.BaseRuntimeException as e: 114 | print(e.__class__.__name__ + ": " + str(e)) 115 | except SystemExit as e: 116 | sys.exit(e) 117 | except BaseException as e: 118 | print(e.__class__.__name__ + ": " + str(e)) 119 | 120 | 121 | def print_greeting(self): 122 | print(self.greeting) 123 | -------------------------------------------------------------------------------- /tinypy/tests/1.txt: -------------------------------------------------------------------------------- 1 | foo = 1234 2 | bar = 1337 3 | baz = foo + bar 4 | 5 | # Hey there test etst 34234 ### sdfsdf 6 | something = "some stuff" 7 | test = something * 2 8 | more = test + '\t\thmmm' 9 | 10 | -------------------------------------------------------------------------------- /tinypy/tests/10.txt: -------------------------------------------------------------------------------- 1 | def hello(first, second, # look, it will work even with a line break inside! 2 | third): 3 | return "world" 4 | 5 | foo = bar 6 | bar = hello(1, 2, 3) 7 | bar 8 | print() 9 | print(hello('hey', bar, 0xFF)) 10 | 11 | 12 | -------------------------------------------------------------------------------- /tinypy/tests/11.txt: -------------------------------------------------------------------------------- 1 | if 1+2 == 0: 2 | test = foo 3 | else: 4 | test = bar 5 | -------------------------------------------------------------------------------- /tinypy/tests/12.txt: -------------------------------------------------------------------------------- 1 | 0 + (1337 + (42 * 10)) 2 | # hey there 3 | 234 4 | 5 | 123123 6 | -------------------------------------------------------------------------------- /tinypy/tests/13.txt: -------------------------------------------------------------------------------- 1 | def test(): 2 | def foo(): 3 | def bar(): 4 | hey() 5 | -------------------------------------------------------------------------------- /tinypy/tests/14.txt: -------------------------------------------------------------------------------- 1 | def test(): 2 | hey() 3 | lala() 4 | -------------------------------------------------------------------------------- /tinypy/tests/15.txt: -------------------------------------------------------------------------------- 1 | if True: 2 | print("this") 3 | 4 | print("shouldn't fail") 5 | -------------------------------------------------------------------------------- /tinypy/tests/16.txt: -------------------------------------------------------------------------------- 1 | print("hello") 2 | -------------------------------------------------------------------------------- /tinypy/tests/17.txt: -------------------------------------------------------------------------------- 1 | def foo(bar, baz, qux): 2 | a = bar + qux 3 | b = baz * a 4 | if b > 10: 5 | return 10 6 | else: 7 | return None 8 | 9 | while True: 10 | print(foo(1,2,3)) 11 | -------------------------------------------------------------------------------- /tinypy/tests/18.txt: -------------------------------------------------------------------------------- 1 | while True: 2 | world = 13*77 3 | print("hello", world) 4 | -------------------------------------------------------------------------------- /tinypy/tests/19.txt: -------------------------------------------------------------------------------- 1 | # Inconsistent use of tabs and spaces (TabError in Python3; but works in Python 2) 2 | if True: 3 | print("one tab") # 1 tab here 4 | print("8 spaces") # 8 spaces here 5 | 6 | -------------------------------------------------------------------------------- /tinypy/tests/2.txt: -------------------------------------------------------------------------------- 1 | pass 2 | while True: 3 | break 4 | while False: 5 | continue 6 | def test(): 7 | return 8 | -------------------------------------------------------------------------------- /tinypy/tests/20.txt: -------------------------------------------------------------------------------- 1 | ((25 + 15) / 10) * 4 2 | (8 - 1 + 3) * 6 - ((3 + 7) * 2) 3 | -(((25 + 15) / 10) * 4 + (8 - 1 + 3) * 6 - ((3 + 7) * 2)) 4 | --(((25 + 15) / 10) * 4 + (8 - 1 + 3) * 6 - ((3 + 7) * 2)) 5 | ---(((25 + 15) / 10) * 4 + (8 - 1 + 3) * 6 - ((3 + 7) * 2)) 6 | 7 | 1 * (1 * 1 * (1 * 1 + 1) + 1 * 1 * 1 * (1 * 1 + 1)) * (((( 1+1*1+1)))) / 1 * (1 * 1 * (1 * 1 + 1) + 1 * 1 * 1 * (1 * 1 + 1)) * (((( 1+1*1+1)))) 8 | 9 | 2 + 2 * 3 / (100-99*1) 10 | 11 | (1 * 2 - 3 * 4) % 2 12 | 1 * 2 - 3 * 4 % 2 13 | -------------------------------------------------------------------------------- /tinypy/tests/21.txt: -------------------------------------------------------------------------------- 1 | - (True and True) 2 | 3 | 4 | - (not False and 3 > 2 > 1) 5 | -------------------------------------------------------------------------------- /tinypy/tests/22.txt: -------------------------------------------------------------------------------- 1 | 2.3 2 | 2.3e-4 3 | 2.23452345234523452342345234523423452 4 | 2.23452345234523452342345234523423452e-2 5 | 6 | 2.23452345234523452342345234523423452e-123 7 | -------------------------------------------------------------------------------- /tinypy/tests/23.txt: -------------------------------------------------------------------------------- 1 | print("hey") 2 | if 10: 3 | print("10") 4 | -------------------------------------------------------------------------------- /tinypy/tests/3.txt: -------------------------------------------------------------------------------- 1 | pass 2 | pass 3 | pass 4 | # comments 5 | -------------------------------------------------------------------------------- /tinypy/tests/4.txt: -------------------------------------------------------------------------------- 1 | 2 + (3*2 - 4) / 2 2 | -------------------------------------------------------------------------------- /tinypy/tests/5.txt: -------------------------------------------------------------------------------- 1 | a = 1 + 13*77 2 | b = a / 2 3 | -------------------------------------------------------------------------------- /tinypy/tests/6.txt: -------------------------------------------------------------------------------- 1 | hey = "there" 2 | so = "hello" 3 | hmm = so + hey 4 | -------------------------------------------------------------------------------- /tinypy/tests/7.txt: -------------------------------------------------------------------------------- 1 | while 1337: 2 | a = 123 3 | -------------------------------------------------------------------------------- /tinypy/tests/8.txt: -------------------------------------------------------------------------------- 1 | def function1(): 2 | a = b + c 3 | 4 | 5 | 6 | 7 | def function2(argument1, argument2): 8 | x = 10 9 | 10 | 11 | return 1337 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /tinypy/tests/9.txt: -------------------------------------------------------------------------------- 1 | a = False 2 | b = True 3 | if a != b: 4 | foo = 10 5 | while foo > 0: 6 | foo = foo + 1 7 | -------------------------------------------------------------------------------- /tinypy/tests/binarysearch.py: -------------------------------------------------------------------------------- 1 | def binary_search1(l, value): 2 | low = 0 3 | high = len(l)-1 4 | while low <= high: 5 | mid = int((low+high)/2) 6 | if l[mid] > value: high = mid-1 7 | elif l[mid] < value: low = mid+1 8 | else: return mid 9 | return -1 10 | 11 | a = [-31, 0, 1, 2, 4, 65, 83, 99, 782] 12 | 13 | print("False:") 14 | print(binary_search1(a, 1234)) 15 | print(binary_search1(a, -123)) 16 | print(binary_search1(a, -1)) 17 | print(binary_search1(a, 44)) 18 | 19 | print('True:') 20 | for x in a: 21 | print(binary_search1(a, x)) 22 | 23 | -------------------------------------------------------------------------------- /tinypy/tests/ethiopian.py: -------------------------------------------------------------------------------- 1 | tutor = True 2 | 3 | def halve(x): 4 | return int(x / 2) 5 | 6 | def double(x): 7 | return x * 2 8 | 9 | def even(x): 10 | return not x % 2 11 | 12 | def ethiopian(multiplier, multiplicand): 13 | if tutor: 14 | print("Ethiopian multiplication of ", multiplier, "and", multiplicand) 15 | 16 | result = 0 17 | while multiplier >= 1: 18 | mtierString = "%4i" % multiplier 19 | mcandString = "%6i" % multiplicand 20 | 21 | if even(multiplier): 22 | if tutor: 23 | print(mtierString, mcandString, "STRUCK") 24 | else: 25 | if tutor: 26 | print(mtierString, mcandString, "KEPT") 27 | 28 | result += multiplicand 29 | multiplier = halve(multiplier) 30 | multiplicand = double(multiplicand) 31 | if tutor: 32 | print() 33 | return result 34 | 35 | print(ethiopian(17, 34)) -------------------------------------------------------------------------------- /tinypy/tests/euler04.py: -------------------------------------------------------------------------------- 1 | def reverse(text): 2 | if len(text) <= 1: 3 | return text 4 | 5 | return reverse(text[1:]) + text[0] 6 | 7 | 8 | def is_palindrome(num): 9 | return str(num) == reverse(str(num)) 10 | 11 | 12 | x = 999 13 | y = 990 # largest 3-digit multiple of 11 14 | maximum = 0 15 | while x >= 100: 16 | while y >= 100: 17 | product = x*y 18 | if is_palindrome(product) and product > maximum: 19 | maximum = product 20 | y -= 11 # decrement by 11 instead of 1 21 | y = 990 22 | x -= 1 23 | 24 | print(maximum) # prints 906609 25 | 26 | -------------------------------------------------------------------------------- /tinypy/tests/euler38.py: -------------------------------------------------------------------------------- 1 | def is_pandigital(num): 2 | num = str(num) 3 | if len(num) != 9: 4 | return False 5 | for digit in range(1, 10): 6 | if str(digit) not in num: 7 | return False 8 | return True 9 | 10 | maximum = 123456789 11 | best_num = 0 12 | 13 | # biggest starting number is 4 digits 14 | for i in range(1, 10000): 15 | collection_of_digits = '' 16 | seed = 0 17 | while len(collection_of_digits) < 10: 18 | seed += 1 19 | collection_of_digits += str(i * seed) 20 | if is_pandigital(collection_of_digits): 21 | if int(collection_of_digits) > maximum: 22 | maximum = int(collection_of_digits) 23 | best_num = i 24 | 25 | print(best_num, " : ", maximum) -------------------------------------------------------------------------------- /tinypy/tests/factorial.py: -------------------------------------------------------------------------------- 1 | def factorial(n): 2 | if n == 0: 3 | return 1 4 | elif n < 0: 5 | return 0 6 | return n*factorial(n-1) 7 | 8 | for i in range(0, 10): 9 | print(i, "|", factorial(i)) -------------------------------------------------------------------------------- /tinypy/tests/fail/1.txt: -------------------------------------------------------------------------------- 1 | while: 2 | # should fail 3 | print("fail?") -------------------------------------------------------------------------------- /tinypy/tests/fail/2.txt: -------------------------------------------------------------------------------- 1 | if True: 2 | fail 3 | -------------------------------------------------------------------------------- /tinypy/tests/fail/3.txt: -------------------------------------------------------------------------------- 1 | if True: 2 | print("hey") # 4 spaces 3 | print("hello") # 3 spaces 4 | -------------------------------------------------------------------------------- /tinypy/tests/fail/4.txt: -------------------------------------------------------------------------------- 1 | if True: 2 | print("hey") # 4 spaces 3 | print("hello") # 5 spaces 4 | -------------------------------------------------------------------------------- /tinypy/tests/fail/5.txt: -------------------------------------------------------------------------------- 1 | if True: 2 | print("one tab") # 1 tab here 3 | print("4 spaces") # 4 spaces here 4 | -------------------------------------------------------------------------------- /tinypy/tests/fail/6.txt: -------------------------------------------------------------------------------- 1 | someStatement() 2 | if foo(): 3 | if bar(): 4 | fooAndBar() 5 | bogusLine() 6 | 7 | -------------------------------------------------------------------------------- /tinypy/tests/fail/7.txt: -------------------------------------------------------------------------------- 1 | x = 0 2 | while x < 3: 3 | x = x + 1 4 | if True: 5 | print("foo") 6 | print("bar") 7 | 8 | if True: 9 | print("baz") 10 | print("ouch") 11 | -------------------------------------------------------------------------------- /tinypy/tests/fail/8.txt: -------------------------------------------------------------------------------- 1 | x = 0 2 | while x < 3: 3 | x = x + 1 4 | if True: 5 | print("foo") 6 | print("bar") 7 | if True: 8 | print("baz") 9 | print("ouch") 10 | -------------------------------------------------------------------------------- /tinypy/tests/fail/9.txt: -------------------------------------------------------------------------------- 1 | x = 0 2 | while x < 3: 3 | x = x + 1 4 | if True: 5 | print("foo") 6 | print("bar") 7 | 8 | if True: 9 | print("baz") 10 | print("ouch") 11 | -------------------------------------------------------------------------------- /tinypy/tests/fibo1.py: -------------------------------------------------------------------------------- 1 | def fibMemo(): 2 | pad = {0:0, 1:1} 3 | def func(n): 4 | if n not in pad: 5 | pad[n] = func(n-1) + func(n-2) 6 | return pad[n] 7 | return func 8 | 9 | fm = fibMemo() 10 | for i in range(1,31): 11 | print(fm(i)) -------------------------------------------------------------------------------- /tinypy/tests/fibo2.py: -------------------------------------------------------------------------------- 1 | def fibFastRec(n): 2 | def fib(prvprv, prv, c): 3 | if c < 1: return prvprv 4 | else: return fib(prv, prvprv + prv, c - 1) 5 | return fib(0, 1, n) 6 | 7 | for i in range(1,31): 8 | print(fibFastRec(i)) -------------------------------------------------------------------------------- /tinypy/tests/fibo3.py: -------------------------------------------------------------------------------- 1 | 2 | def prevPowTwo(n): 3 | 'Gets the power of two that is less than or equal to the given input' 4 | if ((n & -n) == n): 5 | return n 6 | else: 7 | n -= 1 8 | n |= n >> 1 9 | n |= n >> 2 10 | n |= n >> 4 11 | n |= n >> 8 12 | n |= n >> 16 13 | n += 1 14 | return (n/2) 15 | 16 | def crazyFib(n): 17 | 'Crazy fast fibonacci number calculation' 18 | powTwo = prevPowTwo(n) 19 | 20 | q = 1; r = 1; i = 1 21 | s = 0 22 | 23 | while(i < powTwo): 24 | i *= 2 25 | qn = q*q + r*r 26 | rn = r * (q + s) 27 | sn = (r*r + s*s) 28 | q = qn; r = rn; s = sn 29 | 30 | while(i < n): 31 | i += 1 32 | qn = q+r; rn = q; sn = r 33 | q = qn; r = rn; s = sn 34 | 35 | return q 36 | 37 | for i in range(1,31): 38 | print(crazyFib(i)) -------------------------------------------------------------------------------- /tinypy/tests/file.py: -------------------------------------------------------------------------------- 1 | # This file is launched automatically from the outer folder 2 | f = open('tinypy/tests/file.py') 3 | for line in f: 4 | print(line) 5 | -------------------------------------------------------------------------------- /tinypy/tests/fizzbuzz.py: -------------------------------------------------------------------------------- 1 | num = 0 2 | while num < 101: 3 | msg = '' 4 | if num % 3 == 0: 5 | msg += 'Fizz' 6 | if num % 5 == 0: # no more elif 7 | msg += 'Buzz' 8 | if not msg: # check if msg is an empty string 9 | msg += str(num) 10 | print(msg) 11 | num += 1 12 | -------------------------------------------------------------------------------- /tinypy/tests/fizzbuzz2.py: -------------------------------------------------------------------------------- 1 | count = 0 2 | while count < 101: 3 | if count % 5 == 0 and count % 3 == 0: 4 | print("FizzBuzz") 5 | elif count % 3 == 0: 6 | print("Fizz") 7 | elif count % 5 == 0: 8 | print("Buzz") 9 | else: 10 | print(count) 11 | 12 | count += 1 13 | -------------------------------------------------------------------------------- /tinypy/tests/flow1.py: -------------------------------------------------------------------------------- 1 | a = 0 2 | 3 | while a < 5: 4 | a+=1 5 | print(a) 6 | if True: 7 | if True: 8 | if True: 9 | if True: 10 | if True: 11 | if a == 3: break 12 | while True: 13 | print("HI") 14 | break 15 | print("no") 16 | 17 | while True: 18 | while True: 19 | while True: 20 | print('hello') 21 | break 22 | break 23 | break 24 | 25 | # should be 26 | # 1 27 | # 2 28 | # 3 29 | # HI 30 | # hello -------------------------------------------------------------------------------- /tinypy/tests/gcd.py: -------------------------------------------------------------------------------- 1 | #x = int(input("x = ")) 2 | #y = int(input("y = ")) 3 | 4 | x = 12 5 | y = 8 6 | 7 | while y != 0: 8 | hey = x 9 | x = y 10 | y = hey % y 11 | 12 | print("GCD is %d" % x) 13 | -------------------------------------------------------------------------------- /tinypy/tests/logic.py: -------------------------------------------------------------------------------- 1 | a = 4 2 | b = 10 3 | 4 | if a != b or ((a == b and True) or False): 5 | while False and -3 < 5: 6 | print(1337 | 12345) 7 | 8 | a = (1,2,3,4,5) 9 | for x in a: 10 | if x not in a or x > 5: 11 | print("bazqux") 12 | 13 | print(a + (4,5)) 14 | print(a + (6, -3, (123))) 15 | 16 | print(a is (1,2,3,4,5)) 17 | print(a is not a) 18 | print(3 is (not a)) 19 | print(a is not (1,2)) 20 | print( 21 | not a is not a 22 | ) 23 | 24 | print(4 not in a) 25 | print(not 4 not in a) 26 | print(not not 4 not in a) 27 | 28 | print(3 is 3) 29 | print(3 is 4) 30 | print(3 is not 3) 31 | print(3 is not 4) 32 | print(3 is (not 3)) 33 | print(4 is (not 4)) 34 | print(not 3 is not (1,2,3)) 35 | print(3 in (1,2) * 3) 36 | -------------------------------------------------------------------------------- /tinypy/tests/mergesort1.py: -------------------------------------------------------------------------------- 1 | def mergeSort(alist): 2 | print("Splitting ", alist) 3 | if len(alist) > 1: 4 | mid = int(len(alist) / 2) 5 | lefthalf = alist[:mid] 6 | righthalf = alist[mid:] 7 | 8 | mergeSort(lefthalf) 9 | mergeSort(righthalf) 10 | 11 | i=0 12 | j=0 13 | k=0 14 | 15 | while i < len(lefthalf) and j < len(righthalf): 16 | if lefthalf[i] < righthalf[j]: 17 | alist[k]=lefthalf[i] 18 | i=i+1 19 | else: 20 | alist[k]=righthalf[j] 21 | j=j+1 22 | k=k+1 23 | 24 | while i < len(lefthalf): 25 | alist[k]=lefthalf[i] 26 | i=i+1 27 | k=k+1 28 | 29 | while j < len(righthalf): 30 | alist[k]=righthalf[j] 31 | j=j+1 32 | k=k+1 33 | print("Merging ",alist) 34 | 35 | alist = [54,26,93,17,77,31,44,55,20] 36 | mergeSort(alist) 37 | print(alist) 38 | 39 | -------------------------------------------------------------------------------- /tinypy/tests/numbers1.py: -------------------------------------------------------------------------------- 1 | print(0xA, 0o10, 10, 0b10) 2 | print(0xa, 0O10, 10, 0B10) 3 | 4 | # should be 5 | # 10 8 10 2 6 | # 10 8 10 2 7 | -------------------------------------------------------------------------------- /tinypy/tests/parenbalance.py: -------------------------------------------------------------------------------- 1 | if True: 2 | a = ( 3 | 3 + 4 * 2 4 | ) 5 | 6 | print(a) 7 | 8 | # should be 11 -------------------------------------------------------------------------------- /tinypy/tests/parens.py: -------------------------------------------------------------------------------- 1 | test = { 2 | 'first' : 1, 3 | 'second' : 2, 4 | 'third' : 'dsfasdf', 5 | } 6 | 7 | hey = [ 8 | 999, 9 | 888, 10 | 777, 11 | 666, # 'test test' "comment"" test '''''''' 12 | 555, 13 | 444 14 | ] 15 | 16 | print(test['first']) 17 | print(test['third']) 18 | print(len(test), len(hey)) 19 | 20 | for x in hey: 21 | print(x) 22 | 23 | i = 0 24 | while i < len(hey): 25 | print(hey[i]) 26 | i += 1 27 | 28 | -------------------------------------------------------------------------------- /tinypy/tests/quicksort.py: -------------------------------------------------------------------------------- 1 | def quickSort(arr): 2 | less = [] 3 | pivotList = [] 4 | more = [] 5 | if len(arr) <= 1: 6 | return arr 7 | else: 8 | pivot = arr[0] 9 | for i in arr: 10 | if i < pivot: 11 | less.append(i) 12 | elif i > pivot: 13 | more.append(i) 14 | else: 15 | pivotList.append(i) 16 | less = quickSort(less) 17 | more = quickSort(more) 18 | return less + pivotList + more 19 | 20 | a = [4, 65, 2, -31, 0, 99, 83, 782, 1] 21 | print(a) 22 | 23 | a = quickSort(a) 24 | print(a) -------------------------------------------------------------------------------- /tinypy/tests/scope1.py: -------------------------------------------------------------------------------- 1 | var = 'foo' 2 | def ex2(): 3 | var = 'bar' 4 | print('inside the function var is ', var) 5 | 6 | ex2() 7 | print('outside the function var is ', var) 8 | 9 | # should be bar, foo 10 | 11 | -------------------------------------------------------------------------------- /tinypy/tests/scope2.py: -------------------------------------------------------------------------------- 1 | def test1(foo, bar): 2 | foo = 3 3 | def test2(quix): 4 | foo = 4 5 | test2(123) 6 | print(foo) 7 | # Should be 3 8 | -------------------------------------------------------------------------------- /tinypy/tests/shell/assignment.py: -------------------------------------------------------------------------------- 1 | a = 4 2 | a = a 3 | a 4 | print(a) 5 | 6 | b = [4,5,a] 7 | b = b 8 | b 9 | print(b) 10 | 11 | a = b 12 | [a, b] 13 | print([a,b]) 14 | -------------------------------------------------------------------------------- /tinypy/tests/shell/control_flow.py: -------------------------------------------------------------------------------- 1 | 2 | x = [1, 2, 3] 3 | 4 | def test(): 5 | y = [] 6 | z = {0} 7 | for i in range(0, 100): 8 | if i == 50: 9 | print(y) 10 | y 11 | break 12 | pass 13 | print(x) 14 | x 15 | if i % 3 == 0: 16 | y.append(i) 17 | continue 18 | print('test', [x, y]) 19 | print([x, y]) 20 | print([y, x]) 21 | for value in x: 22 | print(y) 23 | z.update({value+100}) 24 | print([y, x]) 25 | if True: 26 | return x 27 | 28 | print(test()) 29 | -------------------------------------------------------------------------------- /tinypy/tests/shell/debug.py: -------------------------------------------------------------------------------- 1 | if True: 2 | foobar = 3*4 + 2 3 | pass 4 | 5 | 6 | if True: 7 | 2 8 | if True: 9 | 3 10 | if True: 11 | 4 12 | 13 | -------------------------------------------------------------------------------- /tinypy/tests/shell/dedents.py: -------------------------------------------------------------------------------- 1 | while True: 2 | if True: 3 | if True: 4 | print('hey!') 5 | break 6 | print('foobar') 7 | print('test') 8 | 9 | -------------------------------------------------------------------------------- /tinypy/tests/shell/lcm.py: -------------------------------------------------------------------------------- 1 | 2 | x = 48 3 | y = 180 4 | 5 | def gcd(x, y): 6 | while y != 0: 7 | hey = x 8 | x = y 9 | y = hey % y 10 | return x 11 | 12 | def lcm(a, b): 13 | return (a * b / gcd(a, b)) 14 | 15 | gcd(x, lcm(x,y)) 16 | 17 | print("GCD is %d" % gcd(x,y)) 18 | print("LCM is %d" % lcm(x,y)) 19 | 20 | gcd(gcd(y,x), lcm(x,y)) 21 | 22 | -------------------------------------------------------------------------------- /tinypy/tests/shell/statement_lists.py: -------------------------------------------------------------------------------- 1 | for i in range(0, 5): 2 | i 3 | 4 | x = 0; i = 3 5 | skipped = False 6 | while x < 100: 7 | x 8 | if x % 2 == 0: 9 | i = x 10 | while i > 0: 11 | if i in [1,2,5,9,30,60]: 12 | [i, x] 13 | x 14 | i -= 1 15 | elif x % 3 == 0: 16 | # comment 17 | 'hey' 18 | pass 19 | elif x % 5 == 0: 20 | [x << 3, x >> 4] 21 | [x ^ 1, [[i], x^2 + 30^2]] 22 | elif x % 13 == 0 and not skipped: 23 | skipped = True 24 | continue 25 | else: 26 | x*x 27 | x += 1 28 | 29 | 30 | -------------------------------------------------------------------------------- /tinypy/tests/trailing_dedents.py: -------------------------------------------------------------------------------- 1 | while True: 2 | pass 3 | if True: 4 | if True: 5 | print('hey world!') 6 | break -------------------------------------------------------------------------------- /tinypy/tests/unicode1.py: -------------------------------------------------------------------------------- 1 | привет = 'привет!' 2 | if привет: 3 | print('Однако, %s' % привет) -------------------------------------------------------------------------------- /tinypy/tinypyapp.py: -------------------------------------------------------------------------------- 1 | import sys, os, stat 2 | import argparse, time 3 | from enum import Enum 4 | 5 | import antlr4 6 | from antlr4.tree.Trees import Trees 7 | 8 | from tinypy.AST.builder.Builder import CustomVisitor 9 | from tinypy.parser.CST import CstFlattened, CstFiltered 10 | from tinypy.parser.Errors import CustomErrorStrategy, CustomErrorListener 11 | from tinypy.parser.CustomLexer import CustomLexer 12 | from tinypy.parser.TinyPyParser import TinyPyParser 13 | from tinypy.shell.shell import InteractiveShell 14 | 15 | 16 | class InputType(Enum): 17 | File = 1 18 | SingleInput = 2 19 | Expression = 3 20 | 21 | 22 | parserRuleFor = { 23 | InputType.File : TinyPyParser.file_input, 24 | InputType.SingleInput : TinyPyParser.single_input, 25 | InputType.Expression : TinyPyParser.eval_input, 26 | } 27 | 28 | visitorRuleFor = { 29 | InputType.File : CustomVisitor.visitFile_input, 30 | InputType.SingleInput : CustomVisitor.visitSingle_input, 31 | InputType.Expression : CustomVisitor.visitEval_input, 32 | } 33 | 34 | 35 | class EvalArguments: 36 | def __init__(self, cst=False, parse_tree=False, parse_only=False, print_timings=False): 37 | self.cst = cst 38 | self.parse_tree = parse_tree 39 | self.parse_only = parse_only 40 | self.print_timings = print_timings 41 | 42 | 43 | def tinypy_eval(sourcecode:str, firstRule=InputType.Expression, args=None): 44 | if args == None: 45 | args = EvalArguments() 46 | 47 | totalTime = time.time() 48 | input_stream = antlr4.InputStream(sourcecode) 49 | 50 | # Instantiate an run generated lexer 51 | lexer = CustomLexer(input_stream) 52 | tokens = antlr4.CommonTokenStream(lexer) 53 | 54 | # Instantiate and run generated parser 55 | parser = TinyPyParser(tokens) 56 | parser._errHandler = CustomErrorStrategy() 57 | 58 | error_listener = CustomErrorListener() 59 | parser.addErrorListener(error_listener) 60 | 61 | # Traverse the parse tree 62 | parseTime = time.time() 63 | try: 64 | parse_tree = parserRuleFor[firstRule](parser) 65 | except Exception as e: 66 | return -1 67 | parseTime = time.time() - parseTime 68 | 69 | if error_listener.errors_encountered != 0: 70 | return -1 71 | 72 | # Print parse trees if need (full or flattened) 73 | if args.parse_tree: 74 | parseTreeString = Trees.toStringTree(parse_tree, recog=parser) 75 | print(parseTreeString) 76 | 77 | if args.cst: 78 | cst = CstFiltered(tree=parse_tree) 79 | print(cst) 80 | 81 | # Build an AST 82 | astBuildTime = time.time() 83 | 84 | visitor = CustomVisitor() 85 | ast = visitorRuleFor[firstRule](visitor, parse_tree) 86 | 87 | astBuildTime = time.time() - astBuildTime 88 | 89 | if ast == None: 90 | return -1 91 | 92 | if args.parse_only: 93 | return 0 94 | 95 | # Evaluate the AST we've built 96 | evalTime = time.time() 97 | try: 98 | evalResult = ast.eval() 99 | except BaseException as e: 100 | print(e.__class__.__name__ + ": " + str(e)) 101 | return -1 102 | 103 | evalTime = time.time() - evalTime 104 | 105 | totalTime = time.time() - totalTime 106 | 107 | if args.print_timings: 108 | timings = [ 109 | ('Parsing', parseTime), 110 | ('Building an AST', astBuildTime), 111 | ('Evaluating', evalTime), 112 | ('Total time', totalTime), 113 | ('Etc', totalTime-parseTime-astBuildTime-evalTime) 114 | ] 115 | print("#"*80) 116 | for timing in timings: 117 | print((timing[0]+": %.3f ms") % (timing[1]*1000)) 118 | 119 | if firstRule == InputType.Expression: 120 | return evalResult 121 | 122 | return 0 123 | 124 | 125 | def main(): 126 | argParser = argparse.ArgumentParser() 127 | argParser.add_argument('filename', type=str, nargs='?', 128 | help='Path to the script file.') 129 | argParser.add_argument('-c', dest='eval_input', type=str, 130 | help='Program passed in as string') 131 | argParser.add_argument('--cst', dest='cst', action='store_true', 132 | help='Show flattened concreted syntax tree for the input (parse tree)') 133 | argParser.add_argument('--tokens', dest='parse_tree', action='store_true', 134 | help='Show string representation of a parse tree for the input') 135 | argParser.add_argument('--parse', dest='parse_only', action='store_true', 136 | help='Parse input without evaluating it.') 137 | argParser.add_argument('--timings', dest='print_timings', action='store_true', 138 | help='Print time spend during parsing, building an AST and evaluating.') 139 | argParser.add_argument('-q', dest='ignore_greeting', action='store_true', 140 | help="Don't print version and copyright messages on interactive startup") 141 | argParser.add_argument('-i', dest='force_promt', action='store_true', 142 | help='forces a prompt even if stdin does not appear to be a terminal') 143 | # 144 | # Parse arguments 145 | # 146 | argParser.set_defaults(cst=False, parse_tree=False, tokens=False, parse=False, timings=False) 147 | args = argParser.parse_args() 148 | 149 | # 150 | # Check whether terminal is attached 151 | # 152 | isatty = True if sys.stdin.isatty() else False 153 | 154 | if args.filename == None and (isatty or args.force_promt) and not args.eval_input: 155 | shell = InteractiveShell(args) 156 | 157 | if not args.ignore_greeting: 158 | shell.print_greeting() 159 | 160 | shell.loop() 161 | 162 | if args.eval_input != None: 163 | firstRule = InputType.SingleInput 164 | content = args.eval_input 165 | else: 166 | firstRule = InputType.File 167 | 168 | if isatty: 169 | with open(args.filename) as file_contents: 170 | content = file_contents.read() 171 | else: 172 | content = ''.join(sys.stdin.readlines()) 173 | 174 | content += '\n' 175 | retvalue = tinypy_eval(content, firstRule, args) 176 | exit(retvalue) 177 | --------------------------------------------------------------------------------