├── .gitignore ├── .gitmodules ├── .travis.yml ├── .vscode ├── extensions.json └── settings.json ├── LICENSE ├── Makefile ├── README.md ├── editor-support └── vscode │ ├── .vscode │ └── launch.json │ ├── language-configuration.json │ ├── package.json │ ├── syntaxes │ └── quill.tmLanguage.json │ └── vscode ├── examples ├── classes.nl └── hello.nl ├── nolang-py ├── nolang ├── __init__.py ├── astnodes.py ├── builtins │ ├── __init__.py │ ├── buffer.py │ ├── builtin.py │ ├── core │ │ ├── __init__.py │ │ ├── freezing.py │ │ ├── reflect.py │ │ └── text.py │ ├── defaults.py │ ├── exception.py │ ├── io.py │ └── spec.py ├── bytecode.py ├── compiler.py ├── error.py ├── frameobject.py ├── function.py ├── importer.py ├── interpreter.py ├── lexer.py ├── main.py ├── module.py ├── objects │ ├── __init__.py │ ├── bool.py │ ├── buffer.py │ ├── dict.py │ ├── int.py │ ├── list.py │ ├── root.py │ ├── space.py │ ├── unicode.py │ ├── userobject.py │ └── usertype.py ├── opcodes.py ├── parser.py └── target.py ├── setup.cfg └── tests ├── __init__.py ├── support.py ├── test_buffer.py ├── test_bytecode.py ├── test_bytecode_compiler.py ├── test_classes.py ├── test_compiler.py ├── test_core.py ├── test_dict.py ├── test_exceptions.py ├── test_freeze.py ├── test_functions.py ├── test_import.py ├── test_interpreter.py ├── test_iterator.py ├── test_lexer.py ├── test_list.py ├── test_main.py ├── test_parser.py ├── test_stringbuilder.py ├── test_strings.py └── test_types.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | 103 | # nolang 104 | nolang-c 105 | # emacs backup files 106 | *~ -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "vendor/pypy"] 2 | path = vendor/pypy 3 | url = https://github.com/mozillazg/pypy 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: python 3 | cache: 4 | directories: 5 | - venv 6 | - vendor/pypy 7 | 8 | python: 2.7 9 | 10 | install: 11 | - make venv 12 | 13 | matrix: 14 | fast_finish: true 15 | include: 16 | - env: RUN=lint 17 | - env: RUN=test 18 | - env: RUN=compile 19 | 20 | script: 21 | - make $RUN RPYTHONFLAGS=--batch 22 | 23 | notifications: 24 | irc: 25 | channels: 26 | - "chat.freenode.net#quill" 27 | on_success: change 28 | on_failure: always 29 | #use_notice: true 30 | #skip_join: true 31 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "donjayamanne.python", 4 | "jmlntw.vscode-ensure-single-final-newline" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "**/*.pyc": true, 4 | "venv": true, 5 | "build": true, 6 | "*.egg-info": true 7 | }, 8 | "files.trimTrailingWhitespace": true, 9 | "files.ensureSingleFinalNewline": true, 10 | "python.linting.pylintEnabled": false, 11 | "python.linting.flake8Enabled": true, 12 | "python.pythonPath": "${workspaceRoot}/venv/bin/python", 13 | "python.unitTest.pyTestEnabled": true, 14 | "python.unitTest.unittestEnabled": false, 15 | "python.unitTest.nosetestsEnabled": false 16 | } 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Maciej Fijalkowski 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: test 2 | 3 | venv: 4 | @git submodule update --init 5 | @virtualenv venv -p `python -c "import sys; print('%s/bin/python' % getattr(sys, 'real_prefix', sys.prefix))"` 6 | @ln -sf ../../../../vendor/pypy/rpython venv/lib/python2.7/site-packages 7 | @echo '#!${PWD}/venv/bin/python\n' > venv/bin/rpython 8 | @cat vendor/pypy/rpython/bin/rpython >> venv/bin/rpython 9 | @chmod +x venv/bin/rpython 10 | @venv/bin/pip install rply pytest flake8 11 | 12 | ensure-venv: 13 | @if [ ! -f venv/bin/pytest ]; then $(MAKE) venv; fi 14 | 15 | compile: ensure-venv 16 | @PYTHONPATH=. venv/bin/rpython $(RPYTHONFLAGS) -O2 nolang/target.py 17 | 18 | clean: 19 | @rm -rf venv 20 | @rm -f nolang-c 21 | 22 | lint: ensure-venv 23 | @venv/bin/flake8 24 | 25 | test: ensure-venv 26 | @venv/bin/pytest tests --tb=short 27 | 28 | check: lint test compile 29 | 30 | install-vscode-extension: 31 | ln -fs `pwd`/editor-support/vscode ~/.vscode/extensions/quill 32 | 33 | uninstall-vscode-extension: 34 | rm -f ~/.vscode/extensions/quill 35 | 36 | .PHONY: all venv ensure-venv clean compile test check lint install-vscode-extension uninstall-vscode-extension 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nolang 2 | A language experiment 3 | 4 | ## General Goals 5 | 6 | Take the good parts of Python, get rid of the bad ones and learn from other 7 | languages 8 | 9 | ## Decisions Taken So Far 10 | 11 | Here is why certain things were decided this way for now: 12 | 13 | ### Immutable Module Scope 14 | 15 | Unlike Python the global scope is immutable. The only thing that can be done 16 | on the global scope is introduce explicit bindings which in themselves can be 17 | mutable. 18 | 19 | ### Semicolons / Braces 20 | 21 | Instead of using indentation based syntax like in Python we go with braces and 22 | semicolons like in Rust. 23 | 24 | The reason this is preferrable over no semicolons and indentation based syntax: 25 | 26 | * easier to write editor support for 27 | * clear ends of blocks (allows better syntax for anonymous functions) 28 | * supports trivial minification in places where this matters 29 | * permits easy code generation 30 | 31 | ### Main Function 32 | 33 | When a file is executed as a script the function named `main` is executed as 34 | entry point. This lets us used an immutable global scope and solves a lot of 35 | issues with the entry point file being different like in Python 36 | 37 | ### Bindings 38 | 39 | Imports and globals are implemented as bindings. A binding is a completely 40 | transparent "proxy" that gives access to the underlying value. A binding can 41 | be of one of the following types: 42 | 43 | * global immutable: a binding that points to a value that cannot be overridden 44 | * contextual immutable: a binding that resolves to the current context and cannot be overridden 45 | * contextual mutable: a binding that resolves to data in the current context and can be modified 46 | 47 | A binding is initialized once (either on the global context or the current local 48 | context) through a callback. It's guaranteed that this is only executed once. 49 | 50 | Pseudocode: 51 | 52 | ``` 53 | binding LANG: str = ContextBinding(lambda: os.environ['LANG']) 54 | ``` 55 | 56 | When `LANG` is accessed it works exactly like any other variable. On first 57 | access per context however that value will be initialized by calling the 58 | function. If it's overridden it will be overridden for the current context 59 | only in this case. 60 | 61 | ### Context Management 62 | 63 | A context is automatically created and can be overridden. A context can be 64 | created in two modes: isolated or non isolated. If a context is created 65 | in an isolated way then all context bindings that are set to be isolated will 66 | be recreated. Non isolated bindings will be inherited. 67 | 68 | ``` 69 | binding current_user: User = ContextBinding( 70 | lambda: get_user_from_request_or_something(), 71 | isolated=true) 72 | 73 | with new_context(isolated=true) { 74 | /* because this is created isolated, the current_user init function 75 | will be called again. It's not inherited into this context. */ 76 | } 77 | ``` 78 | -------------------------------------------------------------------------------- /editor-support/vscode/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that launches the extension inside a new window 2 | { 3 | "version": "0.1.0", 4 | "configurations": [ 5 | { 6 | "name": "Launch Extension", 7 | "type": "extensionHost", 8 | "request": "launch", 9 | "runtimeExecutable": "${execPath}", 10 | "args": ["--extensionDevelopmentPath=${workspaceRoot}" ] 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /editor-support/vscode/language-configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": { 3 | // symbol used for single line comment. Remove this entry if your language does not support line comments 4 | "lineComment": "//", 5 | // symbols used for start and end a block comment. Remove this entry if your language does not support block comments 6 | "blockComment": [ "/*", "*/" ] 7 | }, 8 | // symbols used as brackets 9 | "brackets": [ 10 | ["{", "}"], 11 | ["[", "]"], 12 | ["(", ")"] 13 | ], 14 | // symbols that are auto closed when typing 15 | "autoClosingPairs": [ 16 | ["{", "}"], 17 | ["[", "]"], 18 | ["(", ")"], 19 | ["\"", "\""], 20 | ["'", "'"] 21 | ], 22 | // symbols that that can be used to surround a selection 23 | "surroundingPairs": [ 24 | ["{", "}"], 25 | ["[", "]"], 26 | ["(", ")"], 27 | ["\"", "\""], 28 | ["'", "'"] 29 | ] 30 | } -------------------------------------------------------------------------------- /editor-support/vscode/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "quill", 3 | "displayName": "quill", 4 | "description": "Syntax support for quill", 5 | "version": "0.0.1", 6 | "publisher": "quill", 7 | "engines": { 8 | "vscode": "^1.14.0" 9 | }, 10 | "categories": [ 11 | "Languages" 12 | ], 13 | "contributes": { 14 | "languages": [{ 15 | "id": "quill", 16 | "aliases": ["Quill", "quill"], 17 | "extensions": ["q"], 18 | "configuration": "./language-configuration.json" 19 | }], 20 | "grammars": [{ 21 | "language": "quill", 22 | "scopeName": "source.quill", 23 | "path": "./syntaxes/quill.tmLanguage.json" 24 | }] 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /editor-support/vscode/syntaxes/quill.tmLanguage.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json", 3 | "name": "Quill", 4 | "patterns": [ 5 | { 6 | "include": "#comments" 7 | }, 8 | { 9 | "include": "#keywords" 10 | }, 11 | { 12 | "include": "#literals" 13 | }, 14 | { 15 | "include": "#function" 16 | } 17 | ], 18 | "repository": { 19 | "keywords": { 20 | "patterns": [{ 21 | "name": "keyword.control.quill", 22 | "match": "\\b(def|class|return|var|while|if|or|and|true|false|try|for|in|except|finally|as|raise|import)\\b" 23 | }] 24 | }, 25 | "comments": { 26 | "patterns": [ 27 | { 28 | "name": "comment.block.quill", 29 | "begin": "/\\*", 30 | "end": "\\*/", 31 | "captures": { 32 | "0": { 33 | "name": "punctuation.definition.comment.quill" 34 | } 35 | } 36 | }, 37 | { 38 | "name": "comment.line.double-slash.quill", 39 | "match": "(//).*$\n?", 40 | "captures": { 41 | "1": { 42 | "name": "punctuation.definition.comment.quill" 43 | } 44 | } 45 | } 46 | ] 47 | }, 48 | "literals": { 49 | "patterns": [ 50 | { 51 | "include": "#double-string" 52 | }, 53 | { 54 | "include": "#single-string" 55 | }, 56 | { 57 | "include": "#bools" 58 | }, 59 | { 60 | "include": "#numbers" 61 | } 62 | ] 63 | }, 64 | "double-string": { 65 | "name": "string.quoted.double.quill", 66 | "begin": "\"", 67 | "end": "\"", 68 | "patterns": [ 69 | { 70 | "name": "constant.character.escape.quill", 71 | "match": "\\\\." 72 | } 73 | ] 74 | }, 75 | "single-string": { 76 | "name": "string.quoted.single.quill", 77 | "begin": "'", 78 | "end": "'", 79 | "patterns": [ 80 | { 81 | "name": "constant.character.escape.quill", 82 | "match": "\\\\." 83 | } 84 | ] 85 | }, 86 | "bools": { 87 | "patterns": [ 88 | { 89 | "name": "constant.language.boolean.true.quill", 90 | "match": "(? Str { 12 | return "an animal named {}".format(self.name) 13 | } 14 | 15 | def __repr__(self) -> Str { 16 | return "".format(self.name) 17 | } 18 | } 19 | 20 | class Dog(Animal) { 21 | Bool is_barking 22 | 23 | def __init__(self, Str name, Int age) { 24 | Animal.__init__(self, name, age) 25 | self.is_barking = false 26 | } 27 | } 28 | 29 | def main() { 30 | var items = [ # dynamic typing 31 | Dog('Lumpi', 3), 32 | Dog('Joe', 7), 33 | ] 34 | 35 | for item in items { 36 | print("I have {}; it is {} years old", item, item.age) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /examples/hello.nl: -------------------------------------------------------------------------------- 1 | def main() { 2 | println("Hello {}!", "World!") 3 | } 4 | -------------------------------------------------------------------------------- /nolang-py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | from nolang.main import main 4 | sys.exit(main(sys.argv)) 5 | -------------------------------------------------------------------------------- /nolang/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fijal/quill/c66dc0b66726862f3d0cb065bfa96a2937eaaa44/nolang/__init__.py -------------------------------------------------------------------------------- /nolang/builtins/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fijal/quill/c66dc0b66726862f3d0cb065bfa96a2937eaaa44/nolang/builtins/__init__.py -------------------------------------------------------------------------------- /nolang/builtins/buffer.py: -------------------------------------------------------------------------------- 1 | from nolang.builtins.spec import unwrap_spec 2 | 3 | 4 | @unwrap_spec(size='int') 5 | def buffer(space, size): 6 | return space.newbuf(['\x00'] * size) 7 | 8 | 9 | @unwrap_spec(utf8='utf8') 10 | def buffer_from_utf8(space, utf8): 11 | return space.newbuf([c for c in utf8]) 12 | -------------------------------------------------------------------------------- /nolang/builtins/builtin.py: -------------------------------------------------------------------------------- 1 | from nolang.builtins.spec import unwrap_spec, parameters 2 | from nolang.objects.usertype import W_UserType 3 | 4 | 5 | @unwrap_spec() 6 | def len(space, w_obj): 7 | return space.newint(space.len(w_obj)) 8 | 9 | 10 | @parameters(name='isinstance') 11 | def builtin_isinstance(space, w_obj, w_type): 12 | if not isinstance(w_type, W_UserType): 13 | # typename = space.type(w_type).name 14 | # XXX improve the error message 15 | raise space.apperr(space.w_typeerror, "isinstance right argument is " 16 | "not a type") 17 | return space.newbool(space.isinstance(w_obj, w_type)) 18 | 19 | 20 | @parameters(name='str') 21 | def builtin_str(space, w_obj): 22 | return space.newtext(space.str(w_obj)) 23 | -------------------------------------------------------------------------------- /nolang/builtins/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fijal/quill/c66dc0b66726862f3d0cb065bfa96a2937eaaa44/nolang/builtins/core/__init__.py -------------------------------------------------------------------------------- /nolang/builtins/core/freezing.py: -------------------------------------------------------------------------------- 1 | 2 | def freeze(space, w_obj): 3 | space.freeze(w_obj) 4 | 5 | 6 | def thaw(space, w_obj): 7 | return space.thaw(w_obj) 8 | 9 | 10 | def is_frozen(space, w_obj): 11 | return space.newbool(space.is_frozen(w_obj)) 12 | 13 | 14 | functions = [freeze, thaw, is_frozen] 15 | -------------------------------------------------------------------------------- /nolang/builtins/core/reflect.py: -------------------------------------------------------------------------------- 1 | from nolang.objects.root import W_Root 2 | from nolang.builtins.spec import TypeSpec 3 | 4 | 5 | class W_FrameWrapper(W_Root): 6 | def __init__(self, frameref): 7 | self.frameref = frameref 8 | 9 | def get_filename(self, space): 10 | return space.newtext(self.frameref.bytecode.filename) 11 | 12 | 13 | W_FrameWrapper.spec = TypeSpec( 14 | 'Frame', 15 | constructor=None, 16 | methods={}, 17 | properties={ 18 | 'filename': (W_FrameWrapper.get_filename, None) 19 | } 20 | ) 21 | 22 | 23 | def get_exception_frame(space, w_exc): 24 | pass 25 | 26 | 27 | def get_current_frame(space): 28 | return W_FrameWrapper(space.interpreter.topframeref) 29 | -------------------------------------------------------------------------------- /nolang/builtins/core/text.py: -------------------------------------------------------------------------------- 1 | 2 | from rpython.rlib.rstring import StringBuilder 3 | 4 | from nolang.objects.root import W_Root 5 | from nolang.builtins.spec import TypeSpec, unwrap_spec 6 | 7 | 8 | class W_StringBuilder(W_Root): 9 | def __init__(self, length): 10 | self._s = StringBuilder(length) 11 | 12 | @unwrap_spec(txt='utf8') 13 | def append(self, txt): 14 | self._s.append(txt) 15 | 16 | def build(self, space): 17 | return space.newtext(self._s.build()) 18 | 19 | 20 | @unwrap_spec(length='int') 21 | def new_stringbuilder(space, w_tp, length=0): 22 | return W_StringBuilder(length) 23 | 24 | 25 | W_StringBuilder.spec = TypeSpec( 26 | 'StringBuilder', 27 | methods={ 28 | 'append': W_StringBuilder.append, 29 | 'build': W_StringBuilder.build, 30 | }, 31 | constructor=new_stringbuilder, 32 | ) 33 | -------------------------------------------------------------------------------- /nolang/builtins/defaults.py: -------------------------------------------------------------------------------- 1 | from nolang.builtins.io import magic_print 2 | from nolang.module import create_module 3 | from nolang.builtins.buffer import buffer, buffer_from_utf8 4 | from nolang.builtins import builtin 5 | from nolang.builtins.exception import W_Exception 6 | from nolang.builtins.spec import wrap_function, wrap_type 7 | from nolang.builtins.core.reflect import get_current_frame, W_FrameWrapper 8 | from nolang.builtins.core.text import W_StringBuilder 9 | from nolang.builtins.core import freezing 10 | from nolang.objects.dict import W_DictObject 11 | from nolang.objects.int import W_IntObject 12 | from nolang.objects.list import W_ListObject 13 | from nolang.objects.unicode import W_StrObject 14 | 15 | 16 | def wrap_module(name, functions): 17 | raise NotImplementedError 18 | 19 | 20 | def default_builtins(space): 21 | # XXX all of this should be more streamlined 22 | reflect_module = create_module('reflect', 23 | [wrap_function(space, get_current_frame)]) 24 | text_module = create_module('text', 25 | [wrap_type(space, W_StringBuilder)]) 26 | freezing_module = create_module('freezing', 27 | [wrap_function(space, func) for func in freezing.functions]) 28 | core_module = create_module('core', [reflect_module, text_module, 29 | freezing_module]) 30 | 31 | return [ 32 | # builtins 33 | magic_print, builtin.len, builtin.builtin_isinstance, buffer, 34 | buffer_from_utf8, 35 | W_Exception, W_ListObject, W_DictObject, W_StrObject, W_IntObject, 36 | builtin.builtin_str, 37 | ], core_module, [ 38 | # non-builtins that need to be wrapped 39 | W_FrameWrapper, 40 | ] 41 | -------------------------------------------------------------------------------- /nolang/builtins/exception.py: -------------------------------------------------------------------------------- 1 | from nolang.builtins.spec import unwrap_spec, TypeSpec 2 | from nolang.objects.userobject import W_UserObject 3 | 4 | 5 | class W_Exception(W_UserObject): 6 | def __init__(self, w_tp, message): 7 | W_UserObject.__init__(self, w_tp) 8 | self.message = message 9 | 10 | def get_message(self, space): 11 | return space.newtext(self.message) 12 | 13 | def __repr__(self): 14 | return '<%s %s>' % (self.w_type.name, self.message) 15 | 16 | 17 | @unwrap_spec(msg='utf8') 18 | def allocate(space, w_tp, msg): 19 | return W_Exception(w_tp, msg) 20 | 21 | 22 | W_Exception.spec = TypeSpec('Exception', 23 | constructor=allocate, 24 | methods={ 25 | }, 26 | properties={ 27 | 'message': (W_Exception.get_message, None) 28 | }, 29 | is_subclassable=True 30 | ) 31 | -------------------------------------------------------------------------------- /nolang/builtins/io.py: -------------------------------------------------------------------------------- 1 | from nolang.builtins.spec import parameters 2 | 3 | 4 | @parameters(name='print') 5 | def magic_print(space, args_w): 6 | print space.str(args_w[0]) 7 | -------------------------------------------------------------------------------- /nolang/builtins/spec.py: -------------------------------------------------------------------------------- 1 | """ Various specifications for builtins 2 | """ 3 | 4 | import types 5 | import py 6 | from nolang.function import W_BuiltinFunction, W_Property 7 | from nolang.objects.usertype import W_UserType 8 | 9 | 10 | def unwrap_spec(**spec): 11 | def wrapper(func): 12 | func.unwrap_spec = spec 13 | return func 14 | return wrapper 15 | 16 | 17 | def parameters(**args): 18 | def wrapper(func): 19 | func.unwrap_parameters = args 20 | return func 21 | return wrapper 22 | 23 | 24 | class TypeSpec(object): 25 | def __init__(self, name, constructor, methods=None, properties=None, 26 | parent_name=None, is_subclassable=False): 27 | self.constructor = constructor 28 | self.name = name 29 | if methods is None: 30 | methods = {} 31 | self.methods = methods 32 | if properties is None: 33 | properties = {} 34 | self.properties = properties 35 | self.parent_name = parent_name 36 | self.is_subclassable = is_subclassable 37 | 38 | 39 | def wrap_function(space, f, name=None, exp_name=None): 40 | if name is None: 41 | name = f.__name__ 42 | argnames = f.__code__.co_varnames[:f.__code__.co_argcount] 43 | lines = ['def %s(space, args_w):' % name] 44 | j = 0 45 | numargs = 0 46 | argdefaults = [None] * len(argnames) 47 | defs = [] 48 | if f.__defaults__ is not None: 49 | defs = f.__defaults__ 50 | firstdefault = len(argnames) - len(defs) 51 | for i in range(len(defs)): 52 | if defs[i] is None: 53 | argdefaults[i + firstdefault] = space.w_None 54 | elif isinstance(defs[i], int): 55 | argdefaults[i + firstdefault] = space.newint(defs[i]) 56 | else: 57 | raise NotImplementedError( 58 | "Default handling not supported for %s" % (defs[i],)) 59 | 60 | d = {'orig_' + name: f, 'argdefaults': argdefaults} 61 | for i, argname in enumerate(argnames): 62 | extralines = [] 63 | if argname == 'space': 64 | argval = 'space' 65 | elif argname == 'args_w': 66 | argval = 'args_w' 67 | numargs = -1 68 | assert i == len(argnames) - 1 69 | elif argname == 'self': 70 | if not isinstance(f, types.MethodType): 71 | raise Exception("self argument, but argument is not a method") 72 | argval = 'args_w[0]' 73 | j += 1 74 | numargs += 1 75 | msg = "Expected %s object, got %%s" % exp_name 76 | extralines = [ 77 | ' if not isinstance(arg%d, self_tp):' % i, 78 | ' raise space.apperr(space.w_typeerror, "%s" %% ' 79 | '(space.type(arg%d).name,))' % (msg, i), 80 | ] 81 | d['self_tp'] = f.im_class 82 | elif argname.startswith('w_'): 83 | argval = 'args_w[%d]' % j 84 | j += 1 85 | numargs += 1 86 | else: 87 | if hasattr(f, 'unwrap_spec'): 88 | spec = f.unwrap_spec.get(argname, None) 89 | else: 90 | spec = None 91 | numargs += 1 92 | if spec is None: 93 | raise Exception("No spec found for %s while wrapping %s" % 94 | (argname, name)) 95 | if spec == 'utf8': 96 | argval = 'space.utf8_w(args_w[%d])' % j 97 | j += 1 98 | elif spec == 'int': 99 | argval = 'space.int_w(args_w[%d])' % j 100 | j += 1 101 | elif spec == 'list': 102 | argval = 'space.listview(args_w[%d])' % j 103 | j += 1 104 | elif spec == 'dict': 105 | argval = 'space.dictview(args_w[%d])' % j 106 | j += 1 107 | else: 108 | assert False 109 | if argdefaults[i] is not None: 110 | lines.append(' if len(args_w) <= %d:' % (numargs - 1)) 111 | if argname.startswith('w_'): 112 | lines.append(' arg%d = argdefaults[%d]' % (i, i)) 113 | else: 114 | lines.append(' arg%d = %r' % (i, f.__defaults__[i - firstdefault])) 115 | lines.append(' else:') 116 | lines.append(' arg%d = %s' % (i, argval)) 117 | else: 118 | lines.append(' arg%d = %s' % (i, argval)) 119 | lines += extralines 120 | args = ", ".join(['arg%d' % i for i in range(len(argnames))]) 121 | lines.append(' return orig_%s(%s)' % (name, args)) 122 | src = py.code.Source("\n".join(lines)) 123 | exec src.compile() in d 124 | exported_name = name 125 | if getattr(f, 'unwrap_parameters', None): 126 | exported_name = f.unwrap_parameters.get('name', exported_name) 127 | return W_BuiltinFunction(exported_name, d[name], 128 | min_args=numargs - len(defs), 129 | max_args=numargs) 130 | 131 | 132 | def wrap_type(space, tp): 133 | spec = tp.spec 134 | if spec.constructor is None: 135 | allocate = None 136 | else: 137 | allocate = wrap_function(space, spec.constructor) 138 | properties = [] 139 | for name, (get_prop, set_prop) in spec.properties.iteritems(): 140 | if set_prop is not None: 141 | set_prop = set_prop.im_func 142 | properties.append(W_Property(name, get_prop.im_func, set_prop)) 143 | for name, meth in spec.methods.iteritems(): 144 | properties.append(wrap_function(space, meth, name=name, exp_name=spec.name)) 145 | if spec.parent_name is None: 146 | parent = None 147 | else: 148 | parent = space.builtin_dict[spec.parent_name] 149 | w_tp = W_UserType(allocate, spec.name, properties, parent, default_alloc=False) 150 | if not spec.is_subclassable: 151 | tp.cls_w_type = w_tp 152 | return w_tp 153 | 154 | 155 | def wrap_builtin(space, builtin): 156 | if isinstance(builtin, types.FunctionType): 157 | return wrap_function(space, builtin) 158 | else: 159 | return wrap_type(space, builtin) 160 | -------------------------------------------------------------------------------- /nolang/bytecode.py: -------------------------------------------------------------------------------- 1 | """ Bytecode representation and compilation. See opcodes.py for 2 | details about bytecodes 3 | """ 4 | 5 | from nolang import opcodes 6 | 7 | from rpython.rlib.rstring import StringBuilder 8 | 9 | 10 | class BaseConstant(object): 11 | pass 12 | 13 | 14 | class IntegerConstant(BaseConstant): 15 | def __init__(self, v): 16 | self._intval = v 17 | 18 | def wrap(self, space): 19 | return space.newint(self._intval) 20 | 21 | 22 | class StringConstant(BaseConstant): 23 | def __init__(self, v): 24 | self._strval = v 25 | 26 | def wrap(self, space): 27 | return space.newtext(self._strval) 28 | 29 | 30 | class InvalidStackDepth(Exception): 31 | pass 32 | 33 | 34 | class UnknownGlobalName(Exception): 35 | def __init__(self, name): 36 | self.name = name 37 | 38 | def __str__(self): 39 | return "" % self.name 40 | 41 | 42 | class Bytecode(object): 43 | def __init__(self, filename, source, varnames, module, constants, bytecode, 44 | arglist, defaults, lnotab): 45 | self.filename = filename 46 | self.source = source 47 | self.varnames = varnames 48 | self.module = module 49 | self._constants = constants 50 | self.constants = None 51 | self.bytecode = bytecode 52 | r = self.compute_stack_depth(bytecode) 53 | self.stack_depth, self.resume_stack_depth = r 54 | self.arglist = [x.name for x in arglist] 55 | self.argmapping = {} 56 | for i, item in enumerate(self.arglist): 57 | self.argmapping[item] = i 58 | self.argtypes = [x.tp for x in arglist] 59 | self.defaults = defaults 60 | for i, default in enumerate(self.defaults): 61 | if default != -1: 62 | self.first_default = i 63 | break 64 | else: 65 | self.first_default = -1 66 | if self.first_default == -1: 67 | self.minargs = len(self.arglist) 68 | else: 69 | self.minargs = self.first_default 70 | self.maxargs = len(self.arglist) 71 | self.lnotab = lnotab 72 | 73 | def setup(self, space): 74 | self.constants = [None] * len(self._constants) 75 | for i, constant in enumerate(self._constants): 76 | self.constants[i] = constant.wrap(space) 77 | 78 | def repr(self, numbers=True): 79 | i = 0 80 | res = StringBuilder() 81 | bc = self.bytecode 82 | while i < len(bc): 83 | opcode = opcodes.opcodes[ord(bc[i])] 84 | c = i 85 | if opcode.numargs == 0: 86 | r = " " + opcode.name 87 | i += 1 88 | elif opcode.numargs == 1: 89 | argval = (ord(bc[i + 1]) << 8) + ord(bc[i + 2]) 90 | r = " %s %d" % (opcode.name, argval) 91 | i += 3 92 | else: 93 | assert opcode.numargs == 2 94 | arg1 = (ord(bc[i + 1]) << 8) + ord(bc[i + 2]) 95 | arg2 = (ord(bc[i + 3]) << 8) + ord(bc[i + 4]) 96 | r = " %s %d %d" % (opcode.name, arg1, arg2) 97 | i += 5 98 | if numbers: 99 | res.append("%3d" % c + r) 100 | else: 101 | res.append(r) 102 | res.append("\n") 103 | return res.build() 104 | 105 | @staticmethod 106 | def compute_stack_depth(bc): 107 | i = 0 108 | stack_depth = 0 109 | max_stack_depth = 0 110 | resume_stack_depth = 0 111 | max_resume_stack_depth = 0 112 | while i < len(bc): 113 | opcode = opcodes.opcodes[ord(bc[i])] 114 | if opcode.stack_effect == 255: 115 | var = (ord(bc[i + 1]) << 8) + ord(bc[i + 2]) 116 | var2 = (ord(bc[i + 3]) << 8) + ord(bc[i + 4]) 117 | stack_depth -= var + var2 * 2 118 | elif opcode.stack_effect == 254: 119 | var = (ord(bc[i + 1]) << 8) + ord(bc[i + 2]) 120 | stack_depth -= var - 1 121 | else: 122 | if ord(bc[i]) == opcodes.JUMP_IF_EMPTY: 123 | stack_depth += 1 # HACK for assert below 124 | stack_depth += opcode.stack_effect 125 | if ord(bc[i]) == opcodes.PUSH_RESUME_STACK: 126 | resume_stack_depth += 1 127 | max_resume_stack_depth = max(max_resume_stack_depth, 128 | resume_stack_depth) 129 | if ord(bc[i]) == opcodes.POP_RESUME_STACK: 130 | resume_stack_depth -= 1 131 | if opcode.numargs == 0: 132 | i += 1 133 | elif opcode.numargs == 1: 134 | i += 3 135 | else: 136 | assert opcode.numargs == 2 137 | i += 5 138 | max_stack_depth = max(max_stack_depth, stack_depth) 139 | if stack_depth != 0 or resume_stack_depth != 0: 140 | raise InvalidStackDepth() 141 | return max_stack_depth, max_resume_stack_depth 142 | 143 | 144 | class UndeclaredVariable(Exception): 145 | def __init__(self, name): 146 | self.name = name 147 | 148 | def __str__(self): 149 | return '' % self.name 150 | 151 | 152 | class _BytecodeBuilder(object): 153 | def __init__(self, w_mod, arglist): 154 | self.vars = {} 155 | self.varnames = [] 156 | self.builder = [] 157 | self.constants = [] # XXX implement interning of integers, strings etc. 158 | self.w_mod = w_mod 159 | for var in arglist: 160 | self.register_variable(var.name, var.tp) 161 | self.arglist = arglist 162 | self.lnotab = [] 163 | self.accumulator = [] 164 | 165 | def add_constant(self, const): 166 | no = len(self.constants) 167 | self.constants.append(const) 168 | return no 169 | 170 | def add_int_constant(self, v): 171 | return self.add_constant(IntegerConstant(v)) 172 | 173 | def add_str_constant(self, v): 174 | assert isinstance(v, str) 175 | return self.add_constant(StringConstant(v)) 176 | 177 | def get_variable(self, name): 178 | try: 179 | return opcodes.LOAD_VARIABLE, self.vars[name] 180 | except KeyError: 181 | pass 182 | try: 183 | return opcodes.LOAD_GLOBAL, self.w_mod.name2index[name] 184 | except KeyError: 185 | pass 186 | raise UndeclaredVariable(name) 187 | 188 | def register_variable(self, v, tp): 189 | no = len(self.vars) 190 | self.varnames.append(no) # XXX should we rely on dicts being ordered? 191 | self.vars[v] = no 192 | assert len(self.vars) == len(self.varnames) 193 | return no 194 | 195 | def emit(self, lineno, opcode, arg0=-1, arg1=-1): 196 | self.lnotab.append(lineno) 197 | self.builder.append(chr(opcode)) 198 | if opcodes.opcodes[opcode].numargs == 0: 199 | assert arg0 == -1 200 | else: 201 | assert arg0 >= 0 202 | if opcodes.opcodes[opcode].numargs <= 1: 203 | assert arg1 == -1 204 | else: 205 | assert arg1 >= 0 206 | assert arg0 < 0x10000 207 | assert arg1 < 0x10000 208 | numargs = opcodes.opcodes[opcode].numargs 209 | if numargs > 0: 210 | self.builder.append(chr(arg0 >> 8)) 211 | self.builder.append(chr(arg0 & 0xff)) 212 | self.lnotab += [0] * 2 213 | if numargs > 1: 214 | self.builder.append(chr(arg1 >> 8)) 215 | self.builder.append(chr(arg1 & 0xff)) 216 | self.lnotab += [0] * 2 217 | assert numargs <= 2 218 | 219 | def get_position(self): 220 | return len(self.builder) 221 | 222 | def get_patch_position(self): 223 | return len(self.builder) - 2 224 | 225 | def patch_position(self, pos, target): 226 | assert target < 0x10000 227 | self.builder[pos] = chr(target >> 8) 228 | self.builder[pos + 1] = chr(target & 0xff) 229 | 230 | def _packlnotab(self, lnotab): 231 | return lnotab 232 | 233 | def build(self, filename, source): 234 | defaults = [-1 for i in range(len(self.arglist))] 235 | for i in range(len(self.arglist)): 236 | default = self.arglist[i].default 237 | if default is not None: 238 | defaults[i] = default.add_constant_to_state(self) 239 | return Bytecode(filename, source, self.varnames, self.w_mod, 240 | self.constants, 241 | "".join(self.builder), self.arglist, defaults, 242 | self._packlnotab(self.lnotab)) 243 | 244 | 245 | def compile_bytecode(ast, source, w_mod, arglist=[], startlineno=0): 246 | """ Compile the bytecode from produced AST. 247 | """ 248 | builder = _BytecodeBuilder(w_mod, arglist[:]) 249 | ast.compile(builder) 250 | # hack to enable building for now 251 | builder.emit(ast.getendidx(), opcodes.LOAD_NONE) 252 | builder.emit(ast.getendidx(), opcodes.RETURN) 253 | return builder.build(w_mod.name, source) 254 | -------------------------------------------------------------------------------- /nolang/compiler.py: -------------------------------------------------------------------------------- 1 | """ Main module compiler 2 | """ 3 | 4 | from nolang.module import W_Module 5 | from nolang.objects.userobject import W_UserObject 6 | from nolang.builtins.spec import wrap_function 7 | 8 | from rpython.rlib.objectmodel import specialize 9 | 10 | 11 | def _gather_names(ast, builtins): 12 | name_mapping = {} 13 | if builtins is not None: 14 | for item in builtins: 15 | name_mapping[item.name] = len(name_mapping) 16 | for item in ast.get_element_list(): 17 | item.add_name(name_mapping) 18 | return name_mapping 19 | 20 | 21 | def compile_module(space, filename, dotted_name, source, ast, importer): 22 | name_mapping = _gather_names(ast, space.builtins_w) 23 | if space.builtins_w is not None: 24 | globals_w = space.builtins_w[:] 25 | else: 26 | globals_w = [] 27 | w_mod = W_Module(filename, name_mapping, globals_w) 28 | for item in ast.get_element_list(): 29 | item.add_global_symbols(space, globals_w, source, w_mod) 30 | importer.register_module(space, dotted_name, w_mod) 31 | importer.add_missing_imports(space, ast, w_mod, globals_w) 32 | return w_mod 33 | 34 | 35 | def new_user_object(space, args_w): 36 | return W_UserObject(args_w[0]) 37 | 38 | 39 | @specialize.memo() 40 | def get_alloc(space): 41 | return wrap_function(space, new_user_object) 42 | 43 | 44 | def compile_class(space, source, ast, w_mod, parent=None): 45 | if parent is not None: 46 | w_parent = w_mod.functions[w_mod.name2index[parent]] 47 | else: 48 | w_parent = None 49 | if w_parent is not None: 50 | alloc = w_parent.allocate 51 | default_alloc = w_parent.default_alloc 52 | else: 53 | alloc = get_alloc(space) 54 | default_alloc = True 55 | class_elements_w = [] 56 | for item in ast.get_element_list(): 57 | item.add_global_symbols(space, class_elements_w, source, w_mod) 58 | return alloc, class_elements_w, w_parent, default_alloc 59 | -------------------------------------------------------------------------------- /nolang/error.py: -------------------------------------------------------------------------------- 1 | """ Common errors and error handling 2 | """ 3 | 4 | 5 | class TracebackElem(object): 6 | def __init__(self, frame, position, bytecode, next): 7 | self.frame = frame 8 | self.position = position 9 | self.bytecode = bytecode 10 | self.next = next 11 | 12 | 13 | class AppError(Exception): 14 | def __init__(self, w_exception): 15 | self.w_exception = w_exception 16 | self.traceback = None 17 | 18 | def record_position(self, frame, bytecode, index): 19 | self.traceback = TracebackElem(frame, index, bytecode, self.traceback) 20 | 21 | def match(self, space, w_expected): 22 | return space.issubclass(space.type(self.w_exception), w_expected) 23 | 24 | def __repr__(self): 25 | return '' % (self.w_exception,) 26 | __str__ = __repr__ 27 | -------------------------------------------------------------------------------- /nolang/frameobject.py: -------------------------------------------------------------------------------- 1 | from nolang.objects.root import W_Root 2 | 3 | 4 | class Frame(W_Root): 5 | def __init__(self, bytecode, name=None): 6 | self.name = name 7 | self.bytecode = bytecode 8 | if bytecode.module is not None: # for tests 9 | self.globals_w = bytecode.module.functions 10 | self.locals_w = [None] * len(bytecode.varnames) 11 | self.stack_w = [None] * bytecode.stack_depth 12 | self.resume_stack = [0] * bytecode.resume_stack_depth 13 | self.resume_stack_depth = 0 14 | self.pos = 0 15 | 16 | def populate_args(self, args_w): 17 | for i in range(len(args_w)): 18 | self.locals_w[i] = args_w[i] 19 | 20 | def push(self, w_val): 21 | self.stack_w[self.pos] = w_val 22 | self.pos += 1 23 | 24 | def pop(self): 25 | new_pos = self.pos - 1 26 | w_res = self.stack_w[new_pos] 27 | assert new_pos >= 0 28 | self.pos = new_pos 29 | return w_res 30 | 31 | def peek(self): 32 | return self.stack_w[self.pos - 1] 33 | 34 | def store_var(self, index): 35 | self.locals_w[index] = self.pop() 36 | 37 | 38 | def find_line(bytecode, target_pc): 39 | src = bytecode.source 40 | pos = 0 41 | lineno = 0 42 | target_position = bytecode.lnotab[target_pc] 43 | prev_pos = 0 44 | while pos < target_position: 45 | prev_pos = pos 46 | pos = src.find("\n", pos + 1) 47 | lineno += 1 48 | if pos < 0: 49 | return src[prev_pos + 1:], lineno 50 | return src[prev_pos + 1:pos], lineno 51 | 52 | 53 | def format_traceback(space, apperr): 54 | lines = [] 55 | w_exception = apperr.w_exception 56 | tb_list = [] 57 | tb = apperr.traceback 58 | while tb: 59 | tb_list.append(tb) 60 | tb = tb.next 61 | 62 | for i in range(len(tb_list) - 1, -1, -1): 63 | tb = tb_list[i] 64 | line, lineno = find_line(tb.bytecode, tb.position) 65 | lines.append("file \"%s\", line %d, in %s" % ( 66 | tb.bytecode.filename, 67 | lineno, 68 | tb.frame.name or '', 69 | )) 70 | lines.append(" " + line.strip()) 71 | lines.append("%s: %s" % (space.type(w_exception).name, w_exception.message)) 72 | lines.append("") 73 | return "\n".join(lines) 74 | -------------------------------------------------------------------------------- /nolang/function.py: -------------------------------------------------------------------------------- 1 | """ Main declaration of function 2 | """ 3 | 4 | from nolang.objects.root import W_Root 5 | from nolang.frameobject import Frame 6 | 7 | 8 | def argerr(space, msg): 9 | return space.apperr(space.w_argerror, msg) 10 | 11 | 12 | def argerr_number(space, bytecode, name, arglen): 13 | if bytecode.minargs == bytecode.maxargs: 14 | msg = "expected %d" % bytecode.minargs 15 | else: 16 | msg = "expected between %d and %d" % ( 17 | bytecode.minargs, bytecode.maxargs) 18 | raise argerr(space, "Function %s got %d arguments, %s" % ( 19 | name, arglen, msg)) 20 | 21 | 22 | def prepare_args(space, name, bytecode, args_w, namedargs_w): 23 | num_args = len(bytecode.arglist) 24 | if namedargs_w is None: 25 | # fastpath for just args 26 | if not bytecode.minargs <= len(args_w) <= bytecode.maxargs: 27 | raise argerr_number(space, bytecode, name, len(args_w)) 28 | if bytecode.first_default >= 0: 29 | args_w = args_w + [bytecode.constants[bytecode.defaults[i]] 30 | for i in range(len(args_w), bytecode.maxargs)] 31 | return args_w 32 | 33 | if (not bytecode.minargs <= len(args_w) + len(namedargs_w) 34 | <= bytecode.maxargs): 35 | raise argerr_number( 36 | space, bytecode, name, len(namedargs_w) + len(args_w)) 37 | 38 | vals_w = args_w + [None] * (num_args - len(args_w)) 39 | for k, w_v in namedargs_w: 40 | try: 41 | index = bytecode.argmapping[k] 42 | except KeyError: 43 | raise argerr(space, "Function %s got unexpected keyword " 44 | "argument '%s'" % (name, k)) 45 | if vals_w[index] is not None: 46 | raise argerr(space, "Function %s got multiple values for " 47 | "argument '%s'" % (name, k)) 48 | vals_w[index] = w_v 49 | 50 | if bytecode.first_default >= 0: 51 | for i in range(bytecode.first_default, len(bytecode.defaults)): 52 | def_no = bytecode.defaults[i] 53 | if def_no != -1 and vals_w[i] is not None: 54 | vals_w[i] = bytecode.constants[def_no] 55 | 56 | for item in vals_w: 57 | if item is None: 58 | raise argerr(space, "Function %s didn't receive enough positional " 59 | "arguments" % (name,)) 60 | 61 | return vals_w 62 | 63 | 64 | class W_Function(W_Root): 65 | def __init__(self, name, bytecode): 66 | self.name = name 67 | self.bytecode = bytecode 68 | 69 | def setup(self, space): 70 | self.bytecode.setup(space) 71 | 72 | def call(self, space, interpreter, args_w, kwargs): 73 | frame = Frame(self.bytecode, self.name) 74 | args_w = prepare_args(space, self.name, self.bytecode, args_w, kwargs) 75 | frame.populate_args(args_w) 76 | return interpreter.interpret(space, self.bytecode, frame) 77 | 78 | def bind(self, space, w_obj): 79 | return W_BoundMethod(w_obj, self) 80 | 81 | 82 | class W_BuiltinFunction(W_Root): 83 | def __init__(self, name, callable, min_args, max_args): 84 | self.name = name 85 | self.min_args = min_args 86 | self.max_args = max_args 87 | self.callable = callable 88 | 89 | def setup(self, space): 90 | pass 91 | 92 | def call(self, space, interpreter, args_w, kwargs): 93 | if kwargs is not None: 94 | raise Exception('unimplemented') 95 | if self.min_args != -1 and not self.min_args <= len(args_w) <= self.max_args: 96 | if self.min_args == self.max_args: 97 | msg = "Function %s got %d arguments, expected %d" % (self.name, 98 | len(args_w), self.min_args) 99 | else: 100 | msg = "Function %s got %d arguments, expected %d-%d" % ( 101 | self.name, len(args_w), self.min_args, self.max_args) 102 | raise space.apperr(space.w_argerror, msg) 103 | return self.callable(space, args_w) 104 | 105 | def bind(self, space, w_obj): 106 | return W_BoundMethod(w_obj, self) 107 | 108 | def __repr__(self): 109 | return "" % (self.name, self.min_args, 110 | self.max_args) 111 | 112 | 113 | class W_Property(W_Root): 114 | def __init__(self, name, getter, setter): 115 | self.name = name 116 | self.getter = getter 117 | self.setter = setter 118 | 119 | def bind(self, space, w_obj): 120 | return self.getter(w_obj, space) 121 | 122 | def setup(self, space): 123 | pass 124 | 125 | 126 | class W_BoundMethod(W_Root): 127 | def __init__(self, w_self, w_function): 128 | self.w_self = w_self 129 | self.w_function = w_function 130 | 131 | def call(self, space, interpreter, args_w, kwargs): 132 | return space.call(self.w_function, [self.w_self] + args_w, kwargs) 133 | -------------------------------------------------------------------------------- /nolang/importer.py: -------------------------------------------------------------------------------- 1 | """ Main parts of import system 2 | """ 3 | 4 | import os 5 | 6 | from nolang.module import create_module 7 | from nolang.parser import ParsingState 8 | from nolang.compiler import compile_module 9 | from nolang.module import W_Module 10 | 11 | 12 | # XXX wrap up in AppErr 13 | class ImportError(Exception): 14 | pass 15 | 16 | 17 | class Importer(object): 18 | def __init__(self, space, basepath=None, parser=None, lexer=None): 19 | # XXX basepath is a hack 20 | self.basepath = basepath 21 | self.selfmod = create_module('self', []) 22 | self.parser = parser 23 | self.lexer = lexer 24 | self.cache = {'self': self.selfmod} 25 | self.register_core_module('core', space.coremod) 26 | 27 | def register_core_module(self, name, w_mod): 28 | self.cache[name] = w_mod 29 | for w_element in w_mod.functions: 30 | if isinstance(w_element, W_Module): 31 | self.register_core_module(name + '.' + w_element.name, 32 | w_element) 33 | 34 | def add_missing_imports(self, space, ast, w_mod, globals_w): 35 | for item in ast.get_element_list(): 36 | item.add_missing_imports(space, w_mod, globals_w, self) 37 | 38 | def import_module(self, space, path): 39 | # XXX error handling 40 | pth = os.path.join(self.basepath, os.path.sep.join(path)) + ".q" 41 | try: 42 | source = open(pth).read() 43 | except (IOError, OSError): 44 | raise ImportError("foo") 45 | ast = self.parser.parse(self.lexer.lex(pth, source), 46 | ParsingState(pth, source)) 47 | dotted_name = ".".join(['self'] + path) 48 | w_mod = compile_module(space, pth, dotted_name, source, ast, self) 49 | return w_mod 50 | 51 | def register_module(self, space, dotted_name, w_mod): 52 | parts = dotted_name.split(".") 53 | # XXX think what to do if we have it 54 | assert dotted_name not in self.cache 55 | self.cache[dotted_name] = w_mod 56 | cur_elem = w_mod 57 | for i in range(len(parts) - 1, 0, -1): 58 | cur_name = ".".join(parts[:i]) 59 | if cur_name in self.cache: 60 | self.cache[cur_name].add_element(cur_elem) 61 | return 62 | raise Exception("not implemented logic") 63 | 64 | def get_module(self, space, imp_path): 65 | try: 66 | return self.cache[".".join(imp_path)] 67 | except KeyError: 68 | pass 69 | last_elem = len(imp_path) - 1 70 | assert last_elem >= 0 71 | try: 72 | w_mod = self.cache[".".join(imp_path[:last_elem])] 73 | except KeyError: 74 | pass 75 | else: 76 | return space.getattr(w_mod, imp_path[last_elem]) 77 | if imp_path[0] == 'self': 78 | # allow extra self imports 79 | try: 80 | return self.import_module(space, imp_path[1:]) 81 | except ImportError: 82 | if len(imp_path) == 2: 83 | raise 84 | w_mod = self.import_module(space, imp_path[1:last_elem]) 85 | return space.getattr(w_mod, imp_path[last_elem]) 86 | raise ImportError(".".join(imp_path)) 87 | 88 | def import_names(self, space, imp_path, names, globals_w, idx): 89 | for name in names: 90 | globals_w[idx] = self.get_module(space, imp_path[:] + [name]) 91 | idx += 1 92 | -------------------------------------------------------------------------------- /nolang/interpreter.py: -------------------------------------------------------------------------------- 1 | """ This is the main interpreter file that contains bytecode 2 | dispatch loop. 3 | """ 4 | from rpython.rlib.rstring import StringBuilder 5 | 6 | from nolang import opcodes 7 | from nolang.error import AppError 8 | from nolang.builtins.exception import W_Exception 9 | 10 | 11 | class InvalidOpcode(Exception): 12 | def __init__(self, opcode): 13 | self.opcode = opcode 14 | 15 | def __str__(self): 16 | try: 17 | return "" % opcodes.opcodes[self.opcode].name 18 | except IndexError: 19 | return "" % self.opcode 20 | 21 | 22 | class UninitializedVariable(Exception): 23 | pass # XXX add logic to present the error 24 | 25 | 26 | class Interpreter(object): 27 | def __init__(self): 28 | self.topframeref = None 29 | 30 | def interpret(self, space, bytecode, frame): 31 | back = self.topframeref 32 | try: 33 | self.topframeref = frame 34 | return self._interpret(space, bytecode, frame) 35 | finally: 36 | self.topframeref = back 37 | 38 | def _interpret(self, space, bytecode, frame): 39 | index = 0 40 | # make annotator happy 41 | arg0 = 0 42 | arg1 = 0 43 | bc = bytecode.bytecode 44 | cur_exc = None 45 | while True: 46 | try: 47 | op = ord(bc[index]) 48 | numargs = opcodes.opcodes[op].numargs 49 | if numargs >= 1: 50 | arg0 = (ord(bc[index + 1]) << 8) + ord(bc[index + 2]) 51 | if numargs >= 2: 52 | arg1 = (ord(bc[index + 3]) << 8) + ord(bc[index + 4]) 53 | 54 | if op == opcodes.LOAD_NONE: 55 | frame.push(space.w_None) 56 | elif op == opcodes.LOAD_CONSTANT: 57 | frame.push(bytecode.constants[arg0]) 58 | elif op == opcodes.LOAD_VARIABLE: 59 | self.load_variable(space, frame, index, arg0) 60 | elif op == opcodes.LOAD_GLOBAL: 61 | self.load_global(space, frame, index, arg0) 62 | elif op == opcodes.LOAD_TRUE: 63 | frame.push(space.w_True) 64 | elif op == opcodes.LOAD_FALSE: 65 | frame.push(space.w_False) 66 | elif op == opcodes.DISCARD: 67 | frame.pop() 68 | elif op == opcodes.ADD: 69 | self.binop_add(space, frame) 70 | elif op == opcodes.SUB: 71 | self.binop_sub(space, frame) 72 | elif op == opcodes.MUL: 73 | self.binop_mul(space, frame) 74 | elif op == opcodes.TRUEDIV: 75 | self.binop_truediv(space, frame) 76 | elif op == opcodes.LT: 77 | self.binop_lt(space, frame) 78 | elif op == opcodes.GT: 79 | self.binop_gt(space, frame) 80 | elif op == opcodes.LE: 81 | self.binop_le(space, frame) 82 | elif op == opcodes.GE: 83 | self.binop_ge(space, frame) 84 | elif op == opcodes.EQ: 85 | self.binop_eq(space, frame) 86 | elif op == opcodes.NE: 87 | self.binop_ne(space, frame) 88 | elif op == opcodes.IN: 89 | self.binop_in(space, frame) 90 | elif op == opcodes.NOT: 91 | self.unaryop_not(space, frame) 92 | elif op == opcodes.STORE: 93 | frame.store_var(arg0) 94 | elif op == opcodes.SETATTR: 95 | self.setattr(space, frame, bytecode, arg0) 96 | elif op == opcodes.GETATTR: 97 | self.getattr(space, frame, bytecode, arg0) 98 | elif op == opcodes.SETITEM: 99 | self.setitem(space, frame) 100 | elif op == opcodes.GETITEM: 101 | self.getitem(space, frame) 102 | elif op == opcodes.PUSH_RESUME_STACK: 103 | self.push_resume_stack(space, frame, bytecode, arg0) 104 | elif op == opcodes.POP_RESUME_STACK: 105 | self.pop_resume_stack(frame) 106 | elif op == opcodes.RAISE: 107 | w_exception = frame.pop() 108 | if not isinstance(w_exception, W_Exception): 109 | raise Exception("handle this correctly") 110 | w_exception.frame = frame 111 | raise AppError(w_exception) 112 | elif op == opcodes.COMPARE_EXCEPTION: 113 | index = self.compare_exception(space, frame, 114 | bytecode, arg0, cur_exc, index) 115 | continue 116 | elif op == opcodes.RERAISE: 117 | if cur_exc: 118 | raise AppError(cur_exc) 119 | elif op == opcodes.PUSH_CURRENT_EXC: 120 | frame.push(cur_exc) 121 | elif op == opcodes.JUMP_IF_FALSE: 122 | if not space.is_true(frame.pop()): 123 | index = arg0 124 | continue 125 | elif op == opcodes.JUMP_IF_TRUE_NOPOP: 126 | if space.is_true(frame.peek()): 127 | index = arg0 128 | continue 129 | elif op == opcodes.JUMP_IF_FALSE_NOPOP: 130 | if not space.is_true(frame.peek()): 131 | index = arg0 132 | continue 133 | elif op == opcodes.JUMP_ABSOLUTE: 134 | index = arg0 135 | continue 136 | elif op == opcodes.JUMP_IF_EMPTY: 137 | if frame.peek() is None: 138 | index = arg0 139 | continue 140 | elif op == opcodes.CALL: 141 | self.call(space, frame, index, arg0, arg1) 142 | elif op == opcodes.CREATE_ITER: 143 | self.create_iter(space, frame) 144 | elif op == opcodes.ITER_NEXT: 145 | frame.push(space.iter_next(frame.peek())) 146 | elif op == opcodes.RETURN: 147 | return frame.pop() 148 | elif op == opcodes.LIST_BUILD: 149 | self.list_build(space, frame, bytecode, arg0) 150 | elif op == opcodes.DICT_BUILD: 151 | self.dict_build(space, frame, bytecode, arg0) 152 | elif op == opcodes.TEXT_BUILD: 153 | self.text_build(space, frame, bytecode, arg0) 154 | else: 155 | raise InvalidOpcode(op) 156 | 157 | if numargs == 0: 158 | index += 1 159 | elif numargs == 1: 160 | index += 3 161 | else: 162 | index += 5 163 | except AppError as ae: 164 | ae.record_position(frame, bytecode, index) 165 | res = self.handle_error(space, frame, ae.w_exception) 166 | if res: 167 | cur_exc = ae.w_exception 168 | frame.stack_depth = 0 # clear stack 169 | index = res 170 | continue 171 | raise ae # reraise the error if not handled 172 | 173 | def handle_error(self, space, frame, w_exception): 174 | if frame.resume_stack_depth: 175 | frame.resume_stack_depth -= 1 176 | r = frame.resume_stack[frame.resume_stack_depth] 177 | return r 178 | return 0 179 | 180 | def compare_exception(self, space, frame, bytecode, arg0, cur_exc, 181 | cur_pos): 182 | comp_exc = frame.pop() 183 | if space.issubclass(space.type(cur_exc), comp_exc): 184 | return cur_pos + 3 185 | return arg0 186 | 187 | def load_variable(self, space, frame, bytecode_index, no): 188 | w_res = frame.locals_w[no] 189 | if w_res is None: 190 | raise UninitializedVariable() 191 | frame.push(w_res) 192 | 193 | def load_global(self, space, frame, bytecode_index, no): 194 | frame.push(frame.globals_w[no]) 195 | 196 | def call(self, space, frame, bytecode_index, no, named_no): 197 | if named_no > 0: 198 | kwargs = [(None, None)] * named_no 199 | for i in range(named_no - 1, -1, -1): 200 | w_obj = frame.pop() 201 | name = space.utf8_w(frame.pop()) 202 | kwargs[i] = (name, w_obj) 203 | else: 204 | kwargs = None 205 | args = [None] * no 206 | for i in range(no - 1, -1, -1): 207 | args[i] = frame.pop() 208 | w_callable = frame.pop() 209 | frame.push(space.call(w_callable, args, kwargs)) 210 | 211 | def create_iter(self, space, frame): 212 | w_obj = frame.pop() 213 | frame.push(space.iter(w_obj)) 214 | 215 | def list_build(self, space, frame, bytecode, no): 216 | items = [None] * no 217 | for i in range(no - 1, -1, -1): 218 | items[i] = frame.pop() 219 | frame.push(space.newlist(items)) 220 | 221 | def dict_build(self, space, frame, bytecode, no): 222 | no = no / 2 223 | items = [(None, None)] * no 224 | for i in range(no - 1, -1, -1): 225 | v = frame.pop() 226 | k = frame.pop() 227 | items[i] = (k, v) 228 | frame.push(space.newdict(items)) 229 | 230 | def text_build(self, space, frame, bytecode, no): 231 | items = [None] * no 232 | for i in range(no - 1, -1, -1): 233 | items[i] = space.str(frame.pop()) 234 | sb = StringBuilder() 235 | for item in items: 236 | sb.append(item) 237 | frame.push(space.newtext(sb.build())) 238 | 239 | def setattr(self, space, frame, bytecode, no): 240 | w_arg = frame.pop() 241 | w_lhand = frame.pop() 242 | space.setattr(w_lhand, space.utf8_w(bytecode.constants[no]), w_arg) 243 | 244 | def getattr(self, space, frame, bytecode, no): 245 | w_lhand = frame.pop() 246 | frame.push(space.getattr(w_lhand, space.utf8_w(bytecode.constants[no]))) 247 | 248 | def setitem(self, space, frame): 249 | w_arg = frame.pop() 250 | w_idx = frame.pop() 251 | w_lhand = frame.pop() 252 | space.setitem(w_lhand, w_idx, w_arg) 253 | 254 | def getitem(self, space, frame): 255 | w_idx = frame.pop() 256 | w_lhand = frame.pop() 257 | frame.push(space.getitem(w_lhand, w_idx)) 258 | 259 | def push_resume_stack(self, space, frame, bytecode, arg0): 260 | frame.resume_stack[frame.resume_stack_depth] = arg0 261 | frame.resume_stack_depth += 1 262 | 263 | def pop_resume_stack(self, frame): 264 | frame.resume_stack_depth -= 1 265 | 266 | def binop_lt(self, space, frame): 267 | w_right = frame.pop() 268 | w_left = frame.pop() 269 | frame.push(space.newbool(space.binop_lt(w_left, w_right))) 270 | 271 | def binop_gt(self, space, frame): 272 | w_right = frame.pop() 273 | w_left = frame.pop() 274 | frame.push(space.newbool(space.binop_gt(w_left, w_right))) 275 | 276 | def binop_le(self, space, frame): 277 | w_right = frame.pop() 278 | w_left = frame.pop() 279 | frame.push(space.newbool(space.binop_le(w_left, w_right))) 280 | 281 | def binop_ge(self, space, frame): 282 | w_right = frame.pop() 283 | w_left = frame.pop() 284 | frame.push(space.newbool(space.binop_ge(w_left, w_right))) 285 | 286 | def binop_eq(self, space, frame): 287 | w_right = frame.pop() 288 | w_left = frame.pop() 289 | frame.push(space.newbool(space.binop_eq(w_left, w_right))) 290 | 291 | def binop_ne(self, space, frame): 292 | w_right = frame.pop() 293 | w_left = frame.pop() 294 | frame.push(space.newbool(space.binop_ne(w_left, w_right))) 295 | 296 | def binop_in(self, space, frame): 297 | w_right = frame.pop() 298 | w_left = frame.pop() 299 | frame.push(space.newbool(space.binop_in(w_left, w_right))) 300 | 301 | def binop_add(self, space, frame): 302 | w_right = frame.pop() 303 | w_left = frame.pop() 304 | frame.push(space.w_binop_add(w_left, w_right)) 305 | 306 | def binop_sub(self, space, frame): 307 | w_right = frame.pop() 308 | w_left = frame.pop() 309 | frame.push(space.w_binop_sub(w_left, w_right)) 310 | 311 | def binop_mul(self, space, frame): 312 | w_right = frame.pop() 313 | w_left = frame.pop() 314 | frame.push(space.w_binop_mul(w_left, w_right)) 315 | 316 | def binop_truediv(self, space, frame): 317 | w_right = frame.pop() 318 | w_left = frame.pop() 319 | frame.push(space.w_binop_truediv(w_left, w_right)) 320 | 321 | def unaryop_not(self, space, frame): 322 | w_obj = frame.pop() 323 | frame.push(space.unaryop_not(w_obj)) 324 | -------------------------------------------------------------------------------- /nolang/lexer.py: -------------------------------------------------------------------------------- 1 | from rply.lexergenerator import Rule 2 | from rply.token import Token as RplyToken 3 | 4 | 5 | class Token(RplyToken): 6 | def getsrcpos(self): 7 | return (self.source_pos.start, self.source_pos.end) 8 | 9 | 10 | class SourceRange(object): 11 | def __init__(self, start, end, lineno, colno): 12 | self.start = start 13 | self.end = end 14 | self.lineno = lineno 15 | self.colno = colno 16 | 17 | def __repr__(self): 18 | return "SourceRange(start=%d, end=%d, lineno=%d, colno=%d)" % ( 19 | self.start, self.end, self.lineno, self.colno) 20 | 21 | 22 | class ParseError(Exception): 23 | def __init__(self, msg, line, filename, lineno, start_colno, end_colno): 24 | self.msg = msg 25 | self.line = line 26 | self.filename = filename 27 | self.lineno = lineno 28 | self.start_colno = start_colno 29 | self.end_colno = end_colno 30 | 31 | def __str__(self): 32 | # 6 comes from formatting of ParseError by pytest 33 | return (self.line + "\n" + " " * (self.start_colno - 6) + 34 | "^" * (self.end_colno - self.start_colno)) 35 | 36 | 37 | QUILL_RULES = [ 38 | ('INTEGER', r'\d+'), 39 | ('PLUS', r'\+'), 40 | ('MINUS', r'\-'), 41 | ('LE', r'\<='), 42 | ('GE', r'\>='), 43 | ('LT', r'\<'), 44 | ('GT', r'\>'), 45 | ('STAR', r'\*'), 46 | ('DOT', r'\.'), 47 | ('TRUEDIV', r'\/\/'), 48 | ('COLON', r':'), 49 | ('EQ', r'=='), 50 | ('NE', r'!='), 51 | ('ASSIGN', r'='), 52 | ('ST_DQ_STRING', r'"'), 53 | ('ST_SQ_STRING', r"'"), 54 | ('ST_INTERP_STRING', r'`'), 55 | ('ST_RAW_DQ_STRING', r'r"'), 56 | ('ST_RAW_SQ_STRING', r"r'"), 57 | ('IDENTIFIER', r'[a-zA-Z_][a-zA-Z0-9_]*'), 58 | ('LEFT_CURLY_BRACE', r'\{'), 59 | ('LEFT_PAREN', r'\('), 60 | ('RIGHT_PAREN', r'\)'), 61 | ('RIGHT_CURLY_BRACE', r'\}'), 62 | ('LEFT_SQUARE_BRACKET', r'\['), 63 | ('RIGHT_SQUARE_BRACKET', r'\]'), 64 | ('COMMA', r','), 65 | ('SEMICOLON', r';'), 66 | ] 67 | 68 | KEYWORDS = [ 69 | 'def', 70 | 'class', 71 | 'return', 72 | 'let', 73 | 'while', 74 | 'if', 75 | 'or', 76 | 'and', 77 | 'not', 78 | 'in', 79 | 'true', 80 | 'none', 81 | 'false', 82 | 'else', 83 | 'try', 84 | 'except', 85 | 'finally', 86 | 'as', 87 | 'raise', 88 | 'import', 89 | 'for', 90 | ] 91 | 92 | 93 | def make_string_rules(quote, interp=False): 94 | interp_rules = [] 95 | esc_quote = r'\\' + quote 96 | char = r'[^' + quote + r'\\]' 97 | esc_simple_chars = r'abfnrtv0' 98 | if interp: 99 | esc_simple_chars += r'\$' 100 | interp_rules = [('ST_INTERP', r'\$\{')] 101 | esc_simple = r'\\[' + esc_simple_chars + r']' 102 | esc_unrecognised = r'\\[^' + esc_simple_chars + quote + r'xu\\]' 103 | 104 | rules = [ 105 | ('ESC_QUOTE', esc_quote), 106 | ('ESC_ESC', r'\\\\'), 107 | ] + interp_rules + [ 108 | ('CHAR', char), 109 | ('ESC_SIMPLE', esc_simple), 110 | ('ESC_HEX_8', r'\\x[0-9a-fA-F]{2}'), 111 | ('ESC_HEX_16', r'\\u[0-9a-fA-F]{4}'), 112 | ('ESC_HEX_ANY', r'\\u\{[0-9a-fA-F]+\}'), 113 | ('ESC_UNRECOGNISED', esc_unrecognised), 114 | ('ST_ENDSTRING', quote), 115 | ] 116 | return rules 117 | 118 | 119 | DQ_STRING_RULES = make_string_rules(r'"', False) 120 | SQ_STRING_RULES = make_string_rules(r"'", False) 121 | INTERP_STRING_RULES = make_string_rules(r'`', True) 122 | 123 | QUILL_NO_ALI = ( 124 | 'RIGHT_CURLY_BRACE', 125 | 'RIGHT_PAREN', 126 | 'IDENTIFIER', 127 | 'INTEGER', 128 | 'TRUE', 129 | 'FALSE', 130 | 'NONE', 131 | 'RIGHT_SQUARE_BRACKET', 132 | 'ST_ENDSTRING', 133 | 'ST_ENDRAW', 134 | ) 135 | 136 | 137 | RAW_DQ_STRING_RULES = [ 138 | ('RAW_ESC', r'\\.'), 139 | ('RAW_CHAR', r'[^"\\]'), 140 | ('ST_ENDRAW', r'"'), 141 | ] 142 | 143 | RAW_SQ_STRING_RULES = [ 144 | ('RAW_ESC', r"\\."), 145 | ('RAW_CHAR', r"[^'\\]"), 146 | ('ST_ENDRAW', r"'"), 147 | ] 148 | 149 | 150 | TOKENS = [x[0] for x in QUILL_RULES + INTERP_STRING_RULES + RAW_DQ_STRING_RULES] + [x.upper() for x in KEYWORDS] 151 | 152 | KEYWORD_DICT = dict.fromkeys(KEYWORDS) 153 | 154 | 155 | class QuillLexerStream(object): 156 | _last_token = None 157 | 158 | def __init__(self, lexer, filename, s, state='INITIAL'): 159 | self.lexer = lexer 160 | self._filename = filename 161 | self.s = s 162 | self.state_stack = [] 163 | self.transition_state(state) 164 | 165 | self.idx = 0 166 | self._lineno = 1 167 | 168 | @property 169 | def state(self): 170 | return self.state_stack[-1] 171 | 172 | def transition_state(self, name): 173 | if name is None: 174 | self.state_stack.pop() 175 | else: 176 | self.state_stack.append(self.lexer.get_state(name)) 177 | 178 | def __iter__(self): 179 | return self 180 | 181 | def __next__(self): 182 | return self.next() 183 | 184 | def _update_pos(self, match_start, match_end): 185 | lineno = self._lineno 186 | self.idx = match_end 187 | self._lineno += self.s.count("\n", match_start, match_end) 188 | last_nl = self.s.rfind("\n", 0, match_start) 189 | if last_nl < 0: 190 | colno = match_start + 1 191 | else: 192 | colno = match_start - last_nl 193 | return SourceRange(match_start, match_end, lineno, colno) 194 | 195 | def next(self): 196 | while True: 197 | if self.idx >= len(self.s): 198 | if not self.state.end_allowed: 199 | raise self.parse_error("unterminated string") 200 | raise StopIteration 201 | if self.state.name == 'INITIAL': 202 | assert len(self.state.ignore_rules) == 2 203 | whitespace_rule = self.state.ignore_rules[0] 204 | match = whitespace_rule.matches(self.s, self.idx) 205 | if match is not None: 206 | source_range = self._update_pos(match.start, match.end) 207 | if "\n" in self.s[match.start:match.end]: 208 | if self._last_token and self._last_token.name not in QUILL_NO_ALI: 209 | continue 210 | token = Token( 211 | 'SEMICOLON', self.s[match.start:match.end], source_range 212 | ) 213 | self._last_token = token 214 | return token 215 | else: 216 | match = self.state.ignore_rules[1].matches(self.s, self.idx) 217 | if match is not None: 218 | self._update_pos(match.start, match.end) 219 | continue 220 | break 221 | else: 222 | for rule in self.state.ignore_rules: 223 | match = rule.matches(self.s, self.idx) 224 | if match: 225 | self._update_pos(match.start, match.end) 226 | break 227 | else: 228 | break 229 | 230 | for rule in self.state.rules: 231 | match = rule.matches(self.s, self.idx) 232 | if match: 233 | source_range = self._update_pos(match.start, match.end) 234 | val = self.s[match.start:match.end] 235 | if val in KEYWORD_DICT: 236 | name = val.upper() 237 | else: 238 | name = rule.name 239 | token = Token(name, val, source_range) 240 | self._last_token = token 241 | if name in self.state.transitions: 242 | self.transition_state(self.state.transitions[name]) 243 | return token 244 | else: 245 | raise self.parse_error("unrecognized token") 246 | 247 | def parse_error(self, msg): 248 | last_nl = self.s.rfind("\n", 0, self.idx) 249 | if last_nl < 0: 250 | colno = self.idx - 1 251 | else: 252 | colno = self.idx - last_nl - 1 253 | return ParseError(msg, 254 | self.s.splitlines()[self._lineno - 1], 255 | self._filename, self._lineno, colno, colno + 1) 256 | 257 | 258 | class QuillLexer(object): 259 | def __init__(self, states): 260 | self.states = states 261 | 262 | def get_state(self, name): 263 | return self.states[name] 264 | 265 | def lex(self, filename, s): 266 | return QuillLexerStream(self, filename, s) 267 | 268 | 269 | class LexerState(object): 270 | def __init__(self, name, end_allowed): 271 | self.name = name 272 | self.end_allowed = end_allowed 273 | self.rules = [] 274 | self.ignore_rules = [] 275 | self.transitions = {} 276 | 277 | def add(self, name, pattern, flags=0): 278 | self.rules.append(Rule(name, pattern, flags=flags)) 279 | 280 | def ignore(self, pattern, flags=0): 281 | self.ignore_rules.append(Rule("", pattern, flags=flags)) 282 | 283 | def push_state(self, name, state): 284 | assert name not in self.transitions 285 | self.transitions[name] = state 286 | 287 | def pop_state(self, name): 288 | assert name not in self.transitions 289 | self.transitions[name] = None 290 | 291 | 292 | class QuillLexerGenerator(object): 293 | def __init__(self): 294 | self.states = {} 295 | 296 | def state(self, name, end_allowed=True): 297 | assert name not in self.states 298 | self.states[name] = LexerState(name, end_allowed) 299 | return self.states[name] 300 | 301 | def build(self): 302 | return QuillLexer(self.states) 303 | 304 | 305 | def get_lexer(): 306 | lg = QuillLexerGenerator() 307 | 308 | initial = lg.state('INITIAL') 309 | for name, rule in QUILL_RULES: 310 | initial.add(name, rule) 311 | initial.ignore('\s+') 312 | initial.ignore('#[^\n]+') 313 | initial.push_state('ST_DQ_STRING', 'DQ_STRING') 314 | initial.push_state('ST_SQ_STRING', 'SQ_STRING') 315 | initial.push_state('ST_INTERP_STRING', 'INTERP_STRING') 316 | initial.push_state('ST_RAW_DQ_STRING', 'RAW_DQ_STRING') 317 | initial.push_state('ST_RAW_SQ_STRING', 'RAW_SQ_STRING') 318 | 319 | dq_string = lg.state('DQ_STRING', end_allowed=False) 320 | for name, rule in DQ_STRING_RULES: 321 | dq_string.add(name, rule) 322 | dq_string.pop_state('ST_ENDSTRING') 323 | 324 | sq_string = lg.state('SQ_STRING', end_allowed=False) 325 | for name, rule in SQ_STRING_RULES: 326 | sq_string.add(name, rule) 327 | sq_string.pop_state('ST_ENDSTRING') 328 | 329 | interp_string = lg.state('INTERP_STRING', end_allowed=False) 330 | for name, rule in INTERP_STRING_RULES: 331 | interp_string.add(name, rule) 332 | interp_string.push_state('ST_INTERP', 'INTERP') 333 | interp_string.pop_state('ST_ENDSTRING') 334 | 335 | dq_raw = lg.state('RAW_DQ_STRING', end_allowed=False) 336 | for name, rule in RAW_DQ_STRING_RULES: 337 | dq_raw.add(name, rule) 338 | dq_raw.pop_state('ST_ENDRAW') 339 | 340 | sq_raw = lg.state('RAW_SQ_STRING', end_allowed=False) 341 | for name, rule in RAW_SQ_STRING_RULES: 342 | sq_raw.add(name, rule) 343 | sq_raw.pop_state('ST_ENDRAW') 344 | 345 | # This is the same as the main state, except some rules are unused and we 346 | # pop the lexer state when we see a RIGHT_CURLY_BRACE. 347 | interp = lg.state('INTERP', end_allowed=False) 348 | for name, rule in QUILL_RULES: 349 | interp.add(name, rule) 350 | interp.ignore('\s+') 351 | interp.ignore("#[^n]+") 352 | interp.push_state('ST_DQ_STRING', 'DQ_STRING') 353 | interp.push_state('ST_SQ_STRING', 'SQ_STRING') 354 | interp.push_state('ST_INTERP_STRING', 'INTERP_STRING') 355 | interp.push_state('ST_RAW_DQ_STRING', 'RAW_DQ_STRING') 356 | interp.push_state('ST_RAW_SQ_STRING', 'RAW_SQ_STRING') 357 | interp.pop_state('RIGHT_CURLY_BRACE') 358 | 359 | return lg.build() 360 | -------------------------------------------------------------------------------- /nolang/main.py: -------------------------------------------------------------------------------- 1 | 2 | """ Execute: 3 | 4 | nolang-c 5 | """ 6 | 7 | import os 8 | import sys 9 | 10 | from nolang.interpreter import Interpreter 11 | from nolang.parser import get_parser, ParsingState, ParseError 12 | from nolang.compiler import compile_module 13 | from nolang.builtins.defaults import default_builtins 14 | from nolang.lexer import get_lexer 15 | from nolang.frameobject import format_traceback 16 | from nolang.importer import Importer 17 | from nolang.objects.space import Space 18 | from nolang.error import AppError 19 | 20 | 21 | def dirname(p): 22 | """Returns the directory component of a pathname""" 23 | i = p.rfind(os.path.sep) + 1 24 | assert i >= 0 25 | head = p[:i] 26 | if head and head != os.path.sep * len(head): 27 | head = head.rstrip(os.path.sep) 28 | return head 29 | 30 | 31 | def path_split(p): 32 | """Split a pathname. Returns tuple "(head, tail)" where "tail" is 33 | everything after the final slash. Either part may be empty.""" 34 | i = p.rfind('/') + 1 35 | assert i >= 0 36 | head, tail = p[:i], p[i:] 37 | if head and head != '/' * len(head): 38 | head = head.rstrip('/') 39 | return head, tail 40 | 41 | 42 | def main(argv): 43 | if len(argv) != 2: 44 | print __doc__ 45 | return 1 46 | return run_code(argv[1]) 47 | 48 | 49 | parser = get_parser() 50 | lexer = get_lexer() 51 | space = Space() 52 | space.setup_builtins(*default_builtins(space)) 53 | 54 | 55 | def format_parser_error(pe): 56 | print "Error parsing input file %s, line %d: %s" % (pe.filename, pe.lineno, 57 | pe.msg) 58 | print " " + pe.line 59 | print " " + " " * pe.start_colno + "^" * (pe.end_colno - pe.start_colno) 60 | 61 | 62 | def parse_name(fname): 63 | name = path_split(fname)[-1] 64 | p = name.rfind(".") 65 | if p > 0: 66 | name = name[:p] 67 | name = name.replace(".", "_") # explode? 68 | return "self." + name 69 | 70 | 71 | def run_code(fname): 72 | interpreter = Interpreter() 73 | space.setup(interpreter) 74 | try: 75 | source = open(fname).read() 76 | except (OSError, IOError): 77 | print "Error reading file %s" % fname 78 | return 1 79 | # XXX error handling 80 | try: 81 | ast = parser.parse(lexer.lex(fname, source), ParsingState(fname, 82 | source)) 83 | except ParseError as pe: 84 | format_parser_error(pe) 85 | return 1 86 | importer = Importer(space, dirname(os.path.abspath(fname)), parser, lexer) 87 | dotted_name = parse_name(fname) 88 | w_mod = compile_module(space, fname, dotted_name, source, ast, importer) 89 | w_mod.setup(space) 90 | try: 91 | space.call_method(w_mod, 'main', [], None) 92 | except AppError as e: 93 | os.write(2, format_traceback(space, e)) 94 | return 1 95 | return 0 96 | 97 | 98 | if __name__ == '__main__': 99 | sys.exit(main(sys.argv)) 100 | -------------------------------------------------------------------------------- /nolang/module.py: -------------------------------------------------------------------------------- 1 | 2 | """ Main declaration of a module 3 | """ 4 | 5 | from nolang.objects.root import W_Root 6 | 7 | 8 | def create_module(name, functions): 9 | name2index = {} 10 | for i, func in enumerate(functions): 11 | name2index[func.name] = i 12 | return W_Module(name, name2index, functions[:]) 13 | 14 | 15 | class W_Module(W_Root): 16 | def __init__(self, name, name2index, functions): 17 | self.name = name 18 | self.name2index = name2index 19 | self.functions = functions 20 | 21 | def setup(self, space): 22 | for item in self.functions: 23 | item.setup(space) 24 | 25 | def add_element(self, element): 26 | no = len(self.functions) 27 | self.functions.append(element) 28 | self.name2index[element.name] = no 29 | 30 | def getattr(self, space, name): 31 | try: 32 | return self.functions[self.name2index[name]] 33 | except KeyError: 34 | return W_Root.getattr(self, space, name) # let it raise 35 | 36 | def __repr__(self): 37 | return '' % (self.name, len(self.functions)) 38 | -------------------------------------------------------------------------------- /nolang/objects/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fijal/quill/c66dc0b66726862f3d0cb065bfa96a2937eaaa44/nolang/objects/__init__.py -------------------------------------------------------------------------------- /nolang/objects/bool.py: -------------------------------------------------------------------------------- 1 | from nolang.objects.root import W_Root 2 | 3 | 4 | class W_BoolObject(W_Root): 5 | def __init__(self, boolval): 6 | self._boolval = boolval 7 | 8 | def is_true(self, space): 9 | return self._boolval 10 | 11 | def __bool__(self): 12 | raise Exception("Should not ask for true value of W_Bool") 13 | 14 | def __repr__(self): 15 | if self._boolval: 16 | return '' 17 | else: 18 | return '' 19 | -------------------------------------------------------------------------------- /nolang/objects/buffer.py: -------------------------------------------------------------------------------- 1 | from nolang.objects.root import W_Root 2 | 3 | 4 | class W_BufObject(W_Root): 5 | def __init__(self, chars): 6 | self._chars = chars 7 | 8 | def buffer_w(self, space): 9 | return self._chars 10 | 11 | def str(self, space): 12 | return 'buffer(' + ''.join([str(c) for c in self._chars]) + ')' 13 | -------------------------------------------------------------------------------- /nolang/objects/dict.py: -------------------------------------------------------------------------------- 1 | from rpython.rlib.objectmodel import r_dict 2 | 3 | from nolang.builtins.spec import unwrap_spec, TypeSpec 4 | from nolang.objects.root import W_Root 5 | 6 | 7 | class W_DictObject(W_Root): 8 | def __init__(self, key_eq, key_hash, items_w): 9 | self._items_w = r_dict(key_eq, key_hash) 10 | for k, v in items_w: 11 | self._items_w[k] = v 12 | 13 | def str(self, space): 14 | return '{' + ', '.join([space.str(k) + ': ' + space.str(v) 15 | for k, v in self._items_w.items()]) + '}' 16 | 17 | def dictview(self, space): 18 | return self._items_w 19 | 20 | def len(self, space): 21 | return len(self._items_w) 22 | 23 | def contains(self, space, w_obj): 24 | return w_obj in self._items_w 25 | 26 | def getitem(self, space, w_key): 27 | if w_key not in self._items_w: 28 | raise space.apperr(space.w_keyerror, space.str(w_key)) 29 | return self._items_w[w_key] 30 | 31 | def get(self, space, w_key, w_default=None): 32 | if w_key not in self._items_w: 33 | return w_default 34 | return self._items_w[w_key] 35 | 36 | def dict_pop(self, space, w_key): 37 | if w_key not in self._items_w: 38 | raise space.apperr(space.w_keyerror, space.str(w_key)) 39 | return self._items_w.pop(w_key) 40 | 41 | def setitem(self, space, w_key, w_value): 42 | self._items_w[w_key] = w_value 43 | 44 | def merge(self, space, w_other): 45 | other_w = space.dictview(w_other) 46 | w_res = space.newdict([]) 47 | w_res._items_w.update(self._items_w) 48 | w_res._items_w.update(other_w) 49 | return w_res 50 | 51 | def keys(self, space): 52 | return space.newlist(self._items_w.keys()) 53 | 54 | def values(self, space): 55 | return space.newlist(self._items_w.values()) 56 | 57 | 58 | @unwrap_spec(items_w='dict') 59 | def allocate(space, w_tp, items_w): 60 | w_res = space.newdict([]) 61 | w_res._items_w.update(items_w) 62 | return w_res 63 | 64 | 65 | W_DictObject.spec = TypeSpec( 66 | 'Dict', 67 | constructor=allocate, 68 | methods={ 69 | 'merge': W_DictObject.merge, 70 | 'get': W_DictObject.get, 71 | 'pop': W_DictObject.dict_pop, 72 | 'keys': W_DictObject.keys, 73 | 'values': W_DictObject.values, 74 | }, 75 | properties={}, 76 | ) 77 | -------------------------------------------------------------------------------- /nolang/objects/int.py: -------------------------------------------------------------------------------- 1 | """ Base implementation of W_Int which is a machine-sized integer 2 | """ 3 | 4 | from rpython.rlib.objectmodel import compute_hash 5 | 6 | from nolang.error import AppError 7 | from nolang.objects.root import W_Root, NotImplementedOp 8 | from nolang.builtins.spec import TypeSpec, unwrap_spec 9 | 10 | 11 | class W_IntObject(W_Root): 12 | def __init__(self, intval): 13 | self._intval = intval 14 | 15 | def str(self, space): 16 | return str(self._intval) 17 | 18 | def hash(self, space): 19 | return compute_hash(self._intval) 20 | 21 | def int_w(self, space): 22 | return self._intval 23 | 24 | def lt(self, space, w_other): 25 | return self._intval < w_other._intval 26 | 27 | def eq(self, space, w_other): 28 | try: 29 | other = space.int_w(w_other) 30 | except AppError as ae: 31 | if ae.match(space, space.w_typeerror): 32 | raise NotImplementedOp 33 | raise 34 | return self._intval == other 35 | 36 | def add(self, space, w_other): 37 | return space.newint(self._intval + w_other._intval) 38 | 39 | def sub(self, space, w_other): 40 | return space.newint(self._intval - w_other._intval) 41 | 42 | def mul(self, space, w_other): 43 | return space.newint(self._intval * w_other._intval) 44 | 45 | def truediv(self, space, w_other): 46 | return space.newint(self._intval // w_other._intval) 47 | 48 | def is_true(self, space): 49 | return self._intval != 0 50 | 51 | def __repr__(self): 52 | return '' % self._intval 53 | 54 | 55 | @unwrap_spec(value='int') 56 | def new_int(space, value): 57 | return space.newint(value) 58 | 59 | 60 | W_IntObject.spec = TypeSpec('Int', new_int) 61 | -------------------------------------------------------------------------------- /nolang/objects/list.py: -------------------------------------------------------------------------------- 1 | from nolang.error import AppError 2 | from nolang.objects.root import W_Root 3 | from nolang.builtins.spec import unwrap_spec, TypeSpec 4 | 5 | 6 | class W_ListObject(W_Root): 7 | def __init__(self, items_w): 8 | self._items_w = items_w[:] 9 | 10 | def str(self, space): 11 | return '[' + ', '.join([space.str(i) for i in self._items_w]) + ']' 12 | 13 | def listview(self, space): 14 | return self._items_w 15 | 16 | def len(self, space): 17 | return len(self._items_w) 18 | 19 | def contains(self, space, w_obj): 20 | for w_item in self._items_w: 21 | if space.binop_eq(w_obj, w_item): 22 | return True 23 | return False 24 | 25 | def unwrap_index(self, space, w_index): 26 | try: 27 | i = space.int_w(w_index) 28 | except AppError as ae: 29 | if ae.match(space, space.w_typeerror): 30 | raise space.apperr(space.w_typeerror, 'list index must be int') 31 | raise 32 | if i < 0 or i >= len(self._items_w): 33 | raise space.apperr(space.w_indexerror, 'list index out of range') 34 | return i 35 | 36 | def getitem(self, space, w_index): 37 | return self._items_w[self.unwrap_index(space, w_index)] 38 | 39 | def setitem(self, space, w_index, w_value): 40 | self._items_w[self.unwrap_index(space, w_index)] = w_value 41 | 42 | def iter(self, space): 43 | return W_ListIterator(self) 44 | 45 | def append(self, space, w_obj): 46 | self._items_w.append(w_obj) 47 | 48 | def extend(self, space, w_other): 49 | self._items_w.extend(space.listview(w_other)) 50 | 51 | 52 | @unwrap_spec(items_w='list') 53 | def allocate(space, w_tp, items_w): 54 | return space.newlist(items_w) 55 | 56 | 57 | W_ListObject.spec = TypeSpec( 58 | 'List', 59 | constructor=allocate, 60 | methods={ 61 | 'append': W_ListObject.append, 62 | 'extend': W_ListObject.extend, 63 | }, 64 | properties={}, 65 | ) 66 | 67 | 68 | class W_ListIterator(W_Root): 69 | def __init__(self, w_list): 70 | self.w_list = w_list 71 | self.index = 0 72 | 73 | def iter_next(self, space): 74 | if self.index >= len(self.w_list._items_w): 75 | return None 76 | ind = self.index 77 | self.index = ind + 1 78 | return self.w_list._items_w[ind] 79 | 80 | 81 | W_ListIterator.spec = TypeSpec( 82 | 'ListIterator', 83 | constructor=None, 84 | ) 85 | -------------------------------------------------------------------------------- /nolang/objects/root.py: -------------------------------------------------------------------------------- 1 | """ Base class that contains all the methods on W_Root which is the root 2 | objects of everything wrapped and presented to the user 3 | """ 4 | 5 | # XXX add formatting of types, e.g. "expected integer, got %s" 6 | 7 | 8 | class NotImplementedOp(Exception): 9 | pass 10 | 11 | 12 | class W_Root(object): 13 | cls_w_type = None 14 | 15 | def int_w(self, space): 16 | raise space.apperr(space.w_typeerror, 'expected integer') 17 | 18 | def utf8_w(self, space): 19 | raise space.apperr(space.w_typeerror, 'expected string') 20 | 21 | def buffer_w(self, space): 22 | raise space.apperr(space.w_typeerror, 'expected buffer') 23 | 24 | def listview(self, space): 25 | raise space.apperr(space.w_typeerror, 'expected list') 26 | 27 | def dictview(self, space): 28 | raise space.apperr(space.w_typeerror, 'expected dict') 29 | 30 | def hash(self, space): 31 | raise space.apperr(space.w_typeerror, 'unhashable type') 32 | 33 | def iter(self, space): 34 | raise space.apperr(space.w_typeerror, 'uniterable type') 35 | 36 | def iter_next(self, space): 37 | raise space.apperr(space.w_typeerror, 'object not an iterator') 38 | 39 | def getattr(self, space, attrname): 40 | return space.w_NotImplemented 41 | 42 | def bind(self, space, w_obj): 43 | return self 44 | 45 | def gettype(self, space): 46 | return self.cls_w_type 47 | 48 | def str(self, space): 49 | raise space.apperr(space.w_typeerror, 50 | 'object cannot be converted to str') 51 | 52 | def len(self, space): 53 | raise space.apperr(space.w_typeerror, 'unsized type') 54 | 55 | def is_true(self, space): 56 | return True 57 | 58 | def lt(self, space, w_obj): 59 | raise space.apperr(space.w_typeerror, 'object not comparable') 60 | 61 | def eq(self, space, w_other): 62 | if self is w_other: 63 | return True 64 | if space.type(self) is not space.type(w_other): 65 | raise NotImplementedOp 66 | return False 67 | 68 | def add(self, space, w_other): 69 | raise space.apperr(space.w_typeerror, 'no implementation for `+`') 70 | 71 | def sub(self, space, w_other): 72 | raise space.apperr(space.w_typeerror, 'no implementation for `-`') 73 | 74 | def mul(self, space, w_other): 75 | raise space.apperr(space.w_typeerror, 'no implementation for `*`') 76 | 77 | def truediv(self, space, w_other): 78 | raise space.apperr(space.w_typeerror, 'no implementation for `/`') 79 | 80 | def call(self, space, interpreter, args, kwargs): 81 | raise space.apperr(space.w_typeerror, 'object is not callable') 82 | 83 | def freeze(self, space): 84 | if self.is_frozen(space): 85 | return 86 | raise space.apperr(space.w_freezeerror, 'object not freezable') 87 | 88 | def thaw(self, space): 89 | raise space.apperr(space.w_freezeerror, 'object does not know ' 90 | 'how to thaw, this should be an internal error') 91 | 92 | def is_frozen(self, space): 93 | return False 94 | 95 | 96 | class W_None(W_Root): 97 | 98 | def is_true(self, space): 99 | return False 100 | 101 | def is_frozen(self, space): 102 | return True 103 | -------------------------------------------------------------------------------- /nolang/objects/space.py: -------------------------------------------------------------------------------- 1 | """ Class containing convinient shortcut for calling all things 2 | on objects 3 | """ 4 | 5 | from nolang.error import AppError 6 | from nolang.objects.root import W_None, W_Root, NotImplementedOp 7 | from nolang.objects.int import W_IntObject 8 | from nolang.objects.bool import W_BoolObject 9 | from nolang.objects.buffer import W_BufObject 10 | from nolang.objects.dict import W_DictObject 11 | from nolang.objects.list import W_ListObject 12 | from nolang.objects.unicode import W_StrObject 13 | from nolang.objects.usertype import W_UserType 14 | from nolang.builtins.spec import wrap_builtin 15 | from nolang.builtins.exception import W_Exception 16 | 17 | 18 | class Space(object): 19 | def __init__(self): 20 | self.w_None = W_None() # singleton 21 | self.w_True = W_BoolObject(True) 22 | self.w_False = W_BoolObject(False) 23 | self.w_NotImplemented = W_Root() 24 | 25 | def setup(self, interpreter): 26 | self.interpreter = interpreter 27 | 28 | def setup_builtins(self, builtins, coremod, non_builtins): 29 | self.builtins_w = [] 30 | self.builtin_dict = {} 31 | for builtin in builtins: 32 | self.setup_builtin(wrap_builtin(self, builtin)) 33 | self.coremod = coremod 34 | for non_builtin in non_builtins: 35 | wrap_builtin(self, non_builtin) 36 | self.w_exception = self.builtin_dict['Exception'] 37 | self.w_list = self.builtin_dict['List'] 38 | self.w_dict = self.builtin_dict['Dict'] 39 | self.w_str = self.builtin_dict['Str'] 40 | self.w_indexerror = self.make_exception('IndexError') 41 | self.w_typeerror = self.make_exception('TypeError') 42 | self.w_argerror = self.make_exception('ArgumentError') 43 | self.w_attrerror = self.make_exception('AttributeError') 44 | self.w_keyerror = self.make_exception('KeyError') 45 | self.w_freezeerror = self.make_exception('FreezeError') 46 | 47 | def setup_builtin(self, builtin): 48 | self.builtins_w.append(builtin) 49 | self.builtin_dict[builtin.name] = builtin 50 | return builtin 51 | 52 | def make_subclass(self, w_tp, name): 53 | return W_UserType(w_tp.allocate, name, [], w_tp, w_tp.default_alloc) 54 | 55 | def make_exception(self, name, parent=None): 56 | if parent is None: 57 | parent = self.w_exception 58 | return self.setup_builtin(self.make_subclass(parent, name)) 59 | 60 | def setattr(self, w_obj, attrname, w_value): 61 | w_obj.setattr(self, attrname, w_value) 62 | 63 | def getattr(self, w_obj, attrname): 64 | w_res = w_obj.getattr(self, attrname) 65 | if w_res is self.w_NotImplemented: 66 | ty = self.type(w_obj) 67 | if ty is not None: 68 | # XXX: temporary workaround 69 | w_res = self.getattr(ty, attrname).bind(self, w_obj) 70 | if w_res is self.w_NotImplemented: 71 | raise self.apperr(self.w_attrerror, 'no such attribute "%s"' % (attrname,)) 72 | return w_res 73 | 74 | def setitem(self, w_obj, w_index, w_value): 75 | w_obj.setitem(self, w_index, w_value) 76 | 77 | def getitem(self, w_obj, w_index): 78 | return w_obj.getitem(self, w_index) 79 | 80 | def str(self, w_obj): 81 | return w_obj.str(self) 82 | 83 | def len(self, w_obj): 84 | return w_obj.len(self) 85 | 86 | def hash(self, w_obj): 87 | return w_obj.hash(self) 88 | 89 | def key_eq(self, w_one, w_two): 90 | return self.binop_eq(w_one, w_two) 91 | 92 | def is_none(self, w_obj): 93 | return w_obj is None or w_obj is self.w_None 94 | 95 | # object stuff, hacks so far 96 | def issubclass(self, w_left, w_right): 97 | return w_left.issubclass(w_right) 98 | 99 | def isinstance(self, w_left, w_tp): 100 | assert isinstance(w_tp, W_UserType) 101 | return self.issubclass(self.type(w_left), w_tp) 102 | 103 | def type(self, w_obj): 104 | # for builtin types we know exactly what type is it based on class 105 | return w_obj.gettype(self) 106 | 107 | # newfoo wrappers 108 | def newint(self, intval): 109 | return W_IntObject(intval) 110 | 111 | def newbool(self, boolval): 112 | if boolval: 113 | return self.w_True 114 | return self.w_False 115 | 116 | def newtext(self, utf8val): 117 | return W_StrObject(utf8val) 118 | 119 | def newbuf(self, charsval): 120 | return W_BufObject(charsval) 121 | 122 | def newlist(self, items_w): 123 | return W_ListObject(items_w) 124 | 125 | def newdict(self, items_w): 126 | return W_DictObject(self.key_eq, self.hash, items_w) 127 | 128 | # foo_w unwrappers 129 | def int_w(self, w_obj): 130 | return w_obj.int_w(self) 131 | 132 | def utf8_w(self, w_obj): 133 | return w_obj.utf8_w(self) 134 | 135 | def buffer_w(self, w_obj): 136 | return w_obj.buffer_w(self) 137 | 138 | def listview(self, w_obj): 139 | return w_obj.listview(self) 140 | 141 | def dictview(self, w_obj): 142 | return w_obj.dictview(self) 143 | 144 | # unary operations 145 | def iter(self, w_obj): 146 | return w_obj.iter(self) 147 | 148 | def iter_next(self, w_obj): 149 | return w_obj.iter_next(self) 150 | 151 | def is_true(self, w_obj): 152 | return w_obj.is_true(self) 153 | 154 | def unaryop_not(self, w_obj): 155 | return self.newbool(not self.is_true(w_obj)) 156 | 157 | # binary operations, comparisons and in returns True/False, the rest w_obj 158 | def binop_lt(self, w_one, w_two): 159 | return w_one.lt(self, w_two) 160 | 161 | def binop_gt(self, w_one, w_two): 162 | # Implemented in terms of lt and eq. 163 | return not (self.binop_lt(w_one, w_two) or 164 | self.binop_eq(w_one, w_two)) 165 | 166 | def binop_le(self, w_one, w_two): 167 | # Implemented in terms of lt and eq. 168 | return (self.binop_lt(w_one, w_two) or 169 | self.binop_eq(w_one, w_two)) 170 | 171 | def binop_ge(self, w_one, w_two): 172 | # Implemented in terms of lt and eq. 173 | return not self.binop_lt(w_one, w_two) 174 | 175 | def binop_eq(self, w_one, w_two): 176 | try: 177 | return w_one.eq(self, w_two) 178 | except NotImplementedOp: 179 | pass 180 | try: 181 | return w_two.eq(self, w_one) 182 | except NotImplementedOp: 183 | return False 184 | 185 | def binop_ne(self, w_one, w_two): 186 | # Implemented in terms of lt and eq. 187 | return not self.binop_eq(w_one, w_two) 188 | 189 | def binop_in(self, w_one, w_two): 190 | return w_two.contains(self, w_one) 191 | 192 | def w_binop_add(self, w_one, w_two): 193 | return w_one.add(self, w_two) 194 | 195 | def w_binop_sub(self, w_one, w_two): 196 | return w_one.sub(self, w_two) 197 | 198 | def w_binop_mul(self, w_one, w_two): 199 | return w_one.mul(self, w_two) 200 | 201 | def w_binop_truediv(self, w_one, w_two): 202 | return w_one.truediv(self, w_two) 203 | 204 | # various calls 205 | def call_method(self, w_object, method_name, args, kwargs): 206 | w_obj = w_object.getattr(self, method_name) 207 | return w_obj.call(self, self.interpreter, args, kwargs) 208 | 209 | def call(self, w_object, args, kwargs): 210 | return w_object.call(self, self.interpreter, args, kwargs) 211 | 212 | # exceptions 213 | def apperr(self, w_type_error, msg): 214 | return AppError(W_Exception(w_type_error, msg)) 215 | 216 | # freezing interface 217 | def freeze(self, w_obj): 218 | w_obj.freeze(self) 219 | 220 | def thaw(self, w_obj): 221 | if not self.is_frozen(w_obj): 222 | return w_obj 223 | return w_obj.thaw(self) 224 | 225 | def is_frozen(self, w_obj): 226 | return w_obj.is_frozen(self) 227 | -------------------------------------------------------------------------------- /nolang/objects/unicode.py: -------------------------------------------------------------------------------- 1 | from rpython.rlib.objectmodel import compute_hash 2 | 3 | from nolang.error import AppError 4 | from nolang.objects.root import W_Root, NotImplementedOp 5 | from nolang.builtins.spec import TypeSpec, unwrap_spec 6 | 7 | 8 | class W_StrObject(W_Root): 9 | def __init__(self, utf8val, lgt=-1): 10 | assert isinstance(utf8val, str) 11 | self.utf8val = utf8val 12 | self.lgt = lgt 13 | 14 | def utf8_w(self, space): 15 | return self.utf8val 16 | 17 | def str(self, space): 18 | return self.utf8_w(space) 19 | 20 | def len(self, space): 21 | if self.lgt == -1: 22 | # XXX compute it better once utf8 lands 23 | self.lgt = len(self.utf8val.decode('utf-8')) 24 | return self.lgt 25 | 26 | def hash(self, space): 27 | return compute_hash(self.utf8val) 28 | 29 | def eq(self, space, w_other): 30 | try: 31 | other = space.utf8_w(w_other) 32 | except AppError as ae: 33 | if space.type(ae.w_exception) is space.w_typeerror: 34 | raise NotImplementedOp 35 | raise 36 | return self.utf8val == other 37 | 38 | def is_frozen(self, space): 39 | return True 40 | 41 | 42 | @unwrap_spec(value='utf8') 43 | def new_str(space, value): 44 | return space.newtext(value) 45 | 46 | 47 | W_StrObject.spec = TypeSpec('Str', new_str) 48 | -------------------------------------------------------------------------------- /nolang/objects/userobject.py: -------------------------------------------------------------------------------- 1 | """ User supplied objects 2 | """ 3 | 4 | from nolang.objects.root import W_Root 5 | 6 | 7 | class W_UserObject(W_Root): 8 | def __init__(self, w_type): 9 | self.w_type = w_type 10 | self._dict_w = {} 11 | 12 | def gettype(self, space): 13 | return self.w_type 14 | 15 | def setattr(self, space, attrname, w_val): 16 | if self.w_type.force_names is not None: 17 | if attrname not in self.w_type.force_names: 18 | msg = '%s is not an allowed attribute of object of class %s' % ( 19 | attrname, self.w_type.name) 20 | raise space.apperr(space.w_attrerror, msg) 21 | self._dict_w[attrname] = w_val 22 | 23 | def getattr(self, space, attrname): 24 | try: 25 | return self._dict_w[attrname] 26 | except KeyError: 27 | return space.w_NotImplemented 28 | -------------------------------------------------------------------------------- /nolang/objects/usertype.py: -------------------------------------------------------------------------------- 1 | """ Basic declaration of user type defined with class 2 | """ 3 | 4 | from nolang.objects.root import W_Root 5 | 6 | 7 | class W_UserType(W_Root): 8 | def __init__(self, allocate, name, class_elements_w, w_parent, 9 | default_alloc=True, force_names=None): 10 | self.name = name 11 | self.allocate = allocate 12 | self.class_elements_w = class_elements_w 13 | self.default_alloc = default_alloc 14 | if w_parent is not None: 15 | self._dict_w = w_parent._dict_w.copy() 16 | else: 17 | self._dict_w = {} 18 | for item in class_elements_w: 19 | self._dict_w[item.name] = item 20 | self.w_parent = w_parent 21 | if force_names is None: 22 | self.force_names = None 23 | else: 24 | self.force_names = {} 25 | for elem in force_names: 26 | self.force_names[elem] = None 27 | 28 | def setup(self, space): 29 | for item in self.class_elements_w: 30 | item.setup(space) 31 | 32 | def call(self, space, interpreter, args_w, kwargs): 33 | if self.allocate is None: 34 | raise Exception("cannot be called like that") 35 | w_obj = space.call(self.allocate, [self] + args_w, kwargs) 36 | if '__init__' in self._dict_w: 37 | space.call(self._dict_w['__init__'], [w_obj] + args_w, kwargs) 38 | elif self.default_alloc: 39 | if len(args_w) != 0: 40 | raise space.apperr(space.w_argerror, "Default constructor" 41 | " expecting no arguments") 42 | return w_obj 43 | 44 | def getattr(self, space, attrname): 45 | try: 46 | return self._dict_w[attrname] 47 | except KeyError: 48 | return space.w_NotImplemented 49 | 50 | def issubclass(self, w_type): 51 | cur = self 52 | while cur is not None: 53 | if cur is w_type: 54 | return True 55 | cur = cur.w_parent 56 | return False 57 | 58 | def __repr__(self): 59 | return "" % (self.name,) 60 | -------------------------------------------------------------------------------- /nolang/opcodes.py: -------------------------------------------------------------------------------- 1 | """ Opcode declaration 2 | """ 3 | 4 | 5 | class Opcode(object): 6 | def __init__(self, name, numargs, stack_effect, description): 7 | self.name = name 8 | self.numargs = numargs 9 | self.stack_effect = stack_effect 10 | self.description = description 11 | 12 | def __repr__(self): 13 | return '<%s>' % self.name 14 | 15 | 16 | opcodes = [ 17 | Opcode('INVALID', 0, 0, 'invalid opcode'), 18 | Opcode('LOAD_NONE', 0, 1, 'load None onto stack'), 19 | Opcode('LOAD_VARIABLE', 1, 1, 'load variable onto stack'), 20 | Opcode('LOAD_GLOBAL', 1, 1, 'load global variable'), 21 | Opcode('LOAD_CONSTANT', 1, 1, 'load constant onto stack'), 22 | Opcode('LOAD_TRUE', 0, 1, 'load true onto stack'), 23 | Opcode('LOAD_FALSE', 0, 1, 'load false onto stack'), 24 | Opcode('STORE', 1, -1, 'store top of the stack into variable'), 25 | Opcode('DISCARD', 0, -1, 'discard top of the stack'), 26 | Opcode('GETATTR', 1, 0, 'get attribute from the object on top of the stack ' 27 | 'with string constant as an argument'), 28 | Opcode('SETATTR', 1, -2, 'set attribute on an object'), 29 | Opcode('GETITEM', 0, -1, 'get item from the object on top of the stack ' 30 | 'with next value as index'), 31 | Opcode('SETITEM', 0, -3, 'set item on an object'), 32 | Opcode('LIST_BUILD', 1, 254, 'take N arguments from the stack, build them ' 33 | 'into a list, and push the list'), 34 | Opcode('DICT_BUILD', 1, 254, 'take N arguments from the stack, build them ' 35 | 'into a dict, and push the dict'), 36 | Opcode('TEXT_BUILD', 1, 254, 'take N arguments from the stack, build them ' 37 | 'into a string, and push the dict'), 38 | Opcode('CREATE_ITER', 0, 0, 'pop top of the stack, call iter() and put ' 39 | 'it back'), 40 | Opcode('ITER_NEXT', 0, 1, 'call next() on top of the stack iterator and put' 41 | ' it on the top of the stack'), 42 | # exception handling 43 | Opcode('PUSH_RESUME_STACK', 1, 0, 'add a new resume point to the stack'), 44 | Opcode('POP_RESUME_STACK', 0, 0, 'pop one from resume stack'), 45 | Opcode('COMPARE_EXCEPTION', 1, -1, 'compare current exception with top of ' 46 | 'the stack'), 47 | Opcode('RAISE', 0, -1, 'raise the top of the stack'), 48 | Opcode('RERAISE', 0, 0, 'reraise if exception is set'), 49 | Opcode('PUSH_CURRENT_EXC', 0, 1, 'push the current exception on the stack'), 50 | Opcode('CLEAR_CURRENT_EXC', 0, 0, 'set current exception to nothing'), 51 | # unary ops 52 | Opcode('NOT', 0, 0, 'load one values from the stack and store it boolean' 53 | ' opposite'), 54 | # binary ops 55 | Opcode('ADD', 0, -1, 'load two values from the stack and store the result' 56 | ' of addition'), 57 | Opcode('SUB', 0, -1, 'load two values from the stack and store the result' 58 | ' of subtraction'), 59 | Opcode('MUL', 0, -1, 'load two values from the stack and store the result' 60 | ' of multiplication'), 61 | Opcode('TRUEDIV', 0, -1, 'load two values from the stack and store the result' 62 | ' of integer division'), 63 | Opcode('LT', 0, -1, 'load two value from the stack and store the result' 64 | ' of lower than comparison'), 65 | Opcode('GT', 0, -1, 'load two value from the stack and store the result' 66 | ' of greater than comparison'), 67 | Opcode('LE', 0, -1, 'load two value from the stack and store the result' 68 | ' of equal or lower than comparison'), 69 | Opcode('GE', 0, -1, 'load two value from the stack and store the result' 70 | ' of equal or greater than comparison'), 71 | Opcode('EQ', 0, -1, 'load two value from the stack and store the result' 72 | ' of comparison'), 73 | Opcode('NE', 0, -1, 'load two value from the stack and store the result' 74 | ' of negated comparison'), 75 | Opcode('IN', 0, -1, 'load two value from the stack and store the result' 76 | ' of checking if one contains the other'), 77 | # jumps 78 | Opcode('JUMP_IF_FALSE', 1, -1, 'pop value from the stack and jump if false' 79 | ' to a given position'), 80 | Opcode('JUMP_IF_TRUE_NOPOP', 1, 0, 'peek value from the stack and jump if ' 81 | 'true to a given position'), 82 | Opcode('JUMP_IF_FALSE_NOPOP', 1, 0, 'peek value from the stack and jump if ' 83 | 'false to a given position'), 84 | Opcode('JUMP_ABSOLUTE', 1, 0, 'jump to an absolute position'), 85 | Opcode('JUMP_IF_EMPTY', 1, 0, 'jump to a position if top of the stack is' 86 | ' empty'), 87 | Opcode('CALL', 2, 255, 'take N arguments and M named arguments ' 88 | 'from the stack, pack them into ' 89 | 'args and call the next element'), 90 | Opcode('RETURN', 0, -1, 'return the top of stack') 91 | ] 92 | 93 | 94 | def setup(): 95 | for i, opcode in enumerate(opcodes): 96 | globals()[opcode.name] = i 97 | 98 | 99 | setup() 100 | -------------------------------------------------------------------------------- /nolang/parser.py: -------------------------------------------------------------------------------- 1 | 2 | import rply 3 | from rpython.rlib.runicode import str_decode_utf_8, unicode_encode_utf_8, UNICHR 4 | 5 | from nolang.lexer import TOKENS, ParseError 6 | from nolang import astnodes as ast 7 | 8 | 9 | def sr(p): 10 | return (p[0].getsrcpos()[0], p[-1].getsrcpos()[1]) 11 | 12 | 13 | class ParsingState(object): 14 | def __init__(self, filename, input): 15 | self.input = input 16 | self.filename = filename 17 | 18 | 19 | def errorhandler(state, lookahead, msg="Parsing error"): 20 | sourcepos = lookahead.getsrcpos() 21 | idx = sourcepos[0] 22 | source = state.input 23 | last_nl = source.rfind("\n", 0, idx) 24 | lineno = source.count("\n", 0, idx) 25 | if last_nl < 0: 26 | last_nl = 0 27 | colno = idx - 1 28 | else: 29 | colno = idx - last_nl - 1 30 | next_nl = source.find("\n", idx) 31 | if next_nl < 0: 32 | next_nl = len(source) 33 | line = source[last_nl:next_nl] 34 | raise ParseError(msg, line, state.filename, lineno, colno - 1, 35 | (sourcepos[1] - sourcepos[0]) + colno - 1) 36 | 37 | 38 | def hex_to_utf8(state, token, s): 39 | try: 40 | uchr = UNICHR(int(s, 16)) 41 | return unicode_encode_utf_8(uchr, len(uchr), 'strict') 42 | except (ValueError, UnicodeDecodeError): 43 | # XXX better error message 44 | raise errorhandler(state, token, msg="Error encoding %s" % s) 45 | 46 | 47 | def get_parser(): 48 | pg = rply.ParserGenerator(TOKENS, precedence=[ 49 | ('left', ['AND']), 50 | ('left', ['OR']), 51 | ('left', ['EQ', 'LT', 'GT', 'IN', 'NE', 'LE', 'GE']), 52 | ('left', ['PLUS', 'MINUS']), 53 | ('left', ['TRUEDIV', 'STAR']), 54 | ('left', ['DOT']), 55 | ('left', ['LEFT_PAREN']), 56 | ]) 57 | pg.error(errorhandler) 58 | 59 | @pg.production('program : body') 60 | def program_body(state, p): 61 | element_list = p[0].get_element_list() 62 | for elem in element_list: 63 | if (isinstance(elem, ast.VarDeclaration) or 64 | isinstance(elem, ast.VarDeclarationConstant)): 65 | raise errorhandler(state, elem, 66 | 'var declarations in body disallowed') 67 | return ast.Program(element_list, srcpos=sr(element_list)) 68 | 69 | @pg.production('body :') 70 | def body_empty(state, p): 71 | return ast.FunctionBody(None, None) 72 | 73 | @pg.production('body : body body_element') 74 | def body_body_element(state, p): 75 | return ast.FunctionBody(p[1], p[0]) 76 | 77 | @pg.production('body_element : function') 78 | def body_function(state, p): 79 | return p[0] 80 | 81 | @pg.production('body_element : class_definition') 82 | def body_element_class_definition(state, p): 83 | return p[0] 84 | 85 | @pg.production('body_element : SEMICOLON') 86 | def body_element_semicolon(state, p): 87 | return None 88 | 89 | @pg.production('body_element : global_var_declaration') 90 | def body_element_var_decl(state, p): 91 | return p[0] 92 | 93 | @pg.production('body_element : IMPORT IDENTIFIER dot_identifier_list' 94 | ' optional_import SEMICOLON') 95 | def body_element_import_statement(state, p): 96 | basename = p[1].getstr() 97 | if p[2] is None and p[3] is None: 98 | return ast.Import([basename], [], srcpos=sr(p)) 99 | if p[3] is None: 100 | names = p[2].get_names() 101 | return ast.Import([basename] + names[:-1], [names[-1]], srcpos=sr(p)) 102 | if p[2] is None: 103 | return ast.Import([basename], p[3].get_names(), srcpos=sr(p)) 104 | return ast.Import([basename] + p[2].get_names(), 105 | p[3].get_names(), srcpos=sr(p)) 106 | 107 | @pg.production('optional_import :') 108 | def optional_import_empty(state, p): 109 | return None 110 | 111 | @pg.production('optional_import : ' 112 | 'LEFT_CURLY_BRACE IDENTIFIER identifier_list ' 113 | 'RIGHT_CURLY_BRACE') 114 | def optional_import_brace(state, p): 115 | return ast.IdentifierListPartial(p[1].getstr(), p[2]) 116 | 117 | @pg.production('class_definition : CLASS IDENTIFIER LEFT_CURLY_BRACE body ' 118 | 'RIGHT_CURLY_BRACE') 119 | def class_definition(state, p): 120 | return ast.ClassDefinition(p[1].getstr(), p[3], srcpos=sr(p)) 121 | 122 | @pg.production('class_definition : CLASS IDENTIFIER LEFT_PAREN IDENTIFIER ' 123 | 'RIGHT_PAREN LEFT_CURLY_BRACE body RIGHT_CURLY_BRACE') 124 | def class_definition_inheritance(state, p): 125 | return ast.ClassDefinition(p[1].getstr(), p[6], p[3].getstr(), srcpos=sr(p)) 126 | 127 | @pg.production('function : DEF IDENTIFIER arglist LEFT_CURLY_BRACE' 128 | ' function_body RIGHT_CURLY_BRACE') 129 | def function_function_body(state, p): 130 | lineno = p[0].getsourcepos().lineno 131 | return ast.Function(p[1].getstr(), p[2].get_vars(), 132 | p[4].get_element_list(), lineno, srcpos=sr(p)) 133 | 134 | @pg.production('function_body :') 135 | def function_body_empty(state, p): 136 | return ast.FunctionBody(None, None) 137 | 138 | @pg.production('function_body : function_body statement') 139 | def function_body_statement(state, p): 140 | return ast.FunctionBody(p[1], p[0]) 141 | 142 | @pg.production('statement : expression SEMICOLON') 143 | def statement_expression(state, p): 144 | return ast.Statement(p[0], srcpos=sr(p)) 145 | 146 | @pg.production('statement : SEMICOLON') 147 | def staement_empty(state, p): 148 | return None 149 | 150 | @pg.production('statement : var_declaration') 151 | def statement_var_decl(state, p): 152 | return p[0] 153 | 154 | @pg.production('global_var_declaration : LET IDENTIFIER type_decl ' 155 | 'arg_decl SEMICOLON') 156 | @pg.production('global_var_declaration : LET IDENTIFIER ASSIGN constant_val ' 157 | 'type_decl arg_decl SEMICOLON') 158 | def global_var_declaration(state, p): 159 | if len(p) == 5: 160 | vars = [ast.Var(p[1].getstr(), p[2], None, srcpos=sr([p[1], p[2]]))] + \ 161 | p[3].get_vars() 162 | else: 163 | vars = [ast.Var(p[1].getstr(), p[4], p[3], srcpos=sr([p[1], p[2], p[3]]))] + \ 164 | p[5].get_vars() 165 | return ast.VarDeclarationConstant(vars, srcpos=sr(p)) 166 | 167 | @pg.production('var_declaration : LET IDENTIFIER type_decl var_decl ' 168 | 'SEMICOLON') 169 | @pg.production('var_declaration : LET IDENTIFIER ASSIGN expression ' 170 | 'type_decl var_decl SEMICOLON') 171 | def var_declaration(state, p): 172 | if len(p) == 5: 173 | vars = [ast.Var(p[1].getstr(), p[2], None, srcpos=sr([p[1], p[2]]))] + \ 174 | p[3].get_vars() 175 | else: 176 | vars = [ast.Var(p[1].getstr(), p[4], p[3], srcpos=sr([p[1], p[2], p[3]]))] + \ 177 | p[5].get_vars() 178 | return ast.VarDeclaration(vars, srcpos=sr(p)) 179 | 180 | @pg.production('arg_decl : ') 181 | @pg.production('var_decl : ') 182 | def arg_decl_empty(state, p): 183 | return ast.VarDeclPartial(None, None, None, None, srcpos=(0, 0)) 184 | 185 | @pg.production('arg_decl : COMMA IDENTIFIER type_decl arg_decl') 186 | @pg.production('arg_decl : COMMA IDENTIFIER ASSIGN constant_val type_decl ' 187 | 'arg_decl') 188 | @pg.production('var_decl : COMMA IDENTIFIER type_decl var_decl') 189 | @pg.production('var_decl : COMMA IDENTIFIER ASSIGN expression type_decl ' 190 | 'var_decl') 191 | def arg_decl_identifier(state, p): 192 | if len(p) == 4: 193 | return ast.VarDeclPartial(p[1].getstr(), p[2], None, p[3], 194 | srcpos=sr(p)) 195 | else: 196 | return ast.VarDeclPartial(p[1].getstr(), p[4], p[3], 197 | p[5], srcpos=sr(p)) 198 | 199 | @pg.production('type_decl : ') 200 | def type_decl_empty(state, p): 201 | return ast.NoTypeDecl(srcpos=(0, 0)) 202 | 203 | @pg.production('type_decl : COLON IDENTIFIER') 204 | def type_decl_non_empty(state, p): 205 | return ast.BaseTypeDecl(p[1].getstr(), srcpos=sr([p[1]])) 206 | 207 | @pg.production('statement : IDENTIFIER ASSIGN expression SEMICOLON') 208 | def statement_identifier_assign_expr(state, p): 209 | return ast.Assignment(p[0].getstr(), p[2], srcpos=sr(p)) 210 | 211 | @pg.production('statement : atom DOT IDENTIFIER ASSIGN expression SEMICOLON') 212 | def statement_setattr(state, p): 213 | return ast.Setattr(p[0], p[2].getstr(), p[4], srcpos=sr(p)) 214 | 215 | @pg.production('statement : atom LEFT_SQUARE_BRACKET expression RIGHT_SQUARE_BRACKET' 216 | ' ASSIGN expression SEMICOLON') 217 | def statement_setitem(state, p): 218 | return ast.Setitem(p[0], p[2], p[5], srcpos=sr(p)) 219 | 220 | @pg.production('statement : RETURN expression SEMICOLON') 221 | def statement_return(state, p): 222 | return ast.Return(p[1], srcpos=sr(p)) 223 | 224 | @pg.production('statement : WHILE expression LEFT_CURLY_BRACE function_body' 225 | ' RIGHT_CURLY_BRACE') 226 | def statement_while_loop(state, p): 227 | return ast.While(p[1], p[3].get_element_list(), srcpos=sr(p)) 228 | 229 | @pg.production('statement : FOR IDENTIFIER IN expression LEFT_CURLY_BRACE ' 230 | 'function_body RIGHT_CURLY_BRACE') 231 | def statement_for_loop(state, p): 232 | return ast.For(p[1].getstr(), p[3], p[5].get_element_list(), srcpos=sr(p)) 233 | 234 | @pg.production('statement : IF expression LEFT_CURLY_BRACE function_body' 235 | ' RIGHT_CURLY_BRACE optional_else_block') 236 | def statement_if_block(state, p): 237 | if p[5] is None: 238 | else_block = None 239 | else: 240 | else_block = p[5].get_element_list() 241 | return ast.If(p[1], p[3].get_element_list(), else_block, srcpos=sr([ 242 | p[0], p[1]])) 243 | 244 | @pg.production('optional_else_block : ') 245 | def optional_else_block_empty(state, p): 246 | return None 247 | 248 | @pg.production('optional_else_block : ELSE LEFT_CURLY_BRACE function_body ' 249 | 'RIGHT_CURLY_BRACE') 250 | def optional_else_block(state, p): 251 | return p[2] 252 | 253 | @pg.production('statement : RAISE expression SEMICOLON') 254 | def statement_raise(state, p): 255 | return ast.Raise(p[1], srcpos=sr(p)) 256 | 257 | @pg.production('statement : TRY LEFT_CURLY_BRACE function_body ' 258 | 'RIGHT_CURLY_BRACE except_finally_clauses') 259 | def statement_try_except(state, p): 260 | if p[4] is None: 261 | errorhandler(state, p[0]) 262 | lst = p[4].gather_list() 263 | return ast.TryExcept(p[2].get_element_list(), lst, srcpos=sr([p[0], lst[-1]])) 264 | 265 | @pg.production('except_finally_clauses : ') 266 | def except_finally_clases_empty(state, p): 267 | return None 268 | 269 | @pg.production('except_finally_clauses : EXCEPT expression LEFT_CURLY_BRACE' 270 | ' function_body RIGHT_CURLY_BRACE except_finally_clauses') 271 | def except_finally_clauses_except(state, p): 272 | # We want the position information for the clause, not the list. 273 | return ast.ExceptClauseList([p[1]], None, 274 | p[3].get_element_list(), p[5], 275 | srcpos=sr(p[:-1])) 276 | 277 | @pg.production('except_finally_clauses : EXCEPT expression AS IDENTIFIER ' 278 | 'LEFT_CURLY_BRACE function_body RIGHT_CURLY_BRACE ' 279 | 'except_finally_clauses') 280 | def except_finally_clauses_except_as_identifier(state, p): 281 | # We want the position information for the clause, not the list. 282 | return ast.ExceptClauseList([p[1]], p[3].getstr(), 283 | p[5].get_element_list(), p[7], 284 | srcpos=sr(p[:-1])) 285 | 286 | @pg.production('except_finally_clauses : FINALLY LEFT_CURLY_BRACE ' 287 | 'function_body RIGHT_CURLY_BRACE') 288 | def except_finally_clauses_finally(state, p): 289 | return ast.FinallyClause(p[2].get_element_list(), False, srcpos=sr(p)) 290 | 291 | @pg.production('except_finally_clauses : ELSE LEFT_CURLY_BRACE ' 292 | 'function_body RIGHT_CURLY_BRACE') 293 | def except_finally_clause_else(state, p): 294 | return ast.FinallyClause(p[2].get_element_list(), True, srcpos=sr(p)) 295 | 296 | @pg.production('identifier_list : COMMA IDENTIFIER identifier_list') 297 | def identifier_list_arglist(state, p): 298 | return ast.IdentifierListPartial(p[1].getstr(), p[2]) 299 | 300 | @pg.production('identifier_list :') 301 | def rest_of_identifier_list_empty(state, p): 302 | return None 303 | 304 | @pg.production('dot_identifier_list : DOT IDENTIFIER dot_identifier_list') 305 | def dot_identifier_list_arglist(state, p): 306 | return ast.IdentifierListPartial(p[1].getstr(), p[2]) 307 | 308 | @pg.production('dot_identifier_list :') 309 | def rest_of_dot_identifier_list_empty(state, p): 310 | return None 311 | 312 | @pg.production('arglist : LEFT_PAREN RIGHT_PAREN') 313 | def arglist(state, p): 314 | return ast.ArgList([], srcpos=sr(p)) 315 | 316 | @pg.production('arglist : LEFT_PAREN IDENTIFIER type_decl arg_decl' 317 | ' RIGHT_PAREN') 318 | @pg.production('arglist : LEFT_PAREN IDENTIFIER ASSIGN constant_val ' 319 | 'type_decl arg_decl RIGHT_PAREN') 320 | def arglist_non_empty(state, p): 321 | if len(p) == 5: 322 | vars = ([ast.Var(p[1].getstr(), p[2], None, p[1].getsrcpos())] + 323 | p[3].get_vars()) 324 | else: 325 | vars = ([ast.Var(p[1].getstr(), p[4], p[3], p[1].getsrcpos())] + 326 | p[5].get_vars()) 327 | return ast.ArgList(vars, srcpos=sr(p)) 328 | 329 | @pg.production('constant_val : INTEGER') 330 | def constant_val_int(state, p): 331 | return ast.Number(int(p[0].getstr()), srcpos=sr(p)) 332 | 333 | @pg.production('constant_val : string') 334 | def constant_val_str(state, p): 335 | return p[0] 336 | 337 | @pg.production('expression : INTEGER') 338 | def expression_number(state, p): 339 | return ast.Number(int(p[0].getstr()), srcpos=sr(p)) 340 | 341 | @pg.production('expression : string') 342 | def expression_string(state, p): 343 | return p[0] 344 | 345 | @pg.production('string : ST_DQ_STRING stringcontent ST_ENDSTRING') 346 | @pg.production('string : ST_SQ_STRING stringcontent ST_ENDSTRING') 347 | @pg.production('string : ST_RAW_DQ_STRING rawstringcontent ST_ENDRAW') 348 | @pg.production('string : ST_RAW_SQ_STRING rawstringcontent ST_ENDRAW') 349 | def expression_string_expand(state, p): 350 | val = ''.join(p[1].get_strparts()) 351 | try: 352 | str_decode_utf_8(val, len(val), 'strict', final=True) 353 | except UnicodeDecodeError: 354 | raise errorhandler(state, p[1], msg="Unicode error") 355 | return ast.String(val, srcpos=sr(p)) 356 | 357 | @pg.production('expression : ST_INTERP_STRING interpstr ST_ENDSTRING') 358 | def expression_interpstring(state, p): 359 | strings = p[1].get_strings() 360 | return ast.InterpString(strings, p[1].get_exprs(), srcpos=sr(p)) 361 | 362 | @pg.production('expression : atom') 363 | def expression_atom(state, p): 364 | return p[0] 365 | 366 | @pg.production('expression : expression OR expression') 367 | def expression_or_expression(state, p): 368 | return ast.Or(p[0], p[2], srcpos=sr(p)) 369 | 370 | @pg.production('expression : expression AND expression') 371 | def expression_and_expression(state, p): 372 | return ast.And(p[0], p[2], srcpos=sr(p)) 373 | 374 | @pg.production('interpstr : stringcontent') 375 | def interpstr_start(state, p): 376 | val = ''.join(p[0].get_strparts()) 377 | str_decode_utf_8(val, len(val), 'strict', final=True) 378 | return ast.InterpStringContents([val], []) 379 | 380 | @pg.production('interpstr : interpstr ST_INTERP expression RIGHT_CURLY_BRACE stringcontent') 381 | def interpstr_part(state, p): 382 | val = ''.join(p[4].get_strparts()) 383 | str_decode_utf_8(val, len(val), 'strict', final=True) 384 | return ast.InterpStringContents( 385 | p[0].get_strings() + [val], p[0].get_exprs() + [p[2]]) 386 | 387 | @pg.production('stringcontent : ') 388 | def string_empty(state, p): 389 | return ast.StringContent([], srcpos=(0, 0)) 390 | 391 | @pg.production('stringcontent : stringcontent ESC_QUOTE') 392 | def string_esc_quote(state, p): 393 | return ast.StringContent(p[0].get_strparts() + [p[1].getstr()[1]], 394 | srcpos=sr(p)) 395 | 396 | @pg.production('stringcontent : stringcontent ESC_ESC') 397 | def string_esc_esc(state, p): 398 | return ast.StringContent(p[0].get_strparts() + ['\\'], 399 | srcpos=sr(p)) 400 | 401 | @pg.production('stringcontent : stringcontent ESC_SIMPLE') 402 | def string_esc_simple(state, p): 403 | part = { 404 | 'a': '\a', 405 | 'b': '\b', 406 | 'f': '\f', 407 | 'n': '\n', 408 | 'r': '\r', 409 | 't': '\t', 410 | 'v': '\v', 411 | '0': '\0', 412 | '$': '$', 413 | }[p[1].getstr()[1]] 414 | return ast.StringContent(p[0].get_strparts() + [part], srcpos=sr(p)) 415 | 416 | @pg.production('stringcontent : stringcontent ESC_HEX_8') 417 | @pg.production('stringcontent : stringcontent ESC_HEX_16') 418 | def string_esc_hex_fixed(state, p): 419 | s = p[1].getstr() 420 | return ast.StringContent(p[0].get_strparts() + [hex_to_utf8(state, p[0], 421 | s[2:])], srcpos=sr(p)) 422 | 423 | @pg.production('stringcontent : stringcontent ESC_HEX_ANY') 424 | def string_esc_hex_any(state, p): 425 | s = p[1].getstr() 426 | end = len(s) - 1 427 | assert end >= 0 428 | return ast.StringContent(p[0].get_strparts() + [hex_to_utf8(state, p[0], 429 | s[3:end])], srcpos=sr(p)) 430 | 431 | @pg.production('stringcontent : stringcontent ESC_UNRECOGNISED') 432 | def string_esc_unrecognised(state, p): 433 | return ast.StringContent(p[0].get_strparts() + [p[1].getstr()], 434 | srcpos=sr(p)) 435 | 436 | @pg.production('stringcontent : stringcontent CHAR') 437 | def string_char(state, p): 438 | return ast.StringContent(p[0].get_strparts() + [p[1].getstr()], 439 | srcpos=sr(p)) 440 | 441 | @pg.production('rawstringcontent : ') 442 | def rawstring_empty(state, p): 443 | return ast.StringContent([], srcpos=(0, 0)) 444 | 445 | @pg.production('rawstringcontent : rawstringcontent RAW_ESC') 446 | @pg.production('rawstringcontent : rawstringcontent RAW_CHAR') 447 | def rawstring_char(state, p): 448 | return ast.StringContent(p[0].get_strparts() + [p[1].getstr()], 449 | srcpos=sr(p)) 450 | 451 | @pg.production('atom : TRUE') 452 | def atom_true(state, p): 453 | return ast.TrueNode(srcpos=sr(p)) 454 | 455 | @pg.production('atom : NONE') 456 | def atom_none(state, p): 457 | return ast.NoneNode(srcpos=sr(p)) 458 | 459 | @pg.production('atom : IDENTIFIER') 460 | def atom_identifier(state, p): 461 | return ast.Identifier(p[0].getstr(), srcpos=sr(p)) 462 | 463 | @pg.production('atom : FALSE') 464 | def atom_false(state, p): 465 | return ast.FalseNode(srcpos=sr(p)) 466 | 467 | @pg.production('atom : atom LEFT_PAREN call_args_list ' 468 | 'RIGHT_PAREN') 469 | def atom_call(state, p): 470 | arglist = p[2].get_element_list() 471 | rawargs = [] 472 | namedargs = [] 473 | now_named = False 474 | for arg in arglist: 475 | if isinstance(arg, ast.NamedArg): 476 | now_named = True 477 | namedargs.append(arg) 478 | else: 479 | if now_named: 480 | errorhandler(state, p[2], 481 | msg="Named args before regular args") 482 | rawargs.append(arg) 483 | return ast.Call(p[0], rawargs[:], namedargs[:], 484 | srcpos=sr(p)) 485 | 486 | @pg.production('call_args_list :') 487 | def call_args_list_empty(state, p): 488 | return ast.ExpressionListPartial(None, None) 489 | 490 | @pg.production('call_args_list : expression ASSIGN expression rest_of_args') 491 | def call_args_list_named_rest(state, p): 492 | id = p[0] 493 | if not isinstance(id, ast.Identifier): 494 | raise errorhandler(state, p[0], 495 | msg="Named argument is not an identifier") 496 | return ast.ExpressionListPartial(ast.NamedArg(id.name, p[2]), p[3]) 497 | 498 | @pg.production('call_args_list : expression rest_of_args') 499 | def call_args_list_rest(state, p): 500 | return ast.ExpressionListPartial(p[0], p[1]) 501 | 502 | @pg.production('rest_of_args : COMMA expression ASSIGN expression' 503 | ' rest_of_args') 504 | def rest_of_args_named_arg(state, p): 505 | id = p[1] 506 | if not isinstance(id, ast.Identifier): 507 | raise errorhandler(state, p[1], 508 | msg="Named argument is not an identifier") 509 | return ast.ExpressionListPartial(ast.NamedArg(id.name, p[3], 510 | srcpos=sr([p[1], p[2], p[3]])), 511 | p[4]) 512 | 513 | @pg.production('rest_of_args : COMMA expression rest_of_args') 514 | def rest_of_args_arg(state, p): 515 | return ast.ExpressionListPartial(p[1], p[2]) 516 | 517 | @pg.production('rest_of_args :') 518 | def rest_of_args_empty(state, p): 519 | return ast.ExpressionListPartial(None, None) 520 | 521 | @pg.production('atom : LEFT_PAREN expression RIGHT_PAREN') 522 | def atom_paren_expression_paren(state, p): 523 | return p[1] 524 | 525 | @pg.production('atom : atom DOT IDENTIFIER') 526 | def atom_dot_identifier(state, p): 527 | return ast.Getattr(p[0], p[2].getstr(), srcpos=sr(p)) 528 | 529 | @pg.production('atom : LEFT_SQUARE_BRACKET expression_list RIGHT_SQUARE_BRACKET') 530 | def atom_list_literal(state, p): 531 | return ast.List(p[1].get_element_list(), srcpos=sr(p)) 532 | 533 | @pg.production('atom : LEFT_CURLY_BRACE dict_pair_list RIGHT_CURLY_BRACE') 534 | def atom_dict_literal(state, p): 535 | return ast.Dict(p[1].get_element_list(), srcpos=sr(p)) 536 | 537 | @pg.production('atom : atom LEFT_SQUARE_BRACKET expression RIGHT_SQUARE_BRACKET') 538 | def atom_getitem(state, p): 539 | return ast.Getitem(p[0], p[2], srcpos=sr(p)) 540 | 541 | @pg.production('expression : expression PLUS expression') 542 | @pg.production('expression : expression MINUS expression') 543 | @pg.production('expression : expression STAR expression') 544 | @pg.production('expression : expression TRUEDIV expression') 545 | @pg.production('expression : expression LT expression') 546 | @pg.production('expression : expression GT expression') 547 | @pg.production('expression : expression LE expression') 548 | @pg.production('expression : expression GE expression') 549 | @pg.production('expression : expression EQ expression') 550 | @pg.production('expression : expression IN expression') 551 | @pg.production('expression : expression NE expression') 552 | def expression_binop_expression(state, p): 553 | return ast.BinOp(p[1].getstr(), p[0], p[2], sr([p[1]]), srcpos=sr(p)) 554 | 555 | @pg.production('expression : expression NOT IN expression') 556 | def expression_not_in_expression(state, p): 557 | return ast.BinOp('not in', p[0], p[3], sr([p[1], p[2]]), srcpos=sr(p)) 558 | 559 | @pg.production('expression_list : ') 560 | def expression_list_empty(state, p): 561 | return ast.ExpressionListPartial(None, None) 562 | 563 | @pg.production('expression_list : expression expression_sublist') 564 | def expression_list_expression(state, p): 565 | return ast.ExpressionListPartial(p[0], p[1]) 566 | 567 | @pg.production('expression_sublist : ') 568 | @pg.production('expression_sublist : COMMA') 569 | def expression_sublist_empty(state, p): 570 | return ast.ExpressionListPartial(None, None) 571 | 572 | @pg.production('expression_sublist : COMMA expression expression_sublist') 573 | def expression_sublist_expression(state, p): 574 | return ast.ExpressionListPartial(p[1], p[2]) 575 | 576 | @pg.production('dict_pair_list : ') 577 | def dict_pair_list_empty(state, p): 578 | return ast.ExpressionListPartial(None, None) 579 | 580 | @pg.production('dict_pair_list : expression COLON expression dict_pair_sublist') 581 | def dict_pair_list_expression(state, p): 582 | return ast.ExpressionListPartial(p[0], 583 | ast.ExpressionListPartial(p[2], p[3])) 584 | 585 | @pg.production('dict_pair_sublist : ') 586 | @pg.production('dict_pair_sublist : COMMA') 587 | def dict_pair_sublist_empty(state, p): 588 | return ast.ExpressionListPartial(None, None) 589 | 590 | @pg.production('dict_pair_sublist : COMMA expression COLON expression dict_pair_sublist') 591 | def dict_pair_sublist_expression(state, p): 592 | return ast.ExpressionListPartial(p[1], 593 | ast.ExpressionListPartial(p[3], p[4])) 594 | 595 | res = pg.build() 596 | if res.lr_table.sr_conflicts: 597 | raise Exception("shift reduce conflicts") 598 | return res 599 | -------------------------------------------------------------------------------- /nolang/target.py: -------------------------------------------------------------------------------- 1 | 2 | def target(driver, args): 3 | driver.exe_name = 'nolang-c' 4 | config = driver.config 5 | 6 | from rpython.config.config import to_optparse, SUPPRESS_USAGE 7 | parser = to_optparse(config, parserkwargs={'usage': SUPPRESS_USAGE}) 8 | parser.parse_args(args) 9 | 10 | from nolang.main import main 11 | return main, None 12 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | ignore = F999,E501,E128,E124,E402,W503,E731,C901 3 | max-line-length = 100 4 | exclude = .git,venv/*,vendor/* 5 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fijal/quill/c66dc0b66726862f3d0cb065bfa96a2937eaaa44/tests/__init__.py -------------------------------------------------------------------------------- /tests/support.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from nolang.parser import get_parser, ParsingState, ParseError 4 | from nolang.lexer import get_lexer 5 | from nolang.bytecode import compile_bytecode 6 | from nolang.interpreter import Interpreter 7 | from nolang.compiler import compile_module 8 | from nolang.importer import Importer 9 | from nolang.objects.space import Space 10 | from nolang.builtins.defaults import default_builtins 11 | from nolang.builtins.spec import parameters 12 | 13 | 14 | def reformat_code(body): 15 | bodylines = body.split("\n") 16 | cut = 0 17 | while bodylines[cut].strip(" ") == '': 18 | cut += 1 19 | bodylines = bodylines[cut:] 20 | m = re.match(" *", bodylines[0]) 21 | lgt = len(m.group(0)) 22 | newlines = [] 23 | for i, line in enumerate(bodylines): 24 | if line.strip(" ") == "": 25 | continue 26 | if line[:lgt] != " " * lgt: 27 | raise Exception("bad formatting, line: %d\n%s" % (i, line)) 28 | newlines.append(" " * 4 + line[lgt:]) 29 | return "\n".join(newlines) 30 | 31 | 32 | def reformat_expr(code, extra_import=''): 33 | if extra_import: 34 | extra_import = extra_import + "\n\n" 35 | return extra_import + "def main () {\n" + reformat_code(code) + "\n}" 36 | 37 | 38 | @parameters(name="log") 39 | def magic_log(space, w_obj): 40 | space.log.append(w_obj) 41 | 42 | 43 | class BaseTest(object): 44 | def setup_class(self): 45 | self.parser = get_parser() 46 | self.lexer = get_lexer() 47 | self.space = Space() 48 | builtins, core_mods, not_builtins = default_builtins(self.space) 49 | builtins.append(magic_log) 50 | self.space.setup_builtins(builtins, core_mods, not_builtins) 51 | self.space.log = [] 52 | self.interpreter = Interpreter() 53 | self.space.setup(self.interpreter) 54 | 55 | def setup_method(self, meth): 56 | self.space.log = [] 57 | 58 | def compile(self, body): 59 | program = reformat_expr(body) 60 | ast = self.parse(program) 61 | imp = Importer(self.space) 62 | w_mod = compile_module(self.space, '', 'self.test', program, ast, 63 | imp) 64 | return compile_bytecode(ast.elements[0], program, w_mod) 65 | 66 | def parse(self, program): 67 | return self.parser.parse(self.lexer.lex('', program), 68 | ParsingState('', program)) 69 | 70 | def interpret_expr(self, code, extra_import=''): 71 | return self.interpret(reformat_expr(code, extra_import)) 72 | 73 | def interpret(self, code, args=None): 74 | source = reformat_code(code) 75 | ast = self.parse(source) 76 | imp = Importer(self.space) 77 | w_mod = compile_module(self.space, 'test', 'self.test', source, ast, 78 | imp) 79 | w_mod.setup(self.space) 80 | if args is not None: 81 | args_w = [self.space.newlist([self.space.newtext(x) for x in args])] 82 | else: 83 | args_w = [] 84 | return self.space.call_method(w_mod, 'main', args_w, None) 85 | 86 | def assert_expr_parse_error(self, code): 87 | try: 88 | value = self.interpret_expr(code) 89 | except ParseError: 90 | pass 91 | else: 92 | raise Exception("Incorrectly parsed %r as %r." % (code, value)) 93 | -------------------------------------------------------------------------------- /tests/test_buffer.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from support import BaseTest 4 | 5 | 6 | class TestBuffer(BaseTest): 7 | def test_buffer_from_utf8(self): 8 | w_res = self.interpret_expr('return buffer_from_utf8("foo");') 9 | assert self.space.buffer_w(w_res) == ['f', 'o', 'o'] 10 | 11 | def test_empty_buffer(self): 12 | w_res = self.interpret_expr("return buffer(3);") 13 | assert self.space.buffer_w(w_res) == ['\x00', '\x00', '\x00'] 14 | -------------------------------------------------------------------------------- /tests/test_bytecode.py: -------------------------------------------------------------------------------- 1 | class TestCompiler(object): 2 | pass 3 | -------------------------------------------------------------------------------- /tests/test_bytecode_compiler.py: -------------------------------------------------------------------------------- 1 | import re 2 | from support import BaseTest 3 | 4 | 5 | class TestBytecodeCompiler(BaseTest): 6 | 7 | def assert_equals(self, bytecode, expected): 8 | def reformat(lines): 9 | # find first non empty 10 | for line in lines: 11 | if line.strip(" ") != "": 12 | break 13 | m = re.match(" +", line) 14 | skip = len(m.group(0)) 15 | return [line[(skip - 2):] for line in lines if line.strip(" ") != ""] 16 | 17 | lines = bytecode.repr(False).splitlines() 18 | exp_lines = reformat(expected.splitlines()) 19 | assert lines == exp_lines 20 | 21 | def test_bytecode_simple(self): 22 | body = """ 23 | let x 24 | x = 3 25 | """ 26 | self.assert_equals(self.compile(body), """ 27 | LOAD_CONSTANT 0 28 | STORE 0 29 | LOAD_NONE 30 | RETURN 31 | """) 32 | 33 | def test_bytecode_bit_more_complex(self): 34 | body = """ 35 | let x 36 | x = 3 37 | x = x + 1 38 | """ 39 | bc = self.compile(body) 40 | self.assert_equals(bc, """ 41 | LOAD_CONSTANT 0 42 | STORE 0 43 | LOAD_VARIABLE 0 44 | LOAD_CONSTANT 1 45 | ADD 46 | STORE 0 47 | LOAD_NONE 48 | RETURN 49 | """) 50 | assert bc.stack_depth == 2 51 | 52 | def test_loop(self): 53 | body = """ 54 | let i 55 | i = 0 56 | 57 | while i < 10 { 58 | i = i + 1 59 | } 60 | """ 61 | bc = self.compile(body) 62 | self.assert_equals(bc, """ 63 | LOAD_CONSTANT 0 64 | STORE 0 65 | LOAD_VARIABLE 0 66 | LOAD_CONSTANT 1 67 | LT 68 | JUMP_IF_FALSE 29 69 | LOAD_VARIABLE 0 70 | LOAD_CONSTANT 2 71 | ADD 72 | STORE 0 73 | JUMP_ABSOLUTE 6 74 | LOAD_NONE 75 | RETURN 76 | """) 77 | 78 | def test_compile_exc_handling(self): 79 | bc = self.compile(""" 80 | try { 81 | 82 | } except Exception { 83 | 84 | } 85 | """) 86 | self.assert_equals(bc, """ 87 | PUSH_RESUME_STACK 7 88 | POP_RESUME_STACK 89 | JUMP_ABSOLUTE 17 90 | LOAD_GLOBAL 5 91 | COMPARE_EXCEPTION 17 92 | CLEAR_CURRENT_EXC 93 | JUMP_ABSOLUTE 17 94 | RERAISE 95 | LOAD_NONE 96 | RETURN 97 | """) 98 | 99 | def test_compile_try_just_finally(self): 100 | bc = self.compile(''' 101 | try { 102 | } except Exception { 103 | } finally { 104 | return 3 105 | } 106 | ''') 107 | self.assert_equals(bc, """ 108 | PUSH_RESUME_STACK 7 109 | POP_RESUME_STACK 110 | JUMP_ABSOLUTE 17 111 | LOAD_GLOBAL 5 112 | COMPARE_EXCEPTION 17 113 | CLEAR_CURRENT_EXC 114 | JUMP_ABSOLUTE 17 115 | LOAD_CONSTANT 0 116 | RETURN 117 | RERAISE 118 | LOAD_NONE 119 | RETURN 120 | """) 121 | 122 | def test_compile_try_finally(self): 123 | bc = self.compile(''' 124 | try { 125 | } finally { 126 | return 3 127 | } 128 | ''') 129 | self.assert_equals(bc, """ 130 | PUSH_RESUME_STACK 7 131 | POP_RESUME_STACK 132 | JUMP_ABSOLUTE 7 133 | LOAD_CONSTANT 0 134 | RETURN 135 | RERAISE 136 | LOAD_NONE 137 | RETURN 138 | """) 139 | 140 | def test_for_loop(self): 141 | bc = self.compile(''' 142 | for i in 1 { 1; } 143 | ''') 144 | self.assert_equals(bc, """ 145 | LOAD_CONSTANT 0 146 | CREATE_ITER 147 | ITER_NEXT 148 | JUMP_IF_EMPTY 18 149 | STORE 0 150 | LOAD_CONSTANT 1 151 | DISCARD 152 | JUMP_ABSOLUTE 4 153 | DISCARD 154 | DISCARD 155 | LOAD_NONE 156 | RETURN 157 | """) 158 | -------------------------------------------------------------------------------- /tests/test_classes.py: -------------------------------------------------------------------------------- 1 | 2 | from support import BaseTest 3 | from nolang.error import AppError 4 | 5 | 6 | class TestClasses(BaseTest): 7 | def test_simple_class(self): 8 | w_res = self.interpret(''' 9 | class X { 10 | def __init__(self) { 11 | self.x = 13 12 | } 13 | } 14 | 15 | def main() { 16 | let x 17 | x = X() 18 | return x.x 19 | } 20 | ''') 21 | assert self.space.int_w(w_res) == 13 22 | 23 | def test_method_call(self): 24 | w_res = self.interpret(''' 25 | class X { 26 | def method(self) { 27 | return 3 28 | } 29 | } 30 | 31 | def main() { 32 | let x 33 | x = X() 34 | return x.method() + x.method() 35 | } 36 | ''') 37 | assert self.space.int_w(w_res) == 6 38 | 39 | def test_inheritance_basic(self): 40 | w_res = self.interpret(''' 41 | class X { 42 | def method(self) { 43 | return 3 44 | } 45 | } 46 | class Y(X) { 47 | } 48 | def main() { 49 | let x 50 | x = Y() 51 | return x.method() 52 | } 53 | ''') 54 | assert self.space.int_w(w_res) == 3 55 | 56 | def test_inheritance(self): 57 | w_res = self.interpret(''' 58 | class X { 59 | def method(self) { 60 | return 1 61 | } 62 | } 63 | 64 | class Y(X) { 65 | def method(self) { 66 | return 2 67 | } 68 | } 69 | 70 | def main() { 71 | let x, y 72 | x = X() 73 | y = Y() 74 | return x.method() * 10 + y.method() 75 | } 76 | ''') 77 | assert self.space.int_w(w_res) == 12 78 | 79 | def test_class_attributes(self): 80 | w_res = self.interpret(""" 81 | class X { 82 | let attr; 83 | 84 | def __init__(self, x) { 85 | self.attr = x; 86 | } 87 | } 88 | 89 | def main() { 90 | let x; 91 | x = X(13); 92 | return x.attr; 93 | } 94 | """) 95 | assert self.space.int_w(w_res) == 13 96 | 97 | def test_class_attributes_illegal(self): 98 | try: 99 | self.interpret(""" 100 | class X { 101 | let attr; 102 | 103 | def __init__(self, x) { 104 | self.attr2 = x; 105 | } 106 | } 107 | 108 | def main() { 109 | let x; 110 | x = X(13); 111 | return x.attr2; 112 | } 113 | """) 114 | except AppError as e: 115 | if not e.match(self.space, self.space.w_attrerror): 116 | raise 117 | else: 118 | raise Exception("did not raise") 119 | -------------------------------------------------------------------------------- /tests/test_compiler.py: -------------------------------------------------------------------------------- 1 | 2 | from support import BaseTest, reformat_code 3 | from nolang.compiler import compile_module 4 | from nolang.module import W_Module 5 | from nolang.importer import Importer 6 | 7 | 8 | class TestCompiler(BaseTest): 9 | def test_compile_module(self): 10 | code = reformat_code(''' 11 | def foo() { 12 | return 3; 13 | } 14 | ''') 15 | imp = Importer(self.space) 16 | w_mod = compile_module(self.space, 'test', 'self.test', code, 17 | self.parse(code), imp) 18 | assert isinstance(w_mod, W_Module) 19 | assert w_mod.name2index['foo'] == len(self.space.builtins_w) 20 | -------------------------------------------------------------------------------- /tests/test_core.py: -------------------------------------------------------------------------------- 1 | 2 | from support import BaseTest 3 | 4 | 5 | class TestCore(BaseTest): 6 | def test_core_reflect_getframe(self): 7 | w_res = self.interpret(''' 8 | import core.reflect 9 | 10 | def main() { 11 | let frame 12 | frame = reflect.get_current_frame() 13 | return frame.filename 14 | } 15 | ''') 16 | assert self.space.utf8_w(w_res) == 'test' 17 | -------------------------------------------------------------------------------- /tests/test_dict.py: -------------------------------------------------------------------------------- 1 | from nolang.error import AppError 2 | from support import BaseTest 3 | 4 | 5 | class TestDict(BaseTest): 6 | def test_empty_dict(self): 7 | w_res = self.interpret_expr('return {};') 8 | assert self.space.type(w_res) is self.space.builtin_dict['Dict'] 9 | assert self.space.len(w_res) == 0 10 | 11 | def test_short_dict(self): 12 | w_res = self.interpret_expr('return {"a": "foo", 1: "bar"};') 13 | space = self.space 14 | assert space.len(w_res) == 2 15 | assert space.utf8_w(space.getitem(w_res, space.newtext('a'))) == "foo" 16 | assert space.utf8_w(space.getitem(w_res, space.newint(1))) == "bar" 17 | 18 | def test_trailing_comma(self): 19 | w_res = self.interpret_expr('return {"a": "foo", 1: "bar",};') 20 | space = self.space 21 | assert space.len(w_res) == 2 22 | assert space.utf8_w(space.getitem(w_res, space.newtext('a'))) == "foo" 23 | assert space.utf8_w(space.getitem(w_res, space.newint(1))) == "bar" 24 | 25 | def test_multiple_trailing_commas(self): 26 | self.assert_expr_parse_error('return {,};') 27 | self.assert_expr_parse_error('return {"a": "foo", 1: "bar",,};') 28 | self.assert_expr_parse_error('return {"a": "foo",, 1: "bar"};') 29 | self.assert_expr_parse_error('return {, "a": "foo", 1: "bar"};') 30 | 31 | def test_constructor(self): 32 | w_res = self.interpret_expr('return Dict({});') 33 | assert self.space.type(w_res) is self.space.builtin_dict['Dict'] 34 | assert self.space.len(w_res) == 0 35 | w_res = self.interpret_expr('return Dict({"a": "foo", 1: "bar"});') 36 | space = self.space 37 | assert space.len(w_res) == 2 38 | assert space.utf8_w(space.getitem(w_res, space.newtext('a'))) == "foo" 39 | assert space.utf8_w(space.getitem(w_res, space.newint(1))) == "bar" 40 | 41 | def test_constructor_copies(self): 42 | w_res = self.interpret_expr(''' 43 | let x, y; 44 | x = {0: "a", 1: "b"}; 45 | y = Dict(x); 46 | y[2] = "c"; 47 | return [len(x), len(y)]; 48 | ''') 49 | [w_xl, w_yl] = self.space.listview(w_res) 50 | assert self.space.int_w(w_xl) == 2 51 | assert self.space.int_w(w_yl) == 3 52 | 53 | def test_len(self): 54 | w_res = self.interpret_expr('return len({});') 55 | assert self.space.int_w(w_res) == 0 56 | w_res = self.interpret_expr('return len({"a": "foo", 1: "bar"});') 57 | assert self.space.int_w(w_res) == 2 58 | 59 | def test_get(self): 60 | w_res = self.interpret_expr('return {"a": "foo"}.get("a", "dflt");') 61 | assert self.space.utf8_w(w_res) == "foo" 62 | w_res = self.interpret_expr('return {"a": "foo"}.get("b", "dflt");') 63 | assert self.space.utf8_w(w_res) == "dflt" 64 | w_res = self.interpret_expr('return {"a": "foo"}.get("b");') 65 | assert self.space.is_none(w_res) 66 | 67 | def test_getitem(self): 68 | w_res = self.interpret_expr(''' 69 | return {"a": "foo", 1: "bar"}[1]; 70 | ''') 71 | assert self.space.utf8_w(w_res) == "bar" 72 | 73 | def test_setitem(self): 74 | w_res = self.interpret_expr(''' 75 | let x; 76 | x = {"a": "foo", 1: "bar"}; 77 | x["c"] = "baz"; 78 | return x; 79 | ''') 80 | space = self.space 81 | assert self.space.len(w_res) == 3 82 | assert space.utf8_w(space.getitem(w_res, space.newtext("c"))) == "baz" 83 | 84 | def test_in(self): 85 | w_res = self.interpret_expr('return "a" in {"a": 0, "b": 1};') 86 | assert w_res is self.space.w_True 87 | w_res = self.interpret_expr('return "b" in {"a": 0, "b": 1};') 88 | assert w_res is self.space.w_True 89 | w_res = self.interpret_expr('return "c" in {"a": 0, "b": 1};') 90 | assert w_res is self.space.w_False 91 | 92 | def test_not_in(self): 93 | w_res = self.interpret_expr('return "c" not in {"a": 0, "b": 1};') 94 | assert w_res is self.space.w_True 95 | w_res = self.interpret_expr('return "a" not in {"a": 0, "b": 1};') 96 | assert w_res is self.space.w_False 97 | 98 | def test_getitem_missing(self): 99 | try: 100 | self.interpret_expr('return {"foo": "bar"}["baz"];') 101 | except AppError as ae: 102 | assert ae.match(self.space, self.space.w_keyerror) 103 | assert ae.w_exception.message == 'baz' 104 | else: 105 | raise Exception("Applevel KeyError not raised.") 106 | 107 | def test_execution_order(self): 108 | w_res = self.interpret(''' 109 | def check(l, x) { 110 | l.append(x); 111 | return x; 112 | } 113 | 114 | def main() { 115 | let l; 116 | l = []; 117 | {check(l, 0): check(l, 1), check(l, 2): check(l, 3)}; 118 | return l; 119 | } 120 | ''') 121 | w_ls = self.space.listview(w_res) 122 | assert [self.space.int_w(w) for w in w_ls] == [0, 1, 2, 3] 123 | 124 | def test_merge(self): 125 | w_res = self.interpret_expr(''' 126 | let a, b, ab; 127 | a = {"a": "foo", 1: "bar"}; 128 | b = {"b": "baz", 1: "rab"}; 129 | ab = a.merge(b); 130 | return [a, b, ab]; 131 | ''') 132 | space = self.space 133 | [w_a, w_b, w_ab] = space.listview(w_res) 134 | # a and b are unmodified 135 | assert space.len(w_a) == 2 136 | assert space.utf8_w(space.getitem(w_a, space.newtext("a"))) == "foo" 137 | assert space.utf8_w(space.getitem(w_a, space.newint(1))) == "bar" 138 | assert space.len(w_b) == 2 139 | assert space.utf8_w(space.getitem(w_b, space.newtext("b"))) == "baz" 140 | assert space.utf8_w(space.getitem(w_b, space.newint(1))) == "rab" 141 | # ab is the merged output 142 | assert space.len(w_ab) == 3 143 | assert space.utf8_w(space.getitem(w_ab, space.newtext("a"))) == "foo" 144 | assert space.utf8_w(space.getitem(w_ab, space.newtext("b"))) == "baz" 145 | assert space.utf8_w(space.getitem(w_ab, space.newint(1))) == "rab" 146 | 147 | def test_pop(self): 148 | w_res = self.interpret_expr(''' 149 | let a; 150 | a = {"a": "foo", 1: "bar"}; 151 | return [a.pop(1), a]; 152 | ''') 153 | space = self.space 154 | [w_pval, w_a] = self.space.listview(w_res) 155 | assert space.utf8_w(w_pval) == "bar" 156 | assert space.len(w_a) == 1 157 | assert space.utf8_w(space.getitem(w_a, space.newtext("a"))) == "foo" 158 | 159 | def test_pop_missing(self): 160 | try: 161 | self.interpret_expr('{"a": "bar"}.pop("b");') 162 | except AppError as ae: 163 | assert ae.match(self.space, self.space.w_keyerror) 164 | assert ae.w_exception.message == 'b' 165 | else: 166 | raise Exception("Applevel KeyError not raised.") 167 | 168 | def test_keys(self): 169 | w_res = self.interpret_expr('return {"a": "foo", 1: "bar"}.keys();') 170 | [w_a, w_1] = self.space.listview(w_res) 171 | assert [self.space.utf8_w(w_a), self.space.int_w(w_1)] == ["a", 1] 172 | 173 | def test_values(self): 174 | w_res = self.interpret_expr('return {"a": "foo", 1: "bar"}.values();') 175 | values_w = self.space.listview(w_res) 176 | assert [self.space.utf8_w(w) for w in values_w] == ["foo", "bar"] 177 | -------------------------------------------------------------------------------- /tests/test_exceptions.py: -------------------------------------------------------------------------------- 1 | import py 2 | from support import BaseTest 3 | 4 | 5 | class TestExceptions(BaseTest): 6 | def test_exc_initialize(self): 7 | w_res = self.interpret_expr('return Exception("ekhe");') 8 | assert self.space.utf8_w(self.space.getattr(w_res, 'message')) == 'ekhe' 9 | 10 | def test_exc_raise(self): 11 | w_res = self.interpret_expr(''' 12 | try { 13 | raise Exception("foo"); 14 | } except Exception { 15 | return 13; 16 | } 17 | ''') 18 | assert self.space.int_w(w_res) == 13 19 | 20 | def test_exc_function_call(self): 21 | w_res = self.interpret(''' 22 | class X(Exception) { 23 | 24 | } 25 | def foo() { 26 | raise X("message 2"); 27 | } 28 | def main() { 29 | try { 30 | foo(); 31 | } except Exception as e{ 32 | return e.message; 33 | } 34 | } 35 | ''') 36 | assert self.space.utf8_w(w_res) == "message 2" 37 | 38 | def test_nested_exc_block(self): 39 | w_res = self.interpret(''' 40 | class X(Exception) { 41 | 42 | } 43 | 44 | def main() { 45 | try { 46 | try { 47 | raise Exception("foo"); 48 | } except X { 49 | return 18; 50 | } 51 | } except Exception { 52 | return 15; 53 | } 54 | } 55 | ''') 56 | assert self.space.int_w(w_res) == 15 57 | 58 | def test_exc_double_except(self): 59 | w_res = self.interpret(''' 60 | class A(Exception) { 61 | } 62 | class B(Exception) { 63 | } 64 | 65 | def main() { 66 | try { 67 | raise Exception("foo"); 68 | } except A { 69 | return 1; 70 | } except B { 71 | return 2; 72 | } except Exception { 73 | return 13; 74 | } 75 | } 76 | ''') 77 | assert self.space.int_w(w_res) == 13 78 | 79 | def test_exc_raise_catch_as_e(self): 80 | w_res = self.interpret_expr(''' 81 | try { 82 | raise Exception("foo"); 83 | } except Exception as e { 84 | return e.message; 85 | } 86 | ''') 87 | assert self.space.utf8_w(w_res) == "foo" 88 | 89 | def test_subclass_with_custom_init(self): 90 | py.test.skip("showcase the problem") 91 | w_res = self.interpret(''' 92 | class X(Exception) { 93 | def __init__(self, a, b, c) { 94 | self.a = a; 95 | self.b = b; 96 | self.c = c; 97 | } 98 | } 99 | 100 | def main() { 101 | try { 102 | raise X("foo", "bar", "baz"); 103 | } except X as e { 104 | return e.c; 105 | } 106 | } 107 | ''') 108 | assert self.space.utf8_w(w_res) == "baz" 109 | 110 | def test_exc_type(self): 111 | w_res = self.interpret_expr('return Exception("foo");') 112 | assert self.space.type(w_res) is self.space.w_exception 113 | assert self.space.type(w_res).w_parent is None 114 | 115 | def test_indexerror_type(self): 116 | w_res = self.interpret_expr('return IndexError("foo");') 117 | assert self.space.type(w_res) is self.space.w_indexerror 118 | assert self.space.type(w_res).w_parent is self.space.w_exception 119 | 120 | def test_exc_else_clause(self): 121 | code = ''' 122 | def main(argv) { 123 | try { 124 | if (argv[0] == "foo") { 125 | raise Exception("bar") 126 | } 127 | } except Exception { 128 | return 11 129 | } else { 130 | return 13 131 | } 132 | } 133 | ''' 134 | w_res = self.interpret(code, ["bar"]) 135 | assert self.space.int_w(w_res) == 13 136 | w_res = self.interpret(code, ["foo"]) 137 | assert self.space.int_w(w_res) == 11 138 | 139 | def test_nested_exception_handling(self): 140 | self.interpret(''' 141 | def main() { 142 | try { 143 | } except Exception { 144 | try { 145 | 146 | } except Exception { 147 | 148 | } 149 | } 150 | } 151 | ''') 152 | # assert did not explode in compile() 153 | -------------------------------------------------------------------------------- /tests/test_freeze.py: -------------------------------------------------------------------------------- 1 | 2 | from support import BaseTest 3 | 4 | 5 | class TestFreeze(BaseTest): 6 | def interpret_expr(self, code): 7 | return BaseTest.interpret_expr(self, code, 'import core.freezing{freeze,thaw,is_frozen}') 8 | 9 | def test_freeze_already_frozen(self): 10 | assert self.space.is_true(self.interpret_expr('return is_frozen(none)')) 11 | assert self.space.is_true(self.interpret_expr('return is_frozen("foo")')) 12 | self.interpret_expr('freeze(none)') 13 | self.interpret_expr('freeze("foo")') 14 | # assert did not explode 15 | 16 | def test_cant_freeze_stringbuilder(self): 17 | w_res = self.interpret(''' 18 | import core.freezing.freeze 19 | import core.text.StringBuilder 20 | 21 | def main() { 22 | try { 23 | freeze(StringBuilder()) 24 | } except FreezeError { 25 | return 1 26 | } 27 | return 0 28 | } 29 | ''') 30 | assert self.space.int_w(w_res) == 1 31 | -------------------------------------------------------------------------------- /tests/test_functions.py: -------------------------------------------------------------------------------- 1 | 2 | from nolang.error import AppError 3 | 4 | from support import BaseTest 5 | 6 | 7 | class TestFunctions(BaseTest): 8 | def assert_argerror(self, code, msg=None): 9 | try: 10 | self.interpret(code) 11 | except AppError as e: 12 | if e.match(self.space, self.space.w_argerror): 13 | if msg is not None: 14 | assert e.w_exception.message == msg 15 | else: 16 | raise 17 | else: 18 | raise Exception("did not raise") 19 | 20 | def test_simple_function(self): 21 | w_res = self.interpret(''' 22 | def foo() { 23 | return 13 24 | } 25 | 26 | def main() { 27 | return foo() 28 | } 29 | ''') 30 | assert self.space.int_w(w_res) == 13 31 | 32 | def test_simple_args(self): 33 | w_res = self.interpret(''' 34 | def foo(a, b) { 35 | return a + b 36 | } 37 | 38 | def main() { 39 | return foo(10, 3) 40 | } 41 | ''') 42 | assert self.space.int_w(w_res) == 13 43 | 44 | def test_named_args(self): 45 | w_res = self.interpret(''' 46 | def foo(a, b) { 47 | return a * 10 + b 48 | } 49 | 50 | def main() { 51 | return foo(a=2, b=3) 52 | } 53 | ''') 54 | assert self.space.int_w(w_res) == 23 55 | 56 | w_res = self.interpret(''' 57 | def foo(a, b) { 58 | return a * 10 + b 59 | } 60 | 61 | def main() { 62 | return foo(b=3, a=2) 63 | } 64 | ''') 65 | assert self.space.int_w(w_res) == 23 66 | 67 | def test_too_few_args(self): 68 | self.assert_argerror(''' 69 | def foo(a, b) { 70 | return a * 10 + b 71 | } 72 | 73 | def main() { 74 | return foo(1) 75 | } 76 | ''', "Function foo got 1 arguments, expected 2") 77 | 78 | def test_too_many_args(self): 79 | self.assert_argerror(''' 80 | def foo(a, b) { 81 | return a * 10 + b 82 | } 83 | 84 | def main() { 85 | return foo(1, 2, 3) 86 | } 87 | ''', "Function foo got 3 arguments, expected 2") 88 | self.assert_argerror(''' 89 | def foo(a, b) { 90 | return a * 10 + b 91 | } 92 | 93 | def main() { 94 | return foo(1, b=2, c=3) 95 | } 96 | ''', "Function foo got 3 arguments, expected 2") 97 | 98 | def test_unexpected_keyword(self): 99 | self.assert_argerror(''' 100 | def foo(a, b) { 101 | return a * 10 + b 102 | } 103 | 104 | def main() { 105 | return foo(1, c=2) 106 | } 107 | ''', "Function foo got unexpected keyword argument 'c'") 108 | 109 | def test_duplicate_args(self): 110 | self.assert_argerror(''' 111 | def foo(a, b) { 112 | return a * 10 + b 113 | } 114 | 115 | def main() { 116 | return foo(1, a=2) 117 | } 118 | ''', "Function foo got multiple values for argument 'a'") 119 | self.assert_argerror(''' 120 | def foo(a, b) { 121 | return a * 10 + b 122 | } 123 | 124 | def main() { 125 | return foo(b=1, b=2) 126 | } 127 | ''', "Function foo got multiple values for argument 'b'") 128 | 129 | def test_default_args(self): 130 | w_res = self.interpret(''' 131 | def foo(a, b=1, c="foo") { 132 | return a * 10 + b + len(c) 133 | } 134 | 135 | def main() { 136 | return foo(2, 3, c="x") * 1000 + foo(2) 137 | } 138 | ''') 139 | assert self.space.int_w(w_res) == 24000 + 24 140 | 141 | w_res = self.interpret(''' 142 | def foo(a, b=1) { 143 | return a * 10 + b 144 | } 145 | 146 | def main() { 147 | return foo(2) 148 | } 149 | ''') 150 | assert self.space.int_w(w_res) == 21 151 | 152 | w_res = self.interpret(''' 153 | def foo(a=1, b=2) { 154 | return a * 100 + b 155 | } 156 | 157 | def main() { 158 | return foo() * 100 + foo(3, 4) 159 | } 160 | ''') 161 | assert self.space.int_w(w_res) == 10504 162 | -------------------------------------------------------------------------------- /tests/test_import.py: -------------------------------------------------------------------------------- 1 | from support import BaseTest, reformat_code 2 | from nolang.main import run_code 3 | 4 | 5 | class TestImport(BaseTest): 6 | def test_basic_import_self(self, tmpdir): 7 | main_file = tmpdir.join('main.q') 8 | main_file.write(reformat_code(''' 9 | import self.foo.bar 10 | import self.foo 11 | 12 | def main() { 13 | print(bar(3) + foo.bar(13)) 14 | } 15 | ''')) 16 | 17 | foo_file = tmpdir.join('foo.q') 18 | foo_file.write(reformat_code(''' 19 | def bar(i) { 20 | return i + 3 21 | } 22 | ''')) 23 | run_code(str(main_file)) 24 | -------------------------------------------------------------------------------- /tests/test_interpreter.py: -------------------------------------------------------------------------------- 1 | from support import BaseTest 2 | 3 | 4 | class TestInterpreterBasic(BaseTest): 5 | def test_interpret(self): 6 | w_res = self.interpret_expr(''' 7 | let x; 8 | x = 1; 9 | ''') 10 | assert w_res is self.space.w_None 11 | 12 | def test_var_assign(self): 13 | w_res = self.interpret_expr(''' 14 | let x; 15 | x = 3; 16 | return x; 17 | ''') 18 | assert self.space.int_w(w_res) == 3 19 | 20 | def test_while_loop(self): 21 | w_r = self.interpret_expr(''' 22 | let i, s; 23 | i = 0; 24 | s = 0; 25 | while i < 10 { 26 | i = i + 1; 27 | s = s + i; 28 | } 29 | return s; 30 | ''') 31 | assert self.space.int_w(w_r) == 55 32 | 33 | def test_simple_if(self): 34 | w_r = self.interpret_expr(''' 35 | if 0 < 3 { 36 | return 3; 37 | } 38 | ''') 39 | assert self.space.int_w(w_r) == 3 40 | 41 | def test_logical_or_and(self): 42 | w_r = self.interpret_expr(''' 43 | return 0 or 1; 44 | ''') 45 | assert self.space.int_w(w_r) == 1 46 | w_r = self.interpret_expr(''' 47 | let x; 48 | return 15 or x; 49 | ''') 50 | # unitialized x ignored (for now) 51 | assert self.space.int_w(w_r) == 15 52 | w_r = self.interpret_expr(''' 53 | return 1 and 2; 54 | ''') 55 | assert self.space.int_w(w_r) == 2 56 | w_r = self.interpret_expr(''' 57 | let x; 58 | return 0 and x; 59 | ''') 60 | assert self.space.int_w(w_r) == 0 61 | w_r = self.interpret_expr(''' 62 | return 1 and true; 63 | ''') 64 | assert self.space.w_True is w_r 65 | 66 | def test_mul_div(self): 67 | assert self.space.int_w(self.interpret_expr('return 13 // 2;')) == 6 68 | assert self.space.int_w(self.interpret_expr('return 2 * 6;')) == 12 69 | 70 | def test_comparisons(self): 71 | assert self.interpret_expr('return 1 == 1;') is self.space.w_True 72 | assert self.interpret_expr('return 1 == 2;') is self.space.w_False 73 | assert self.interpret_expr('return 2 == 1;') is self.space.w_False 74 | 75 | assert self.interpret_expr('return 1 != 1;') is self.space.w_False 76 | assert self.interpret_expr('return 1 != 2;') is self.space.w_True 77 | assert self.interpret_expr('return 2 != 1;') is self.space.w_True 78 | 79 | assert self.interpret_expr('return 1 < 1;') is self.space.w_False 80 | assert self.interpret_expr('return 1 < 2;') is self.space.w_True 81 | assert self.interpret_expr('return 2 < 1;') is self.space.w_False 82 | 83 | assert self.interpret_expr('return 1 > 1;') is self.space.w_False 84 | assert self.interpret_expr('return 1 > 2;') is self.space.w_False 85 | assert self.interpret_expr('return 2 > 1;') is self.space.w_True 86 | 87 | assert self.interpret_expr('return 1 <= 1;') is self.space.w_True 88 | assert self.interpret_expr('return 1 <= 2;') is self.space.w_True 89 | assert self.interpret_expr('return 2 <= 1;') is self.space.w_False 90 | 91 | assert self.interpret_expr('return 1 >= 1;') is self.space.w_True 92 | assert self.interpret_expr('return 1 >= 2;') is self.space.w_False 93 | assert self.interpret_expr('return 2 >= 1;') is self.space.w_True 94 | 95 | def test_operator_precedence(self): 96 | assert self.space.int_w(self.interpret_expr('return 2 + 2 * 2;')) == 6 97 | assert self.space.int_w(self.interpret_expr('return 2 * 2 + 2;')) == 6 98 | assert self.space.int_w(self.interpret_expr('return 2 * 2 and 2;')) == 2 99 | assert self.space.int_w(self.interpret_expr('return 2 and 2 * 2;')) == 4 100 | assert self.space.int_w(self.interpret_expr('return (2 + 2) * 2;')) == 8 101 | assert self.space.int_w(self.interpret_expr('return 2 * (2 + 2);')) == 8 102 | 103 | def test_longer_blocks(self): 104 | code = '\n'.join(['if 0 < 3 {'] + [' 1;'] * 300 + ['}']) 105 | self.interpret_expr(code) # assert did not crash 106 | 107 | 108 | class TestInterpreter(BaseTest): 109 | def test_basic(self): 110 | w_res = self.interpret(''' 111 | def main() { 112 | return 3; 113 | } 114 | ''') 115 | assert self.space.int_w(w_res) == 3 116 | 117 | def test_function_declaration_and_call(self): 118 | w_res = self.interpret(''' 119 | def foo() { 120 | return 3; 121 | } 122 | 123 | def main() { 124 | return foo() + 1; 125 | } 126 | ''') 127 | assert self.space.int_w(w_res) == 4 128 | 129 | def test_function_call_args(self): 130 | w_res = self.interpret(''' 131 | def foo(a0, a1) { 132 | return a0 + a1; 133 | } 134 | def main() { 135 | return foo(1, 3); 136 | } 137 | ''') 138 | assert self.space.int_w(w_res) == 4 139 | 140 | def test_function_call_wrong_args(self): 141 | w_res = self.interpret(''' 142 | def foo(a0, a1) { 143 | } 144 | 145 | def main() { 146 | try { 147 | foo(13) 148 | } except ArgumentError { 149 | return true 150 | } 151 | return false 152 | } 153 | ''') 154 | assert self.space.is_true(w_res) 155 | 156 | def test_recursive_call(self): 157 | w_res = self.interpret(''' 158 | def fib(n) { 159 | if (n == 0) or (n == 1) { 160 | return 1; 161 | } 162 | return fib(n - 1) + fib(n - 2); 163 | } 164 | def main() { 165 | return fib(5); 166 | } 167 | ''') 168 | assert self.space.int_w(w_res) == 8 169 | 170 | def test_initializers(self): 171 | w_res = self.interpret(''' 172 | def main() { 173 | let a = 3, b, c = 12 174 | return a + c * 10 175 | } 176 | ''') 177 | assert self.space.int_w(w_res) == 123 178 | 179 | def test_magic_log(self): 180 | self.interpret_expr(''' 181 | log(1) 182 | log("foo") 183 | log([1, 2]) 184 | ''') 185 | space = self.space 186 | assert len(space.log) == 3 187 | assert space.int_w(space.log[0]) == 1 188 | assert space.utf8_w(space.log[1]) == "foo" 189 | assert space.type(space.log[2]) is space.w_list 190 | 191 | def test_var_declaration_not_constant(self): 192 | self.interpret_expr(''' 193 | let foo = 1 + 3 194 | log(foo) 195 | ''') 196 | assert self.space.int_w(self.space.log[0]) == 4 197 | 198 | def test_isinstance(self): 199 | self.interpret_expr(''' 200 | log(isinstance(1, Int)) 201 | log(isinstance(1, List)) 202 | log(isinstance([], List)) 203 | ''') 204 | exp = [True, False, True] 205 | assert [self.space.is_true(w_e) for w_e in self.space.log] == exp 206 | 207 | def test_if_else(self): 208 | w_res = self.interpret_expr(''' 209 | if (0) { 210 | return 1 211 | } else { 212 | return 2 213 | } 214 | ''') 215 | assert self.space.int_w(w_res) == 2 216 | w_res = self.interpret_expr(''' 217 | if (1) { 218 | return 1 219 | } else { 220 | return 2 221 | } 222 | ''') 223 | assert self.space.int_w(w_res) == 1 224 | 225 | def test_str(self): 226 | w_res = self.interpret_expr('return str(1)') 227 | assert self.space.utf8_w(w_res) == '1' 228 | -------------------------------------------------------------------------------- /tests/test_iterator.py: -------------------------------------------------------------------------------- 1 | from support import BaseTest 2 | 3 | 4 | class TestIterator(BaseTest): 5 | def test_list_iterator(self): 6 | self.interpret_expr(''' 7 | for i in [1, 2, 3] { 8 | log(i) 9 | } 10 | ''') 11 | -------------------------------------------------------------------------------- /tests/test_lexer.py: -------------------------------------------------------------------------------- 1 | from nolang.lexer import get_lexer 2 | 3 | 4 | class TestLexing(object): 5 | def test_program(self): 6 | tokens = [x.name for x in get_lexer().lex('', "def name () {}")] 7 | assert tokens == ['DEF', 'IDENTIFIER', 'LEFT_PAREN', 'RIGHT_PAREN', 8 | 'LEFT_CURLY_BRACE', 'RIGHT_CURLY_BRACE'] 9 | 10 | def test_one_plus_two(self): 11 | tokens = [x.name for x in get_lexer().lex('', '1+1')] 12 | assert tokens == ['INTEGER', 'PLUS', 'INTEGER'] 13 | 14 | def test_keyword_varname(self): 15 | tokens = [x.name for x in get_lexer().lex('', '1 + varfoo')] 16 | assert tokens == ['INTEGER', 'PLUS', 'IDENTIFIER'] 17 | tokens = [x.name for x in get_lexer().lex('', '1 + let')] 18 | assert tokens == ['INTEGER', 'PLUS', 'LET'] 19 | -------------------------------------------------------------------------------- /tests/test_list.py: -------------------------------------------------------------------------------- 1 | from nolang.error import AppError 2 | from support import BaseTest 3 | 4 | 5 | class TestList(BaseTest): 6 | def test_empty_list(self): 7 | w_res = self.interpret_expr('return [];') 8 | assert self.space.type(w_res) is self.space.builtin_dict['List'] 9 | assert self.space.len(w_res) == 0 10 | 11 | def test_short_list(self): 12 | w_res = self.interpret_expr('return ["foo", "bar"];') 13 | space = self.space 14 | assert space.len(w_res) == 2 15 | assert space.utf8_w(space.getitem(w_res, space.newint(0))) == "foo" 16 | assert space.utf8_w(space.getitem(w_res, space.newint(1))) == "bar" 17 | 18 | def test_trailing_comma(self): 19 | w_res = self.interpret_expr('return ["foo", "bar",];') 20 | space = self.space 21 | assert space.len(w_res) == 2 22 | assert space.utf8_w(space.getitem(w_res, space.newint(0))) == "foo" 23 | assert space.utf8_w(space.getitem(w_res, space.newint(1))) == "bar" 24 | 25 | def test_extra_commas(self): 26 | self.assert_expr_parse_error('return [,];') 27 | self.assert_expr_parse_error('return ["foo", "bar",,];') 28 | self.assert_expr_parse_error('return ["foo", , "bar"];') 29 | self.assert_expr_parse_error('return [, "foo", "bar"];') 30 | 31 | def test_constructor(self): 32 | w_res = self.interpret_expr('return List([]);') 33 | assert self.space.type(w_res) is self.space.builtin_dict['List'] 34 | assert self.space.len(w_res) == 0 35 | w_res = self.interpret_expr('return List(["foo", "bar"]);') 36 | space = self.space 37 | assert space.len(w_res) == 2 38 | assert space.utf8_w(space.getitem(w_res, space.newint(0))) == "foo" 39 | assert space.utf8_w(space.getitem(w_res, space.newint(1))) == "bar" 40 | 41 | def test_constructor_copies(self): 42 | w_res = self.interpret_expr(''' 43 | let x, y; 44 | x = [0, 1, 2]; 45 | y = List(x); 46 | y.append(3) 47 | return [len(x), len(y)]; 48 | ''') 49 | [w_xl, w_yl] = self.space.listview(w_res) 50 | assert self.space.int_w(w_xl) == 3 51 | assert self.space.int_w(w_yl) == 4 52 | 53 | def test_len(self): 54 | w_res = self.interpret_expr('return len([]);') 55 | assert self.space.int_w(w_res) == 0 56 | w_res = self.interpret_expr('return len(["foo", 2]);') 57 | assert self.space.int_w(w_res) == 2 58 | 59 | def test_getitem(self): 60 | w_res = self.interpret_expr('return ["foo", "bar"][0];') 61 | assert self.space.utf8_w(w_res) == "foo" 62 | 63 | def test_setitem(self): 64 | w_res = self.interpret_expr(''' 65 | let x; 66 | x = ["foo"]; 67 | x[0] = "bar"; 68 | return x[0]; 69 | ''') 70 | assert self.space.utf8_w(w_res) == "bar" 71 | 72 | def test_in(self): 73 | assert self.interpret_expr('return "a" in ["a", "b"];') is self.space.w_True 74 | assert self.interpret_expr('return "b" in ["a", "b"];') is self.space.w_True 75 | assert self.interpret_expr('return "c" in ["a", "b"];') is self.space.w_False 76 | 77 | def test_not_in(self): 78 | assert self.interpret_expr('return "c" not in ["a", "b"];') is self.space.w_True 79 | assert self.interpret_expr('return "a" not in ["a", "b"];') is self.space.w_False 80 | 81 | def test_append(self): 82 | w_res = self.interpret_expr(''' 83 | let x; 84 | x = ["foo"]; 85 | x.append("bar"); 86 | return x; 87 | ''') 88 | space = self.space 89 | assert space.len(w_res) == 2 90 | assert space.utf8_w(space.getitem(w_res, space.newint(1))) == "bar" 91 | 92 | def test_extend(self): 93 | w_res = self.interpret_expr(''' 94 | let x; 95 | x = ["a", "b"]; 96 | x.extend(["c", "d"]); 97 | return x; 98 | ''') 99 | list_w = self.space.listview(w_res) 100 | assert [self.space.utf8_w(w) for w in list_w] == ["a", "b", "c", "d"] 101 | 102 | def test_extend_nonlist(self): 103 | try: 104 | self.interpret_expr('["a", "b"].extend("c");') 105 | except AppError as ae: 106 | assert ae.match(self.space, self.space.w_typeerror) 107 | assert ae.w_exception.message == 'expected list' 108 | else: 109 | raise Exception("Applevel TypeError not raised.") 110 | 111 | def test_method_call_on_wrong_obj(self): 112 | try: 113 | self.interpret_expr(''' 114 | List.append(1, 2) 115 | ''') 116 | except AppError as ae: 117 | if not ae.match(self.space, self.space.w_typeerror): 118 | raise 119 | assert ae.w_exception.message == "Expected List object, got Int" 120 | else: 121 | raise Exception("did not raise") 122 | 123 | def test_getitem_out_of_range(self): 124 | try: 125 | self.interpret_expr('return ["foo", "bar"][2];') 126 | except AppError as ae: 127 | assert ae.match(self.space, self.space.w_indexerror) 128 | assert ae.w_exception.message == 'list index out of range' 129 | else: 130 | raise Exception("Applevel IndexError not raised.") 131 | 132 | def test_getitem_index_not_int(self): 133 | try: 134 | self.interpret_expr('return ["foo", "bar"]["zero"];') 135 | except AppError as ae: 136 | assert ae.match(self.space, self.space.w_typeerror) 137 | assert ae.w_exception.message == 'list index must be int' 138 | else: 139 | raise Exception("Applevel TypeError not raised.") 140 | 141 | def test_execution_order(self): 142 | w_res = self.interpret(''' 143 | def check(l, x) { 144 | l.append(x); 145 | return x; 146 | } 147 | 148 | def main() { 149 | let l; 150 | l = []; 151 | [check(l, 0), check(l, 1)]; 152 | return l; 153 | } 154 | ''') 155 | [w_0, w_1] = self.space.listview(w_res) 156 | assert [self.space.int_w(w_0), self.space.int_w(w_1)] == [0, 1] 157 | -------------------------------------------------------------------------------- /tests/test_main.py: -------------------------------------------------------------------------------- 1 | 2 | import re 3 | 4 | from support import reformat_code 5 | from nolang.main import main 6 | 7 | 8 | class TestMain(object): 9 | def test_main(self, tmpdir): 10 | fname = tmpdir.join("foo.q") 11 | fname.write(reformat_code(""" 12 | def main() { 13 | print(3); 14 | } 15 | """)) 16 | assert main(['nolang-c', str(fname)]) == 0 17 | 18 | def test_main_lex_error(self, tmpdir, capsys): 19 | fname = tmpdir.join("foo.q") 20 | fname.write(reformat_code(""" 21 | def main() { 22 | $ 23 | } 24 | """)) 25 | assert main(['nolang-c', str(fname)]) == 1 26 | out, err = capsys.readouterr() 27 | assert out.find("unrecognized token") > 0 28 | 29 | def test_main_traceback_formatting(self, tmpdir, capfd): 30 | fname = tmpdir.join("foo.q") 31 | fname.write(reformat_code(""" 32 | def foo() { 33 | raise Exception("foo") 34 | } 35 | 36 | def main() { 37 | foo() 38 | } 39 | """)) 40 | assert main(['nolang-c', str(fname)]) == 1 41 | out, err = capfd.readouterr() 42 | expected = [ 43 | 'file ".*/foo.q", line 2, in foo', 44 | 'raise Exception\("foo"\)', 45 | 'file ".*/foo.q", line 5, in main', 46 | 'foo\(\)', 47 | 'Exception: foo' 48 | ] 49 | lines = err.splitlines() 50 | assert len(lines) == len(expected) 51 | for i in range(len(lines)): 52 | assert re.search(expected[i], lines[i]) 53 | -------------------------------------------------------------------------------- /tests/test_parser.py: -------------------------------------------------------------------------------- 1 | from nolang.lexer import get_lexer 2 | from nolang.parser import get_parser, ParsingState, ParseError 3 | from nolang import astnodes as ast 4 | 5 | from tests.support import reformat_expr, reformat_code 6 | 7 | 8 | class BaseTest(object): 9 | def setup_class(self): 10 | self.parser = get_parser() 11 | self.lexer = get_lexer() 12 | 13 | def parse_bad(self, expr): 14 | try: 15 | value = self.parse(expr) 16 | except ParseError: 17 | pass 18 | else: 19 | raise Exception("Incorrectly parsed %r as %r." % (expr, value)) 20 | 21 | 22 | class TestStringParser(BaseTest): 23 | def parse_expr(self, expr): 24 | program = "def foo () { " + expr + "; }" 25 | ast = self.parser.parse(self.lexer.lex('test', program), 26 | ParsingState('test', program)) 27 | return ast.elements[0].body[0].expr 28 | 29 | def parse(self, expr): 30 | return self.parse_expr(expr).value 31 | 32 | def test_doublequote_simple(self): 33 | assert self.parse('"foo"') == 'foo' 34 | 35 | def test_doublequote_quotes(self): 36 | assert self.parse(r'''"foo'\"bar"''') == '''foo'"bar''' 37 | 38 | def test_singlequote_simple(self): 39 | assert self.parse("'foo'") == 'foo' 40 | 41 | def test_singlequote_quotes(self): 42 | assert self.parse(r"""'foo\'"bar'""") == """foo'"bar""" 43 | 44 | def test_quote_only(self): 45 | assert self.parse(r'"\""') == '"' 46 | assert self.parse(r"'\''") == "'" 47 | 48 | def test_non_strings(self): 49 | self.parse_bad(r'"\"') 50 | self.parse_bad(r'"\\\"') 51 | self.parse_bad(r"'\'") 52 | self.parse_bad(r"'\\\'") 53 | 54 | def test_escaped_escapes(self): 55 | assert self.parse(r'"\\"') == '\\' 56 | assert self.parse(r'"\\\""') == '\\"' 57 | assert self.parse(r'"\\\"\\"') == '\\"\\' 58 | 59 | def test_simple_escapes(self): 60 | assert self.parse(r'"\a"') == '\a' 61 | assert self.parse(r'"\b"') == '\b' 62 | assert self.parse(r'"\f"') == '\f' 63 | assert self.parse(r'"\n"') == '\n' 64 | assert self.parse(r'"\r"') == '\r' 65 | assert self.parse(r'"\t"') == '\t' 66 | assert self.parse(r'"\v"') == '\v' 67 | assert self.parse(r'"\0"') == '\0' 68 | 69 | def test_unrecognised_escapes(self): 70 | assert self.parse(r'"\q"') == '\\q' 71 | assert self.parse(r'"\1"') == '\\1' 72 | 73 | def test_hex_escapes(self): 74 | assert self.parse(r'"\x20"') == ' ' 75 | assert self.parse(r'"\xff"') == '\xc3\xbf' 76 | assert self.parse(r'"\u0020"') == ' ' 77 | assert self.parse(r'"\u1020"') == '\xe1\x80\xa0' 78 | assert self.parse(r'"\uffff"') == '\xef\xbf\xbf' 79 | assert self.parse(r'"\u{0}"') == '\x00' 80 | assert self.parse(r'"\u{20}"') == ' ' 81 | assert self.parse(r'"\u{000000020}"') == ' ' 82 | assert self.parse(r'"\u{1020}"') == '\xe1\x80\xa0' 83 | assert self.parse(r'"\u{ffff}"') == '\xef\xbf\xbf' 84 | assert self.parse(r'"\u{10000}"') == '\xf0\x90\x80\x80' 85 | assert self.parse(r'"\u{102030}"') == '\xf4\x82\x80\xb0' 86 | assert self.parse(r'"\u{10ffff}"') == '\xf4\x8f\xbf\xbf' 87 | 88 | def test_bad_hex_excapes(self): 89 | self.parse_bad(r'"\xf"') 90 | self.parse_bad(r'"\xfq"') 91 | self.parse_bad(r'"\u000q"') 92 | self.parse_bad(r'"\u{}"') 93 | self.parse_bad(r'"\u{q}"') 94 | self.parse_bad(r'"\u{110000}"') 95 | 96 | def test_bad_utf8(self): 97 | self.parse_bad('"\xff"',) 98 | self.parse_bad('"\xc0q"') 99 | self.parse_bad('"\xc0c0"') 100 | self.parse_bad('"\xdfq"') 101 | self.parse_bad('"\xe0q"') 102 | self.parse_bad('"\xe0\x80q"') 103 | self.parse_bad('"\xef\x80q"') 104 | self.parse_bad('"\xf0q"') 105 | self.parse_bad('"\xf0\x80q"') 106 | self.parse_bad('"\xf0\x80\x80q"') 107 | self.parse_bad('"\xf7\x80\x80q"') 108 | self.parse_bad('"\xf8"') 109 | 110 | def test_good_utf8(self): 111 | assert self.parse('"\x00"') == '\x00' 112 | assert self.parse('"\x7f"') == '\x7f' 113 | assert self.parse('"\xc2\x80"') == '\xc2\x80' 114 | assert self.parse('"\xc2\xbf"') == '\xc2\xbf' 115 | assert self.parse('"\xdf\xbf"') == '\xdf\xbf' 116 | assert self.parse('"\xe0\xbf\xbf"') == '\xe0\xbf\xbf' 117 | assert self.parse('"\xef\x80\x80"') == '\xef\x80\x80' 118 | assert self.parse('"\xf0\xbf\xbf\xbf"') == '\xf0\xbf\xbf\xbf' 119 | assert self.parse('"\xf4\x80\x80\xbf"') == '\xf4\x80\x80\xbf' 120 | 121 | def test_bad_raw_strings(self): 122 | self.parse_bad(r"r'\'") 123 | self.parse_bad(r"r'\\\'") 124 | 125 | def test_raw_escapes(self): 126 | assert self.parse(r"r'\\'") == r"\\" 127 | assert self.parse(r"r'\''") == r"\'" 128 | assert self.parse(r"r'\"'") == r"\"" 129 | assert self.parse(r'r"\\"') == r"\\" 130 | assert self.parse(r'r"\'"') == r"\'" 131 | assert self.parse(r'r"\""') == r"\"" 132 | assert self.parse(r"r'\\\'\\'") == r"\\\'\\" 133 | assert self.parse(r"r'\n'") == r"\n" 134 | assert self.parse(r"r'\xq'") == r"\xq" 135 | assert self.parse(r"r'\uq'") == r"\uq" 136 | 137 | def test_interp(self): 138 | assert self.parse_expr(r'``') == ast.InterpString([''], []) 139 | assert self.parse_expr(r'`${1}`') == ast.InterpString( 140 | ['', ''], [ast.Number(1)]) 141 | assert self.parse_expr(r'`foo${1}bar`') == ast.InterpString( 142 | ['foo', 'bar'], [ast.Number(1)]) 143 | assert self.parse_expr(r'`${1 + 2}`') == ast.InterpString( 144 | ['', ''], [ast.BinOp('+', ast.Number(1), ast.Number(2), oppos=(18, 19))]) 145 | 146 | 147 | class TestExpressionParser(BaseTest): 148 | def parse(self, expr): 149 | program = "def foo () { " + expr + "; }" 150 | ast = self.parser.parse(self.lexer.lex('test', program), 151 | ParsingState('test', program)) 152 | return ast.elements[0].body[0].expr 153 | 154 | def test_add(self): 155 | assert self.parse('1 + 1') == ast.BinOp( 156 | '+', ast.Number(1), ast.Number(1), oppos=(15, 16)) 157 | 158 | def test_various_kinds_of_calls(self): 159 | r = self.parse('x(1, 2, 3)') 160 | assert r == ast.Call(ast.Identifier('x'), [ast.Number(1), ast.Number(2), 161 | ast.Number(3)], []) 162 | r = self.parse('(1)(2)') 163 | assert r == ast.Call(ast.Number(1), [ast.Number(2)], []) 164 | 165 | def test_call_with_named_args(self): 166 | r = self.parse('x(a=1, b=2)') 167 | assert r == ast.Call(ast.Identifier('x'), [], [ 168 | ast.NamedArg('a', ast.Number(1)), 169 | ast.NamedArg('b', ast.Number(2)), 170 | ]) 171 | r = self.parse('x(1, b=2)') 172 | assert r == ast.Call(ast.Identifier('x'), [ast.Number(1)], [ 173 | ast.NamedArg('b', ast.Number(2)), 174 | ]) 175 | self.parse_bad('x(1=b)') 176 | self.parse_bad('x(1, a=2, 3') 177 | 178 | 179 | class TestParseFunctionBody(BaseTest): 180 | def parse(self, body): 181 | program = reformat_expr(body) 182 | ast = self.parser.parse(self.lexer.lex('test', program), 183 | ParsingState('test', program)) 184 | return ast.elements[0].body 185 | 186 | def test_var(self): 187 | r = self.parse(''' 188 | let x; 189 | x = 3; 190 | x = x + 1; 191 | ''') 192 | assert r == [ 193 | ast.VarDeclaration([ast.Var('x', ast.NoTypeDecl(), None)]), 194 | ast.Assignment('x', ast.Number(3)), 195 | ast.Assignment('x', ast.BinOp('+', ast.Identifier('x'), 196 | ast.Number(1), oppos=(46, 47)))] 197 | 198 | def test_while_loop(self): 199 | r = self.parse(''' 200 | let i, s; 201 | i = 0; 202 | while i < 10 { 203 | i = i + 1; 204 | s = s + i; 205 | } 206 | return s; 207 | ''') 208 | assert r == [ 209 | ast.VarDeclaration([ast.Var('i', ast.NoTypeDecl(), None), 210 | ast.Var('s', ast.NoTypeDecl(), None)]), 211 | ast.Assignment('i', ast.Number(0)), 212 | ast.While( 213 | ast.BinOp('<', ast.Identifier('i'), ast.Number(10), oppos=(51, 52)), [ 214 | ast.Assignment('i', ast.BinOp( 215 | '+', ast.Identifier('i'), ast.Number(1), oppos=(72, 73))), 216 | ast.Assignment('s', ast.BinOp( 217 | '+', ast.Identifier('s'), ast.Identifier('i'), oppos=(91, 92)))]), 218 | ast.Return(ast.Identifier('s'))] 219 | 220 | 221 | class TestFullProgram(BaseTest): 222 | 223 | def parse(self, code): 224 | program = reformat_code(code) 225 | return self.parser.parse(self.lexer.lex('test', program), 226 | ParsingState('test', program)) 227 | 228 | def test_function_declaration(self): 229 | r = self.parse(''' 230 | def foo() { 231 | let x; 232 | } 233 | 234 | def main() { 235 | } 236 | ''') 237 | expected = ast.Program([ 238 | ast.Function('foo', [], [ 239 | ast.VarDeclaration([ast.Var('x', ast.NoTypeDecl(), None)]) 240 | ], lineno=1), 241 | ast.Function('main', [], [], lineno=4) 242 | ]) 243 | assert r == expected 244 | 245 | def test_function_declaration_args(self): 246 | r = self.parse(''' 247 | def foo(a0, a1) { 248 | } 249 | ''') 250 | expected = ast.Program([ 251 | ast.Function('foo', [ast.Var('a0', ast.NoTypeDecl(), None), 252 | ast.Var('a1', ast.NoTypeDecl(), None)], [ 253 | ], lineno=1) 254 | ]) 255 | assert r == expected 256 | 257 | def test_empty_try(self): 258 | try: 259 | self.parse(''' 260 | def foo() { 261 | try { 262 | } 263 | } 264 | ''') 265 | except ParseError: 266 | pass 267 | else: 268 | raise Exception("DID NOT RAISE") 269 | 270 | def test_import_stuff(self): 271 | r = self.parse(''' 272 | import foo 273 | import foo.bar 274 | import foo{a,b,c,d} 275 | import foo.bar{a,b,c}; 276 | ''') 277 | expected = ast.Program([ 278 | ast.Import(["foo"], []), 279 | ast.Import(["foo"], ["bar"]), 280 | ast.Import(["foo"], ["a", "b", "c", "d"]), 281 | ast.Import(["foo", "bar"], ["a", "b", 'c']) 282 | ]) 283 | assert r == expected 284 | 285 | def test_ast_pos(self): 286 | program = self.parse(''' 287 | def foo(n) { 288 | return n + 1; 289 | } 290 | 291 | def main() { 292 | } 293 | ''') 294 | assert program.getsrcpos() == (4, 67) 295 | [func_foo, func_main] = program.elements 296 | assert func_foo.getsrcpos() == (4, 44) 297 | assert func_main.getsrcpos() == (49, 67) 298 | [ret] = func_foo.body 299 | assert ret.getsrcpos() == (25, 38) 300 | binop = ret.expr 301 | assert binop.getsrcpos() == (32, 37) 302 | assert binop.left.getsrcpos() == (32, 33) 303 | assert binop.right.getsrcpos() == (36, 37) 304 | 305 | def test_ast_pos_except(self): 306 | program = self.parse(''' 307 | def main() { 308 | try { 309 | raise Exception("foo"); 310 | } except A { 311 | return 1; 312 | } except Exception { 313 | } 314 | } 315 | ''') 316 | [func_main] = program.elements 317 | assert func_main.getsrcpos() == (4, 154) 318 | [try_except] = func_main.body 319 | assert try_except.getsrcpos() == (25, 148) 320 | [except_A, except_Exception] = try_except.except_blocks 321 | assert except_A.getsrcpos() == (77, 119) 322 | assert except_Exception.getsrcpos() == (120, 148) 323 | 324 | def test_var_in_body_decl(self): 325 | self.parse_bad(''' 326 | let foo; 327 | 328 | def main() { 329 | } 330 | ''') 331 | 332 | def test_comments(self): 333 | self.parse(''' 334 | # some comment 335 | 336 | def main() { # more comment 337 | # comment line 338 | 1 # even more 339 | 2 340 | } 341 | ''') 342 | -------------------------------------------------------------------------------- /tests/test_stringbuilder.py: -------------------------------------------------------------------------------- 1 | 2 | from support import BaseTest 3 | 4 | 5 | class TestStringBuilder(BaseTest): 6 | def test_basic(self): 7 | w_res = self.interpret(""" 8 | import core.text.StringBuilder 9 | 10 | def main() { 11 | let s = StringBuilder() 12 | log(s.build()) 13 | s = StringBuilder(11) 14 | s.append("foo") 15 | s.append("a\\n") 16 | return s.build() 17 | } 18 | """) 19 | assert self.space.utf8_w(self.space.log[0]) == '' 20 | assert self.space.utf8_w(w_res) == "fooa\n" 21 | -------------------------------------------------------------------------------- /tests/test_strings.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from support import BaseTest 4 | 5 | 6 | class TestStrings(BaseTest): 7 | def test_string_literal(self): 8 | w_res = self.interpret_expr('return "foo";') 9 | assert self.space.utf8_w(w_res) == "foo" 10 | 11 | def test_unicode_literal(self): 12 | w_res = self.interpret_expr(u'return "wziąść";'.encode('utf8')) 13 | assert self.space.utf8_w(w_res) == u"wziąść".encode('utf8') 14 | 15 | def test_string_equality(self): 16 | assert self.interpret_expr('return "foo" == "foo";') is self.space.w_True 17 | assert self.interpret_expr('return "foo" == "bar";') is self.space.w_False 18 | 19 | def test_automatic_semicolon_insertion(self): 20 | w_res = self.interpret(''' 21 | def main() { 22 | let x, y 23 | x = "hello" 24 | y = 42 25 | return `[${x}, ${y}]` 26 | } 27 | ''') 28 | assert self.space.utf8_w(w_res) == "[hello, 42]" 29 | 30 | 31 | class TestInterpolatedStrings(BaseTest): 32 | def test_simple_expressions(self): 33 | w_res = self.interpret_expr('return `${1} + ${2} = ${1 + 2}`;') 34 | assert self.space.utf8_w(w_res) == "1 + 2 = 3" 35 | 36 | def test_string_expressions(self): 37 | w_res = self.interpret_expr('''return `${'a'} + ${"b"} = ${r'c'}`;''') 38 | assert self.space.utf8_w(w_res) == "a + b = c" 39 | 40 | def test_vars_and_calls(self): 41 | w_res = self.interpret(''' 42 | def foo(a, b) { 43 | return a + b; 44 | } 45 | 46 | def main() { 47 | let x, y; 48 | x = "hello"; 49 | y = 7; 50 | return `[${x} ${y} ${foo(1, 2)}]`; 51 | } 52 | ''') 53 | assert self.space.utf8_w(w_res) == "[hello 7 3]" 54 | 55 | def test_nested(self): 56 | w_res = self.interpret_expr('return `[${"o"}]`;') 57 | assert self.space.utf8_w(w_res) == "[o]" 58 | w_res = self.interpret_expr('return `[${`<${"o"}>`}]`;') 59 | assert self.space.utf8_w(w_res) == "[]" 60 | w_res = self.interpret_expr('return `[${`<${`(${"o"})`}>`}]`;') 61 | assert self.space.utf8_w(w_res) == "[<(o)>]" 62 | -------------------------------------------------------------------------------- /tests/test_types.py: -------------------------------------------------------------------------------- 1 | 2 | from support import BaseTest 3 | 4 | 5 | class TestTypes(BaseTest): 6 | def test_str_enforcement(self): 7 | self.interpret(""" 8 | def main() { 9 | let x: Str 10 | x = 13 11 | } 12 | """) 13 | --------------------------------------------------------------------------------