├── .coveragerc ├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.rst ├── core ├── __builtins__.rns ├── personality.c ├── rt.ll └── unwind.h ├── doc ├── Makefile ├── _themes │ └── runa │ │ ├── layout.html │ │ ├── searchbox.html │ │ ├── static │ │ └── runa.css_t │ │ └── theme.conf ├── conf.py ├── grammar.py ├── hacking.rst ├── index.rst ├── notes.rst ├── overview.rst └── refs.rst ├── llize ├── misc └── runa.nanorc ├── runa ├── runac ├── __init__.py ├── __main__.py ├── ast.py ├── blocks.py ├── codegen.py ├── destructor.py ├── escapes.py ├── liveness.py ├── parser.py ├── pretty.py ├── specialize.py ├── typer.py ├── types.py └── util.py ├── tb.txt ├── test.py └── tests ├── arith-int.out ├── arith-int.rns ├── ast-err.err ├── ast-err.rns ├── bitwise.out ├── bitwise.rns ├── bool-bool.out ├── bool-bool.rns ├── bool-ops-precedence.out ├── bool-ops-precedence.rns ├── bool-ops.out ├── bool-ops.rns ├── break.out ├── break.rns ├── catch.out ├── catch.rns ├── check-rtype.err ├── check-rtype.rns ├── class.out ├── class.rns ├── cmp.out ├── cmp.rns ├── const.out ├── const.rns ├── continue.out ├── continue.rns ├── cycle-typing.out ├── cycle-typing.rns ├── early-return-owner.out ├── early-return-owner.rns ├── elem-proto.err ├── elem-proto.rns ├── err-escaping-owner.err ├── err-escaping-owner.rns ├── file.lng ├── file.out ├── float.out ├── float.rns ├── for.out ├── for.rns ├── force-void.err ├── force-void.rns ├── function.out ├── function.rns ├── half-defined.err ├── half-defined.rns ├── hello.out ├── hello.rns ├── if.out ├── if.rns ├── immutable-ref.err ├── immutable-ref.rns ├── init-rtype.err ├── init-rtype.rns ├── inline-catch.out ├── inline-catch.rns ├── item-call.err ├── item-call.rns ├── iter-obj.out ├── iter-obj.rns ├── main-type-arg-0.err ├── main-type-arg-0.rns ├── main-type-arg-1.err ├── main-type-arg-1.rns ├── main-type-r.err ├── main-type-r.rns ├── method-arg-name.err ├── method-arg-name.rns ├── method-select-fail.err ├── method-select-fail.rns ├── multi-return.out ├── multi-return.rns ├── mutable-owner.out ├── mutable-owner.rns ├── mutable-ref.out ├── mutable-ref.rns ├── named-args.out ├── named-args.rns ├── no-arg-call.out ├── no-arg-call.rns ├── no-arg-type.err ├── no-arg-type.rns ├── no-compare.err ├── no-compare.rns ├── no-func.err ├── no-func.rns ├── no-init.err ├── no-init.rns ├── no-method.err ├── no-method.rns ├── no-self.err ├── no-self.rns ├── non-type.err ├── non-type.rns ├── none.out ├── none.rns ├── num-params.err ├── num-params.rns ├── oddeven.out ├── oddeven.rns ├── opt-check.out ├── opt-check.rns ├── opt-resolve.out ├── opt-resolve.rns ├── opt-return.out ├── opt-return.rns ├── opt-use-attrib.err ├── opt-use-attrib.rns ├── owner-after-pass.err ├── owner-after-pass.rns ├── owner-reassign.out ├── owner-reassign.rns ├── pass-ref-as-owner.err ├── pass-ref-as-owner.rns ├── pos-after-named.err ├── pos-after-named.rns ├── pretty.out ├── pretty.rns ├── print-var.out ├── print-var.rns ├── retval.out ├── retval.rns ├── retype.err ├── retype.rns ├── rtype.err ├── rtype.rns ├── self-type.err ├── self-type.rns ├── str-ops.out ├── str-ops.rns ├── ternary.out ├── ternary.rns ├── ternop-err.err ├── ternop-err.rns ├── type-diff.err ├── type-diff.rns ├── undefined.err ├── undefined.rns ├── unhandled.err ├── unhandled.rns ├── unmatched.err ├── unmatched.rns ├── void-print.err ├── void-print.rns ├── while.out ├── while.rns ├── yield-type.err ├── yield-type.rns ├── zero.out └── zero.rns /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | source = runac 3 | 4 | [report] 5 | exclude_lines = 6 | pragma: no cover 7 | def __repr__ 8 | assert 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **.pyc 2 | .coverage 3 | core/personality.ll 4 | test.c 5 | tests/*.test 6 | tests/*.ll 7 | doc/_build 8 | doc/grammar.rst 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | sudo: false 3 | python: 4 | - "2.7" 5 | install: 6 | - pip install rply 7 | - pip install coveralls 8 | script: 9 | make coverage 10 | after_success: 11 | coveralls 12 | notifications: 13 | webhooks: 14 | urls: 15 | - https://webhooks.gitter.im/e/b81f9ec6e179a91aaca0 16 | on_success: change 17 | on_failure: always 18 | on_start: false 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2011-2014 Dirkjan Ochtman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: test doc 2 | 3 | test: 4 | python test.py 5 | 6 | coverage: 7 | coverage run test.py 8 | 9 | doc: 10 | make -C doc html 11 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Runa 2 | ==== 3 | 4 | .. image:: https://travis-ci.org/djc/runa.svg?branch=master 5 | :target: https://travis-ci.org/djc/runa 6 | .. image:: https://img.shields.io/coveralls/djc/runa.svg?branch=master 7 | :target: https://coveralls.io/r/djc/runa?branch=master 8 | .. image:: https://badges.gitter.im/Join%20Chat.svg 9 | :alt: Join the chat at https://gitter.im/djc/runa 10 | :target: https://gitter.im/djc/runa 11 | 12 | A Python-like systems programming language. 13 | This means that the design borrows as much from Python 14 | as makes sense in the context of a statically-typed, compiled language, 15 | and tries to apply the `Zen of Python`_ to everything else. 16 | The most important design goals for Runa are developer ergonomics 17 | and performance. 18 | The compiler is written in Python and targets LLVM IR; 19 | there's no run-time. 20 | More information, including a project rationale, can be found on the project `website`_. 21 | 22 | Note: this is pre-alpha quality software. Use at your own peril. 23 | 24 | All feedback welcome. Feel free to file bugs, requests for documentation and 25 | any other feedback to the `issue tracker`_, `tweet me`_ or join the #runa 26 | channel on freenode. 27 | 28 | .. _Zen of Python: https://www.python.org/dev/peps/pep-0020/ 29 | .. _website: http://runa-lang.org/ 30 | .. _issue tracker: https://github.com/djc/runa/issues 31 | .. _tweet me: https://twitter.com/djco/ 32 | 33 | 34 | Installation 35 | ------------ 36 | 37 | Dependencies: 38 | 39 | * Python 2.7 or 3.3 (3.4 probably works as well) 40 | * rply (tested with 0.7.2) 41 | * Clang (tested with 3.3 and later) 42 | 43 | The compiler is being tested on 64-bits OS X and Linux and 32-bits Linux. 44 | 45 | Preliminary testing has been done on 64-bit Windows 7 as well. This seems 46 | to work okay when compiling against mingw-w64, although the test suite fails 47 | because newlines get rewritten to ``\r\n`` when using ``write()`` with 48 | ``stdout``. Compiling against the MS platform libs has been tried (through 49 | ``clang-cl``), but I have not yet been able to fix all the undefined symbol 50 | errors. 51 | 52 | 53 | How to get started 54 | ------------------ 55 | 56 | Type the following program into a file called ``hello.rns``: 57 | 58 | .. code:: 59 | 60 | def main(): 61 | print('hello, world') 62 | 63 | Make sure to use tabs for indentation. 64 | Now, run the compiler to generate a binary, then run it: 65 | 66 | .. code:: 67 | 68 | djc@enrai runa $ ./runa compile hello.rns 69 | djc@enrai runa $ ./hello 70 | hello, world 71 | 72 | Review the test cases in ``tests/`` for other code that should work. 73 | -------------------------------------------------------------------------------- /core/__builtins__.rns: -------------------------------------------------------------------------------- 1 | from __internal__ import __malloc__, __memcpy__, __offset__ 2 | from libc.string import strncmp, strlen 3 | from libc.stdio import snprintf 4 | from libc.unistd import write 5 | 6 | # basic types 7 | 8 | class NoType: 9 | pass 10 | 11 | # exception handling 12 | 13 | class UnwEx: 14 | ehid: u64 15 | clean: &byte 16 | priv1: u64 17 | priv2: u64 18 | 19 | class Exception: 20 | 21 | header: UnwEx 22 | switch_value: i32 23 | lsda: &byte 24 | lpad: &byte 25 | msg: $Str 26 | 27 | def __init__(self, msg: $Str): 28 | self.msg = msg 29 | 30 | # bool 31 | 32 | class bool: 33 | def __str__(self) -> $Str: 34 | return 'True' if self else 'False' 35 | 36 | # ints 37 | 38 | class byte: 39 | pass 40 | 41 | class u8: 42 | def __str__(self) -> $Str: 43 | data = __malloc__(3) 44 | len = snprintf(data, 3, '%hhu\0'.data, self) 45 | return Str(len as uint, data) 46 | 47 | class i8: 48 | def __str__(self) -> $Str: 49 | data = __malloc__(4) 50 | len = snprintf(data, 4, '%hhi\0'.data, self) 51 | return Str(len as uint, data) 52 | 53 | class i32: 54 | def __str__(self) -> $Str: 55 | data = __malloc__(20) 56 | len = snprintf(data, 20, '%i\0'.data, self) 57 | return Str(len as uint, data) 58 | 59 | class u32: 60 | def __str__(self) -> $Str: 61 | data = __malloc__(20) 62 | len = snprintf(data, 20, '%u\0'.data, self) 63 | return Str(len as uint, data) 64 | 65 | class u64: 66 | def __str__(self) -> $Str: 67 | data = __malloc__(20) 68 | len = snprintf(data, 20, '%lu\0'.data, self) 69 | return Str(len as uint, data) 70 | 71 | class int: 72 | 73 | def __bool__(self) -> bool: 74 | return self != 0 75 | 76 | def __str__(self) -> $Str: 77 | data = __malloc__(20) 78 | len = snprintf(data, 20, '%ld\0'.data, self) 79 | return Str(len as uint, data) 80 | 81 | class uint: 82 | 83 | def __bool__(self) -> bool: 84 | return self != 0 85 | 86 | def __str__(self) -> $Str: 87 | data = __malloc__(20) 88 | len = snprintf(data, 20, '%lu\0'.data, self) 89 | return Str(len as uint, data) 90 | 91 | # float 92 | 93 | class float: 94 | 95 | def __bool__(self) -> bool: 96 | return self != 0.0 97 | 98 | def __str__(self) -> $Str: 99 | data = __malloc__(20) 100 | len = snprintf(data, 20, '%lf\0'.data, self) 101 | return Str(len as uint, data) 102 | 103 | # Str 104 | 105 | class Str: 106 | 107 | len: uint 108 | data: $byte 109 | 110 | def __new__(src: &ToStr) -> $Str: 111 | return src.__str__() 112 | 113 | def __init__(self, len: uint, data: $byte): 114 | self.len = len 115 | self.data = data 116 | 117 | def __init__(self, data: $byte): 118 | self.len = strlen(data) 119 | self.data = data 120 | 121 | def __copy__(self) -> $Str: 122 | data = __malloc__(self.len) 123 | __memcpy__(data, self.data, self.len) 124 | return Str(self.len, data) 125 | 126 | def __bool__(self) -> bool: 127 | return self.len > 0 128 | 129 | def __str__(self) -> $Str: 130 | return self.__copy__() 131 | 132 | def __eq__(self, other: &Str) -> bool: 133 | if self.len != other.len: 134 | return False 135 | len = self.len if self.len < other.len else other.len 136 | cmp = strncmp(self.data, other.data, len) 137 | return cmp == 0 138 | 139 | def __lt__(self, other: &Str) -> bool: 140 | len = self.len if self.len < other.len else other.len 141 | cmp = strncmp(self.data, other.data, len) 142 | if cmp < 0: 143 | return True 144 | elif cmp > 0: 145 | return False 146 | else: 147 | return self.len < other.len 148 | 149 | def __add__(self, other: &Str) -> $Str: 150 | sum = self.len + other.len 151 | data = __malloc__(sum) 152 | __memcpy__(data, self.data, self.len) 153 | rest = __offset__(data, self.len) 154 | __memcpy__(rest, other.data, other.len) 155 | return Str(sum, data) 156 | 157 | # Array 158 | 159 | class Array[T]: 160 | len: uint 161 | data: $T 162 | 163 | # traits 164 | 165 | trait ToStr: 166 | def __str__(self) -> $Str 167 | 168 | trait ToBool: 169 | def __bool__(self) -> bool 170 | 171 | # functions 172 | 173 | def print(src: &ToStr): 174 | s = Str(src) 175 | write(1, s.data, s.len) 176 | write(1, "\n".data, 1) 177 | -------------------------------------------------------------------------------- /core/personality.c: -------------------------------------------------------------------------------- 1 | // -*- C++ -*- The GNU C++ exception personality routine. 2 | // Copyright (C) 2001-2015 Free Software Foundation, Inc. 3 | // 4 | // This file is part of GCC. 5 | // 6 | // GCC is free software; you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation; either version 3, or (at your option) 9 | // any later version. 10 | // 11 | // GCC is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | // 16 | // Under Section 7 of GPL version 3, you are granted additional 17 | // permissions described in the GCC Runtime Library Exception, version 18 | // 3.1, as published by the Free Software Foundation. 19 | 20 | // You should have received a copy of the GNU General Public License and 21 | // a copy of the GCC Runtime Library Exception along with this program; 22 | // see the files COPYING3 and COPYING.RUNTIME respectively. If not, see 23 | // . 24 | 25 | // Cobbled together by djc from different files: 26 | // * gcc/libstdc++-v3/libsupc++/eh_personality.cc 27 | // * gcc/libgcc/unwind-pe.h 28 | // ... and probably a few others? 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | #include "unwind.h" 38 | 39 | /* 40 | * Pointer encodings documented at: 41 | * http://refspecs.freestandards.org/LSB_1.3.0/gLSB/gLSB/ehframehdr.html 42 | */ 43 | 44 | #define DW_EH_PE_omit 0xff /* no data follows */ 45 | 46 | #define DW_EH_PE_absptr 0x00 47 | #define DW_EH_PE_uleb128 0x01 48 | #define DW_EH_PE_udata2 0x02 49 | #define DW_EH_PE_udata4 0x03 50 | #define DW_EH_PE_udata8 0x04 51 | #define DW_EH_PE_sleb128 0x09 52 | #define DW_EH_PE_sdata2 0x0A 53 | #define DW_EH_PE_sdata4 0x0B 54 | #define DW_EH_PE_sdata8 0x0C 55 | 56 | #define DW_EH_PE_pcrel 0x10 57 | #define DW_EH_PE_textrel 0x20 58 | #define DW_EH_PE_datarel 0x30 59 | #define DW_EH_PE_funcrel 0x40 60 | #define DW_EH_PE_aligned 0x50 61 | #define DW_EH_PE_indirect 0x80 /* gcc extension */ 62 | 63 | typedef unsigned _Unwind_Ptr __attribute__((__mode__(__pointer__))); 64 | typedef unsigned _Unwind_Internal_Ptr __attribute__((__mode__(__pointer__))); 65 | typedef unsigned _Unwind_Exception_Class __attribute__((__mode__(__DI__))); 66 | 67 | #if __SIZEOF_LONG__ >= __SIZEOF_POINTER__ 68 | typedef long _sleb128_t; 69 | typedef unsigned long _uleb128_t; 70 | #elif __SIZEOF_LONG_LONG__ >= __SIZEOF_POINTER__ 71 | typedef long long _sleb128_t; 72 | typedef unsigned long long _uleb128_t; 73 | #else 74 | # error "What type shall we use for _sleb128_t?" 75 | #endif 76 | 77 | static _Unwind_Ptr 78 | base_of_encoded_value(unsigned char encoding, struct _Unwind_Context *context) { 79 | 80 | if (encoding == DW_EH_PE_omit) 81 | return 0; 82 | 83 | switch (encoding & 0x70) { 84 | case DW_EH_PE_absptr: 85 | case DW_EH_PE_pcrel: 86 | case DW_EH_PE_aligned: 87 | return 0; 88 | // No support for textrel or datarel, apparently? 89 | #if 0 90 | case DW_EH_PE_textrel: 91 | return _Unwind_GetTextRelBase(context); 92 | case DW_EH_PE_datarel: 93 | return _Unwind_GetDataRelBase(context); 94 | #endif 95 | case DW_EH_PE_funcrel: 96 | return _Unwind_GetRegionStart(context); 97 | } 98 | abort(); 99 | 100 | } 101 | 102 | static const unsigned char * 103 | read_uleb128(const unsigned char *p, _uleb128_t *val) { 104 | 105 | unsigned int shift = 0; 106 | unsigned char byte; 107 | _uleb128_t result; 108 | 109 | result = 0; 110 | do { 111 | byte = *p++; 112 | result |= ((_uleb128_t)byte & 0x7f) << shift; 113 | shift += 7; 114 | } while (byte & 0x80); 115 | 116 | *val = result; 117 | return p; 118 | 119 | } 120 | 121 | static const unsigned char * 122 | read_sleb128(const unsigned char *p, _sleb128_t *val) { 123 | 124 | unsigned int shift = 0; 125 | unsigned char byte; 126 | _uleb128_t result; 127 | 128 | result = 0; 129 | do { 130 | byte = *p++; 131 | result |= ((_uleb128_t)byte & 0x7f) << shift; 132 | shift += 7; 133 | } while (byte & 0x80); 134 | 135 | /* Sign-extend a negative value. */ 136 | if (shift < 8 * sizeof(result) && (byte & 0x40) != 0) 137 | result |= -(((_uleb128_t) 1L) << shift); 138 | 139 | *val = (_sleb128_t) result; 140 | return p; 141 | 142 | } 143 | 144 | static const unsigned char * 145 | read_encoded_value_with_base(unsigned char encoding, _Unwind_Ptr base, 146 | const unsigned char *p, _Unwind_Ptr *val) { 147 | 148 | union unaligned { 149 | void *ptr; 150 | unsigned u2 __attribute__ ((mode (HI))); 151 | unsigned u4 __attribute__ ((mode (SI))); 152 | unsigned u8 __attribute__ ((mode (DI))); 153 | signed s2 __attribute__ ((mode (HI))); 154 | signed s4 __attribute__ ((mode (SI))); 155 | signed s8 __attribute__ ((mode (DI))); 156 | } __attribute__((__packed__)); 157 | 158 | const union unaligned *u = (const union unaligned *) p; 159 | _Unwind_Internal_Ptr result; 160 | 161 | if (encoding == DW_EH_PE_aligned) { 162 | _Unwind_Internal_Ptr a = (_Unwind_Internal_Ptr) p; 163 | a = (a + sizeof (void *) - 1) & - sizeof(void *); 164 | result = *(_Unwind_Internal_Ptr *) a; 165 | p = (const unsigned char *) (_Unwind_Internal_Ptr) (a + sizeof (void *)); 166 | } else { 167 | 168 | switch (encoding & 0x0f) { 169 | 170 | case DW_EH_PE_absptr: 171 | result = (_Unwind_Internal_Ptr) u->ptr; 172 | p += sizeof (void *); 173 | break; 174 | 175 | case DW_EH_PE_uleb128: { 176 | _uleb128_t tmp; 177 | p = read_uleb128(p, &tmp); 178 | result = (_Unwind_Internal_Ptr) tmp; 179 | } 180 | break; 181 | 182 | case DW_EH_PE_sleb128: { 183 | _sleb128_t tmp; 184 | p = read_sleb128 (p, &tmp); 185 | result = (_Unwind_Internal_Ptr) tmp; 186 | } 187 | break; 188 | 189 | case DW_EH_PE_udata2: 190 | result = u->u2; 191 | p += 2; 192 | break; 193 | case DW_EH_PE_udata4: 194 | result = u->u4; 195 | p += 4; 196 | break; 197 | case DW_EH_PE_udata8: 198 | result = u->u8; 199 | p += 8; 200 | break; 201 | 202 | case DW_EH_PE_sdata2: 203 | result = u->s2; 204 | p += 2; 205 | break; 206 | case DW_EH_PE_sdata4: 207 | result = u->s4; 208 | p += 4; 209 | break; 210 | case DW_EH_PE_sdata8: 211 | result = u->s8; 212 | p += 8; 213 | break; 214 | 215 | default: 216 | abort(); 217 | 218 | } 219 | 220 | if (result != 0) { 221 | result += ((encoding & 0x70) == DW_EH_PE_pcrel ? (_Unwind_Internal_Ptr) u : base); 222 | if (encoding & DW_EH_PE_indirect) 223 | result = *(_Unwind_Internal_Ptr*) result; 224 | } 225 | 226 | } 227 | 228 | *val = result; 229 | return p; 230 | 231 | } 232 | 233 | static inline const unsigned char * 234 | read_encoded_value(struct _Unwind_Context *context, unsigned char encoding, 235 | const unsigned char *p, _Unwind_Ptr *val) { 236 | return read_encoded_value_with_base(encoding, 237 | base_of_encoded_value(encoding, context), p, val); 238 | } 239 | 240 | /* read a uleb128 encoded value and advance pointer */ 241 | static uintptr_t readULEB128(const uint8_t** data) { 242 | uintptr_t result = 0; 243 | uintptr_t shift = 0; 244 | unsigned char byte; 245 | const uint8_t* p = *data; 246 | do { 247 | byte = *p++; 248 | result |= (byte & 0x7f) << shift; 249 | shift += 7; 250 | } while (byte & 0x80); 251 | *data = p; 252 | return result; 253 | } 254 | 255 | /* read a pointer encoded value and advance pointer */ 256 | static uintptr_t readEncodedPointer(const uint8_t** data, uint8_t encoding) { 257 | 258 | const uint8_t* p = *data; 259 | uintptr_t result = 0; 260 | 261 | if (encoding == DW_EH_PE_omit) 262 | return 0; 263 | 264 | /* first get value */ 265 | switch (encoding & 0x0F) { 266 | case DW_EH_PE_absptr: 267 | result = *((const uintptr_t*) p); 268 | p += sizeof(uintptr_t); 269 | break; 270 | case DW_EH_PE_uleb128: 271 | result = readULEB128(&p); 272 | break; 273 | case DW_EH_PE_udata2: 274 | result = *((const uint16_t*) p); 275 | p += sizeof(uint16_t); 276 | break; 277 | case DW_EH_PE_udata4: 278 | result = *((const uint32_t*) p); 279 | p += sizeof(uint32_t); 280 | break; 281 | case DW_EH_PE_udata8: 282 | result = *((const uint64_t*) p); 283 | p += sizeof(uint64_t); 284 | break; 285 | case DW_EH_PE_sdata2: 286 | result = *((const int16_t*) p); 287 | p += sizeof(int16_t); 288 | break; 289 | case DW_EH_PE_sdata4: 290 | result = *((const int32_t*) p); 291 | p += sizeof(int32_t); 292 | break; 293 | case DW_EH_PE_sdata8: 294 | result = *((const int64_t*) p); 295 | p += sizeof(int64_t); 296 | break; 297 | case DW_EH_PE_sleb128: 298 | default: 299 | /* not supported */ 300 | abort(); 301 | break; 302 | } 303 | 304 | /* then add relative offset */ 305 | switch (encoding & 0x70) { 306 | case DW_EH_PE_absptr: 307 | /* do nothing */ 308 | break; 309 | case DW_EH_PE_pcrel: 310 | result += (uintptr_t)(*data); 311 | break; 312 | case DW_EH_PE_textrel: 313 | case DW_EH_PE_datarel: 314 | case DW_EH_PE_funcrel: 315 | case DW_EH_PE_aligned: 316 | default: 317 | /* not supported */ 318 | abort(); 319 | break; 320 | } 321 | 322 | /* then apply indirection */ 323 | if (encoding & DW_EH_PE_indirect) { 324 | result = *((const uintptr_t*) result); 325 | } 326 | 327 | *data = p; 328 | return result; 329 | } 330 | 331 | struct lsda_header_info { 332 | _Unwind_Ptr Start; 333 | _Unwind_Ptr LPStart; 334 | _Unwind_Ptr ttype_base; 335 | const unsigned char *TType; 336 | const unsigned char *action_table; 337 | unsigned char ttype_encoding; 338 | unsigned char call_site_encoding; 339 | }; 340 | 341 | static const unsigned char * 342 | parse_lsda_header(struct _Unwind_Context *context, const unsigned char *p, 343 | struct lsda_header_info *info) { 344 | 345 | _uleb128_t tmp; 346 | unsigned char lpstart_encoding; 347 | info->Start = (context ? _Unwind_GetRegionStart(context) : 0); 348 | 349 | // Find @LPStart, the base to which landing pad offsets are relative. 350 | lpstart_encoding = *p++; 351 | if (lpstart_encoding != DW_EH_PE_omit) 352 | p = read_encoded_value(context, lpstart_encoding, p, &info->LPStart); 353 | else 354 | info->LPStart = info->Start; 355 | 356 | // Find @TType, the base of the handler and exception spec type data. 357 | info->ttype_encoding = *p++; 358 | if (info->ttype_encoding != DW_EH_PE_omit) { 359 | p = read_uleb128 (p, &tmp); 360 | info->TType = p + tmp; 361 | } else { 362 | info->TType = 0; 363 | } 364 | 365 | // The encoding and length of the call-site table; the action table 366 | // immediately follows. 367 | info->call_site_encoding = *p++; 368 | p = read_uleb128 (p, &tmp); 369 | info->action_table = p + tmp; 370 | 371 | return p; 372 | 373 | } 374 | 375 | // Runa-specific parts start here! 376 | 377 | // Keep in sync with Exception class in core/eh.rns 378 | struct RunaException { 379 | struct _Unwind_Exception header; 380 | int switch_value; 381 | const unsigned char* lsda; 382 | _Unwind_Ptr lpad; 383 | }; 384 | 385 | static inline void save_caught_exception(struct _Unwind_Exception* ue_header, 386 | int handler_switch_value, const unsigned char* lsda, _Unwind_Ptr landing_pad) { 387 | struct RunaException* exc = (struct RunaException*) ue_header; 388 | exc->switch_value = handler_switch_value; 389 | exc->lsda = lsda; 390 | exc->lpad = landing_pad; 391 | } 392 | 393 | static inline void restore_caught_exception(struct _Unwind_Exception* ue_header, 394 | int* handler_switch_value, const unsigned char** lsda, _Unwind_Ptr* landing_pad) { 395 | struct RunaException* exc = (struct RunaException*) ue_header; 396 | *handler_switch_value = exc->switch_value; 397 | *lsda = exc->lsda; 398 | *landing_pad = exc->lpad; 399 | } 400 | 401 | #define RUNA_CLASS 19507889121949010 402 | 403 | _Unwind_Reason_Code __runa_personality(int version, 404 | _Unwind_Action actions, _Unwind_Exception_Class exception_class, 405 | struct _Unwind_Exception *ue_header, struct _Unwind_Context *context) { 406 | 407 | enum found_handler_type { 408 | found_nothing, found_terminate, found_cleanup, found_handler 409 | } found_type; 410 | 411 | struct lsda_header_info info; 412 | const unsigned char *language_specific_data; 413 | const unsigned char *action_record; 414 | const unsigned char *p; 415 | _Unwind_Ptr landing_pad, ip; 416 | int handler_switch_value; 417 | void* thrown_ptr = 0; 418 | bool foreign_exception; 419 | int ip_before_insn = 0; 420 | 421 | //__cxa_exception* xh = __get_exception_header_from_ue(ue_header); 422 | 423 | // Interface version check. 424 | if (version != 1) 425 | return _URC_FATAL_PHASE1_ERROR; 426 | 427 | foreign_exception = exception_class != RUNA_CLASS; 428 | 429 | // Shortcut for phase 2 found handler for domestic exception. 430 | if (actions == (_UA_CLEANUP_PHASE | _UA_HANDLER_FRAME) && !foreign_exception) { 431 | restore_caught_exception(ue_header, &handler_switch_value, 432 | &language_specific_data, &landing_pad); 433 | found_type = (landing_pad == 0 ? found_terminate : found_handler); 434 | goto install_context; 435 | } 436 | 437 | language_specific_data = (const unsigned char *) _Unwind_GetLanguageSpecificData(context); 438 | 439 | // If no LSDA, then there are no handlers or cleanups. 440 | if (!language_specific_data) 441 | return _URC_CONTINUE_UNWIND; 442 | 443 | // Parse the LSDA header. 444 | p = parse_lsda_header(context, language_specific_data, &info); 445 | info.ttype_base = base_of_encoded_value(info.ttype_encoding, context); 446 | ip = _Unwind_GetIPInfo(context, &ip_before_insn); 447 | //ip = _Unwind_GetIP(context); 448 | if (!ip_before_insn) 449 | --ip; 450 | 451 | landing_pad = 0; 452 | action_record = 0; 453 | handler_switch_value = 0; 454 | 455 | // Search the call-site table for the action associated with this IP. 456 | while (p < info.action_table) { 457 | 458 | _Unwind_Ptr cs_start, cs_len, cs_lp; 459 | _uleb128_t cs_action; 460 | 461 | // Note that all call-site encodings are "absolute" displacements. 462 | p = read_encoded_value(0, info.call_site_encoding, p, &cs_start); 463 | p = read_encoded_value(0, info.call_site_encoding, p, &cs_len); 464 | p = read_encoded_value(0, info.call_site_encoding, p, &cs_lp); 465 | p = read_uleb128(p, &cs_action); 466 | 467 | // The table is sorted, so if we've passed the ip, stop. 468 | if (ip < info.Start + cs_start) { 469 | p = info.action_table; 470 | } else if (ip < info.Start + cs_start + cs_len) { 471 | if (cs_lp) 472 | landing_pad = info.LPStart + cs_lp; 473 | if (cs_action) 474 | action_record = info.action_table + cs_action - 1; 475 | goto found_something; 476 | } 477 | 478 | } 479 | 480 | // If ip is not present in the table, call terminate. This is for 481 | // a destructor inside a cleanup, or a library routine the compiler 482 | // was not expecting to throw. 483 | found_type = found_terminate; 484 | goto do_something; 485 | 486 | found_something: 487 | if (landing_pad == 0) { 488 | // If ip is present, and has a null landing pad, there are 489 | // no cleanups or handlers to be run. 490 | found_type = found_nothing; 491 | } else if (action_record == 0) { 492 | // If ip is present, has a non-null landing pad, and a null 493 | // action table offset, then there are only cleanups present. 494 | // Cleanups use a zero switch value, as set above. 495 | found_type = found_cleanup; 496 | } else { 497 | 498 | // Otherwise we have a catch handler or exception specification. 499 | _sleb128_t ar_filter, ar_disp; 500 | //const std::type_info* catch_type; 501 | //_throw_typet* throw_type; 502 | bool saw_cleanup = false; 503 | bool saw_handler = false; 504 | 505 | { 506 | //thrown_ptr = __get_object_from_ue(ue_header); 507 | //throw_type = __get_exception_header_from_obj(thrown_ptr)->exceptionType; 508 | } 509 | 510 | while (1) { 511 | 512 | p = action_record; 513 | p = read_sleb128(p, &ar_filter); 514 | read_sleb128(p, &ar_disp); 515 | 516 | if (ar_filter == 0) { 517 | // Zero filter values are cleanups. 518 | saw_cleanup = true; 519 | } else if (ar_filter > 0) { 520 | 521 | // Positive filter values are handlers. 522 | //catch_type = get_ttype_entry(&info, ar_filter); 523 | //printf("positive filter value -- handler found\n"); 524 | saw_handler = true; 525 | break; 526 | 527 | // Null catch type is a catch-all handler; we can catch foreign 528 | // exceptions with this. Otherwise we must match types. 529 | 530 | //if (!catch_type || (throw_type && 531 | // get_adjusted_ptr(catch_type, throw_type, &thrown_ptr))) { 532 | // saw_handler = true; 533 | // break; 534 | //} 535 | 536 | } else { 537 | 538 | printf("negative filter value -- exception spec\n"); 539 | // Negative filter values are exception specifications. 540 | // ??? How do foreign exceptions fit in? As far as I can 541 | // see we can't match because there's no __cxa_exception 542 | // object to stuff bits in for __cxa_call_unexpected to use. 543 | // Allow them iff the exception spec is non-empty. I.e. 544 | // a throw() specification results in __unexpected. 545 | 546 | //if ((throw_type && !(actions & _UA_FORCE_UNWIND) && !foreign_exception) ? 547 | // !check_exception_spec(&info, throw_type, thrown_ptr, ar_filter) : 548 | // empty_exception_spec(&info, ar_filter)) { 549 | // saw_handler = true; 550 | // break; 551 | //} 552 | 553 | } 554 | 555 | if (ar_disp == 0) 556 | break; 557 | action_record = p + ar_disp; 558 | 559 | } 560 | 561 | if (saw_handler) { 562 | handler_switch_value = ar_filter; 563 | found_type = found_handler; 564 | } else { 565 | found_type = (saw_cleanup ? found_cleanup : found_nothing); 566 | } 567 | 568 | } 569 | 570 | do_something: 571 | 572 | if (found_type == found_nothing) 573 | return _URC_CONTINUE_UNWIND; 574 | 575 | if (actions & _UA_SEARCH_PHASE) { 576 | 577 | if (found_type == found_cleanup) 578 | return _URC_CONTINUE_UNWIND; 579 | 580 | // For domestic exceptions, we cache data from phase 1 for phase 2. 581 | if (!foreign_exception) { 582 | save_caught_exception(ue_header, handler_switch_value, 583 | language_specific_data, landing_pad); 584 | } 585 | 586 | return _URC_HANDLER_FOUND; 587 | } 588 | 589 | install_context: 590 | 591 | // We can't use any of the cxa routines with foreign exceptions, 592 | // because they all expect ue_header to be a struct __cxa_exception. 593 | // So in that case, call terminate or unexpected directly. 594 | if ((actions & _UA_FORCE_UNWIND) || foreign_exception) { 595 | 596 | if (found_type == found_terminate) 597 | abort(); // std::terminate(); 598 | else if (handler_switch_value < 0) { 599 | printf("WTF\n"); 600 | //__try 601 | // { std::unexpected (); } 602 | //__catch(...) 603 | // { std::terminate (); } 604 | } 605 | 606 | } else { 607 | 608 | if (found_type == found_terminate) 609 | abort(); //__cxa_call_terminate(ue_header); 610 | 611 | // Cache the TType base value for __cxa_call_unexpected, as we won't 612 | // have an _Unwind_Context then. 613 | if (handler_switch_value < 0) { 614 | parse_lsda_header(context, language_specific_data, &info); 615 | info.ttype_base = base_of_encoded_value(info.ttype_encoding, context); 616 | //xh->catchTemp = base_of_encoded_value (info.ttype_encoding, context); 617 | } 618 | 619 | } 620 | 621 | /* For targets with pointers smaller than the word size, we must extend the 622 | pointer, and this extension is target dependent. */ 623 | _Unwind_SetGR(context, __builtin_eh_return_data_regno(0), __builtin_extend_pointer(ue_header)); 624 | _Unwind_SetGR(context, __builtin_eh_return_data_regno(1), handler_switch_value); 625 | _Unwind_SetIP(context, landing_pad); 626 | return _URC_INSTALL_CONTEXT; 627 | 628 | } 629 | -------------------------------------------------------------------------------- /core/rt.ll: -------------------------------------------------------------------------------- 1 | declare void @exit(i32); 2 | declare i8* @malloc({{ WORD }}) 3 | declare void @free(i8*) 4 | declare void @llvm.memcpy.p0i8.p0i8.{{ WORD }}(i8*, i8*, {{ WORD }}, i32, i1) 5 | declare {{ WORD }} @write(i32, i8*, {{ WORD }}) 6 | 7 | @fmt_MALLOC = constant [16 x i8] c"malloc(%ld) %p\0a\00" 8 | @fmt_FREE = constant [10 x i8] c"free(%p)\0a\00" 9 | 10 | define i8* @Runa.rt.malloc({{ WORD }} %sz) alwaysinline { 11 | %ptr = call i8* @malloc({{ WORD }} %sz) 12 | ; %fmt = getelementptr inbounds [16 x i8]* @fmt_MALLOC, i32 0, i32 0 13 | ; call i32 (i8*, ...)* @printf(i8* %fmt, {{ WORD }} %sz, i8* %ptr) 14 | ret i8* %ptr 15 | } 16 | 17 | define void @Runa.rt.free(i8* %ptr) alwaysinline { 18 | ; %fmt = getelementptr inbounds [10 x i8]* @fmt_FREE, i32 0, i32 0 19 | ; call i32 (i8*, ...)* @printf(i8* %fmt, i8* %ptr) 20 | call void @free(i8* %ptr) 21 | ret void 22 | } 23 | 24 | define i8* @Runa.rt.offset(i8* %base, {{ WORD }} %offset) alwaysinline { 25 | %i = ptrtoint i8* %base to {{ WORD }} 26 | %new = add {{ WORD }} %i, %offset 27 | %res = inttoptr {{ WORD }} %new to i8* 28 | ret i8* %res 29 | } 30 | 31 | define void @Runa.rt.memcpy(i8* %dst, i8* %src, {{ WORD }} %len) alwaysinline { 32 | call void @llvm.memcpy.p0i8.p0i8.{{ WORD }}(i8* %dst, i8* %src, {{ WORD }} %len, i32 1, i1 0) 33 | ret void 34 | } 35 | 36 | %Str = type { {{ WORD }}, i8* } 37 | @Str.size = external constant {{ WORD }} 38 | %Array$Str = type { {{ WORD }}, [0 x %Str] } 39 | declare void @Runa.core.Str.__init__$RStr.Obyte(%Str* %self, i8* %data) uwtable 40 | 41 | define %Array$Str* @Runa.rt.args(i32 %argc, i8** %argv) { 42 | 43 | %c64 = sext i32 %argc to {{ WORD }} 44 | %num = sub {{ WORD }} %c64, 1 45 | 46 | %str.size = load {{ WORD }}* @Str.size 47 | %arsz = mul {{ WORD }} %num, %str.size 48 | %objsz = add {{ WORD }} {{ BYTES }}, %arsz 49 | %array.raw = call i8* @Runa.rt.malloc({{ WORD }} %objsz) 50 | %array = bitcast i8* %array.raw to %Array$Str* 51 | 52 | %array.data = getelementptr %Array$Str* %array, i32 0, i32 1 53 | %array.len = getelementptr %Array$Str* %array, i32 0, i32 0 54 | store {{ WORD }} %num, {{ WORD }}* %array.len 55 | 56 | %itervar = alloca {{ WORD }} 57 | store {{ WORD }} 0, {{ WORD }}* %itervar 58 | %first = icmp sgt {{ WORD }} %num, 0 59 | br i1 %first, label %Body, label %Done 60 | 61 | Body: 62 | 63 | %idx = load {{ WORD }}* %itervar 64 | %orig.idx = add {{ WORD }} %idx, 1 65 | %arg.ptr = getelementptr inbounds i8** %argv, {{ WORD }} %orig.idx 66 | %arg = load i8** %arg.ptr 67 | 68 | %obj = getelementptr [0 x %Str]* %array.data, i32 0, {{ WORD }} %idx 69 | call void @Runa.core.Str.__init__$RStr.Obyte(%Str* %obj, i8* %arg) 70 | 71 | %next = add {{ WORD }} %idx, 1 72 | store {{ WORD }} %next, {{ WORD }}* %itervar 73 | %more = icmp slt {{ WORD }} %next, %num 74 | br i1 %more, label %Body, label %Done 75 | 76 | Done: 77 | ret %Array$Str* %array 78 | 79 | } 80 | 81 | %UnwEx = type { i64, i8*, i64, i64 } 82 | %Exception = type { %UnwEx, i32, i8*, i8*, %Str* } 83 | %UnwExClean = type void (i32, %UnwEx*)* 84 | 85 | declare i32 @_Unwind_RaiseException(%UnwEx*) 86 | 87 | @ExcErr = constant [44 x i8] c"!!! Runa: error while raising exception: %i\0a" 88 | @ForeignExc = constant [35 x i8] c"!!! Runa: foreign exception caught\0a" 89 | @Unhandled = constant [21 x i8] c"Unhandled Exception: " 90 | @NL = constant [1 x i8] c"\0a" 91 | 92 | define void @Runa.rt.unhandled(%Exception* %exc) { 93 | %prefix = getelementptr inbounds [21 x i8]* @Unhandled, i32 0, i32 0 94 | call {{ WORD }} @write(i32 2, i8* %prefix, {{ WORD }} 21) 95 | %msg.slot = getelementptr %Exception* %exc, i32 0, i32 4 96 | %msg = load %Str** %msg.slot 97 | %msg.data.slot = getelementptr %Str* %msg, i32 0, i32 1 98 | %msg.data = load i8** %msg.data.slot 99 | %msg.len.slot = getelementptr %Str* %msg, i32 0, i32 0 100 | %msg.len = load {{ WORD }}* %msg.len.slot 101 | call {{ WORD }} @write(i32 2, i8* %msg.data, {{ WORD }} %msg.len) 102 | %nl = getelementptr inbounds [1 x i8]* @NL, i32 0, i32 0 103 | call {{ WORD }} @write(i32 2, i8* %nl, {{ WORD }} 1) 104 | ret void 105 | } 106 | 107 | define void @Runa.rt.clean(i32 %reason, %UnwEx* %exc) { 108 | %cond = icmp eq i32 %reason, 1 ; _URC_FOREIGN_EXCEPTION_CAUGHT 109 | br i1 %cond, label %Foreign, label %Normal 110 | Foreign: 111 | %msg = getelementptr inbounds [35 x i8]* @ForeignExc, i32 0, i32 0 112 | call {{ WORD }} @write(i32 2, i8* %msg, {{ WORD }} 35) 113 | call void @exit(i32 1) 114 | ret void 115 | Normal: 116 | %bland = bitcast %UnwEx* %exc to i8* 117 | call void @free(i8* %bland) 118 | ret void 119 | } 120 | 121 | define void @Runa.rt.raise(%Exception* %obj) { 122 | %exc = bitcast %Exception* %obj to %UnwEx* 123 | %class = getelementptr %UnwEx* %exc, i32 0, i32 0 124 | store i64 19507889121949010, i64* %class ; 'RunaRNE\x00' 125 | %slot = getelementptr %UnwEx* %exc, i32 0, i32 1 126 | %clean = bitcast %UnwExClean @Runa.rt.clean to i8* 127 | store i8* %clean, i8** %slot 128 | %err = call i32 @_Unwind_RaiseException(%UnwEx* %exc) 129 | %cond = icmp eq i32 %err, 5 ; _URC_END_OF_STACK 130 | br i1 %cond, label %Unhandled, label %Other 131 | Unhandled: 132 | call void @Runa.rt.unhandled(%Exception* %obj) 133 | br label %End 134 | Other: 135 | %msg = getelementptr inbounds [44 x i8]* @ExcErr, i32 0, i32 0 136 | call {{ WORD }} @write(i32 2, i8* %msg, {{ WORD }} 44) 137 | br label %End 138 | End: 139 | call void @exit(i32 1) 140 | ret void 141 | } 142 | -------------------------------------------------------------------------------- /core/unwind.h: -------------------------------------------------------------------------------- 1 | /* libunwind - a platform-independent unwind library 2 | Copyright (C) 2003 Hewlett-Packard Co 3 | Contributed by David Mosberger-Tang 4 | 5 | This file is part of libunwind. 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining 8 | a copy of this software and associated documentation files (the 9 | "Software"), to deal in the Software without restriction, including 10 | without limitation the rights to use, copy, modify, merge, publish, 11 | distribute, sublicense, and/or sell copies of the Software, and to 12 | permit persons to whom the Software is furnished to do so, subject to 13 | the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 22 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 24 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ 25 | 26 | #ifndef _UNWIND_H 27 | #define _UNWIND_H 28 | 29 | /* For uint64_t */ 30 | #include 31 | 32 | #ifdef __cplusplus 33 | extern "C" { 34 | #endif 35 | 36 | /* Minimal interface as per C++ ABI draft standard: 37 | 38 | http://www.codesourcery.com/cxx-abi/abi-eh.html */ 39 | 40 | typedef enum 41 | { 42 | _URC_NO_REASON = 0, 43 | _URC_FOREIGN_EXCEPTION_CAUGHT = 1, 44 | _URC_FATAL_PHASE2_ERROR = 2, 45 | _URC_FATAL_PHASE1_ERROR = 3, 46 | _URC_NORMAL_STOP = 4, 47 | _URC_END_OF_STACK = 5, 48 | _URC_HANDLER_FOUND = 6, 49 | _URC_INSTALL_CONTEXT = 7, 50 | _URC_CONTINUE_UNWIND = 8 51 | } 52 | _Unwind_Reason_Code; 53 | 54 | typedef int _Unwind_Action; 55 | 56 | #define _UA_SEARCH_PHASE 1 57 | #define _UA_CLEANUP_PHASE 2 58 | #define _UA_HANDLER_FRAME 4 59 | #define _UA_FORCE_UNWIND 8 60 | 61 | struct _Unwind_Context; /* opaque data-structure */ 62 | struct _Unwind_Exception; /* forward-declaration */ 63 | 64 | typedef void (*_Unwind_Exception_Cleanup_Fn) (_Unwind_Reason_Code, 65 | struct _Unwind_Exception *); 66 | 67 | typedef _Unwind_Reason_Code (*_Unwind_Stop_Fn) (int, _Unwind_Action, 68 | uint64_t, 69 | struct _Unwind_Exception *, 70 | struct _Unwind_Context *, 71 | void *); 72 | 73 | /* The C++ ABI requires exception_class, private_1, and private_2 to 74 | be of type uint64 and the entire structure to be 75 | double-word-aligned. Please note that exception_class stays 64-bit 76 | even on 32-bit machines for gcc compatibility. */ 77 | struct _Unwind_Exception 78 | { 79 | uint64_t exception_class; 80 | _Unwind_Exception_Cleanup_Fn exception_cleanup; 81 | unsigned long private_1; 82 | unsigned long private_2; 83 | } __attribute__((__aligned__)); 84 | 85 | extern _Unwind_Reason_Code _Unwind_RaiseException (struct _Unwind_Exception *); 86 | extern _Unwind_Reason_Code _Unwind_ForcedUnwind (struct _Unwind_Exception *, 87 | _Unwind_Stop_Fn, void *); 88 | extern void _Unwind_Resume (struct _Unwind_Exception *); 89 | extern void _Unwind_DeleteException (struct _Unwind_Exception *); 90 | extern unsigned long _Unwind_GetGR (struct _Unwind_Context *, int); 91 | extern void _Unwind_SetGR (struct _Unwind_Context *, int, unsigned long); 92 | extern unsigned long _Unwind_GetIP (struct _Unwind_Context *); 93 | extern unsigned long _Unwind_GetIPInfo (struct _Unwind_Context *, int *); 94 | extern void _Unwind_SetIP (struct _Unwind_Context *, unsigned long); 95 | extern unsigned long _Unwind_GetLanguageSpecificData (struct _Unwind_Context*); 96 | extern unsigned long _Unwind_GetRegionStart (struct _Unwind_Context *); 97 | 98 | #ifdef _GNU_SOURCE 99 | 100 | /* Callback for _Unwind_Backtrace(). The backtrace stops immediately 101 | if the callback returns any value other than _URC_NO_REASON. */ 102 | typedef _Unwind_Reason_Code (*_Unwind_Trace_Fn) (struct _Unwind_Context *, 103 | void *); 104 | 105 | /* See http://gcc.gnu.org/ml/gcc-patches/2001-09/msg00082.html for why 106 | _UA_END_OF_STACK exists. */ 107 | # define _UA_END_OF_STACK 16 108 | 109 | /* If the unwind was initiated due to a forced unwind, resume that 110 | operation, else re-raise the exception. This is used by 111 | __cxa_rethrow(). */ 112 | extern _Unwind_Reason_Code 113 | _Unwind_Resume_or_Rethrow (struct _Unwind_Exception *); 114 | 115 | /* See http://gcc.gnu.org/ml/gcc-patches/2003-09/msg00154.html for why 116 | _Unwind_GetBSP() exists. */ 117 | extern unsigned long _Unwind_GetBSP (struct _Unwind_Context *); 118 | 119 | /* Return the "canonical frame address" for the given context. 120 | This is used by NPTL... */ 121 | extern unsigned long _Unwind_GetCFA (struct _Unwind_Context *); 122 | 123 | /* Return the base-address for data references. */ 124 | extern unsigned long _Unwind_GetDataRelBase (struct _Unwind_Context *); 125 | 126 | /* Return the base-address for text references. */ 127 | extern unsigned long _Unwind_GetTextRelBase (struct _Unwind_Context *); 128 | 129 | /* Call _Unwind_Trace_Fn once for each stack-frame, without doing any 130 | cleanup. The first frame for which the callback is invoked is the 131 | one for the caller of _Unwind_Backtrace(). _Unwind_Backtrace() 132 | returns _URC_END_OF_STACK when the backtrace stopped due to 133 | reaching the end of the call-chain or _URC_FATAL_PHASE1_ERROR if it 134 | stops for any other reason. */ 135 | extern _Unwind_Reason_Code _Unwind_Backtrace (_Unwind_Trace_Fn, void *); 136 | 137 | /* Find the start-address of the procedure containing the specified IP 138 | or NULL if it cannot be found (e.g., because the function has no 139 | unwind info). Note: there is not necessarily a one-to-one 140 | correspondence between source-level functions and procedures: some 141 | functions don't have unwind-info and others are split into multiple 142 | procedures. */ 143 | extern void *_Unwind_FindEnclosingFunction (void *); 144 | 145 | /* See also Linux Standard Base Spec: 146 | http://www.linuxbase.org/spec/refspecs/LSB_1.3.0/gLSB/gLSB/libgcc-s.html */ 147 | 148 | #endif /* _GNU_SOURCE */ 149 | 150 | #ifdef __cplusplus 151 | }; 152 | #endif 153 | 154 | #endif /* _UNWIND_H */ 155 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | 49 | clean: 50 | rm -rf $(BUILDDIR)/* 51 | 52 | grammar.rst: grammar.py ../runac/parser.py 53 | python grammar.py > $@ 54 | 55 | html: grammar.rst 56 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 57 | @echo 58 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 59 | 60 | dirhtml: 61 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 62 | @echo 63 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 64 | 65 | singlehtml: 66 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 67 | @echo 68 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 69 | 70 | pickle: 71 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 72 | @echo 73 | @echo "Build finished; now you can process the pickle files." 74 | 75 | json: 76 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 77 | @echo 78 | @echo "Build finished; now you can process the JSON files." 79 | 80 | htmlhelp: 81 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 82 | @echo 83 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 84 | ".hhp project file in $(BUILDDIR)/htmlhelp." 85 | 86 | qthelp: 87 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 88 | @echo 89 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 90 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 91 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Runa.qhcp" 92 | @echo "To view the help file:" 93 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Runa.qhc" 94 | 95 | devhelp: 96 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 97 | @echo 98 | @echo "Build finished." 99 | @echo "To view the help file:" 100 | @echo "# mkdir -p $$HOME/.local/share/devhelp/Runa" 101 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Runa" 102 | @echo "# devhelp" 103 | 104 | epub: 105 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 106 | @echo 107 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 108 | 109 | latex: 110 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 111 | @echo 112 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 113 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 114 | "(use \`make latexpdf' here to do that automatically)." 115 | 116 | latexpdf: 117 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 118 | @echo "Running LaTeX files through pdflatex..." 119 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 120 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 121 | 122 | latexpdfja: 123 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 124 | @echo "Running LaTeX files through platex and dvipdfmx..." 125 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 126 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 127 | 128 | text: 129 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 130 | @echo 131 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 132 | 133 | man: 134 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 135 | @echo 136 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 137 | 138 | texinfo: 139 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 140 | @echo 141 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 142 | @echo "Run \`make' in that directory to run these through makeinfo" \ 143 | "(use \`make info' here to do that automatically)." 144 | 145 | info: 146 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 147 | @echo "Running Texinfo files through makeinfo..." 148 | make -C $(BUILDDIR)/texinfo info 149 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 150 | 151 | gettext: 152 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 153 | @echo 154 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 155 | 156 | changes: 157 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 158 | @echo 159 | @echo "The overview file is in $(BUILDDIR)/changes." 160 | 161 | linkcheck: 162 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 163 | @echo 164 | @echo "Link check complete; look for any errors in the above output " \ 165 | "or in $(BUILDDIR)/linkcheck/output.txt." 166 | 167 | doctest: 168 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 169 | @echo "Testing of doctests in the sources finished, look at the " \ 170 | "results in $(BUILDDIR)/doctest/output.txt." 171 | 172 | xml: 173 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 174 | @echo 175 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 176 | 177 | pseudoxml: 178 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 179 | @echo 180 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 181 | -------------------------------------------------------------------------------- /doc/_themes/runa/layout.html: -------------------------------------------------------------------------------- 1 | {%- block doctype -%} 2 | 3 | {%- endblock -%} 4 | 5 | {%- set reldelim1 = reldelim1 is not defined and ' »' or reldelim1 %} 6 | {%- set reldelim2 = reldelim2 is not defined and ' |' or reldelim2 %} 7 | {%- set render_sidebar = (not embedded) and (not theme_nosidebar|tobool) and 8 | (sidebars != []) %} 9 | {%- set url_root = pathto('', 1) %} 10 | {# XXX necessary? #} 11 | {%- if url_root == '#' %}{% set url_root = '' %}{% endif %} 12 | {%- if not embedded and docstitle %} 13 | {%- set titlesuffix = " — "|safe + docstitle|e %} 14 | {%- else %} 15 | {%- set titlesuffix = "" %} 16 | {%- endif %} 17 | 18 | {%- macro relbar() %} 19 | 20 | {{ _('Navigation') }} 21 | 22 | {%- for rellink in rellinks %} 23 | 24 | {{ rellink[3] }} 26 | {%- if not loop.first %}{{ reldelim2 }}{% endif %} 27 | {%- endfor %} 28 | {%- block rootrellink %} 29 | {{ shorttitle|e }}{{ reldelim1 }} 30 | {%- endblock %} 31 | {%- for parent in parents %} 32 | {{ parent.title }}{{ reldelim1 }} 33 | {%- endfor %} 34 | {%- block relbaritems %} {% endblock %} 35 | 36 | 37 | {%- endmacro %} 38 | 39 | {%- macro sidebar() %} 40 | {%- if render_sidebar %} 41 | 42 | 43 | {%- block sidebarlogo %} 44 | {%- if logo %} 45 | 46 | 47 | 48 | {%- endif %} 49 | {%- endblock %} 50 | {%- if sidebars != None %} 51 | {#- new style sidebar: explicitly include/exclude templates #} 52 | {%- for sidebartemplate in sidebars %} 53 | {%- include sidebartemplate %} 54 | {%- endfor %} 55 | {%- else %} 56 | {#- old style sidebars: using blocks -- should be deprecated #} 57 | {%- block sidebartoc %} 58 | {%- include "localtoc.html" %} 59 | {%- endblock %} 60 | {%- block sidebarrel %} 61 | {# include "relations.html" #} 62 | {%- endblock %} 63 | {%- block sidebarsourcelink %} 64 | {%- include "sourcelink.html" %} 65 | {%- endblock %} 66 | {%- if customsidebar %} 67 | {%- include customsidebar %} 68 | {%- endif %} 69 | {%- block sidebarsearch %} 70 | {%- include "searchbox.html" %} 71 | {%- endblock %} 72 | {%- endif %} 73 | 74 | 75 | {%- endif %} 76 | {%- endmacro %} 77 | 78 | {%- macro script() %} 79 | 88 | {%- for scriptfile in script_files %} 89 | 90 | {%- endfor %} 91 | {%- endmacro %} 92 | 93 | {%- macro css() %} 94 | 95 | 96 | {%- for cssfile in css_files %} 97 | 98 | {%- endfor %} 99 | {%- endmacro -%} 100 | 101 | 102 | 103 | {%- block htmltitle %} 104 | {{ title|striptags|e }}{{ titlesuffix }} 105 | {%- endblock %} 106 | 107 | {{ metatags }} 108 | 109 | {{ css() }} 110 | {%- if not embedded %} 111 | {{ script() }} 112 | {%- if use_opensearch %} 113 | 116 | {%- endif %} 117 | {%- if favicon %} 118 | 119 | {%- endif %} 120 | {%- endif %} 121 | {%- block linktags %} 122 | {%- if hasdoc('about') %} 123 | 124 | {%- endif %} 125 | {%- if hasdoc('genindex') %} 126 | 127 | {%- endif %} 128 | {%- if hasdoc('search') %} 129 | 130 | {%- endif %} 131 | {%- if hasdoc('copyright') %} 132 | 133 | {%- endif %} 134 | 135 | {%- if parents %} 136 | 137 | {%- endif %} 138 | {%- if next %} 139 | 140 | {%- endif %} 141 | {%- if prev %} 142 | 143 | {%- endif %} 144 | {%- endblock %} 145 | {%- block extrahead %} {% endblock %} 146 | 147 | 148 | 149 | 150 | Runa 151 | a Python-like systems language 152 | 153 | 154 | 155 | {% block header %}{% endblock %} 156 | 157 | {%- block relbar1 %}{% endblock %} 158 | 159 | {%- block content %} 160 | {%- block sidebar1 %} {# possible location for sidebar #} {% endblock %} 161 | 162 | 163 | {%- block document %} 164 | 165 | {%- if render_sidebar %} 166 | 167 | {%- endif %} 168 | 169 | {% block body %} {% endblock %} 170 | 171 | {%- if render_sidebar %} 172 | 173 | {%- endif %} 174 | 175 | {%- endblock %} 176 | 177 | {%- block sidebar2 %}{{ sidebar() }}{% endblock %} 178 | 179 | 180 | {%- endblock %} 181 | 182 | {%- block relbar2 %}{% endblock %} 183 | 184 | {%- block footer %} 185 | 200 | {%- endblock %} 201 | 202 | 210 | 211 | 212 | -------------------------------------------------------------------------------- /doc/_themes/runa/searchbox.html: -------------------------------------------------------------------------------- 1 | {%- if pagename != "search" and builder != "singlehtml" %} 2 | 3 | {{ _('Quick search') }} 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | {%- endif %} 13 | -------------------------------------------------------------------------------- /doc/_themes/runa/static/runa.css_t: -------------------------------------------------------------------------------- 1 | /* Color theme: 2 | http://paletton.com/#uid=13J0u0ksIM7d8ZwlWTX-TtPy5b+ 3 | */ 4 | 5 | html, body, div, p, footer, header, nav, li { 6 | box-sizing: border-box; 7 | font-family: 'PT Sans', sans-serif; 8 | } 9 | 10 | html, body { 11 | margin: 0; 12 | padding: 0; 13 | } 14 | 15 | a, a:visited { 16 | color: #009; 17 | text-decoration: none; 18 | } 19 | 20 | a:hover, a:active { 21 | color: #900; 22 | } 23 | 24 | tt, code { 25 | font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; 26 | font-size: 11pt; 27 | padding: 0.1em 0.3em; 28 | font-size: 85%; 29 | background: rgba(0, 0, 0, 0.08); 30 | } 31 | 32 | .container { 33 | margin: 0px auto; 34 | width: 800px; 35 | } 36 | 37 | header { 38 | height: 80px; 39 | background: #2964d7; 40 | padding-top: 10px; 41 | } 42 | 43 | header h1 { 44 | margin: 0; 45 | color: #9ebefb; 46 | font-size: 3em; 47 | padding: 10px 0 0 0; 48 | display: inline; 49 | } 50 | 51 | header h1 a, header h1 a:visited { 52 | color: #9ebefb; 53 | } 54 | 55 | header span { 56 | color: #9ebefb; 57 | padding-left: 30px; 58 | } 59 | 60 | footer { 61 | text-align: center; 62 | font-size: 0.9em; 63 | color: #999; 64 | clear: both; 65 | padding-right: 200px; 66 | padding-bottom: 20px; 67 | padding-top: 20px; 68 | } 69 | 70 | footer a, footer a:visited { 71 | color: #99f; 72 | } 73 | 74 | a.headerlink { 75 | visibility: hidden; 76 | padding-left: 5px; 77 | } 78 | 79 | h1:hover > a.headerlink, h2:hover > a.headerlink, h3:hover > a.headerlink, 80 | h4:hover > a.headerlink, h5:hover > a.headerlink, h6:hover > a.headerlink, 81 | dt:hover > a.headerlink { 82 | visibility: visible; 83 | } 84 | 85 | div.documentwrapper { 86 | float: left; 87 | width: 600px; 88 | padding-right: 20px; 89 | } 90 | 91 | div.documentwrapper ul { 92 | padding-left: 20px; 93 | } 94 | 95 | nav { 96 | background: #9ebefb; 97 | float: right; 98 | width: 200px; 99 | padding: 15px; 100 | } 101 | 102 | nav h3, nav h4 { 103 | margin: 5px 0; 104 | } 105 | 106 | div.sphinxsidebarwrapper > ul { 107 | margin: 5px; 108 | } 109 | 110 | nav ul { 111 | margin: 0; 112 | padding: 0 15px; 113 | } 114 | 115 | nav li { 116 | font-size: 11pt; 117 | } 118 | 119 | nav div#searchbox { 120 | margin-top: 10px; 121 | } 122 | 123 | nav div#searchbox h3 { 124 | margin-bottom: 10px; 125 | } 126 | 127 | table { 128 | cell-spacing: 0; 129 | border: 1px solid #ddd; 130 | border-collapse: collapse; 131 | } 132 | 133 | td { 134 | border: 1px solid #ddd; 135 | padding: 5px 7px 5px 8px; 136 | } 137 | 138 | div.body ol li { 139 | margin-bottom: 5px; 140 | } 141 | -------------------------------------------------------------------------------- /doc/_themes/runa/theme.conf: -------------------------------------------------------------------------------- 1 | [theme] 2 | inherit = basic 3 | stylesheet = runa.css 4 | -------------------------------------------------------------------------------- /doc/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Runa documentation build configuration file, created by 4 | # sphinx-quickstart on Fri Jan 2 14:15:31 2015. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import sys 16 | import os 17 | dir = os.path.dirname(__file__) 18 | sys.path.insert(0, os.path.abspath(os.path.join(dir, '..'))) 19 | 20 | # If extensions (or modules to document with autodoc) are in another directory, 21 | # add these directories to sys.path here. If the directory is relative to the 22 | # documentation root, use os.path.abspath to make it absolute, like shown here. 23 | #sys.path.insert(0, os.path.abspath('.')) 24 | 25 | # -- General configuration ------------------------------------------------ 26 | 27 | # If your documentation needs a minimal Sphinx version, state it here. 28 | #needs_sphinx = '1.0' 29 | 30 | # Add any Sphinx extension module names here, as strings. They can be 31 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 32 | # ones. 33 | extensions = ['sphinx.ext.autodoc'] 34 | 35 | # Add any paths that contain templates here, relative to this directory. 36 | templates_path = ['_templates'] 37 | 38 | # The suffix of source filenames. 39 | source_suffix = '.rst' 40 | 41 | # The encoding of source files. 42 | #source_encoding = 'utf-8-sig' 43 | 44 | # The master toctree document. 45 | master_doc = 'index' 46 | 47 | # General information about the project. 48 | project = u'Runa' 49 | copyright = u'2015, Dirkjan Ochtman' 50 | 51 | # The version info for the project you're documenting, acts as replacement for 52 | # |version| and |release|, also used in various other places throughout the 53 | # built documents. 54 | # 55 | # The short X.Y version. 56 | version = '0.1' 57 | # The full version, including alpha/beta/rc tags. 58 | release = '0.1' 59 | 60 | # The language for content autogenerated by Sphinx. Refer to documentation 61 | # for a list of supported languages. 62 | #language = None 63 | 64 | # There are two options for replacing |today|: either, you set today to some 65 | # non-false value, then it is used: 66 | #today = '' 67 | # Else, today_fmt is used as the format for a strftime call. 68 | #today_fmt = '%B %d, %Y' 69 | 70 | # List of patterns, relative to source directory, that match files and 71 | # directories to ignore when looking for source files. 72 | exclude_patterns = ['_build'] 73 | 74 | # The reST default role (used for this markup: `text`) to use for all 75 | # documents. 76 | #default_role = None 77 | 78 | # If true, '()' will be appended to :func: etc. cross-reference text. 79 | #add_function_parentheses = True 80 | 81 | # If true, the current module name will be prepended to all description 82 | # unit titles (such as .. function::). 83 | #add_module_names = True 84 | 85 | # If true, sectionauthor and moduleauthor directives will be shown in the 86 | # output. They are ignored by default. 87 | #show_authors = False 88 | 89 | # The name of the Pygments (syntax highlighting) style to use. 90 | pygments_style = 'sphinx' 91 | 92 | # A list of ignored prefixes for module index sorting. 93 | #modindex_common_prefix = [] 94 | 95 | # If true, keep warnings as "system message" paragraphs in the built documents. 96 | #keep_warnings = False 97 | 98 | 99 | # -- Options for HTML output ---------------------------------------------- 100 | 101 | # The theme to use for HTML and HTML Help pages. See the documentation for 102 | # a list of builtin themes. 103 | html_theme = 'runa' 104 | 105 | # Theme options are theme-specific and customize the look and feel of a theme 106 | # further. For a list of options available for each theme, see the 107 | # documentation. 108 | #html_theme_options = {} 109 | 110 | # Add any paths that contain custom themes here, relative to this directory. 111 | html_theme_path = ['_themes'] 112 | 113 | # The name for this set of Sphinx documents. If None, it defaults to 114 | # " v documentation". 115 | #html_title = None 116 | 117 | # A shorter title for the navigation bar. Default is the same as html_title. 118 | #html_short_title = None 119 | 120 | # The name of an image file (relative to this directory) to place at the top 121 | # of the sidebar. 122 | #html_logo = None 123 | 124 | # The name of an image file (within the static path) to use as favicon of the 125 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 126 | # pixels large. 127 | #html_favicon = None 128 | 129 | # Add any paths that contain custom static files (such as style sheets) here, 130 | # relative to this directory. They are copied after the builtin static files, 131 | # so a file named "default.css" will overwrite the builtin "default.css". 132 | html_static_path = ['_static'] 133 | 134 | # Add any extra paths that contain custom files (such as robots.txt or 135 | # .htaccess) here, relative to this directory. These files are copied 136 | # directly to the root of the documentation. 137 | #html_extra_path = [] 138 | 139 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 140 | # using the given strftime format. 141 | #html_last_updated_fmt = '%b %d, %Y' 142 | 143 | # If true, SmartyPants will be used to convert quotes and dashes to 144 | # typographically correct entities. 145 | #html_use_smartypants = True 146 | 147 | # Custom sidebar templates, maps document names to template names. 148 | #html_sidebars = {} 149 | 150 | # Additional templates that should be rendered to pages, maps page names to 151 | # template names. 152 | #html_additional_pages = {} 153 | 154 | # If false, no module index is generated. 155 | #html_domain_indices = True 156 | 157 | # If false, no index is generated. 158 | #html_use_index = True 159 | 160 | # If true, the index is split into individual pages for each letter. 161 | #html_split_index = False 162 | 163 | # If true, links to the reST sources are added to the pages. 164 | #html_show_sourcelink = True 165 | 166 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 167 | #html_show_sphinx = True 168 | 169 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 170 | #html_show_copyright = True 171 | 172 | # If true, an OpenSearch description file will be output, and all pages will 173 | # contain a tag referring to it. The value of this option must be the 174 | # base URL from which the finished HTML is served. 175 | #html_use_opensearch = '' 176 | 177 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 178 | #html_file_suffix = None 179 | 180 | # Output file base name for HTML help builder. 181 | htmlhelp_basename = 'Runadoc' 182 | 183 | 184 | # -- Options for LaTeX output --------------------------------------------- 185 | 186 | latex_elements = { 187 | # The paper size ('letterpaper' or 'a4paper'). 188 | #'papersize': 'letterpaper', 189 | 190 | # The font size ('10pt', '11pt' or '12pt'). 191 | #'pointsize': '10pt', 192 | 193 | # Additional stuff for the LaTeX preamble. 194 | #'preamble': '', 195 | } 196 | 197 | # Grouping the document tree into LaTeX files. List of tuples 198 | # (source start file, target name, title, 199 | # author, documentclass [howto, manual, or own class]). 200 | latex_documents = [ 201 | ('index', 'Runa.tex', u'Runa Documentation', 202 | u'Dirkjan Ochtman', 'manual'), 203 | ] 204 | 205 | # The name of an image file (relative to this directory) to place at the top of 206 | # the title page. 207 | #latex_logo = None 208 | 209 | # For "manual" documents, if this is true, then toplevel headings are parts, 210 | # not chapters. 211 | #latex_use_parts = False 212 | 213 | # If true, show page references after internal links. 214 | #latex_show_pagerefs = False 215 | 216 | # If true, show URL addresses after external links. 217 | #latex_show_urls = False 218 | 219 | # Documents to append as an appendix to all manuals. 220 | #latex_appendices = [] 221 | 222 | # If false, no module index is generated. 223 | #latex_domain_indices = True 224 | 225 | 226 | # -- Options for manual page output --------------------------------------- 227 | 228 | # One entry per manual page. List of tuples 229 | # (source start file, name, description, authors, manual section). 230 | man_pages = [ 231 | ('index', 'runa', u'Runa Documentation', 232 | [u'Dirkjan Ochtman'], 1) 233 | ] 234 | 235 | # If true, show URL addresses after external links. 236 | #man_show_urls = False 237 | 238 | 239 | # -- Options for Texinfo output ------------------------------------------- 240 | 241 | # Grouping the document tree into Texinfo files. List of tuples 242 | # (source start file, target name, title, author, 243 | # dir menu entry, description, category) 244 | texinfo_documents = [ 245 | ('index', 'Runa', u'Runa Documentation', 246 | u'Dirkjan Ochtman', 'Runa', 'One line description of project.', 247 | 'Miscellaneous'), 248 | ] 249 | 250 | # Documents to append as an appendix to all manuals. 251 | #texinfo_appendices = [] 252 | 253 | # If false, no module index is generated. 254 | #texinfo_domain_indices = True 255 | 256 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 257 | #texinfo_show_urls = 'footnote' 258 | 259 | # If true, do not generate a @detailmenu in the "Top" node's menu. 260 | #texinfo_no_detailmenu = False 261 | -------------------------------------------------------------------------------- /doc/grammar.py: -------------------------------------------------------------------------------- 1 | import ast, _ast, sys, collections 2 | sys.path.append('..') 3 | from runac import parser 4 | 5 | TITLE = 'Language grammar' 6 | PARSER_FILE = '../runac/parser.py' 7 | 8 | INTRO = '''The table below (which is generated from the parser's source code) 9 | can serve as a guide to Runa's grammar. 10 | Code literals in rules represent regular expressions. 11 | The special INDENT and DEDENT tokens are inserted by a secondary pass, 12 | after the initial tokenization of source code; 13 | they represent the increase and decrease of the indentation level.''' 14 | 15 | def get_rules(): 16 | 17 | with open(PARSER_FILE) as f: 18 | src = f.read() 19 | 20 | rules = collections.OrderedDict() 21 | for node in ast.parse(src).body: 22 | 23 | if not isinstance(node, _ast.FunctionDef): 24 | continue 25 | 26 | if not node.decorator_list: 27 | continue 28 | 29 | assert len(node.decorator_list) == 1 30 | decorator = node.decorator_list[0] 31 | if not isinstance(decorator, _ast.Call): 32 | continue 33 | 34 | func = decorator.func 35 | if not isinstance(func, _ast.Attribute): 36 | continue 37 | 38 | assert func.attr == 'production' 39 | ln = decorator.args[0].s 40 | name, match = ln.split(' : ', 1) 41 | rules.setdefault(name, []).append(tuple(match.split())) 42 | 43 | return rules 44 | 45 | def get_tokens(): 46 | 47 | tokens = {'INDENT': 'INDENT', 'DEDENT': 'DEDENT'} 48 | for rule in parser.LEXER.rules: 49 | name, pattern = rule.name, rule.re.pattern 50 | tokens[rule.name] = rule.re.pattern 51 | 52 | for word in parser.NAME_LIKE: 53 | tokens[word.upper()] = word 54 | 55 | return tokens 56 | 57 | def main(): 58 | 59 | rules, tokens = get_rules(), get_tokens() 60 | lines, columns = [], [0, 0] 61 | for name, expands in rules.iteritems(): 62 | columns[0] = max(columns[0], len(name)) 63 | for i, expand in enumerate(expands): 64 | 65 | bits = list(expand) 66 | for idx, s in enumerate(expand): 67 | if s.upper() == s: 68 | bits[idx] = '``' + tokens[s] + '``' 69 | 70 | defn = ' '.join(bits) 71 | columns[1] = max(columns[1], len(defn)) 72 | lines.append((name if not i else '', defn)) 73 | 74 | separator = ''.join(( 75 | '+', 76 | '-' * (columns[0] + 2), 77 | '+', 78 | '-' * (columns[1] + 2), 79 | '+', 80 | )) 81 | fmt = '| %%-%is | %%-%is |' % tuple(columns) 82 | 83 | print '*' * len(TITLE) 84 | print TITLE 85 | print '*' * len(TITLE) 86 | print 87 | print INTRO 88 | print 89 | 90 | for i, ln in enumerate(lines): 91 | print separator 92 | print fmt % ln 93 | print separator 94 | 95 | if __name__ == '__main__': 96 | main() 97 | -------------------------------------------------------------------------------- /doc/hacking.rst: -------------------------------------------------------------------------------- 1 | ******************** 2 | Hacking the compiler 3 | ******************** 4 | 5 | Here are some pointers if you want to read some source code. 6 | The compiler driver is in ``runac/__main__.py``: 7 | it's a small script implementing a few useful commands. 8 | The code actually driving the compiler is in ``runac/__init__.py``. 9 | Here you can see the lexer, parser, transformation passes, codegen, 10 | and compilation of LLVM IR to machine code being done. 11 | The general structure is like this: 12 | 13 | 1. Parser phase (includes lexing and parsing), in ``runac/parser.py`` 14 | 2. :ref:`blocks`, in ``runac/blocks.py`` 15 | 3. Transformation passes: 16 | 17 | a. :ref:`liveness`, in ``runac/liveness.py`` 18 | b. :ref:`typer`, in ``runac/typer.py`` 19 | c. :ref:`specialize`, in ``runac/specialize.py`` 20 | d. :ref:`escapes`, in ``runac/escapes.py`` 21 | e. :ref:`destructor`, in ``runac/destructor.py`` 22 | 23 | 4. :ref:`codegen`, in ``runac/codegen.py`` 24 | 25 | The parser, which is based on rply, returns an AST (node classes in 26 | ``runac/ast.py``). This gets processed by the AST walker in 27 | ``runac/blocks.py`` to get to a control flow graph with shallow basic blocks: 28 | all expressions are flattened into a single statement, with assignment to 29 | temporary variables, and all control flow is structured as a graph, with 30 | relevant AST nodes at the top of this file. 31 | 32 | The resulting tree is then passed through a number of transformation passes. 33 | Currently, the ``liveness`` pass determines variable liveness, the ``typer`` 34 | pass performs type inference, the ``specialize`` pass improves on the 35 | inferenced types, the ``escapes`` pass performs an escape analysis, and the 36 | ``destruct`` pass inserts code to clean up heap-allocated objects. 37 | 38 | The transformed tree is then passed to the AST walker in ``runac/codegen.py``, 39 | where LLVM IR is generated. This can then be passed into ``clang``. 40 | 41 | A regression test suite is implemented in the ``tests/`` dir, where each 42 | source file (``rns`` extension) represents a single test case. Execute the 43 | entire suite by executing ``make test`` in the root directory. 44 | 45 | 46 | .. _blocks: 47 | 48 | AST to CFG transformation 49 | ========================= 50 | 51 | .. automodule:: runac.blocks 52 | 53 | 54 | .. _liveness: 55 | 56 | Liveness analysis 57 | ================= 58 | 59 | .. automodule:: runac.liveness 60 | 61 | 62 | .. _typer: 63 | 64 | Type inference and type checking 65 | ================================ 66 | 67 | .. automodule:: runac.typer 68 | 69 | 70 | .. _specialize: 71 | 72 | Type specialization 73 | =================== 74 | 75 | .. automodule:: runac.specialize 76 | 77 | 78 | .. _escapes: 79 | 80 | Escape analysis 81 | =============== 82 | 83 | .. automodule:: runac.escapes 84 | 85 | 86 | .. _destructor: 87 | 88 | Destructor insertion 89 | ==================== 90 | 91 | .. automodule:: runac.destructor 92 | 93 | 94 | .. _codegen: 95 | 96 | Code generation 97 | =============== 98 | 99 | .. automodule:: runac.codegen 100 | -------------------------------------------------------------------------------- /doc/index.rst: -------------------------------------------------------------------------------- 1 | Start 2 | ===== 3 | 4 | .. image:: https://travis-ci.org/djc/runa.svg?branch=master 5 | :target: https://travis-ci.org/djc/runa 6 | .. image:: https://img.shields.io/coveralls/djc/runa.svg?branch=master 7 | :target: https://coveralls.io/r/djc/runa?branch=master 8 | 9 | Runa is a Python-like systems programming language. 10 | This means that the design borrows as much from Python 11 | as makes sense in the context of a statically-typed, compiled language, 12 | and tries to apply the `Zen of Python`_ to everything else. 13 | The most important design goals for Runa are developer ergonomics 14 | and performance. 15 | The compiler is written in Python and targets LLVM IR; 16 | there's no run-time. 17 | 18 | Note: this is pre-alpha quality software. Use at your own peril. 19 | 20 | All feedback welcome. Feel free to file bugs, requests for documentation and 21 | any other feedback to the `issue tracker`_, `tweet me`_ or join the #runa 22 | channel on freenode. 23 | 24 | .. _issue tracker: https://github.com/djc/runa/issues 25 | .. _tweet me: https://twitter.com/djco/ 26 | .. _Zen of Python: https://www.python.org/dev/peps/pep-0020/ 27 | 28 | Table of contents 29 | ----------------- 30 | 31 | .. toctree:: 32 | :maxdepth: 2 33 | 34 | overview.rst 35 | hacking.rst 36 | grammar.rst 37 | refs.rst 38 | notes.rst 39 | -------------------------------------------------------------------------------- /doc/notes.rst: -------------------------------------------------------------------------------- 1 | ***** 2 | Notes 3 | ***** 4 | 5 | Basics 6 | ====== 7 | 8 | Goal: a Python-like static language, i.e. doing away with much of the C noise. 9 | To do this, I'd like to get rid of explicit pointer syntax; all mutable types 10 | whose size is larger than a word will be passed by reference. To make that 11 | possible, I need to get rid of outparams. To enable that, I need multiple 12 | return values. 13 | 14 | More C noise elimination: return on errors by default, i.e. exception flow. 15 | Also, limit the amount of explicit types. Use local type inference for most 16 | declarations, with types for function signatures (and possibly generics). 17 | 18 | 19 | Secondary goals 20 | =============== 21 | 22 | * Definitely want operator overloading 23 | * Deterministic (RAII-like) memory management, no GC 24 | * Minimal generics rather then templates 25 | * Error propagation by default, light-weight exceptions 26 | * Optional, but inclusive standard library 27 | * Easy FFI would be nice 28 | 29 | 30 | Implementation notes 31 | ==================== 32 | 33 | Standard streams: 34 | 35 | - stdin: 0 (unistd.h: STDIN_FILENO) 36 | - stdout: 1 (unistd.h: STDOUT_FILENO) 37 | - stderr: 2 (unistd.h: STDERR_FILENO) 38 | 39 | Context managers: 40 | 41 | - def __enter__(self) 42 | - def __exit__(self, type, value, traceback) 43 | 44 | Generics: 45 | 46 | Can we prevent keeping a pointer to the class inside every object? Only if 47 | the compiler knows all of the types in advance. Alternative: keep a pointer to 48 | the type of the contained type in containers. Simple first problem: str() 49 | should accept any type and call its __str__() method. 50 | 51 | Covariance and contravariance: 52 | 53 | If B is a subtype of A, then all member functions of B must return the same or 54 | a narrower set of types as A; the return type is said to be covariant. On the 55 | other hand, if the member functions of B take the same or broader set of 56 | arguments compared with the member functions of A, the argument type is said 57 | to be contravariant. The problem for instances of B is how to be perfectly 58 | substitutable for instances of A. The only way to guarantee type safety and 59 | substitutability is to be equally or more liberal than A on inputs, and to 60 | be equally or more strict than A on outputs. 61 | 62 | 63 | How a Pratt parser works 64 | ======================== 65 | 66 | nud: null denotation, used when token appears at beginning of construct 67 | led: left denotation, used when it appears inside the construct (left of rest) 68 | lbp: left binding power, controls operator precedence; higher binds to right 69 | 70 | - start by looking at first token, calling nud() on it 71 | - if lbp of next is >= given rbp, call led for next 72 | 73 | 74 | Memory model 75 | ============ 76 | 77 | Goals: 78 | 79 | - Should not leak memory or double-free (d0h!) 80 | - Should be easy to use, not much of a distraction in the code 81 | - Should be deterministic; no separate garbage collection 82 | - Should support the language's semantics 83 | - Should be efficient/performant 84 | 85 | 86 | Ideas 87 | ----- 88 | 89 | Allocate statically-sized variables on the caller's stack. Ignoring global 90 | variables for now, memory references can only flow up the stack in (a) return 91 | variables (which got allocated on the upstream stack in the first place) and 92 | (b) as part of a larger structure that gets returned up the stack. 93 | 94 | Dynamically-sized variables must be allocated on the heap. They are always 95 | wrapped in a stack-allocated variable to keep a reference around. Multiple 96 | wrappers are allowed to reference to the same block of heap-allocated 97 | memory (because these blocks may be large and copying them would be expensive 98 | in both memory and CPU time). The memory must be freed once all wrappers 99 | get cleaned up; reference counting seems like a good fit for this. 100 | -------------------------------------------------------------------------------- /doc/overview.rst: -------------------------------------------------------------------------------- 1 | ******************* 2 | High-level overview 3 | ******************* 4 | 5 | Rationale 6 | ========= 7 | 8 | Having written lots of Python code over the past 10 years (in several capacities), 9 | I greatly admire the Python programming language. 10 | Here are some important things it gets right: 11 | 12 | 1. **Readability** -- Python is often mentioned as almost pseudo-language; 13 | there is very little syntactic noise. 14 | Using indentation to mark blocks is a great way to ensure 15 | visual scanning matches the understanding of the parser, 16 | and the colons used to introduce blocks make this even easier. 17 | 18 | 2. **Built-in namespacing** -- except for a relatively small set of built-ins, 19 | every name used in Python code is introduced in the same file. 20 | This makes it much easier to understand new code; 21 | you can always figure out how a variable was introduced into the current context. 22 | (Similarly, explicit ``self`` means that arguments and other variables are clearly 23 | distinguished from object members.) 24 | 25 | 3. **Exceptions** -- while this point may be controversial, 26 | I think exceptions are a better error handling method than using status returns. 27 | Errors signalled through exceptions can be handled at the correct layer, 28 | whereas status returns have to be handled and propagated to the next layer explicitly 29 | (resulting in cluttered code). 30 | Ned Batchelder has written `eloquently`_ on `this subject`_. 31 | 32 | 4. **Flexible type system** -- relying on duck typing of well-defined "protocols" 33 | trivially allows implementation of new classes conforming to pre-existing interfaces. 34 | Run-time reflection is available to do run-time type checking where necessary, 35 | and no extra work is necessary to implement generics. 36 | 37 | However, the Python language also has some clear drawbacks. 38 | 39 | 1. **Implementation complexity** -- as a scripting language, 40 | Python code requires substantial accidental complexity to run 41 | (e.g. lots of hash table lookups and inefficient memory allocation). 42 | In CPython, this manifests as run-time overhead (i.e. lower performance) 43 | while keeping the virtual machine implementation relatively simple. 44 | Alternative implementations, such as `PyPy`_ or `Pyston`_, 45 | attempt to eliminate the run-time overhead at the cost of requiring 46 | significantly more complexity in a good JIT compiler. 47 | 48 | 2. **Basic mistakes are caught later** -- unlike languages where more time is spent 49 | on checking the source code in a compilation step before running it, 50 | most errors in Python code are only caught at run-time. 51 | This makes it harder to catch mistakes like typos or type errors, 52 | especially in large or legacy projects. 53 | Automated tests can be a good way to make these easier to find, 54 | but generating enough coverage is a significant investment. 55 | 56 | 3. **Implicit types** -- while explicit type annotations are mostly superfluous 57 | in small projects, research has shown that explicit type annotations can 58 | provide benefits as a form of documentation in larger projects. 59 | 60 | It has been my hypothesis for some time that many of the attractive qualities of Python 61 | do not depend on it being a dynamically-typed interpreted language. 62 | Runa is my attempt to verify this hypothesis, 63 | by building a compiler for a language that has the benefits mentioned above, 64 | while avoiding the stated drawbacks. 65 | 66 | .. _eloquently: http://nedbatchelder.com/text/exceptions-vs-status.html 67 | .. _this subject: http://nedbatchelder.com/text/exceptions-in-the-rainforest.html 68 | .. _PyPy: http://pypy.org/ 69 | .. _Pyston: http://blog.pyston.org/ 70 | 71 | 72 | Roadmap 73 | ======= 74 | 75 | I'm currently working to build an initial release of the Runa compiler. 76 | Here are some things left to do that should be done before such a release: 77 | 78 | * **Type system** -- immmutability, correct handling of owner types 79 | * **Memory/resource management** -- make sure cleanup works correctly 80 | * **Fill out core types** -- number types, ``Str`` and ``Array`` 81 | * **Add collection types** -- ``List``, ``Dict`` and ``Set`` 82 | * **Argument handling** -- default args, ``*args``, ``**kwargs`` 83 | * **I/O interactions** -- reading from and writing to files and network 84 | * **Documentation** -- some tutorial materials, API reference, etc 85 | -------------------------------------------------------------------------------- /doc/refs.rst: -------------------------------------------------------------------------------- 1 | *********** 2 | Inspiration 3 | *********** 4 | 5 | Some sources of inspiration and interesting stuff to review upon further 6 | expansion of the language design (e.g. concurrency): 7 | 8 | * `Three bad things: threads, garbage collection, and nondeterministic destructors 9 | `_ 10 | 11 | Avery Pennarun 12 | 13 | * `How to design a replacement for C++ 14 | `_ 15 | 16 | Avery Pennarun 17 | 18 | * `Function annotations 19 | `_ 20 | 21 | Collin Winter e.a. 22 | 23 | * `Why Git is so fast (Java vs C) 24 | `_ 25 | 26 | Shawn O. Pearce 27 | 28 | * `Is Fortran faster than C? 29 | `_ 30 | 31 | * `Languages faster than C++ 32 | `_ 33 | 34 | * `A sample of a pyparsing grammar using indentation for grouping 35 | `_ 36 | 37 | Paul McGuire 38 | 39 | * `tinypy: parse.py (Python Pratt parser, in Python) 40 | `_ 41 | 42 | Phil Hassey 43 | 44 | * `Faster than C 45 | `_ 46 | 47 | Andreas Zwinkau 48 | 49 | * `Go Data Structures: Interfaces 50 | `_ 51 | 52 | Russ Cox 53 | 54 | * `What Every Programmer Should Know About Memory 55 | `_ 56 | 57 | Ulrich Drepper 58 | 59 | * `Socio-PLT: Principles for Programming Language Adoption 60 | `_ 61 | 62 | Leo Meyerov 63 | 64 | * `Baggy Bounds Checking 65 | `_ 66 | 67 | Periklis Akritidis et al. 68 | 69 | * `Things I like about programming in Go 70 | `_ 71 | 72 | John Graham-Cunning 73 | 74 | * `Borrowed Pointer Tutorial 75 | `_ 76 | 77 | Niko Matsakis 78 | 79 | * `Why I should have written ZeroMQ in C, not C++ (part II) 80 | `_ 81 | 82 | Martin Sustrik 83 | 84 | * `Error codes vs exceptions: critical code vs typical code 85 | `_ 86 | 87 | Yossi Kreinin 88 | 89 | * `C's Biggest Mistake 90 | `_ 91 | 92 | Walter Bright 93 | 94 | * `Scalable computer programming languages 95 | `_ 96 | 97 | Mike Vanier 98 | 99 | * `The Unreasonable Effectiveness of C 100 | `_ 101 | 102 | Damien Katz 103 | 104 | * `Exceptions and Stack Traces for C on Windows, Linux & Mac 105 | `_ 106 | 107 | Job Vranish 108 | 109 | * `Macroscopic Data Structure Analysis and Optimization 110 | `_ 111 | 112 | Chris Lattner 113 | 114 | * `Making Context-Sensitivie Points-to Analysis with Heap Closing Practical For The Real World 115 | `_ 116 | 117 | Chris Lattner et al. 118 | 119 | * `Rust wiki: Bikeshed Numeric Traits 120 | `_ 121 | 122 | * `Introduction to Go 1.1 123 | `_ 124 | 125 | * `How to Spread The Word About Your Code 126 | `_ 127 | 128 | Peter Cooper and Robert Nyman 129 | 130 | * `Deep Wizardry: Stack Unwinding 131 | `_ 132 | 133 | Josh Haberman 134 | 135 | * `Little things that matter in language design 136 | `_ 137 | 138 | Neil Brown 139 | 140 | * `How to maintain a successful open source project 141 | `_ 142 | 143 | Andrey Petrov 144 | 145 | * `Python 2 vs. Python 3: A retrospective 146 | `_ 147 | 148 | Guido van Rossum 149 | 150 | * `Inside Swift 151 | `_ 152 | 153 | Evan Swick 154 | 155 | * `What Python can learn from Haskell 156 | `_ 157 | 158 | Bob Ippolito 159 | 160 | * `The true cost of zero cost exceptions 161 | `_ 162 | 163 | edA-qa mort-ora-y 164 | 165 | * `C++ Exception Handling - Base ABI 166 | `_ 167 | 168 | Theofilos Petsios 169 | 170 | * `Wren: a classy little scripting language 171 | `_ 172 | 173 | Robert Nystrom 174 | 175 | * `Faster, more memory efficient and more ordered dictionaries on PyPy 176 | `_ 177 | 178 | The PyPy team 179 | 180 | * `Statements and Expressions 181 | `_ 182 | 183 | Andrew Barnert 184 | 185 | * `Syntax across languages 186 | `_ 187 | 188 | Pascal Rigaux 189 | 190 | * `C++ Seasoning 191 | `_ 192 | 193 | Sean Parent 194 | 195 | * `.eh_frame 196 | `_ 197 | Ian Lance Taylor 198 | 199 | * `.gcc_except_table 200 | `_ 201 | Ian Lance Taylor 202 | 203 | * `Programmatic access to the call stack in C++ 204 | `_ 205 | Eli Bendersky 206 | 207 | * `Sharp Regrets: Top 10 Worst C# Features 208 | `_ 209 | Eric Lippert 210 | 211 | * `Error Handling Rationale and Proposal 212 | `_ 213 | Swift team 214 | 215 | * `The Error Model 216 | `_ 217 | Joe Duffy 218 | -------------------------------------------------------------------------------- /llize: -------------------------------------------------------------------------------- 1 | clang -emit-llvm -S $1 -o $2.ll -O0 2 | cat $2.ll 3 | rm $2.ll 4 | -------------------------------------------------------------------------------- /misc/runa.nanorc: -------------------------------------------------------------------------------- 1 | syntax "runa" "\.rns$" 2 | color brightcyan "\<(and|as|break|class|continue|def|elif|else|except|for|from|if|import|is|not|or|pass|raise|return|trait|try|while|yield)\>" 3 | color brightgreen "''" "['][^']*[^\\][']" "[']{3}.*[^\\][']{3}" 4 | color brightgreen "["][^"]*[^\\]["]" "["]{3}.*[^\\]["]{3}" 5 | color brightgreen start=""""[^"]" end=""""" start="'''[^']" end="'''" 6 | color brightred "#.*$" 7 | -------------------------------------------------------------------------------- /runa: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | python -m runac $* 3 | -------------------------------------------------------------------------------- /runac/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | from . import ( 3 | parser, blocks, liveness, typer, specialize, 4 | escapes, destructor, codegen, util, pretty 5 | ) 6 | import os, subprocess, collections, re 7 | 8 | PASSES = collections.OrderedDict(( 9 | ('liveness', liveness.liveness), 10 | ('typer', typer.typer), 11 | ('specialize', specialize.specialize), 12 | ('escapes', escapes.escapes), 13 | ('destruct', destructor.destruct), 14 | )) 15 | 16 | def lex(src): 17 | '''Takes a string containing source code, returns list of token tuples''' 18 | return parser.lex(src) 19 | 20 | def parse(fn): 21 | '''Takes a string containing file name, returns an AST File node''' 22 | return parser.parse(fn) 23 | 24 | def _core(): 25 | fn = os.path.join(util.CORE_DIR, '__builtins__.rns') 26 | base = {t.__name__: t() for t in types.BASE} 27 | mod = blocks.Module('Runa.core', parse(fn), base) 28 | for name, fun in util.items(PASSES): 29 | fun(mod) 30 | return mod 31 | 32 | CORE = _core() 33 | 34 | def module(path, name='Runa.__main__'): 35 | '''Takes a file (or directory, at some point), returns a Module containing 36 | declarations and code objects, to be submitted for further processing.''' 37 | assert not os.path.isdir(path), path 38 | return blocks.Module(name, parser.parse(path), CORE.scope) 39 | 40 | def show(fn, last): 41 | '''Show Runa high-level intermediate representation for the source code 42 | in the given file name (`fn`). `last` contains the last pass from 43 | PASSES to apply to the module before generating the IR. 44 | 45 | Returns a dict with function names (string or tuple) -> IR (string). 46 | Functions from modules other than the given module are ignored.''' 47 | 48 | mod = module(fn) 49 | for name, fun in util.items(PASSES): 50 | fun(mod) 51 | if name == last: 52 | break 53 | 54 | data = [] 55 | for name, code in mod.code: 56 | data.append(pretty.prettify(name, code)) 57 | 58 | return data 59 | 60 | def ir(fn): 61 | '''Generate LLVM IR for the given module. Takes a string file name and 62 | returns a string of LLVM IR, for the host architecture.''' 63 | mod = module(fn) 64 | for name, fun in util.items(PASSES): 65 | fun(mod) 66 | return codegen.generate(mod) 67 | 68 | CORE_IR = codegen.generate(CORE) 69 | RT_IR = codegen.rt() 70 | 71 | def compile(fn, outfn): 72 | '''Compiles LLVM IR into a binary. Takes a string file name and a string 73 | output file name. Writes IR to a temporary file for the main module as 74 | well as personality, rt, and builtins modules, then calls clang on them. 75 | (Fix me: shelling out to clang is pretty inefficient.)''' 76 | 77 | with open('builtins.ll', 'w') as f: 78 | f.write(CORE_IR.encode('ascii')) 79 | 80 | # Write LLVM IR for main module to a file, making sure that the file 81 | # is cleaned up if an error occurs during code generation. 82 | 83 | name = os.path.basename(fn).rsplit('.rns')[0] 84 | mod_fn = name + '.ll' 85 | with open(mod_fn, 'w') as f: 86 | try: 87 | f.write(ir(fn).encode('ascii')) 88 | except Exception: 89 | os.unlink(mod_fn) 90 | raise 91 | 92 | # Write LLVM IR for rt library 93 | 94 | with open('rt.ll', 'w') as f: 95 | f.write(RT_IR) 96 | 97 | # Prepare clang command for compiling, depending on platform 98 | 99 | eh_fn = os.path.join(util.CORE_DIR, 'personality.c') 100 | files = eh_fn, 'rt.ll', 'builtins.ll', mod_fn 101 | triple = codegen.triple() 102 | if 'windows-msvc' in triple: 103 | cmd = ['clang-cl', '-Fe' + outfn, '-m64'] + files 104 | cmd += ['/link', 'msvcrt.lib'] 105 | else: 106 | cmd = ['clang', '-o', outfn] 107 | cmd.append('-m64' if triple.split('-')[0] == 'x86_64' else '-m32') 108 | cmd += files 109 | 110 | # Execute clang, cleaning up as necessary 111 | 112 | try: 113 | subprocess.check_call(cmd) 114 | except OSError as e: 115 | if e.errno == 2: 116 | print('error: clang not found') 117 | else: 118 | raise 119 | except subprocess.CalledProcessError: 120 | pass 121 | finally: 122 | os.unlink('rt.ll') 123 | os.unlink('builtins.ll') 124 | os.unlink(mod_fn) 125 | -------------------------------------------------------------------------------- /runac/__main__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import print_function 4 | import optparse, sys, os 5 | from runac import util 6 | import runac 7 | 8 | def tokens(fn, opts): 9 | '''Print a list of tokens and location info''' 10 | with open(fn) as f: 11 | for x in runac.lex(f.read()): 12 | print(x.name, x.value, (x.source_pos.lineno, x.source_pos.colno)) 13 | 14 | def parse(fn, opts): 15 | '''Print the syntax tree resulting from parsing the source''' 16 | print(runac.parse(fn)) 17 | 18 | def show(fn, opts): 19 | '''Print syntax tree after processing the pass specified by --last''' 20 | for cfg in runac.show(fn, opts.last): 21 | print(cfg) 22 | 23 | def generate(fn, opts): 24 | '''Print LLVM IR as generated by the code generation process''' 25 | 26 | ir = runac.ir(fn) 27 | if opts.test: 28 | return 29 | 30 | if opts.outfile is None: 31 | print(ir) 32 | return 33 | 34 | with open(opts.outfile, 'w') as f: 35 | print(ir, file=f) 36 | 37 | def compile(fn, opts): 38 | '''Compile the given program to a binary of the same name''' 39 | outfn = os.path.basename(fn).rsplit('.rns')[0] 40 | runac.compile(fn, outfn if opts.outfile is None else opts.outfile) 41 | 42 | COMMANDS = { 43 | 'tokens': tokens, 44 | 'parse': parse, 45 | 'show': show, 46 | 'generate': generate, 47 | 'compile': compile, 48 | } 49 | 50 | def find(cmd): 51 | if cmd in COMMANDS: return COMMANDS[cmd] 52 | matched = sorted(i for i in COMMANDS if i.startswith(cmd)) 53 | if len(matched) == 1: 54 | return COMMANDS[matched[0]] 55 | elif len(matched) > 1: 56 | print('ambiguous command: %r' % cmd) 57 | return lambda x, y: None 58 | else: 59 | print('no command found: %r' % cmd) 60 | return lambda x, y: None 61 | 62 | if __name__ == '__main__': 63 | 64 | parser = optparse.OptionParser() 65 | 66 | parser.add_option('--last', help='last pass', default='destruct') 67 | parser.add_option('-o', '--outfile', help='output file', dest='outfile') 68 | parser.add_option('--test', help='no output', action='store_true') 69 | parser.add_option('--traceback', help='show full traceback', 70 | action='store_true') 71 | opts, args = parser.parse_args() 72 | 73 | if len(args) < 1: 74 | print('The Runa compiler. A command takes a single file as an argument.') 75 | print('\nCommands:\n') 76 | for cmd, fun in sorted(COMMANDS.items()): 77 | print('%s: %s' % (cmd, fun.__doc__)) 78 | print('\nAny unique command abbrevation will also work.') 79 | parser.print_help() 80 | sys.exit(1) 81 | 82 | try: 83 | find(args[0])(args[1], opts) 84 | except util.Error as e: 85 | if opts.traceback: 86 | raise 87 | sys.stderr.write(e.show()) 88 | except util.ParseError as e: 89 | if opts.traceback: 90 | raise 91 | sys.stderr.write(e.show()) 92 | -------------------------------------------------------------------------------- /runac/ast.py: -------------------------------------------------------------------------------- 1 | '''Contains classes for all the syntax tree node types used in the parser. 2 | 3 | All classes have a `pos` field containing location information. It can be 4 | None in nodes that have been inserted by the compiler. Classes should have 5 | a `fields` attribute containing a sequence of properties that either contain 6 | another AST node or a list of AST nodes, so we can walk the tree somehow. 7 | 8 | Some node types are defined in other modules: 9 | 10 | - blocks: SetAttr, Branch, CondBranch, Phi, Constant, LoopSetup, LoopHeader, 11 | LPad 12 | - typer: Init 13 | - destructor: Free 14 | 15 | For files containing source code, a File node is at the root of the tree. 16 | ''' 17 | 18 | from . import util 19 | 20 | # Base class 21 | 22 | IGNORE = {'pos'} 23 | 24 | class Registry(type): 25 | types = [] 26 | def __init__(cls, name, bases, dict): 27 | Registry.types.append(cls) 28 | 29 | class Node(util.AttribRepr): 30 | __metaclass__ = Registry 31 | def __init__(self, pos): 32 | self.pos = pos 33 | def __hash__(self): 34 | values = tuple(sorted((k, v) for (k, v) in util.items(self.__dict__))) 35 | return hash((self.__class__.__name__,) + values) 36 | 37 | class Expr(Node): 38 | fields = () 39 | def __init__(self, pos): 40 | Node.__init__(self, pos) 41 | self.type = None 42 | self.escapes = False 43 | 44 | class NoneVal(Expr): 45 | pass 46 | 47 | class Bool(Expr): 48 | def __init__(self, val, pos): 49 | Expr.__init__(self, pos) 50 | self.val = True if val == 'True' else False 51 | 52 | class Int(Expr): 53 | def __init__(self, num, pos): 54 | Expr.__init__(self, pos) 55 | self.val = num 56 | 57 | class Float(Expr): 58 | def __init__(self, num, pos): 59 | Expr.__init__(self, pos) 60 | self.val = num 61 | 62 | class String(Expr): 63 | def __init__(self, value, pos): 64 | Expr.__init__(self, pos) 65 | self.val = value 66 | 67 | class Name(Expr): 68 | def __init__(self, name, pos): 69 | Expr.__init__(self, pos) 70 | self.name = name 71 | 72 | # Expression-level 73 | 74 | class Attrib(Expr): 75 | fields = 'obj', 76 | 77 | class Elem(Expr): 78 | fields = 'obj', 'key' 79 | 80 | class Add(Expr): 81 | fields = 'left', 'right' 82 | 83 | class Sub(Expr): 84 | fields = 'left', 'right' 85 | 86 | class Mul(Expr): 87 | fields = 'left', 'right' 88 | 89 | class Div(Expr): 90 | fields = 'left', 'right' 91 | 92 | class Mod(Expr): 93 | fields = 'left', 'right' 94 | 95 | class BWAnd(Expr): 96 | fields = 'left', 'right' 97 | 98 | class BWOr(Expr): 99 | fields = 'left', 'right' 100 | 101 | class BWXor(Expr): 102 | fields = 'left', 'right' 103 | 104 | class Not(Expr): 105 | fields = 'value', 106 | 107 | class Owner(Expr): 108 | fields = 'value' 109 | 110 | class Ref(Expr): 111 | fields = 'value' 112 | 113 | class Opt(Expr): 114 | fields = 'value' 115 | 116 | class Mut(Expr): 117 | fields = 'value' 118 | 119 | class In(Expr): 120 | fields = 'left', 'right' 121 | 122 | class And(Expr): 123 | fields = 'left', 'right' 124 | 125 | class Or(Expr): 126 | fields = 'left', 'right' 127 | 128 | class Is(Expr): 129 | fields = 'left', 'right' 130 | 131 | class EQ(Expr): 132 | fields = 'left', 'right' 133 | 134 | class NE(Expr): 135 | fields = 'left', 'right' 136 | 137 | class LT(Expr): 138 | fields = 'left', 'right' 139 | 140 | class GT(Expr): 141 | fields = 'left', 'right' 142 | 143 | class As(Expr): 144 | fields = 'left', 145 | 146 | class Tuple(Expr): 147 | fields = 'values', 148 | 149 | class Call(Expr): 150 | fields = 'args', 151 | 152 | class NamedArg(Expr): 153 | fields = 'val', # plus non-field 'name' 154 | 155 | # Statement-level 156 | 157 | class Assign(Node): 158 | fields = 'left', 'right' 159 | 160 | class IAdd(Node): 161 | fields = 'left', 'right' 162 | 163 | class Raise(Node): 164 | fields = 'value', 165 | 166 | class Yield(Node): 167 | fields = 'value', 168 | 169 | class Except(Node): 170 | fields = 'type', 'suite' 171 | 172 | class Suite(Node): 173 | fields = 'stmts', 174 | 175 | class Argument(Node): 176 | fields = 'name', 177 | def __init__(self, pos): 178 | Node.__init__(self, pos) 179 | self.type = None 180 | 181 | class Decl(Node): 182 | fields = 'decor', 'name', 'args', 'rtype' 183 | 184 | class TryBlock(Node): 185 | fields = 'suite', 'catch' 186 | 187 | class Function(Node): 188 | fields = 'decor', 'name', 'args', 'rtype', 'suite' 189 | 190 | class Break(Node): 191 | fields = () 192 | 193 | class Continue(Node): 194 | fields = () 195 | 196 | class Pass(Node): 197 | fields = () 198 | 199 | class Return(Node): 200 | fields = 'value', 201 | 202 | class Ternary(Expr): 203 | fields = 'cond', 'values' 204 | 205 | class If(Node): 206 | fields = 'blocks', 207 | 208 | class Import(Node): 209 | fields = 'names', 210 | 211 | class RelImport(Node): 212 | fields = 'base', 'names' 213 | 214 | class For(Node): 215 | fields = 'lvar', 'source', 'suite' 216 | 217 | class While(Node): 218 | fields = 'cond', 'suite' 219 | 220 | class Class(Node): 221 | fields = 'decor', 'name', 'params', 'attribs', 'methods' 222 | 223 | class Trait(Node): 224 | fields = 'decor', 'name', 'params', 'methods' 225 | 226 | class File(Node): 227 | fields = 'suite', 228 | def __init__(self): 229 | self.suite = [] 230 | -------------------------------------------------------------------------------- /runac/destructor.py: -------------------------------------------------------------------------------- 1 | '''This pass inserts calls to object destructors. 2 | 3 | Owner-typed pointers represent heap-allocated memory which has no pointers 4 | which live outside the current function call. 5 | They should therefore be cleaned up before returning to the caller. 6 | Owner-typed pointers must also be cleaned up before 7 | a new object is assigned to the variable name 8 | (except if the owner was stored somewhere else -- 9 | this is probably not handled right now, TODO). 10 | 11 | This pass inserts ``Free`` nodes into the CFG, 12 | which are then expanded into function calls during the code generation phase. 13 | ''' 14 | 15 | from . import ast, blocks, types, util 16 | 17 | class Free(util.AttribRepr): 18 | fields = 'value', 19 | def __init__(self, value): 20 | self.value = value 21 | 22 | def destructify(mod, code): 23 | 24 | returns, reassign, left = {}, [], {} 25 | for i, bl in util.items(code.flow.blocks): 26 | 27 | # For each block that returns, find the set of transitive 28 | # predecessor blocks; these assignments will need freeing. 29 | 30 | if bl.returns: 31 | returns[i], q = set(), {i} 32 | while q: 33 | cur = q.pop() 34 | returns[i].add(cur) 35 | for p in code.flow.blocks[cur].preds: 36 | if p.id in returns[i] or p.id in q: 37 | continue 38 | q.add(p.id) 39 | 40 | # Find assignments to owner variables; the last assignment 41 | # will be freed before return, earlier ones before next assign. 42 | 43 | for var, data in util.items(code.flow.vars): 44 | 45 | if bl.id not in data.get('sets', {}): 46 | continue 47 | 48 | assigns = sorted(data['sets'][bl.id]) 49 | for idx, sid in enumerate(assigns): 50 | 51 | step = bl.steps[sid] 52 | if isinstance(step, blocks.LoopHeader): 53 | type = step.lvar.type 54 | else: 55 | type = step.right.type 56 | 57 | if not isinstance(type, types.owner): 58 | continue 59 | 60 | if code.flow.origins(var, (bl.id, sid)) - {None}: 61 | reassign.append((var, i, sid, type)) 62 | continue 63 | 64 | if var in bl.escapes: 65 | continue 66 | 67 | left.setdefault(var, (type, set()))[1].add(i) 68 | 69 | # Remove assignments from Phi nodes; these would result in 70 | # double freeing (objects are freed through original var). 71 | 72 | for sid, step in enumerate(bl.steps): 73 | 74 | if not isinstance(step, ast.Assign): 75 | continue 76 | 77 | if isinstance(step.right, blocks.Phi): 78 | if isinstance(step.right.left[1].type, types.owner): 79 | left.pop(step.right.left[1].name, None) 80 | if isinstance(step.right.right[1].type, types.owner): 81 | left.pop(step.right.right[1].name, None) 82 | 83 | if code.irname == 'main' and code.args: 84 | left['args'] = mod.type('$Array[Str]'), {None} 85 | 86 | for name, bid, sid, type in reassign: 87 | node = ast.Name(name, None) 88 | node.type = type 89 | code.flow.blocks[bid].steps.insert(sid, Free(node)) 90 | 91 | for name, (type, abls) in sorted(util.items(left)): 92 | for rbli, reachable in sorted(util.items(returns)): 93 | 94 | if not (abls & reachable): 95 | continue 96 | 97 | node = ast.Name(name, None) 98 | node.type = type 99 | rbl = code.flow.blocks[rbli] 100 | rbl.steps.insert(-1, Free(node)) 101 | 102 | def destruct(mod): 103 | for name, code in mod.code: 104 | destructify(mod, code) 105 | -------------------------------------------------------------------------------- /runac/escapes.py: -------------------------------------------------------------------------------- 1 | '''The escapes pass marks objects that will escape the function. 2 | 3 | This pass exists for memory management purposes. 4 | We want to free heap-allocated objects that 5 | do not survive the current function call, 6 | so we must know which objects are returned to the caller or 7 | stored in an object. 8 | This can either be a returned object, 9 | or something that was passed in that survives this call. 10 | Escaping objects will not be freed before returning. 11 | 12 | This data is also important for our survival analysis (yet to be implemented), 13 | where data on survival requirements becomes part of the function type. 14 | For example, "argument 3 must live at least as long as argument 1". 15 | ''' 16 | 17 | from . import ast, blocks, types, util 18 | 19 | class EscapeFinder(object): 20 | 21 | def __init__(self, mod, fun): 22 | self.mod = mod 23 | self.fun = fun 24 | self.cfg = fun.flow 25 | self.track = set() 26 | self.cur = None 27 | 28 | def visit(self, node, escape=None): 29 | getattr(self, node.__class__.__name__)(node, escape) 30 | 31 | # Constants 32 | 33 | def NoneVal(self, node, escape=None): 34 | pass 35 | 36 | def Bool(self, node, escape=None): 37 | pass 38 | 39 | def Int(self, node, escape=None): 40 | pass 41 | 42 | def Float(self, node, escape=None): 43 | pass 44 | 45 | def String(self, node, escape=None): 46 | if not escape: 47 | node.type = self.mod.type('&Str') 48 | else: 49 | node.escapes = True 50 | 51 | def Name(self, node, escape=None): 52 | if not escape: return 53 | self.track.add(node.name) 54 | 55 | def Tuple(self, node, escape=None): 56 | for n in node.values: 57 | self.visit(n) 58 | 59 | # Boolean operators 60 | 61 | def Not(self, node, escape=None): 62 | pass 63 | 64 | def And(self, node, escape=None): 65 | pass 66 | 67 | def Or(self, node, escape=None): 68 | pass 69 | 70 | # Comparison operators 71 | 72 | def Is(self, node, escape=None): 73 | pass 74 | 75 | def EQ(self, node, escape=None): 76 | pass 77 | 78 | def NE(self, node, escape=None): 79 | pass 80 | 81 | def LT(self, node, escape=None): 82 | pass 83 | 84 | def GT(self, node, escape=None): 85 | pass 86 | 87 | # Arithmetic operators 88 | 89 | def Add(self, node, escape=None): 90 | pass 91 | 92 | def Sub(self, node, escape=None): 93 | pass 94 | 95 | def Mod(self, node, escape=None): 96 | pass 97 | 98 | def Mul(self, node, escape=None): 99 | pass 100 | 101 | def Div(self, node, escape=None): 102 | pass 103 | 104 | # Bitwise operators 105 | 106 | def BWAnd(self, node, escape=None): 107 | pass 108 | 109 | def BWOr(self, node, escape=None): 110 | pass 111 | 112 | def BWXor(self, node, escape=None): 113 | pass 114 | 115 | def Raise(self, node, escape=None): 116 | self.visit(node.value, True) 117 | 118 | def Init(self, node, escape=None): 119 | if escape: 120 | node.escapes = True 121 | 122 | def As(self, node, escape=None): 123 | self.visit(node.left, escape) 124 | 125 | def Assign(self, node, escape=None): 126 | 127 | if isinstance(node.left, ast.Tuple): 128 | tracked = [n.name in self.track for n in node.left.values] 129 | if sum(1 if i else 0 for i in tracked) not in {0, len(tracked)}: 130 | assert False, 'partly tracked tuple elements' 131 | else: 132 | self.visit(node.right, all(tracked)) 133 | elif isinstance(node.left, ast.Name): 134 | self.visit(node.right, node.left.name in self.track) 135 | elif isinstance(node.left, blocks.SetAttr): 136 | 137 | self.visit(node.left.obj) 138 | if not node.left.obj.escapes: 139 | return 140 | 141 | assert False, 'assign to escaping object' 142 | 143 | else: 144 | assert False 145 | 146 | def IAdd(self, node, escape=None): 147 | allowed = tuple(t.__class__ for t in types.INTS | types.FLOATS) 148 | assert isinstance(node.left.type, allowed) 149 | 150 | def Pass(self, node, escape=None): 151 | pass 152 | 153 | def CondBranch(self, node, escape=None): 154 | pass 155 | 156 | def Branch(self, node, escape=None): 157 | pass 158 | 159 | def Attrib(self, node, escape=None): 160 | assert not escape or not isinstance(node.type, types.WRAPPERS) 161 | 162 | def Elem(self, node, escape=None): 163 | self.Attrib(node, escape) 164 | 165 | def LoopSetup(self, node, escape=None): 166 | pass 167 | 168 | def LoopHeader(self, node, escape=None): 169 | pass 170 | 171 | def Phi(self, node, escape=None): 172 | self.visit(node.left[1], escape) 173 | self.visit(node.right[1], escape) 174 | 175 | def LPad(self, node, escape=None): 176 | pass 177 | 178 | def Resume(self, node, escape=None): 179 | pass 180 | 181 | def Call(self, node, escape=None): 182 | 183 | if node.fun.name == 'Runa.rt.free' and self.fun.name.name == '__del__': 184 | return 185 | 186 | for i, arg in enumerate(node.fun.type.over[1]): 187 | if isinstance(arg, types.owner): 188 | self.visit(node.args[i], True) 189 | self.note(node.args[i]) 190 | else: 191 | self.visit(node.args[i]) 192 | 193 | if not escape: 194 | return 195 | 196 | if node.fun.name == 'Runa.rt.malloc': 197 | node.escapes = True 198 | return 199 | 200 | if node.fun.name.endswith('.__init__'): 201 | node.args[0].escapes = True 202 | 203 | def Yield(self, node, escape=None): 204 | self.Return(node, escape) 205 | 206 | def Return(self, node, escape=None): 207 | 208 | if node.value is None: 209 | return 210 | 211 | if not isinstance(node.value.type, types.owner): 212 | return 213 | 214 | self.visit(node.value, True) 215 | self.note(node.value) 216 | 217 | def note(self, val): 218 | 219 | if isinstance(val, ast.String): 220 | return 221 | 222 | assert isinstance(val, ast.Name), val 223 | ls = self.cur[0].escapes.setdefault(val.name, []) 224 | ls.append((self.cur[1], val.type)) 225 | 226 | def find(self): 227 | for bl in reversed(list(util.values(self.cfg.blocks))): 228 | for i, step in reversed(list(enumerate(bl.steps))): 229 | self.cur = bl, i 230 | self.visit(step) 231 | 232 | def escapes(mod): 233 | for name, code in mod.code: 234 | EscapeFinder(mod, code).find() 235 | -------------------------------------------------------------------------------- /runac/liveness.py: -------------------------------------------------------------------------------- 1 | '''The liveness pass collects data on uses of and assignments to variables, 2 | and stores it in the CFG's ``Block`` objects for easy reference in further 3 | passes. 4 | 5 | Because local types are inferred and 6 | because no variable declarations are required, 7 | finding the assignments that dominate any given use of a variable 8 | isn't completely trivial. 9 | While we can simply store pointers on the stack in many cases 10 | during code generation in order to leave analysis to LLVM, 11 | all the analyses we have to do 12 | still need somewhat accurate data on variable usage. 13 | ''' 14 | 15 | from . import ast, util 16 | 17 | class Analyzer(object): 18 | 19 | def __init__(self): 20 | self.vars = None 21 | 22 | def visit(self, node): 23 | 24 | if node is None: 25 | return 26 | 27 | if hasattr(self, node.__class__.__name__): 28 | getattr(self, node.__class__.__name__)(node) 29 | return 30 | 31 | for k in node.fields: 32 | self.visit(getattr(node, k)) 33 | 34 | def Name(self, node): 35 | self.vars[0].add(node.name) 36 | 37 | def Tuple(self, node): 38 | for n in node.values: 39 | self.visit(n) 40 | 41 | def Attrib(self, node): 42 | self.visit(node.obj) 43 | 44 | def SetAttr(self, node): 45 | self.visit(node.obj) 46 | 47 | def As(self, node): 48 | self.visit(node.left) 49 | 50 | def LoopSetup(self, node): 51 | self.visit(node.loop.source) 52 | 53 | def LoopHeader(self, node): 54 | self.vars[1].add(node.lvar.name) 55 | 56 | def Assign(self, node): 57 | if isinstance(node.left, ast.Tuple): 58 | for n in node.left.values: 59 | if isinstance(n, ast.Name): 60 | self.vars[1].add(n.name) 61 | else: 62 | self.visit(n) 63 | elif isinstance(node.left, ast.Name): 64 | self.vars[1].add(node.left.name) 65 | else: 66 | self.visit(node.left) 67 | self.visit(node.right) 68 | 69 | def Call(self, node): 70 | self.visit(node.name) 71 | for arg in node.args: 72 | self.visit(arg) 73 | 74 | def Phi(self, node): 75 | self.visit(node.left[1]) 76 | self.visit(node.right[1]) 77 | 78 | def liveness(mod): 79 | 80 | analyzer = Analyzer() 81 | for fname, code in mod.code: 82 | 83 | vars = code.flow.vars 84 | for arg in code.args: 85 | name = arg.name.name 86 | sets = vars.setdefault(name, {}).setdefault('sets', {}) 87 | sets[None] = {-1: None} 88 | 89 | blocks = sorted(util.items(code.flow.blocks)) 90 | for id, bl in blocks: 91 | 92 | for i, step in enumerate(bl.steps): 93 | 94 | analyzer.vars = set(), set() 95 | analyzer.visit(step) 96 | 97 | for name in analyzer.vars[0]: 98 | uses = vars.setdefault(name, {}).setdefault('uses', {}) 99 | uses.setdefault(id, set()).add(i) 100 | 101 | for name in analyzer.vars[1]: 102 | sets = vars.setdefault(name, {}).setdefault('sets', {}) 103 | sets.setdefault(id, {})[i] = None 104 | -------------------------------------------------------------------------------- /runac/parser.py: -------------------------------------------------------------------------------- 1 | from . import ast, util 2 | import rply 3 | 4 | NAME_LIKE = { 5 | 'and', 'as', 'break', 'class', 'continue', 'def', 'elif', 'else', 6 | 'except', 'for', 'from', 'if', 'import', 'in', 'is', 'not', 'or', 7 | 'pass', 'raise', 'return', 'trait', 'try', 'while', 'yield', 8 | } 9 | 10 | def lexer(): 11 | lg = rply.LexerGenerator() 12 | lg.add('ARROW', '->') 13 | lg.add('IADD', '\+=') 14 | lg.add('EQ', '==') 15 | lg.add('NE', '!=') 16 | lg.add('GE', '>=') 17 | lg.add('LE', '<=') 18 | lg.add('LBRA', '\[') 19 | lg.add('RBRA', '\]') 20 | lg.add('PLUS', '\+') 21 | lg.add('MINUS', '-') 22 | lg.add('MUL', '\*') 23 | lg.add('DIV', '/') 24 | lg.add('LACC', '{') 25 | lg.add('RACC', '}') 26 | lg.add('LT', '<') 27 | lg.add('GT', '>') 28 | lg.add('DOT', '\.') 29 | lg.add('AMP', '&') 30 | lg.add('DOLLAR', '\$') 31 | lg.add('PIPE', '\|') 32 | lg.add('CARET', '\^') 33 | lg.add('TILDE', '~') 34 | lg.add('MOD', '%') 35 | lg.add('LPAR', '\(') 36 | lg.add('RPAR', '\)') 37 | lg.add('ASGT', '=') 38 | lg.add('COMMA', ',') 39 | lg.add('COLON', ':') 40 | lg.add('QM', '\?') 41 | lg.add('STR', r"'(.*?)'") 42 | lg.add('STR', r'"(.*?)"') 43 | lg.add('BOOL', 'True|False') 44 | lg.add('NONE', 'None') 45 | lg.add('NAME', r'[a-zA-Z_][a-zA-Z0-9_]*') 46 | lg.add('NUM', r'[-+?[0-9]*\.?[0-9]+') 47 | lg.add('NL', r'\n') 48 | lg.add('COM', r'#(.*)') 49 | lg.add('TABS', r'\t+') 50 | lg.ignore(r' +') 51 | return lg.build() 52 | 53 | LEXER = lexer() 54 | 55 | def lex(src): 56 | '''Takes a string containing source code and returns a generator over 57 | tokens, represented by a three-element tuple: 58 | 59 | - Token type (from the list in lexer(), above) 60 | - The literal token contents 61 | - Position, as a tuple of line and column (both 1-based) 62 | 63 | This is mostly a wrapper around the rply lexer, but it reprocesses 64 | TABS tokens (which should only appear at the start of a line) into 65 | INDENT and DEDENT tokens, which only appear if the indentation 66 | level increases or decreases. 67 | 68 | Comment tokens do not appear in the output generator.''' 69 | level = 0 70 | hold = [] 71 | for t in LEXER.lex(src): 72 | 73 | if t.name == 'COM': 74 | continue 75 | elif t.name == 'NL' and hold: 76 | hold = [t] 77 | continue 78 | elif t.name == 'NL' or (hold and t.name == 'TABS'): 79 | hold.append(t) 80 | continue 81 | 82 | if hold: 83 | yield hold[0] 84 | cur = len(hold[1].value) if len(hold) > 1 else 0 85 | pos = hold[1 if len(hold) > 1 else 0].source_pos 86 | for i in range(abs(cur - level)): 87 | type = 'INDENT' if cur > level else 'DEDENT' 88 | yield rply.Token(type, '', pos) 89 | level = cur 90 | hold = [] 91 | 92 | if t.name == 'NAME' and t.value in NAME_LIKE: 93 | t.name = t.value.upper() 94 | 95 | yield t 96 | 97 | for t in hold: 98 | yield t 99 | 100 | while level > 0: 101 | yield rply.Token('DEDENT', '', t.source_pos) 102 | level -= 1 103 | 104 | pg = rply.ParserGenerator([ 105 | 'AMP', 'AND', 'ARROW', 'AS', 'ASGT', 106 | 'BOOL', 'BREAK', 107 | 'CARET', 'CLASS', 'COLON', 'COMMA', 'CONTINUE', 108 | 'DEDENT', 'DEF', 'DIV', 'DOLLAR', 'DOT', 109 | 'ELIF', 'ELSE', 'EQ', 'EXCEPT', 110 | 'FOR', 'FROM', 111 | 'GE', 'GT', 112 | 'IADD', 'IF', 'IMPORT', 'IN', 'INDENT', 'IS', 113 | 'LBRA', 'LE', 'LPAR', 'LT', 114 | 'MINUS', 'MUL', 'MOD', 115 | 'NAME', 'NE', 'NL', 'NONE', 'NOT', 'NUM', 116 | 'OR', 117 | 'PASS', 'PIPE', 'PLUS', 118 | 'QM', 119 | 'RAISE', 'RBRA', 'RETURN', 'RPAR', 120 | 'STR', 121 | 'TILDE', 'TRAIT', 'TRY', 122 | 'WHILE', 123 | 'YIELD', 124 | ], precedence=[ 125 | ('left', ['COMMA']), 126 | ('left', ['IF']), 127 | ('left', ['OR']), 128 | ('left', ['AND']), 129 | ('right', ['NOT']), 130 | ('left', ['LT', 'LE', 'GT', 'GE', 'NE', 'EQ', 'IS']), 131 | ('left', ['PIPE']), 132 | ('left', ['CARET']), 133 | ('left', ['AMP']), 134 | ('left', ['PLUS', 'MINUS']), 135 | ('left', ['MUL', 'DIV', 'MOD']), 136 | ('left', ['AS']), 137 | ('right', ['LBRA']), 138 | ('left', ['DOT']), 139 | ] 140 | ) 141 | 142 | @pg.production('module : module-elems') 143 | def module(s, p): 144 | res = ast.File() 145 | res.suite = p[0] 146 | return res 147 | 148 | @pg.production('module : NL module-elems') 149 | def module_after_line(s, p): 150 | res = ast.File() 151 | res.suite = p[1] 152 | return res 153 | 154 | @pg.production('module-elems : module-elems module-elem') 155 | def module_elems(s, p): 156 | p[0].append(p[1]) 157 | return p[0] 158 | 159 | @pg.production('module-elems : module-elem') 160 | def module_elems_single(s, p): 161 | return [p[0]] 162 | 163 | @pg.production('module-elem : function') 164 | def function_module_elem(s, p): 165 | return p[0] 166 | 167 | @pg.production('module-elem : function-decl') 168 | def decl_module_elem(s, p): 169 | return p[0] 170 | 171 | @pg.production('module-elem : class') 172 | def class_module_elem(s, p): 173 | return p[0] 174 | 175 | @pg.production('module-elem : asgt') 176 | def asgt_module_elem(s, p): 177 | return p[0] 178 | 179 | @pg.production('module-elem : trait') 180 | def trait_module_elem(s, p): 181 | return p[0] 182 | 183 | @pg.production('module-elem : FROM dotted IMPORT names NL') 184 | def from_import(s, p): 185 | res = ast.RelImport(s.pos(p[0])) 186 | res.base = p[1] 187 | res.names = p[3] 188 | return res 189 | 190 | @pg.production('dotted : var DOT NAME') 191 | def dotted_attr(s, p): 192 | res = ast.Attrib(s.pos(p[1])) 193 | res.obj = p[0] 194 | res.attrib = p[2].value 195 | return res 196 | 197 | @pg.production('dotted : var') 198 | def dotted_var(s, p): 199 | return p[0] 200 | 201 | @pg.production('names : names COMMA var') 202 | def names(s, p): 203 | p[0].append(p[2]) 204 | return p[0] 205 | 206 | @pg.production('names : var') 207 | def names_single(s, p): 208 | return [p[0]] 209 | 210 | @pg.production('trait : TRAIT var type-params COLON NL INDENT function-decls DEDENT') 211 | def trait(s, p): 212 | res = ast.Trait(s.pos(p[0])) 213 | res.decor = set() 214 | res.name = p[1] 215 | res.params = p[2] 216 | res.methods = p[6] 217 | return res 218 | 219 | @pg.production('function-decls : function-decls function-decl') 220 | def function_decls(s, p): 221 | p[0].append(p[1]) 222 | return p[0] 223 | 224 | @pg.production('function-decls : function-decl') 225 | def function_decls_single(s, p): 226 | return [p[0]] 227 | 228 | @pg.production('function-decl : DEF var formal-list rtype NL') 229 | def function_decl(s, p): 230 | res = ast.Decl(s.pos(p[0])) 231 | res.decor = set() 232 | res.name = p[1] 233 | res.args = p[2] 234 | res.rtype = p[3] 235 | return res 236 | 237 | @pg.production('class : CLASS var type-params COLON NL INDENT class-body DEDENT') 238 | def cls(s, p): 239 | res = ast.Class(s.pos(p[0])) 240 | res.decor = set() 241 | res.name = p[1] 242 | res.params = p[2] 243 | res.attribs = p[6][0] 244 | res.methods = p[6][1] 245 | return res 246 | 247 | @pg.production('type-params : LBRA type-params COMMA type-param RBRA') 248 | def params(s, p): 249 | p[1].append(p[3]) 250 | return p[1] 251 | 252 | @pg.production('type-params : LBRA type-param RBRA') 253 | def params_single(s, p): 254 | return [p[1]] 255 | 256 | @pg.production('type-params : ') 257 | def no_params(s, p): 258 | return [] 259 | 260 | @pg.production('type-param : type') 261 | def param(s, p): 262 | return p[0] 263 | 264 | @pg.production('class-body : attributes functions') 265 | def complete_class_body(s, p): 266 | return p[0], p[1] 267 | 268 | @pg.production('class-body : attributes') 269 | def attrs_class_body(s, p): 270 | return p[0], [] 271 | 272 | @pg.production('class-body : functions') 273 | def methods_class_body(s, p): 274 | return [], p[0] 275 | 276 | @pg.production('class-body : PASS NL') 277 | def empty_class_body(s, p): 278 | return [], [] 279 | 280 | @pg.production('functions : functions function') 281 | def functions(s, p): 282 | p[0].append(p[1]) 283 | return p[0] 284 | 285 | @pg.production('functions : function') 286 | def functions_single(s, p): 287 | return [p[0]] 288 | 289 | @pg.production('attributes : attributes attr-decl') 290 | def attributes(s, p): 291 | p[0].append(p[1]) 292 | return p[0] 293 | 294 | @pg.production('attributes : attr-decl') 295 | def attributes_single(s, p): 296 | return [p[0]] 297 | 298 | @pg.production('attr-decl : var COLON type NL') 299 | def attr_decl(s, p): 300 | return p[2], p[0] 301 | 302 | @pg.production('function : DEF var formal-list rtype COLON suite') 303 | def function(s, p): 304 | res = ast.Function(s.pos(p[0])) 305 | res.decor = set() 306 | res.name = p[1] 307 | res.args = p[2] 308 | res.rtype = p[3] 309 | res.suite = p[5] 310 | return res 311 | 312 | @pg.production('rtype : ') 313 | def void_rtype(s, p): 314 | return 315 | 316 | @pg.production('rtype : ARROW type') 317 | def type_rtype(s, p): 318 | return p[1] 319 | 320 | @pg.production('formal-list : LPAR RPAR') 321 | def no_formals(s, p): 322 | return [] 323 | 324 | @pg.production('formal-list : LPAR formals RPAR') 325 | def formals_list(s, p): 326 | return p[1] 327 | 328 | @pg.production('formals : formals COMMA formal') 329 | def formals(s, p): 330 | p[0].append(p[2]) 331 | return p[0] 332 | 333 | @pg.production('formals : formal') 334 | def formals_single(s, p): 335 | return [p[0]] 336 | 337 | @pg.production('formal : var') 338 | def untyped_formal(s, p): 339 | res = ast.Argument(p[0].pos) 340 | res.name = p[0] 341 | return res 342 | 343 | @pg.production('formal : var COLON type') 344 | def typed_formal(s, p): 345 | res = ast.Argument(p[0].pos) 346 | res.name = p[0] 347 | res.type = p[2] 348 | return res 349 | 350 | @pg.production('suite : NL INDENT statements DEDENT') 351 | def suite(s, p): 352 | return p[2] 353 | 354 | @pg.production('statements : statements stmt') 355 | def statements(s, p): 356 | p[0].stmts.append(p[1]) 357 | return p[0] 358 | 359 | @pg.production('statements : stmt') 360 | def statements_single(s, p): 361 | res = ast.Suite(p[0].pos) 362 | res.stmts = [p[0]] 363 | return res 364 | 365 | @pg.production('stmt : TRY COLON suite EXCEPT var COLON suite') 366 | def try_stmt(s, p): 367 | res = ast.TryBlock(s.pos(p[0])) 368 | res.suite = p[2] 369 | handler = ast.Except(s.pos(p[3])) 370 | handler.type = p[4] 371 | handler.suite = p[6] 372 | res.catch = [handler] 373 | return res 374 | 375 | @pg.production('stmt : FOR lval IN expr-tuple COLON suite') 376 | def for_stmt(s, p): 377 | res = ast.For(s.pos(p[0])) 378 | res.lvar = p[1] 379 | res.source = p[3] 380 | res.suite = p[5] 381 | return res 382 | 383 | @pg.production('stmt : WHILE ternary COLON suite') 384 | def while_stmt(s, p): 385 | res = ast.While(s.pos(p[0])) 386 | res.cond = p[1] 387 | res.suite = p[3] 388 | return res 389 | 390 | @pg.production('stmt : if-suite') 391 | def if_suite_stmt(s, p): 392 | return p[0] 393 | 394 | @pg.production('if-suite : IF ternary COLON suite') 395 | def simple_if_suite(s, p): 396 | res = ast.If(s.pos(p[0])) 397 | res.blocks = [(p[1], p[3])] 398 | return res 399 | 400 | @pg.production('if-suite : IF ternary COLON suite ELSE COLON suite') 401 | def if_else_suite(s, p): 402 | res = ast.If(s.pos(p[0])) 403 | res.blocks = [(p[1], p[3]), (None, p[6])] 404 | return res 405 | 406 | @pg.production('if-suite : IF ternary COLON suite elifs') 407 | def if_with_elifs(s, p): 408 | res = ast.If(s.pos(p[0])) 409 | res.blocks = [(p[1], p[3])] + p[4] 410 | return res 411 | 412 | @pg.production('if-suite : IF ternary COLON suite elifs ELSE COLON suite') 413 | def if_with_elifs_and_else(s, p): 414 | res = ast.If(s.pos(p[0])) 415 | res.blocks = [(p[1], p[3])] + p[4] + [(None, p[7])] 416 | return res 417 | 418 | @pg.production('elifs : elifs elif') 419 | def elifs(s, p): 420 | p[0].append(p[1]) 421 | return p[0] 422 | 423 | @pg.production('elifs : elif') 424 | def elifs_single(s, p): 425 | return [p[0]] 426 | 427 | @pg.production('elif : ELIF ternary COLON suite') 428 | def elif_(s, p): 429 | return p[1], p[3] 430 | 431 | @pg.production('stmt : asgt') 432 | def asgt_stmt(s, p): 433 | return p[0] 434 | 435 | @pg.production('asgt : lval ASGT expr-tuple NL') 436 | def asgt(s, p): 437 | return binop(s, ast.Assign, p) 438 | 439 | @pg.production('stmt : aug-asgt') 440 | def aug_asgt_stmt(s, p): 441 | return p[0] 442 | 443 | @pg.production('aug-asgt : lval IADD expr-tuple NL') 444 | def iadd(s, p): 445 | return binop(s, ast.IAdd, p) 446 | 447 | @pg.production('stmt : yield') 448 | def yield_stmt(s, p): 449 | return p[0] 450 | 451 | @pg.production('yield : YIELD expr-tuple NL') 452 | def value_yield(s, p): 453 | res = ast.Yield(s.pos(p[0])) 454 | res.value = p[1] 455 | return res 456 | 457 | @pg.production('stmt : return') 458 | def return_stmt(s, p): 459 | return p[0] 460 | 461 | @pg.production('return : RETURN expr-tuple NL') 462 | def value_return(s, p): 463 | res = ast.Return(s.pos(p[0])) 464 | res.value = p[1] 465 | return res 466 | 467 | @pg.production('return : RETURN NL') 468 | def void_return(s, p): 469 | res = ast.Return(s.pos(p[0])) 470 | res.value = None 471 | return res 472 | 473 | @pg.production('stmt : RAISE ternary NL') 474 | def raise_(s, p): 475 | res = ast.Raise(s.pos(p[0])) 476 | res.value = p[1] 477 | return res 478 | 479 | @pg.production('stmt : BREAK NL') 480 | def break_(s, p): 481 | return ast.Break(s.pos(p[0])) 482 | 483 | @pg.production('stmt : CONTINUE NL') 484 | def continue_(s, p): 485 | return ast.Continue(s.pos(p[0])) 486 | 487 | @pg.production('stmt : PASS NL') 488 | def pass_(s, p): 489 | return ast.Pass(s.pos(p[0])) 490 | 491 | @pg.production('stmt : expr NL') 492 | def expr_stmt(s, p): 493 | return p[0] 494 | 495 | @pg.production('type : LPAR type-tuple RPAR') 496 | def tuple_type(s, p): 497 | return p[1] 498 | 499 | @pg.production('type-tuple : type-tuple COMMA type') 500 | def type_tuple(s, p): 501 | p[0].append(p[2]) 502 | return p[0] 503 | 504 | @pg.production('type-tuple : type COMMA type') 505 | def two_type_tuple(s, p): 506 | res = ast.Tuple(s.pos(p[1])) 507 | res.values = [p[0], p[2]] 508 | return res 509 | 510 | @pg.production('type : QM type') 511 | def opt_type(s, p): 512 | res = ast.Opt(s.pos(p[0])) 513 | res.value = p[1] 514 | return res 515 | 516 | @pg.production('type : ptr-type') 517 | def ptr_type_type(s, p): 518 | return p[0] 519 | 520 | @pg.production('type : TILDE ptr-type') 521 | def mut_type(s, p): 522 | res = ast.Mut(s.pos(p[0])) 523 | res.value = p[1] 524 | return res 525 | 526 | @pg.production('ptr-type : DOLLAR vtype') 527 | def owner_type(s, p): 528 | res = ast.Owner(s.pos(p[0])) 529 | res.value = p[1] 530 | return res 531 | 532 | @pg.production('ptr-type : AMP vtype') 533 | def ref_type(s, p): 534 | res = ast.Ref(s.pos(p[0])) 535 | res.value = p[1] 536 | return res 537 | 538 | @pg.production('type : vtype') 539 | def vtype_type(s, p): 540 | return p[0] 541 | 542 | @pg.production('vtype : var LBRA type RBRA', precedence='LBRA') 543 | def param_type(s, p): 544 | res = ast.Elem(s.pos(p[1])) # XXX CHANGE TO p[0]? 545 | res.obj = p[0] 546 | res.key = p[2] 547 | return res 548 | 549 | @pg.production('vtype : var') 550 | def name_type(s, p): 551 | return p[0] 552 | 553 | @pg.production('expr-tuple : ternary COMMA ternary') 554 | def expr_tuple_multi(s, p): 555 | res = ast.Tuple(s.pos(p[1])) 556 | res.values = [p[0], p[2]] 557 | return res 558 | 559 | @pg.production('expr-tuple : ternary') 560 | def expr_tuple_base(s, p): 561 | return p[0] 562 | 563 | @pg.production('ternary : expr IF expr ELSE expr') 564 | def actual_ternary(s, p): 565 | res = ast.Ternary(s.pos(p[1])) 566 | res.cond = p[2] 567 | res.values = [p[0], p[4]] 568 | return res 569 | 570 | @pg.production('ternary : expr') 571 | def ternary_expr(s, p): 572 | return p[0] 573 | 574 | @pg.production('expr : expr LBRA expr RBRA') 575 | def elem(s, p): 576 | res = ast.Elem(s.pos(p[1])) 577 | res.obj = p[0] 578 | res.key = p[2] 579 | return res 580 | 581 | @pg.production('expr : LPAR ternary RPAR') 582 | def parenthesized(s, p): 583 | return p[1] 584 | 585 | @pg.production('expr : expr LPAR actuals RPAR') 586 | def call(s, p): 587 | res = ast.Call(s.pos(p[1])) 588 | res.name = p[0] 589 | res.callbr = None 590 | res.fun = None 591 | res.virtual = None 592 | res.args = p[2] 593 | return res 594 | 595 | @pg.production('actuals : actuals COMMA actual') 596 | def actuals(s, p): 597 | p[0].append(p[2]) 598 | return p[0] 599 | 600 | @pg.production('actuals : actual') 601 | def actuals_single(s, p): 602 | return [p[0]] 603 | 604 | @pg.production('actuals : ') 605 | def actuals_empty(s, p): 606 | return [] 607 | 608 | @pg.production('actual : var ASGT ternary') 609 | def named_actual(s, p): 610 | res = ast.NamedArg(p[0].pos) 611 | res.name = p[0].name 612 | res.val = p[2] 613 | return res 614 | 615 | @pg.production('actual : ternary') 616 | def expr_actual(s, p): 617 | return p[0] 618 | 619 | def binop(s, cls, p): 620 | res = cls(s.pos(p[1])) 621 | res.left = p[0] 622 | res.right = p[2] 623 | return res 624 | 625 | @pg.production('expr : expr AND expr') 626 | def and_(s, p): 627 | return binop(s, ast.And, p) 628 | 629 | @pg.production('expr : expr OR expr') 630 | def or_(s, p): 631 | return binop(s, ast.Or, p) 632 | 633 | @pg.production('expr : NOT expr') 634 | def not_(s, p): 635 | res = ast.Not(s.pos(p[0])) 636 | res.value = p[1] 637 | return res 638 | 639 | @pg.production('expr : expr AMP expr') 640 | def bwand(s, p): 641 | return binop(s, ast.BWAnd, p) 642 | 643 | @pg.production('expr : expr PIPE expr') 644 | def bwor(s, p): 645 | return binop(s, ast.BWOr, p) 646 | 647 | @pg.production('expr : expr CARET expr') 648 | def bwxor(s, p): 649 | return binop(s, ast.BWXor, p) 650 | 651 | @pg.production('expr : expr IS expr') 652 | def is_(s, p): 653 | return binop(s, ast.Is, p) 654 | 655 | @pg.production('expr : expr EQ expr') 656 | def eq(s, p): 657 | return binop(s, ast.EQ, p) 658 | 659 | @pg.production('expr : expr NE expr') 660 | def ne(s, p): 661 | return binop(s, ast.NE, p) 662 | 663 | @pg.production('expr : expr LT expr') 664 | def lt(s, p): 665 | return binop(s, ast.LT, p) 666 | 667 | @pg.production('expr : expr GT expr') 668 | def gt(s, p): 669 | return binop(s, ast.GT, p) 670 | 671 | @pg.production('expr : expr LE expr') 672 | def le(s, p): 673 | return binop(s, ast.LE, p) 674 | 675 | @pg.production('expr : expr GE expr') 676 | def ge(s, p): 677 | return binop(s, ast.ge, p) 678 | 679 | @pg.production('expr : expr MOD expr') 680 | def mod(s, p): 681 | return binop(s, ast.Mod, p) 682 | 683 | @pg.production('expr : expr MUL expr') 684 | def mul(s, p): 685 | return binop(s, ast.Mul, p) 686 | 687 | @pg.production('expr : expr DIV expr') 688 | def div(s, p): 689 | return binop(s, ast.Div, p) 690 | 691 | @pg.production('expr : expr PLUS expr') 692 | def plus(s, p): 693 | return binop(s, ast.Add, p) 694 | 695 | @pg.production('expr : expr MINUS expr') 696 | def minus(s, p): 697 | return binop(s, ast.Sub, p) 698 | 699 | @pg.production('expr : expr AS type') 700 | def as_(s, p): 701 | return binop(s, ast.As, p) 702 | 703 | @pg.production('expr : expr DOT NAME') 704 | def attr_expr(s, p): 705 | res = ast.Attrib(s.pos(p[1])) 706 | res.obj = p[0] 707 | res.attrib = p[2].value 708 | return res 709 | 710 | @pg.production('expr : var') 711 | def var_expr(s, p): 712 | return p[0] 713 | 714 | @pg.production('lval : lval COMMA lval') 715 | def lval_tuple(s, p): 716 | res = ast.Tuple(s.pos(p[1])) 717 | res.values = [p[0], p[2]] 718 | return res 719 | 720 | @pg.production('lval : var') 721 | def var_lval(s, p): 722 | return p[0] 723 | 724 | @pg.production('lval : expr DOT NAME') 725 | def attr_lval(s, p): 726 | res = ast.Attrib(s.pos(p[1])) 727 | res.obj = p[0] 728 | res.attrib = p[2].value 729 | return res 730 | 731 | @pg.production('var : NAME') 732 | def var(s, p): 733 | return ast.Name(p[0].value, s.pos(p[0])) 734 | 735 | @pg.production('expr : STR') 736 | def string(s, p): 737 | return ast.String(p[0].value[1:-1], s.pos(p[0])) 738 | 739 | @pg.production('expr : NUM') 740 | def number(s, p): 741 | if '.' in p[0].value: 742 | return ast.Float(p[0].value, s.pos(p[0])) 743 | else: 744 | return ast.Int(p[0].value, s.pos(p[0])) 745 | 746 | @pg.production('expr : BOOL') 747 | def bool_(s, p): 748 | return ast.Bool(p[0].value, s.pos(p[0])) 749 | 750 | @pg.production('expr : NONE') 751 | def none(s, p): 752 | return ast.NoneVal(s.pos(p[0])) 753 | 754 | @pg.error 755 | def error(s, t): 756 | raise util.ParseError(s.fn, t, s.pos(t)) 757 | 758 | parser = pg.build() 759 | 760 | class State(object): 761 | 762 | def __init__(self, fn): 763 | self.fn = fn 764 | with open(fn) as f: 765 | self.src = f.read() 766 | self.lines = self.src.splitlines() 767 | 768 | def pos(self, t): 769 | '''Reprocess location information (see parse() for more details).''' 770 | ln = t.source_pos.lineno - 1 771 | if t.value and t.value[0] == '\n': 772 | ln -= 1 773 | 774 | col = t.source_pos.colno - 1 775 | line = self.lines[ln] if ln < len(self.lines) else '' 776 | return (ln, col), (ln, col + len(t.value)), line, self.fn 777 | 778 | def parse(fn): 779 | '''Takes a file name and returns the AST corresponding to the source 780 | contained in the file. The State thing is here mostly to reprocess 781 | location information from rply into something easier to use. AST nodes 782 | get a pos field containing a 4-element tuple: 783 | 784 | - Tuple of start location, as 0-based line and column numbers 785 | - Tuple of end location, as 0-based line and column numbers 786 | - The full line 787 | - The file name 788 | 789 | This should be everything we need to build good error messages.''' 790 | state = State(fn) 791 | return parser.parse(lex(state.src), state=state) 792 | -------------------------------------------------------------------------------- /runac/pretty.py: -------------------------------------------------------------------------------- 1 | '''A pretty printer for Runa CFG nodes. 2 | 3 | This module implements another CFG walker, which spits out a string 4 | represenation of the given CFG object. This is intended as a debugging aid 5 | during compiler development, and can be invoked using the compiler driver's 6 | `show` command (parametrized with the last pass to run before printing. 7 | ''' 8 | 9 | from . import types, util 10 | 11 | class PrettyPrinter(object): 12 | 13 | def __init__(self): 14 | self.buf = [] 15 | 16 | def newline(self): 17 | self.buf.append('\n') 18 | 19 | def write(self, data): 20 | assert isinstance(data, str), data 21 | self.buf.append(data) 22 | 23 | def writeline(self, ln): 24 | if not ln: return 25 | self.write(ln + '\n') 26 | 27 | def visit(self, node): 28 | if isinstance(node, types.base): 29 | self.type(node) 30 | else: 31 | getattr(self, node.__class__.__name__)(node) 32 | 33 | def build(self, name, fun): 34 | 35 | name = '.'.join(name) if isinstance(name, tuple) else name 36 | self.start = False 37 | self.write('def %s(' % name) 38 | for i, node in enumerate(fun.args): 39 | self.visit(node) 40 | if i < len(fun.args) - 1: 41 | self.write(', ') 42 | 43 | self.write(')') 44 | if fun.rtype is not None: 45 | self.write(' -> ') 46 | self.type(fun.rtype) 47 | 48 | self.write(':') 49 | self.newline() 50 | 51 | for i, bl in sorted(util.items(fun.flow.blocks)): 52 | self.writeline(' %2i: # %s' % (i, bl.anno)) 53 | for sid, step in enumerate(bl.steps): 54 | self.write(' {%02i} ' % sid) 55 | self.visit(step) 56 | self.newline() 57 | 58 | return ''.join(self.buf) 59 | 60 | def type(self, node): 61 | self.write(node.name) 62 | 63 | def anno(self, node): 64 | 65 | if node.type is None and not getattr(node, 'escapes', False): 66 | return 67 | 68 | if isinstance(node.type, (types.function, types.Type)): 69 | return 70 | 71 | self.write(' [') 72 | if node.type is not None: 73 | self.visit(node.type) 74 | 75 | if node.type is not None and getattr(node, 'escapes', False): 76 | self.write(':') 77 | 78 | if getattr(node, 'escapes', False): 79 | self.write('E') 80 | 81 | self.write(']') 82 | 83 | def NoneVal(self, node): 84 | self.write('NoneVal') 85 | self.anno(node) 86 | 87 | def Name(self, node): 88 | self.write(node.name) 89 | self.anno(node) 90 | 91 | def Bool(self, node): 92 | self.write(str(node.val)) 93 | self.anno(node) 94 | 95 | def Int(self, node): 96 | self.write(str(node.val)) 97 | self.anno(node) 98 | 99 | def Float(self, node): 100 | self.write(str(node.val)) 101 | self.anno(node) 102 | 103 | def String(self, node): 104 | self.write(repr(node.val)) 105 | self.anno(node) 106 | 107 | def Tuple(self, node): 108 | self.write('(') 109 | for i, n in enumerate(node.values): 110 | if i: 111 | self.write(', ') 112 | self.visit(n) 113 | self.write(')') 114 | 115 | def binary(self, op, node): 116 | self.write(op + ' ') 117 | self.visit(node.left) 118 | self.write(' ') 119 | self.visit(node.right) 120 | 121 | def As(self, node): 122 | self.write('As ') 123 | self.visit(node.left) 124 | self.write(' ') 125 | self.visit(node.type) 126 | 127 | def Not(self, node): 128 | self.write('Not ') 129 | self.visit(node.value) 130 | 131 | def And(self, node): 132 | self.binary('And', node) 133 | 134 | def Or(self, node): 135 | self.binary('Or', node) 136 | 137 | def Is(self, node): 138 | self.binary('Is', node) 139 | 140 | def NE(self, node): 141 | self.binary('NE', node) 142 | 143 | def GT(self, node): 144 | self.binary('GT', node) 145 | 146 | def LT(self, node): 147 | self.binary('LT', node) 148 | 149 | def EQ(self, node): 150 | self.binary('EQ', node) 151 | 152 | def Add(self, node): 153 | self.binary('Add', node) 154 | 155 | def Sub(self, node): 156 | self.binary('Sub', node) 157 | 158 | def Mod(self, node): 159 | self.binary('Mod', node) 160 | 161 | def Mul(self, node): 162 | self.binary('Mul', node) 163 | 164 | def Div(self, node): 165 | self.binary('Div', node) 166 | 167 | def BWAnd(self, node): 168 | self.binary('BWAnd', node) 169 | 170 | def BWOr(self, node): 171 | self.binary('BWOr', node) 172 | 173 | def BWXor(self, node): 174 | self.binary('BWXor', node) 175 | 176 | def IAdd(self, node): 177 | self.binary('IAdd', node) 178 | 179 | def Pass(self, node): 180 | self.write('Pass') 181 | 182 | def Init(self, node): 183 | self.write('Init ') 184 | self.visit(node.type) 185 | 186 | def Argument(self, node): 187 | self.write(node.name.name) 188 | self.anno(node) 189 | 190 | def Assign(self, node): 191 | self.visit(node.left) 192 | self.write(' = ') 193 | self.visit(node.right) 194 | 195 | def Call(self, node): 196 | self.visit(node.name) 197 | self.write('(') 198 | for i, arg in enumerate(node.args): 199 | self.visit(arg) 200 | if i < len(node.args) - 1: 201 | self.write(', ') 202 | self.write(')') 203 | self.anno(node) 204 | if node.callbr is not None: 205 | self.write(' => %s, %s' % node.callbr) 206 | 207 | def Return(self, node): 208 | self.write('Return') 209 | if node.value is not None: 210 | self.write(' ') 211 | self.visit(node.value) 212 | 213 | def Yield(self, node): 214 | self.write('Yield') 215 | if node.value is not None: 216 | self.write(' ') 217 | self.visit(node.value) 218 | 219 | def Raise(self, node): 220 | self.write('Raise ') 221 | self.visit(node.value) 222 | 223 | def CondBranch(self, node): 224 | self.write('CondBranch ') 225 | self.visit(node.cond) 226 | self.write(' ? %s : %s' % (node.tg1, node.tg2)) 227 | 228 | def Branch(self, node): 229 | self.write('Branch %s' % node.label) 230 | 231 | def Attrib(self, node): 232 | self.visit(node.obj) 233 | self.write(' . ') 234 | self.write(node.attrib) 235 | self.anno(node) 236 | 237 | def SetAttr(self, node): 238 | self.Attrib(node) 239 | 240 | def Elem(self, node): 241 | self.write('Elem(') 242 | self.visit(node.obj) 243 | self.write(', ') 244 | self.visit(node.key) 245 | self.write(')') 246 | self.anno(node) 247 | 248 | def Phi(self, node): 249 | self.write('Phi ') 250 | self.write('%s:' % node.left[0]) 251 | self.visit(node.left[1]) 252 | self.write(', ') 253 | self.write('%i:' % node.right[0]) 254 | self.visit(node.right[1]) 255 | 256 | def For(self, node): 257 | self.visit(node.lvar) 258 | self.write(' <- ') 259 | self.visit(node.source) 260 | 261 | def LPad(self, node): 262 | self.write('LPad: %s {' % node.var) 263 | for k, v in util.items(node.map): 264 | self.visit(k) 265 | self.write(': ' + str(v)) 266 | self.write('}') 267 | 268 | def Resume(self, node): 269 | self.write('Resume: %s' % node.var) 270 | 271 | def LoopSetup(self, node): 272 | self.write('LoopSetup ') 273 | self.visit(node.loop) 274 | 275 | def LoopHeader(self, node): 276 | self.write('LoopHeader ctx:') 277 | self.visit(node.ctx) 278 | self.write(' lvar:') 279 | self.visit(node.lvar) 280 | self.write(' %s:%s' % (node.tg1, node.tg2)) 281 | 282 | def Free(self, node): 283 | self.write('Free(') 284 | self.visit(node.value) 285 | self.write(')') 286 | 287 | def prettify(name, flow): 288 | pp = PrettyPrinter() 289 | return pp.build(name, flow) 290 | -------------------------------------------------------------------------------- /runac/specialize.py: -------------------------------------------------------------------------------- 1 | '''Specialize any* types and traits, where necessary. 2 | Propagate type information so that imprecisely typed variables 3 | get a concrete type. 4 | In cases where no specific information is available for number types, 5 | we just pick ``int`` (word-sized) or ``float`` (C double, so 64 bits). 6 | 7 | TODO: Rust has changed their defaults a number of times. Read up on 8 | http://discuss.rust-lang.org/t/restarting-the-int-uint-discussion/1131, 9 | see if it makes sense to change int to be ``i32``. Currently, we only have 10 | a single float type; it seems like ``float`` == ``f64`` might make sense. 11 | ''' 12 | 13 | from . import ast, types, util 14 | 15 | class Specializer(object): 16 | 17 | def __init__(self, mod, fun): 18 | self.mod = mod 19 | self.fun = fun 20 | self.cfg = fun.flow 21 | self.track = {} 22 | 23 | def visit(self, node, type=None): 24 | getattr(self, node.__class__.__name__)(node, type) 25 | 26 | def specialize(self, node, dst): 27 | if node.type == dst: 28 | return 29 | elif node.type == types.anyint() and dst is None: 30 | node.type = self.mod.type('int') 31 | elif node.type == types.anyfloat() and dst is None: 32 | node.type = self.mod.type('float') 33 | elif dst is None: 34 | return 35 | elif node.type == types.anyint() and types.unwrap(dst) in types.INTS: 36 | if isinstance(node, ast.Int): 37 | dst = types.unwrap(dst) 38 | node.type = dst 39 | if not dst.signed: 40 | assert int(node.val) >= 0 41 | else: 42 | assert False, (node.type, dst) 43 | elif node.type == types.anyfloat() and types.unwrap(dst) in types.FLOATS: 44 | if isinstance(node, ast.Float): 45 | node.type = types.unwrap(dst) 46 | else: 47 | assert False, (node.type, dst) 48 | elif isinstance(dst, types.concrete): 49 | ttypes = [] 50 | for i, e in enumerate(node.type.params): 51 | assert types.compat(e, dst.params[i]) 52 | ttypes.append(dst.params[i]) 53 | node.type = self.mod.type(('tuple', ttypes)) 54 | elif isinstance(types.unwrap(dst), types.trait): 55 | if node.type == types.anyint(): 56 | node.type = self.mod.type('int') 57 | elif node.type == types.anyfloat(): 58 | node.type = self.mod.type('float') 59 | else: 60 | assert False, 'specialize %s to trait' % node.type 61 | else: 62 | assert False, '%s -> %s' % (node.type, dst) 63 | 64 | def binspec(self, node, type): 65 | if types.generic(node.left.type) and types.generic(node.right.type): 66 | self.visit(node.left) 67 | self.visit(node.right) 68 | elif types.generic(node.left.type): 69 | self.visit(node.left, node.right.type) 70 | self.visit(node.right) 71 | elif types.generic(node.right.type): 72 | self.visit(node.right, node.left.type) 73 | self.visit(node.left) 74 | 75 | # Constants 76 | 77 | def NoneVal(self, node, type=None): 78 | pass 79 | 80 | def Bool(self, node, type=None): 81 | pass 82 | 83 | def Int(self, node, type=None): 84 | self.specialize(node, type) 85 | 86 | def Float(self, node, type=None): 87 | self.specialize(node, type) 88 | 89 | def String(self, node, type=None): 90 | pass 91 | 92 | def Name(self, node, type=None): 93 | 94 | params = [] 95 | if hasattr(node.type, 'params'): 96 | params = [types.generic(t) for t in node.type.params] 97 | 98 | if not types.generic(node.type) and not any(params): 99 | return 100 | else: 101 | self.specialize(node, type) 102 | 103 | def Tuple(self, node, type=None): 104 | ttypes = [None] * len(node.values) if type is None else type.params 105 | for i, e in enumerate(node.values): 106 | if types.generic(e.type): 107 | self.specialize(e, ttypes[i]) 108 | node.type = self.mod.type(('tuple', (n.type for n in node.values))) 109 | 110 | def Init(self, node, type=None): 111 | pass 112 | 113 | # Boolean operators 114 | 115 | def Not(self, node, type=None): 116 | self.specialize(node.value, None) 117 | 118 | def And(self, node, type=None): 119 | self.specialize(node.left, None) 120 | self.specialize(node.right, None) 121 | 122 | def Or(self, node, type=None): 123 | self.specialize(node.left, None) 124 | self.specialize(node.right, None) 125 | 126 | # Comparison operators 127 | 128 | def Is(self, node, type): 129 | self.binspec(node, type) 130 | 131 | def compare(self, node, type): 132 | self.binspec(node, type) 133 | 134 | def EQ(self, node, type=None): 135 | self.compare(node, type) 136 | 137 | def NE(self, node, type=None): 138 | self.compare(node, type) 139 | 140 | def LT(self, node, type=None): 141 | self.compare(node, type) 142 | 143 | def GT(self, node, type=None): 144 | self.compare(node, type) 145 | 146 | # Arithmetic operators 147 | 148 | def arith(self, op, node, type): 149 | self.binspec(node, type) 150 | assert node.left.type == node.right.type 151 | node.type = node.left.type 152 | 153 | def Add(self, node, type=None): 154 | self.arith('add', node, type) 155 | 156 | def Sub(self, node, type=None): 157 | self.arith('sub', node, type) 158 | 159 | def Mod(self, node, type=None): 160 | self.arith('mod', node, type) 161 | 162 | def Mul(self, node, type=None): 163 | self.arith('mul', node, type) 164 | 165 | def Div(self, node, type=None): 166 | self.arith('div', node, type) 167 | 168 | # Bitwise operators 169 | 170 | def bitwise(self, op, node, type): 171 | self.binspec(node, type) 172 | 173 | def BWAnd(self, node, type=None): 174 | self.bitwise('and', node, type) 175 | 176 | def BWOr(self, node, type=None): 177 | self.bitwise('or', node, type) 178 | 179 | def BWXor(self, node, type=None): 180 | self.bitwise('xor', node, type) 181 | 182 | # Miscellaneous 183 | 184 | def Pass(self, node, type=None): 185 | pass 186 | 187 | def LPad(self, node, type=None): 188 | pass 189 | 190 | def Resume(self, node, type=None): 191 | pass 192 | 193 | def As(self, node, type=None): 194 | if types.generic(node.left.type): 195 | self.visit(node.left, node.type) 196 | else: 197 | self.visit(node.left) 198 | 199 | def LoopSetup(self, node, type=None): 200 | self.visit(node.loop.source, type) 201 | 202 | def LoopHeader(self, node, type=None): 203 | self.visit(node.lvar, self.track.get(node.lvar.name)) 204 | 205 | def CondBranch(self, node, type=None): 206 | self.visit(node.cond, self.mod.type('ToBool')) 207 | 208 | def Assign(self, node, type=None): 209 | if isinstance(node.left, ast.Name): 210 | self.visit(node.right, self.track.get(node.left.name)) 211 | else: 212 | self.visit(node.right, node.left.type) 213 | 214 | def IAdd(self, node, type=None): 215 | if isinstance(node.left, ast.Name): 216 | self.visit(node.right, self.track.get(node.left.name)) 217 | else: 218 | self.visit(node.right, node.left.type) 219 | 220 | def Elem(self, node, type=None): 221 | self.visit(node.obj) 222 | self.visit(node.key) 223 | # TODO: should specialize key type 224 | assert not types.generic(node.type) 225 | 226 | def Attrib(self, node, type=None): 227 | self.visit(node.obj) 228 | assert not types.generic(node.type) 229 | 230 | def SetAttr(self, node, type=None): 231 | self.Attrib(node, type) 232 | 233 | def Return(self, node, type=None): 234 | if node.value is not None: 235 | self.visit(node.value, self.fun.rtype) 236 | 237 | def Yield(self, node, type=None): 238 | if node.value is not None: 239 | self.visit(node.value, self.fun.rtype.params[0]) 240 | 241 | def Call(self, node, type=None): 242 | for i, arg in enumerate(node.args): 243 | 244 | if not types.generic(arg.type): 245 | self.visit(arg) 246 | continue 247 | 248 | self.visit(arg, node.fun.type.over[1][i]) 249 | if not isinstance(arg, ast.Name): 250 | assert not types.generic(arg.type), arg.type 251 | 252 | def Phi(self, node, type=None): 253 | 254 | self.visit(node.left[1], type) 255 | self.visit(node.right[1], type) 256 | 257 | if node.left[1].type == node.right[1].type: 258 | node.type = node.left[1].type 259 | return 260 | 261 | if node.left[1].type == self.mod.type('NoType'): 262 | assert node.type == types.opt(node.right[1].type) 263 | node.left[1].type = node.type 264 | return 265 | elif node.right[1].type == self.mod.type('NoType'): 266 | assert node.type == types.opt(node.left[1].type) 267 | node.right[1].type = node.type 268 | return 269 | 270 | assert False, (node.left[1].type, node.right[1].type) 271 | 272 | def Branch(self, node, type=None): 273 | pass 274 | 275 | def Raise(self, node, type=None): 276 | pass 277 | 278 | def propagate(self): 279 | for i, bl in util.items(self.cfg.blocks): 280 | for step in reversed(bl.steps): 281 | self.visit(step) 282 | 283 | def specialize(mod): 284 | for name, code in mod.code: 285 | Specializer(mod, code).propagate() 286 | -------------------------------------------------------------------------------- /runac/types.py: -------------------------------------------------------------------------------- 1 | '''Functionality for the Runa type system. 2 | 3 | The core types are outlined in this file: 4 | 5 | - void 6 | - bool, byte 7 | - i8, u8, i32, u32, int, uint, float 8 | - function 9 | - iter 10 | - tuple (as a `build_tuple()` constructor) 11 | - owner, ref, opt 12 | 13 | Actual implementations attached to these types are mostly contained in the 14 | core library. Additionally, helpers are provided for template types and their 15 | concrete variants; these are used for traits and generics support. 16 | ''' 17 | 18 | from . import ast, util 19 | import copy, platform 20 | 21 | WORD_SIZE = int(platform.architecture()[0][:2]) 22 | 23 | BASIC = { 24 | 'bool': 'i1', 25 | 'byte': 'i8', 26 | 'i8': 'i8', 27 | 'u8': 'i8', 28 | 'i32': 'i32', 29 | 'u32': 'i32', 30 | 'u64': 'i64', 31 | 'int': 'i%i' % WORD_SIZE, 32 | 'uint': 'i%i' % WORD_SIZE, 33 | 'float': 'double', 34 | } 35 | 36 | INTEGERS = { 37 | 'i8': (True, 8), 38 | 'u8': (False, 8), 39 | 'i32': (True, 32), 40 | 'u32': (False, 32), 41 | 'u64': (False, 64), 42 | 'int': (True, WORD_SIZE), 43 | 'uint': (False, WORD_SIZE), 44 | } 45 | 46 | BASIC_FLOATS = {'float': 64} 47 | 48 | class Type(object): 49 | def __eq__(self, other): 50 | return self.__class__ == other.__class__ 51 | 52 | class ReprId(object): 53 | 54 | def __hash__(self): 55 | return hash(repr(self)) 56 | 57 | def __eq__(self, other): 58 | return repr(self) == repr(other) 59 | 60 | def __ne__(self, other): 61 | return not self.__eq__(other) 62 | 63 | def select(self, node, name, positional, named): 64 | 65 | if name not in self.methods: 66 | msg = "%s does not have a method '%s'" 67 | if node.args: 68 | t = node.args[0].type.name 69 | elif isinstance(node.name.type, Type): 70 | t = node.name.name 71 | else: 72 | assert False, node 73 | raise util.Error(node, msg % (t, name)) 74 | 75 | opts = copy.copy(self.methods[name]) 76 | if name == '__init__' and '__new__' in self.methods: 77 | opts += self.methods['__new__'] 78 | 79 | if named: 80 | assert False, named 81 | 82 | res, candidates = [], [] 83 | for fun in opts: 84 | 85 | tmp = positional 86 | if '__init__' in fun.decl: 87 | tmp = [ref(self)] + positional 88 | 89 | candidates.append((fun.name, tmp, fun.type.over[1])) 90 | if len(fun.type.over[1]) != len(tmp) + len(named): 91 | continue 92 | 93 | score = 0 94 | for at, ft in zip(tmp, fun.type.over[1]): 95 | if not compat(at, ft, 'args'): 96 | score -= 1000 97 | break 98 | elif at == ft: 99 | score += 10 100 | else: 101 | score += 1 102 | 103 | if score > 0: 104 | res.append(fun) 105 | 106 | if not res: 107 | msg = ['no matching method found, candidates tried:'] 108 | for name, atypes, ftypes in candidates: 109 | anames = ', '.join(t.name for t in atypes) 110 | fnames = ', '.join(t.name for t in ftypes) 111 | display = name.split('$', 1)[0] 112 | msg.append(' (%s) -> %s(%s)' % (anames, display, fnames)) 113 | raise util.Error(node, '\n'.join(msg)) 114 | 115 | assert len(res) == 1, res 116 | return res[0] 117 | 118 | class base(ReprId): 119 | 120 | byval = False 121 | mut = False 122 | attribs = {} 123 | methods = {} 124 | type = Type() 125 | 126 | @property 127 | def name(self): 128 | return self.__class__.__name__ 129 | 130 | @property 131 | def ir(self): 132 | return '%' + self.name 133 | 134 | def __repr__(self): 135 | return '' % self.__class__.__name__ 136 | 137 | class trait(ReprId): 138 | 139 | byval = False 140 | attribs = {} 141 | methods = {} 142 | type = Type() 143 | 144 | @property 145 | def name(self): 146 | return self.__class__.__name__ 147 | 148 | @property 149 | def ir(self): 150 | return '%%%s.wrap' % self.name 151 | 152 | def __repr__(self): 153 | return '' % self.name 154 | 155 | class concrete(base): 156 | attribs = {} 157 | methods = {} 158 | 159 | class template(ReprId): 160 | 161 | byval = False 162 | attribs = {} 163 | methods = {} 164 | type = Type() 165 | 166 | @property 167 | def name(self): 168 | return self.__class__.__name__ 169 | 170 | @property 171 | def ir(self): 172 | assert False, '%s is not a concrete type' % self.name 173 | 174 | def __repr__(self): 175 | return '' % self.__class__.__name__ 176 | 177 | class iter(template): 178 | params = 'T', 179 | attribs = {} 180 | methods = {} 181 | 182 | class void(base): 183 | ir = 'void' 184 | attribs = {} 185 | methods = {} 186 | byval = True 187 | 188 | class anyint(base): 189 | 190 | attribs = {} 191 | methods = {} 192 | 193 | @property 194 | def ir(self): 195 | assert False, 'not a concrete type' 196 | 197 | class anyfloat(base): 198 | 199 | attribs = {} 200 | methods = {} 201 | 202 | @property 203 | def ir(self): 204 | assert False, 'not a concrete type' 205 | 206 | class module(base): 207 | def __init__(self, path=None): 208 | self.path = path 209 | self.functions = {} 210 | 211 | class owner(base): 212 | 213 | def __init__(self, over): 214 | self.over = over 215 | self.mut = True 216 | 217 | @property 218 | def name(self): 219 | return '$%s' % self.over.name 220 | 221 | @property 222 | def ir(self): 223 | return self.over.ir + '*' 224 | 225 | def __repr__(self): 226 | return '' % (self.over.name) 227 | 228 | class ref(base): 229 | 230 | def __init__(self, over, mut=False): 231 | self.over = over 232 | self.mut = mut 233 | 234 | @property 235 | def name(self): 236 | return '%s&%s' % ('~' if self.mut else '', self.over.name) 237 | 238 | @property 239 | def ir(self): 240 | return self.over.ir + '*' 241 | 242 | def __repr__(self): 243 | return '' % (self.over.name) 244 | 245 | class opt(base): 246 | 247 | def __init__(self, over): 248 | self.over = over 249 | 250 | @property 251 | def name(self): 252 | return '?%s' % self.over.name 253 | 254 | @property 255 | def ir(self): 256 | return self.over.ir 257 | 258 | def __repr__(self): 259 | return '' % (self.over.name) 260 | 261 | SINTS = {anyint()} 262 | UINTS = set() 263 | INTS = {anyint()} 264 | FLOATS = {anyfloat()} 265 | WRAPPERS = owner, ref 266 | BASE = void, anyint, anyfloat, iter 267 | 268 | class function(base): 269 | 270 | def __init__(self, rtype, formal): 271 | self.over = rtype, formal 272 | self.args = None 273 | 274 | def __repr__(self): 275 | if not self.over[1] or isinstance(self.over[1][0], tuple): 276 | formal = ['%r' % i[1] for i in self.over[1]] 277 | else: 278 | formal = ['%r' % i for i in self.over[1]] 279 | return '' % (self.over[0], ', '.join(formal)) 280 | 281 | @property 282 | def ir(self): 283 | args = ', '.join(a.ir for a in self.over[1]) 284 | return '%s (%s)*' % (self.over[0].ir, args) 285 | 286 | def wrapped(t): 287 | return isinstance(t, WRAPPERS) 288 | 289 | def unwrap(t): 290 | while isinstance(t, WRAPPERS): 291 | t = t.over 292 | return t 293 | 294 | def generic(t): 295 | return isinstance(unwrap(t), (anyint, anyfloat)) 296 | 297 | def wrangle(s): 298 | s = s.replace('&', 'R') 299 | s = s.replace('$', 'O') 300 | s = s.replace('[', 'BT') 301 | s = s.replace(']', 'ET') 302 | return s 303 | 304 | def compat(a, f, mode='default'): 305 | 306 | assert mode in {'default', 'args', 'return'} 307 | if isinstance(a, concrete) and isinstance(f, concrete): 308 | pairs = zip(a.params, f.params) 309 | return all(compat(i[0], i[1], mode) for i in pairs) 310 | 311 | if isinstance(a, (tuple, list)) and isinstance(f, (tuple, list)): 312 | if len(a) != len(f): 313 | return False 314 | if f and f[-1] == VarArgs(): 315 | return all(compat(i[0], i[1], mode) for i in zip(a, f[:-1])) 316 | return all(compat(i[0], i[1], mode) for i in zip(a, f)) 317 | 318 | if a == f: 319 | return True 320 | elif isinstance(a, anyint) and f in INTS: 321 | return True 322 | elif isinstance(a, ref) and isinstance(f, owner): 323 | return False 324 | elif mode == 'return' and isinstance(a, owner) and isinstance(f, ref): 325 | return False 326 | elif not isinstance(a, opt) and isinstance(f, opt): 327 | return compat(a, f.over, mode) 328 | elif a in UINTS and f in UINTS: 329 | return a.bits < f.bits 330 | 331 | if wrapped(a) and wrapped(f): 332 | return compat(unwrap(a), unwrap(f), mode) 333 | elif mode == 'args' and (wrapped(a) or wrapped(f)): 334 | return compat(unwrap(a), unwrap(f), mode) 335 | 336 | if not isinstance(f, trait): 337 | return False 338 | 339 | for k, malts in util.items(f.methods): 340 | 341 | if k not in a.methods: 342 | return False 343 | 344 | art = a.methods[k][0].type.over[0] 345 | frt = malts[0].type.over[0] 346 | rc = compat(art, frt, mode) 347 | if not rc: 348 | return False 349 | 350 | tmalts = set() 351 | for fun in malts: 352 | tmalts.add(tuple(at for at in fun.type.over[1][1:])) 353 | 354 | amalts = set() 355 | for fun in a.methods[k]: 356 | amalts.add(tuple(at for at in fun.type.over[1][1:])) 357 | 358 | if tmalts != amalts: 359 | return False 360 | 361 | return True 362 | 363 | class Stub(object): 364 | def __init__(self, name): 365 | self.name = name 366 | def __repr__(self): 367 | return '<%s(%r)>' % (self.__class__.__name__, self.name) 368 | 369 | class VarArgs(base): 370 | @property 371 | def ir(self): 372 | return '...' 373 | 374 | class FunctionDecl(util.AttribRepr): 375 | 376 | def __init__(self, decl, type): 377 | self.decl = decl 378 | self.type = type 379 | self.name = decl # might be overridden by the Module 380 | 381 | @classmethod 382 | def from_decl(cls, mod, node): 383 | atypes = [mod.type(a.type) for a in node.args] 384 | funtype = function(mod.type(node.rtype), atypes) 385 | return cls(node.name.name, funtype) 386 | 387 | @classmethod 388 | def from_ast(cls, mod, node, type=None, stubs={}): 389 | 390 | args = [] 391 | for i, arg in enumerate(node.args): 392 | if not i and arg.name.name == 'self': 393 | wrapper = owner if node.name.name == '__del__' else ref 394 | args.append((wrapper(type), 'self')) 395 | elif arg.type is None: 396 | msg = "missing type for argument '%s'" 397 | raise util.Error(arg, msg % arg.name.name) 398 | else: 399 | args.append((mod.type(arg.type, stubs), arg.name.name)) 400 | 401 | rtype = void() 402 | if node.rtype is not None: 403 | rtype = mod.type(node.rtype, stubs) 404 | 405 | funtype = function(rtype, tuple(i[0] for i in args)) 406 | funtype.args = tuple(i[1] for i in args) 407 | 408 | name = node.name.name 409 | irname = mod.name + '.' + name 410 | if type is not None: 411 | irname = mod.name + '.%s.%s' % (type.name, name) 412 | if name in type.methods: 413 | wrangled = '.'.join(wrangle(t.name) for t in funtype.over[1]) 414 | irname = irname + '$' + wrangled 415 | assert funtype.over[0] == type.methods[name][0].type.over[0] 416 | 417 | irname = 'main' if irname == 'Runa.__main__.main' else irname 418 | return cls(irname, funtype) 419 | 420 | def create(node): 421 | 422 | if isinstance(node, ast.Trait): 423 | parent = trait 424 | elif node.params: 425 | parent = template 426 | else: 427 | parent = base 428 | 429 | if isinstance(node, ast.Trait): 430 | fields = {'methods': {}} 431 | elif node.params: 432 | fields = {'methods': {}, 'attribs': {}, 'params': {}} 433 | else: 434 | fields = {'methods': {}, 'attribs': {}} 435 | 436 | if node.name.name in BASIC: 437 | fields['ir'] = BASIC[node.name.name] 438 | fields['byval'] = True 439 | 440 | return type(node.name.name, (parent,), fields)() 441 | 442 | def build_tuple(params): 443 | params = tuple(params) 444 | name = 'tuple[%s]' % ', '.join(p.name for p in params) 445 | internal = name.replace('%', '_').replace('.', '_') 446 | return type(internal, (concrete,), { 447 | 'ir': '%tuple$' + '.'.join(wrangle(t.name) for t in params), 448 | 'name': name, 449 | 'params': params, 450 | 'methods': {'v%i' % i: (i, t) for (i, t) in enumerate(params)}, 451 | 'attribs': {}, 452 | })() 453 | 454 | def apply(tpl, params): 455 | 456 | assert isinstance(params, tuple) 457 | name = '%s[%s]' % (tpl.name, ', '.join(p.name for p in params)) 458 | internal = name.replace('$', '_').replace('.', '_') 459 | cls = type(internal, (concrete,), { 460 | 'ir': '%' + tpl.name + '$' + '.'.join(t.name for t in params), 461 | 'name': name, 462 | 'params': params, 463 | 'methods': {}, 464 | 'attribs': {}, 465 | }) 466 | 467 | trans = {k: v for (k, v) in zip(tpl.params, params)} 468 | for k, v in util.items(tpl.attribs): 469 | if isinstance(v[1], Stub): 470 | cls.attribs[k] = v[0], trans[v[1].name] 471 | elif isinstance(v[1], WRAPPERS) and isinstance(v[1].over, Stub): 472 | cls.attribs[k] = v[0], v[1].__class__(trans[v[1].over.name]) 473 | else: 474 | cls.attribs[k] = v[0], v[1] 475 | 476 | for k, mtypes in util.items(tpl.methods): 477 | for method in mtypes: 478 | 479 | rtype = method.type.over[0] 480 | if rtype == tpl: 481 | rtype = cls() 482 | 483 | formal = method.type.over[1] 484 | formal = (ref(cls()),) + formal[1:] 485 | t = function(rtype, formal) 486 | 487 | pmd = tpl.name + '$' + '.'.join(t.name for t in params) 488 | decl = method.decl.replace(tpl.name, pmd) 489 | cls.methods.setdefault(k, []).append(FunctionDecl(decl, t)) 490 | 491 | return cls() 492 | 493 | def fill(mod, node): 494 | 495 | obj = mod.scope[node.name.name] 496 | cls, stubs = obj.__class__, {} 497 | if not isinstance(node, ast.Trait): 498 | cls.params = tuple(n.name for n in node.params) 499 | stubs = {n.name: Stub(n.name) for n in node.params} 500 | for i, (atype, name) in enumerate(node.attribs): 501 | cls.attribs[name.name] = i, mod.type(atype, stubs) 502 | 503 | for method in node.methods: 504 | name = method.name.name 505 | fun = FunctionDecl.from_ast(mod, method, obj, stubs) 506 | cls.methods.setdefault(name, []).append(fun) 507 | method.irname = fun.decl 508 | 509 | if node.name.name in INTEGERS: 510 | 511 | cls.signed, cls.bits = INTEGERS[node.name.name] 512 | INTS.add(obj) 513 | if cls.signed: 514 | SINTS.add(obj) 515 | else: 516 | UINTS.add(obj) 517 | 518 | mod.scope['anyint'].methods.update(cls.methods) 519 | 520 | elif node.name.name in BASIC_FLOATS: 521 | cls.bits = BASIC_FLOATS[node.name.name] 522 | FLOATS.add(obj) 523 | mod.scope['anyfloat'].methods.update(cls.methods) 524 | -------------------------------------------------------------------------------- /runac/util.py: -------------------------------------------------------------------------------- 1 | import os, sys 2 | 3 | BASE = os.path.dirname(os.path.dirname(__file__)) 4 | CORE_DIR = os.path.join(BASE, 'core') 5 | IGNORE = {'pos'} 6 | 7 | if sys.version_info[0] < 3: 8 | def keys(d): 9 | return d.iterkeys() 10 | def values(d): 11 | return d.itervalues() 12 | def items(d): 13 | return d.iteritems() 14 | else: 15 | def keys(d): 16 | return d.keys() 17 | def values(d): 18 | return d.values() 19 | def items(d): 20 | return d.items() 21 | 22 | class AttribRepr(object): 23 | '''Helper class to provide a nice __repr__ for other classes''' 24 | def __repr__(self): 25 | contents = sorted(items(self.__dict__)) 26 | show = ('%s=%r' % (k, v) for (k, v) in contents if k not in IGNORE) 27 | return '<%s(%s)>' % (self.__class__.__name__, ', '.join(show)) 28 | 29 | def error(fn, msg, pos): 30 | '''Helper function to print useful error messages. 31 | 32 | Tries to mangle location information and message into a layout that's 33 | easy to read and provides good data about the underlying error message. 34 | 35 | This is in a separate function because of the differences between Error 36 | and ParseError, which both need this functionality.''' 37 | if pos is None: 38 | return '%s: %s\n' % (fn, msg) 39 | 40 | col = len(pos[2][:pos[0][1]].replace('\t', ' ' * 4)) + 1 41 | desc = '%s [%s.%s]: %s' % (fn, pos[0][0] + 1, col, msg) 42 | if not pos[2]: 43 | return desc + '\n' 44 | 45 | line = pos[2].replace('\t', ' ' * 4).rstrip() 46 | spaces = pos[0][1] + 3 * min(pos[0][1], pos[2].count('\t')) 47 | return '\n'.join((desc, line, ' ' * spaces + '^')) + '\n' 48 | 49 | class Error(Exception): 50 | '''Error class used for throwing user errors from the compiler''' 51 | 52 | def __init__(self, node, msg): 53 | Exception.__init__(self, msg) 54 | self.node = node 55 | self.msg = msg 56 | 57 | def show(self): 58 | fn = os.path.basename(self.node.pos[3]) 59 | return error(fn, self.msg, getattr(self.node, 'pos', None)) 60 | 61 | class ParseError(Exception): 62 | '''Parse errors, raised from rply's error handling function''' 63 | 64 | def __init__(self, fn, t, pos): 65 | self.fn = fn 66 | self.t = t 67 | self.pos = pos 68 | 69 | def show(self): 70 | fn = os.path.basename(self.pos[3]) 71 | msg = 'unexpected token %s (%r)' % (self.t.name, self.t.value) 72 | return error(fn, msg, self.pos) 73 | -------------------------------------------------------------------------------- /tb.txt: -------------------------------------------------------------------------------- 1 | 09:30 < djc> is there any sane way with LLVM's EH to get a kind of stack trace? 2 | 09:31 <@baldrick> djc: not sure what getting a stack trace has to do with EH? 3 | 09:31 < djc> baldrick: I would like to make my language emit Python-like 4 | tracebacks when there's an uncaught exception 5 | 09:32 <@baldrick> djc: OK, but getting a stack trace isn't anything to do with 6 | EH 7 | 09:32 <@baldrick> djc: there's something for this IIRC, hang on 8 | 09:33 < djc> maybe I'm not using the "stack trace" term right 9 | 09:33 <@baldrick> djc: maybe you can do something with 10 | http://llvm.org/docs/LangRef.html#int_frameaddress 11 | 09:34 <@baldrick> djc: maybe libunwind and/or the eh functions in libgcc have 12 | something that can help you here 13 | 09:39 <@baldrick> djc: take a look at tb-gcc.c in gcc/ada/ from recent versions 14 | of gcc 15 | 09:40 < djc> baldrick: libunwind looks interesting, thanks for the pointers 16 | 09:40 <@baldrick> djc: that file I mentioned is (a) pretty short, and (b) gets 17 | the backtrace using libgcc's unwind stuff 18 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import sys, os, unittest, subprocess, json 3 | from runac import util 4 | import runac 5 | 6 | DIR = os.path.dirname(__file__) 7 | TEST_DIR = os.path.join(DIR, 'tests') 8 | 9 | class RunaTest(unittest.TestCase): 10 | 11 | def __init__(self, fn): 12 | unittest.TestCase.__init__(self) 13 | self.fn = fn 14 | self.base = self.fn.rsplit('.rns', 1)[0] 15 | self.bin = self.base + '.test' 16 | self.opts = self.getspec() 17 | 18 | def getspec(self): 19 | with open(self.fn) as f: 20 | h = f.readline() 21 | if h.startswith('# test: '): 22 | return json.loads(h[8:]) 23 | else: 24 | return {} 25 | 26 | def compile(self): 27 | if self.opts.get('type', 'test') == 'show': 28 | return [0, '\n'.join(runac.show(self.fn, None)) + '\n', bytes()] 29 | try: 30 | runac.compile(self.fn, self.bin) 31 | return [0, bytes(), bytes()] 32 | except util.Error as e: 33 | return [0, bytes(), e.show()] 34 | except util.ParseError as e: 35 | return [0, bytes(), e.show()] 36 | 37 | def runTest(self): 38 | 39 | res = self.compile() 40 | if any(res) and sys.version_info[0] > 2: 41 | for i, s in enumerate(res[1:]): 42 | res[i + 1] = s.encode('utf-8') 43 | 44 | if not any(res): 45 | cmd = [self.bin] + self.opts.get('args', []) 46 | opts = {'stdout': subprocess.PIPE, 'stderr': subprocess.PIPE} 47 | proc = subprocess.Popen(cmd, **opts) 48 | res = [proc.wait(), proc.stdout.read(), proc.stderr.read()] 49 | proc.stdout.close() 50 | proc.stderr.close() 51 | 52 | expected = [self.opts.get('ret', 0), bytes(), bytes()] 53 | for i, ext in enumerate(('.out', '.err')): 54 | if os.path.exists(self.base + ext): 55 | with open(self.base + ext, 'rb') as f: 56 | expected[i + 1] = f.read() 57 | 58 | if self is None: 59 | return res == expected 60 | elif res[1]: 61 | self.assertEqual(expected[0], res[0]) 62 | self.assertEqual(expected[1], res[1]) 63 | self.assertEqual(expected[2], res[2]) 64 | elif res[2]: 65 | self.assertEqual(expected[2], res[2]) 66 | self.assertEqual(expected[1], res[1]) 67 | self.assertEqual(expected[0], res[0]) 68 | 69 | def tests(): 70 | tests = [] 71 | for fn in os.listdir(TEST_DIR): 72 | fn = os.path.join(TEST_DIR, fn) 73 | if fn.endswith('.rns'): 74 | tests.append(RunaTest(fn)) 75 | return tests 76 | 77 | def suite(): 78 | suite = unittest.TestSuite() 79 | suite.addTests(tests()) 80 | return suite 81 | 82 | IGNORE = [ 83 | 'Memcheck', 'WARNING:', 'HEAP SUMMARY:', 'LEAK SUMMARY:', 84 | 'All heap blocks', 'For counts', 85 | ] 86 | 87 | def valgrind(test): 88 | 89 | args = test.opts.get('args', []) 90 | cmd = ['valgrind', '--leak-check=full', test.bin] + args 91 | streams = {'stdout': subprocess.PIPE, 'stderr': subprocess.PIPE} 92 | proc = subprocess.Popen(cmd, **streams) 93 | proc.wait() 94 | err = proc.stderr.read() 95 | 96 | blocks, cur = [], [] 97 | for ln in err.splitlines(): 98 | 99 | if not ln.startswith('=='): 100 | continue 101 | 102 | ln = ln.split(' ', 1)[1] 103 | if not ln.strip(): 104 | if cur: 105 | blocks.append(cur) 106 | cur = [] 107 | continue 108 | 109 | cur.append(ln) 110 | 111 | errors = [] 112 | for bl in blocks: 113 | if not any(flag for flag in IGNORE if bl[0].startswith(flag)): 114 | errors.append(bl) 115 | 116 | return len(errors) 117 | 118 | def leaks(): 119 | 120 | for test in tests(): 121 | 122 | compiled = os.path.exists(test.bin) 123 | if not compiled or os.stat(test.fn).st_mtime >= os.stat(test.bin).st_mtime: 124 | res = test.compile() 125 | if res[2] is not None: 126 | continue 127 | 128 | print('Running %s...' % test.bin, end=' ') 129 | count = valgrind(test) 130 | print(' ' * (40 - len(test.bin)), '%3i' % count) 131 | 132 | if __name__ == '__main__': 133 | if len(sys.argv) > 1 and sys.argv[1] == '--leaks': 134 | leaks() 135 | elif len(sys.argv) > 1: 136 | print(run(None, sys.argv[1])) 137 | else: 138 | unittest.main(defaultTest='suite') 139 | -------------------------------------------------------------------------------- /tests/arith-int.out: -------------------------------------------------------------------------------- 1 | 34 2 | 17 3 | -------------------------------------------------------------------------------- /tests/arith-int.rns: -------------------------------------------------------------------------------- 1 | def main(): 2 | x = 5 * 6 + 3 - 2 + 15 / 5 3 | print(x) 4 | y = 6 + 3 * 5 - 2 - 22 / 11 5 | print(y) 6 | -------------------------------------------------------------------------------- /tests/ast-err.err: -------------------------------------------------------------------------------- 1 | ast-err.rns [2.1]: unexpected token INDENT ('') 2 | pass 3 | ^ 4 | -------------------------------------------------------------------------------- /tests/ast-err.rns: -------------------------------------------------------------------------------- 1 | def main() 2 | pass 3 | -------------------------------------------------------------------------------- /tests/bitwise.out: -------------------------------------------------------------------------------- 1 | 1 2 | 7 3 | 2 4 | -------------------------------------------------------------------------------- /tests/bitwise.rns: -------------------------------------------------------------------------------- 1 | def main(): 2 | print(3 & 1) 3 | print(3 | 4) 4 | print(3 ^ 1) 5 | -------------------------------------------------------------------------------- /tests/bool-bool.out: -------------------------------------------------------------------------------- 1 | False 2 | True 3 | False 4 | -------------------------------------------------------------------------------- /tests/bool-bool.rns: -------------------------------------------------------------------------------- 1 | def main(): 2 | print(True and False) 3 | print(True or False) 4 | print(not True) 5 | -------------------------------------------------------------------------------- /tests/bool-ops-precedence.out: -------------------------------------------------------------------------------- 1 | False 2 | -------------------------------------------------------------------------------- /tests/bool-ops-precedence.rns: -------------------------------------------------------------------------------- 1 | def main(): 2 | print(3 == 6 and False) 3 | -------------------------------------------------------------------------------- /tests/bool-ops.out: -------------------------------------------------------------------------------- 1 | b 2 | 3 | 4 | a 5 | a 6 | a 7 | True 8 | -------------------------------------------------------------------------------- /tests/bool-ops.rns: -------------------------------------------------------------------------------- 1 | def main(): 2 | print('a' and 'b') 3 | print('' and 'a') 4 | print('a' and '') 5 | print('a' or 'b') 6 | print('' or 'a') 7 | print('a' or '') 8 | print(True) 9 | -------------------------------------------------------------------------------- /tests/break.out: -------------------------------------------------------------------------------- 1 | 0 2 | 1 3 | 2 4 | -------------------------------------------------------------------------------- /tests/break.rns: -------------------------------------------------------------------------------- 1 | def main(): 2 | i = 0 3 | while i < 5: 4 | if i == 3: 5 | break 6 | print(i) 7 | i += 1 8 | -------------------------------------------------------------------------------- /tests/catch.out: -------------------------------------------------------------------------------- 1 | CAUGHT 2 | -------------------------------------------------------------------------------- /tests/catch.rns: -------------------------------------------------------------------------------- 1 | def func(): 2 | raise Exception('test') 3 | 4 | def main(): 5 | try: 6 | func() 7 | except Exception: 8 | print('CAUGHT') 9 | -------------------------------------------------------------------------------- /tests/check-rtype.err: -------------------------------------------------------------------------------- 1 | check-rtype.rns [1.1]: function may not return value of type 'void' 2 | def main() -> i32: 3 | ^ 4 | -------------------------------------------------------------------------------- /tests/check-rtype.rns: -------------------------------------------------------------------------------- 1 | def main() -> i32: 2 | print('x') 3 | -------------------------------------------------------------------------------- /tests/class.out: -------------------------------------------------------------------------------- 1 | 0 2 | 5 3 | 1 4 | -------------------------------------------------------------------------------- /tests/class.rns: -------------------------------------------------------------------------------- 1 | class xrange: 2 | 3 | start: int 4 | stop: int 5 | step: int 6 | 7 | def __init__(self, start: int, stop: int, step: int): 8 | self.start = start 9 | self.stop = stop 10 | self.step = step 11 | 12 | def main(): 13 | r = xrange(0, 5, 1) 14 | print(r.start) 15 | print(r.stop) 16 | print(r.step) 17 | -------------------------------------------------------------------------------- /tests/cmp.out: -------------------------------------------------------------------------------- 1 | bool 2 | True 3 | False 4 | eq, int/bool 5 | False 6 | True 7 | True 8 | False 9 | eq, str 10 | False 11 | False 12 | False 13 | True 14 | ne 15 | True 16 | False 17 | True 18 | lt, int 19 | True 20 | False 21 | False 22 | lt, str 23 | True 24 | True 25 | False 26 | False 27 | -------------------------------------------------------------------------------- /tests/cmp.rns: -------------------------------------------------------------------------------- 1 | def main(): 2 | 3 | print('bool') 4 | print(True) 5 | print(False) 6 | 7 | print('eq, int/bool') 8 | print(1 == 3) 9 | print(2 == 2) 10 | print(True == True) 11 | print(False == True) 12 | 13 | print('eq, str') 14 | print("a" == "b") 15 | print("aa" == "b") 16 | print("aa" == "bb") 17 | print("a" == "a") 18 | 19 | print('ne') 20 | print(1 != 4) 21 | print(False != False) 22 | print("a" != "b") 23 | 24 | print('lt, int') 25 | print(1 < 4) 26 | print(2 < 2) 27 | print(5 < 4) 28 | 29 | print('lt, str') 30 | print('a' < 'aa') 31 | print('a' < 'b') 32 | print('a' < 'a') 33 | print('b' < 'a') 34 | -------------------------------------------------------------------------------- /tests/const.out: -------------------------------------------------------------------------------- 1 | Asara 2 | 1983 3 | -------------------------------------------------------------------------------- /tests/const.rns: -------------------------------------------------------------------------------- 1 | 2 | URL = 'Asara' 3 | YEAR = 1983 4 | 5 | def main(): 6 | print(URL) 7 | print(Str(YEAR)) 8 | -------------------------------------------------------------------------------- /tests/continue.out: -------------------------------------------------------------------------------- 1 | 2 2 | 4 3 | 6 4 | -------------------------------------------------------------------------------- /tests/continue.rns: -------------------------------------------------------------------------------- 1 | def main(): 2 | i = 0 3 | while i < 7: 4 | i += 1 5 | if i % 2 == 1: 6 | continue 7 | print(i) 8 | -------------------------------------------------------------------------------- /tests/cycle-typing.out: -------------------------------------------------------------------------------- 1 | 1 2 | 2 3 | 3 4 | 4 5 | -------------------------------------------------------------------------------- /tests/cycle-typing.rns: -------------------------------------------------------------------------------- 1 | def main(): 2 | a = 1 3 | while a < 5: 4 | print(a) 5 | a += 1 6 | -------------------------------------------------------------------------------- /tests/early-return-owner.out: -------------------------------------------------------------------------------- 1 | winter 2 | -------------------------------------------------------------------------------- /tests/early-return-owner.rns: -------------------------------------------------------------------------------- 1 | def trains() -> $Str: 2 | return 'winter' 3 | 4 | def main(): 5 | if False: 6 | return 7 | print(trains()) 8 | return 9 | -------------------------------------------------------------------------------- /tests/elem-proto.err: -------------------------------------------------------------------------------- 1 | elem-proto.rns [3.12]: incorrect type for element protocol: anyint 2 | print(a[0]) 3 | ^ 4 | -------------------------------------------------------------------------------- /tests/elem-proto.rns: -------------------------------------------------------------------------------- 1 | def test(): 2 | a = 1 3 | print(a[0]) 4 | -------------------------------------------------------------------------------- /tests/err-escaping-owner.err: -------------------------------------------------------------------------------- 1 | err-escaping-owner.rns [2.12]: return value type does not match declared return type 2 | '$Str' vs '&Str' 3 | return 'Heby' 4 | ^ 5 | -------------------------------------------------------------------------------- /tests/err-escaping-owner.rns: -------------------------------------------------------------------------------- 1 | def test() -> &Str: 2 | return 'Heby' 3 | -------------------------------------------------------------------------------- /tests/file.lng: -------------------------------------------------------------------------------- 1 | def main(name: str, args: array[str]): 2 | f = open('tests/file.lng') 3 | print(f.read(37)) 4 | f.close() 5 | -------------------------------------------------------------------------------- /tests/file.out: -------------------------------------------------------------------------------- 1 | def main(name: str, args: array[str]) 2 | -------------------------------------------------------------------------------- /tests/float.out: -------------------------------------------------------------------------------- 1 | 2.500000 2 | -------------------------------------------------------------------------------- /tests/float.rns: -------------------------------------------------------------------------------- 1 | def main(): 2 | print(2.5) 3 | -------------------------------------------------------------------------------- /tests/for.out: -------------------------------------------------------------------------------- 1 | 0 2 | 1 3 | 2 4 | 3 5 | 4 6 | -------------------------------------------------------------------------------- /tests/for.rns: -------------------------------------------------------------------------------- 1 | def range(start: int, stop: int) -> iter[int]: 2 | i = start 3 | while i < stop: 4 | yield i 5 | i = i + 1 6 | 7 | def main(): 8 | for i in range(0, 5): 9 | print(i) 10 | -------------------------------------------------------------------------------- /tests/force-void.err: -------------------------------------------------------------------------------- 1 | force-void.rns [2.12]: function must return type 'void' ('anyint' not allowed) 2 | return 0 3 | ^ 4 | -------------------------------------------------------------------------------- /tests/force-void.rns: -------------------------------------------------------------------------------- 1 | def do(): 2 | return 0 3 | -------------------------------------------------------------------------------- /tests/function.out: -------------------------------------------------------------------------------- 1 | 7 2 | -------------------------------------------------------------------------------- /tests/function.rns: -------------------------------------------------------------------------------- 1 | def add(x: int, y: int) -> int: 2 | return x + y 3 | 4 | def main(): 5 | print(add(3, 4)) 6 | -------------------------------------------------------------------------------- /tests/half-defined.err: -------------------------------------------------------------------------------- 1 | half-defined.rns [4.11]: undefined name 'a' 2 | print(a) 3 | ^ 4 | -------------------------------------------------------------------------------- /tests/half-defined.rns: -------------------------------------------------------------------------------- 1 | def main(): 2 | if 0: 3 | a = 'x' 4 | print(a) 5 | -------------------------------------------------------------------------------- /tests/hello.out: -------------------------------------------------------------------------------- 1 | hello, world 2 | -------------------------------------------------------------------------------- /tests/hello.rns: -------------------------------------------------------------------------------- 1 | def main(): 2 | print('hello, world') 3 | -------------------------------------------------------------------------------- /tests/if.out: -------------------------------------------------------------------------------- 1 | T 2 | O 3 | E 4 | -------------------------------------------------------------------------------- /tests/if.rns: -------------------------------------------------------------------------------- 1 | def main(): 2 | 3 | if 1: 4 | print('T') 5 | else: 6 | print('F') 7 | 8 | if 3: 9 | print('O') 10 | 11 | if 0: 12 | print('N') 13 | elif 2: 14 | print('E') 15 | else: 16 | print('A') 17 | -------------------------------------------------------------------------------- /tests/immutable-ref.err: -------------------------------------------------------------------------------- 1 | immutable-ref.rns [7.8]: immutable type '&Test' may not be mutated 2 | obj.i = 2 3 | ^ 4 | -------------------------------------------------------------------------------- /tests/immutable-ref.rns: -------------------------------------------------------------------------------- 1 | class Test: 2 | i: int 3 | def __init__(self, i: int): 4 | self.i = i 5 | 6 | def test(obj: &Test): 7 | obj.i = 2 8 | 9 | def main(): 10 | t = Test(1) 11 | test(t) 12 | -------------------------------------------------------------------------------- /tests/init-rtype.err: -------------------------------------------------------------------------------- 1 | init-rtype.rns [2.27]: method '__init__' must return type 'void' 2 | def __init__(self) -> i32: 3 | ^ 4 | -------------------------------------------------------------------------------- /tests/init-rtype.rns: -------------------------------------------------------------------------------- 1 | class test: 2 | def __init__(self) -> i32: 3 | return 1 4 | -------------------------------------------------------------------------------- /tests/inline-catch.out: -------------------------------------------------------------------------------- 1 | CAUGHT 2 | -------------------------------------------------------------------------------- /tests/inline-catch.rns: -------------------------------------------------------------------------------- 1 | def main(): 2 | try: 3 | raise Exception('error') 4 | except Exception: 5 | print('CAUGHT') 6 | -------------------------------------------------------------------------------- /tests/item-call.err: -------------------------------------------------------------------------------- 1 | item-call.rns [2.9]: object is not a function 2 | args[0]() 3 | ^ 4 | -------------------------------------------------------------------------------- /tests/item-call.rns: -------------------------------------------------------------------------------- 1 | def main(name: &Str, args: &Array[Str]): 2 | args[0]() 3 | -------------------------------------------------------------------------------- /tests/iter-obj.out: -------------------------------------------------------------------------------- 1 | 0 2 | 1 3 | 2 4 | 3 5 | 4 6 | -------------------------------------------------------------------------------- /tests/iter-obj.rns: -------------------------------------------------------------------------------- 1 | class range: 2 | 3 | start: int 4 | stop: int 5 | 6 | def __init__(self, start: int, stop: int): 7 | self.start = start 8 | self.stop = stop 9 | 10 | def __iter__(self) -> iter[int]: 11 | i = self.start 12 | while i < self.stop: 13 | yield i 14 | i = i + 1 15 | 16 | def main(): 17 | for i in range(0, 5): 18 | print(i) 19 | -------------------------------------------------------------------------------- /tests/main-type-arg-0.err: -------------------------------------------------------------------------------- 1 | main-type-arg-0.rns [1.16]: 1st argument to main() must be of type &Str 2 | def main(name: Str, args: Array[Str]): 3 | ^ 4 | -------------------------------------------------------------------------------- /tests/main-type-arg-0.rns: -------------------------------------------------------------------------------- 1 | def main(name: Str, args: Array[Str]): 2 | print(x) 3 | -------------------------------------------------------------------------------- /tests/main-type-arg-1.err: -------------------------------------------------------------------------------- 1 | main-type-arg-1.rns [1.33]: 2nd argument to main() must be of type &Array[Str] 2 | def main(name: &Str, args: Array[Str]): 3 | ^ 4 | -------------------------------------------------------------------------------- /tests/main-type-arg-1.rns: -------------------------------------------------------------------------------- 1 | def main(name: &Str, args: Array[Str]): 2 | print(x) 3 | -------------------------------------------------------------------------------- /tests/main-type-r.err: -------------------------------------------------------------------------------- 1 | main-type-r.rns [1.1]: main() return type must be i32 2 | def main(name: &Str, args: &Array[Str]) -> float: 3 | ^ 4 | -------------------------------------------------------------------------------- /tests/main-type-r.rns: -------------------------------------------------------------------------------- 1 | def main(name: &Str, args: &Array[Str]) -> float: 2 | print(x) 3 | -------------------------------------------------------------------------------- /tests/method-arg-name.err: -------------------------------------------------------------------------------- 1 | method-arg-name.rns [2.18]: first method argument must be called 'self' 2 | def __init__(i: int): 3 | ^ 4 | -------------------------------------------------------------------------------- /tests/method-arg-name.rns: -------------------------------------------------------------------------------- 1 | class test: 2 | def __init__(i: int): 3 | return 4 | -------------------------------------------------------------------------------- /tests/method-select-fail.err: -------------------------------------------------------------------------------- 1 | method-select-fail.rns [2.20]: no matching method found, candidates tried: 2 | (anyint, anyint) -> Runa.core.i32.__str__(&i32) 3 | print(9.__str__(4)) 4 | ^ 5 | -------------------------------------------------------------------------------- /tests/method-select-fail.rns: -------------------------------------------------------------------------------- 1 | def main(): 2 | print(9.__str__(4)) 3 | -------------------------------------------------------------------------------- /tests/multi-return.out: -------------------------------------------------------------------------------- 1 | 5 2 | 6 3 | -------------------------------------------------------------------------------- /tests/multi-return.rns: -------------------------------------------------------------------------------- 1 | def pick_two() -> (int, int): 2 | return 5, 6 3 | 4 | def main(): 5 | a, b = pick_two() 6 | print(a) 7 | print(b) 8 | -------------------------------------------------------------------------------- /tests/mutable-owner.out: -------------------------------------------------------------------------------- 1 | 2 2 | -------------------------------------------------------------------------------- /tests/mutable-owner.rns: -------------------------------------------------------------------------------- 1 | class Test: 2 | i: int 3 | def __init__(self, i: int): 4 | self.i = i 5 | 6 | def test(obj: $Test) -> $Test: 7 | obj.i = 2 8 | return obj 9 | 10 | def main(): 11 | t = Test(1) 12 | print(test(t).i) 13 | -------------------------------------------------------------------------------- /tests/mutable-ref.out: -------------------------------------------------------------------------------- 1 | 2 2 | -------------------------------------------------------------------------------- /tests/mutable-ref.rns: -------------------------------------------------------------------------------- 1 | class Test: 2 | i: int 3 | def __init__(self, i: int): 4 | self.i = i 5 | 6 | def test(obj: ~&Test): 7 | obj.i = 2 8 | 9 | def main(): 10 | t = Test(1) 11 | test(t) 12 | print(t.i) 13 | -------------------------------------------------------------------------------- /tests/named-args.out: -------------------------------------------------------------------------------- 1 | 1 2 | 2 3 | 3 4 | 4 5 | 5 6 | 6 7 | -------------------------------------------------------------------------------- /tests/named-args.rns: -------------------------------------------------------------------------------- 1 | def test(a: uint, b: uint): 2 | print(a) 3 | print(b) 4 | 5 | def main(): 6 | test(b=2, a=1) 7 | test(3, b=4) 8 | test(5, 6) 9 | -------------------------------------------------------------------------------- /tests/no-arg-call.out: -------------------------------------------------------------------------------- 1 | Spock 2 | -------------------------------------------------------------------------------- /tests/no-arg-call.rns: -------------------------------------------------------------------------------- 1 | def rpsls() -> $Str: 2 | return 'Spock' 3 | 4 | def main(): 5 | print(rpsls()) 6 | -------------------------------------------------------------------------------- /tests/no-arg-type.err: -------------------------------------------------------------------------------- 1 | no-arg-type.rns [1.9]: missing type for argument 'x' 2 | def foo(x): 3 | ^ 4 | -------------------------------------------------------------------------------- /tests/no-arg-type.rns: -------------------------------------------------------------------------------- 1 | def foo(x): 2 | pass 3 | -------------------------------------------------------------------------------- /tests/no-compare.err: -------------------------------------------------------------------------------- 1 | no-compare.rns [2.16]: types 'byte' and 'anyint' cannot be compared 2 | return val == 0 3 | ^ 4 | -------------------------------------------------------------------------------- /tests/no-compare.rns: -------------------------------------------------------------------------------- 1 | def test(val: byte) -> bool: 2 | return val == 0 3 | -------------------------------------------------------------------------------- /tests/no-func.err: -------------------------------------------------------------------------------- 1 | no-func.rns [2.5]: undefined name 'blah' 2 | blah() 3 | ^ 4 | -------------------------------------------------------------------------------- /tests/no-func.rns: -------------------------------------------------------------------------------- 1 | def main(): 2 | blah() 3 | -------------------------------------------------------------------------------- /tests/no-init.err: -------------------------------------------------------------------------------- 1 | no-init.rns [5.18]: Sheldon does not have a method '__init__' 2 | print(Sheldon()) 3 | ^ 4 | -------------------------------------------------------------------------------- /tests/no-init.rns: -------------------------------------------------------------------------------- 1 | class Sheldon: 2 | pass 3 | 4 | def main(): 5 | print(Sheldon()) 6 | -------------------------------------------------------------------------------- /tests/no-method.err: -------------------------------------------------------------------------------- 1 | no-method.rns [2.18]: anyint does not have a method 'penny' 2 | print(9.penny()) 3 | ^ 4 | -------------------------------------------------------------------------------- /tests/no-method.rns: -------------------------------------------------------------------------------- 1 | def main(): 2 | print(9.penny()) 3 | -------------------------------------------------------------------------------- /tests/no-self.err: -------------------------------------------------------------------------------- 1 | no-self.rns [2.5]: missing 'self' argument 2 | def __init__(): 3 | ^ 4 | -------------------------------------------------------------------------------- /tests/no-self.rns: -------------------------------------------------------------------------------- 1 | class test: 2 | def __init__(): 3 | return 4 | -------------------------------------------------------------------------------- /tests/non-type.err: -------------------------------------------------------------------------------- 1 | non-type.rns [1.12]: type 'FlooBar' not found 2 | def do(x: &FlooBar): 3 | ^ 4 | -------------------------------------------------------------------------------- /tests/non-type.rns: -------------------------------------------------------------------------------- 1 | def do(x: &FlooBar): 2 | pass 3 | -------------------------------------------------------------------------------- /tests/none.out: -------------------------------------------------------------------------------- 1 | success 2 | -------------------------------------------------------------------------------- /tests/none.rns: -------------------------------------------------------------------------------- 1 | from libc.stdlib import getenv 2 | 3 | def main(): 4 | print('success' if getenv('EAST_BUDLEIGH'.data) is None else 'failure') 5 | -------------------------------------------------------------------------------- /tests/num-params.err: -------------------------------------------------------------------------------- 1 | num-params.rns [5.8]: arguments (anyint, anyint) cannot be passed as (i32) 2 | foo(3, 4) 3 | ^ 4 | -------------------------------------------------------------------------------- /tests/num-params.rns: -------------------------------------------------------------------------------- 1 | def foo(i: i32) -> i32: 2 | return i 3 | 4 | def main(): 5 | foo(3, 4) 6 | -------------------------------------------------------------------------------- /tests/oddeven.out: -------------------------------------------------------------------------------- 1 | 1 2 | 0 3 | 3 4 | 2 5 | 5 6 | -------------------------------------------------------------------------------- /tests/oddeven.rns: -------------------------------------------------------------------------------- 1 | def oddeven(start: int, after: int) -> iter[int]: 2 | i = start 3 | while i < after: 4 | if (i % 2) == 0: 5 | yield i + 1 6 | else: 7 | yield i - 1 8 | i = i + 1 9 | 10 | def main(): 11 | for i in oddeven(0, 5): 12 | print(i) 13 | -------------------------------------------------------------------------------- /tests/opt-check.out: -------------------------------------------------------------------------------- 1 | no val 2 | 1 3 | -------------------------------------------------------------------------------- /tests/opt-check.rns: -------------------------------------------------------------------------------- 1 | class test: 2 | val: uint 3 | def __init__(self, v: uint): 4 | self.val = v 5 | 6 | def maybe(i: int) -> ?$test: 7 | return test(1) if i & 2 else None 8 | 9 | def main(): 10 | 11 | obj = maybe(1) 12 | if obj is None: 13 | print('no val') 14 | else: 15 | print(obj.val) 16 | 17 | obj = maybe(3) 18 | if obj is None: 19 | print('no val') 20 | else: 21 | print(obj.val) 22 | -------------------------------------------------------------------------------- /tests/opt-resolve.out: -------------------------------------------------------------------------------- 1 | 1 2 | -------------------------------------------------------------------------------- /tests/opt-resolve.rns: -------------------------------------------------------------------------------- 1 | class test: 2 | val: uint 3 | def __init__(self, v: uint): 4 | self.val = v 5 | 6 | def maybe(i: int) -> ?$test: 7 | return test(1) if i & 2 else None 8 | 9 | def main(): 10 | obj = maybe(3) 11 | if obj is None: 12 | obj = test(0) 13 | print(obj.val) 14 | -------------------------------------------------------------------------------- /tests/opt-return.out: -------------------------------------------------------------------------------- 1 | 1 2 | -------------------------------------------------------------------------------- /tests/opt-return.rns: -------------------------------------------------------------------------------- 1 | class test: 2 | val: uint 3 | def __init__(self, v: uint): 4 | self.val = v 5 | 6 | def maybe(i: int) -> ?$test: 7 | return test(1) if i & 2 else None 8 | 9 | def main(): 10 | obj = maybe(2) 11 | if obj is None: 12 | return 13 | print(obj.val) 14 | -------------------------------------------------------------------------------- /tests/opt-use-attrib.err: -------------------------------------------------------------------------------- 1 | opt-use-attrib.rns [10.19]: opt type '?$test' not allowed here 2 | print(maybe(3).val) 3 | ^ 4 | -------------------------------------------------------------------------------- /tests/opt-use-attrib.rns: -------------------------------------------------------------------------------- 1 | class test: 2 | val: uint 3 | def __init__(self, v: uint): 4 | self.val = v 5 | 6 | def maybe(i: int) -> ?$test: 7 | return test(1) if i & 2 else None 8 | 9 | def main(): 10 | print(maybe(3).val) 11 | -------------------------------------------------------------------------------- /tests/owner-after-pass.err: -------------------------------------------------------------------------------- 1 | owner-after-pass.rns [7.11]: undefined name 's' 2 | print(s) 3 | ^ 4 | -------------------------------------------------------------------------------- /tests/owner-after-pass.rns: -------------------------------------------------------------------------------- 1 | def foo(s: $Str): 2 | pass 3 | 4 | def main(): 5 | s = 'bar' 6 | foo(s) 7 | print(s) 8 | -------------------------------------------------------------------------------- /tests/owner-reassign.out: -------------------------------------------------------------------------------- 1 | 2014 2 | 2015 3 | 2016 4 | -------------------------------------------------------------------------------- /tests/owner-reassign.rns: -------------------------------------------------------------------------------- 1 | def main(): 2 | x = Str(2014) 3 | print(x) 4 | x = Str(2015) 5 | print(x) 6 | if True: 7 | x = Str(2016) 8 | print(x) 9 | -------------------------------------------------------------------------------- /tests/pass-ref-as-owner.err: -------------------------------------------------------------------------------- 1 | pass-ref-as-owner.rns [11.9]: arguments (&Str) cannot be passed as ($Str) 2 | test(t.s) 3 | ^ 4 | -------------------------------------------------------------------------------- /tests/pass-ref-as-owner.rns: -------------------------------------------------------------------------------- 1 | class Test: 2 | s: $Str 3 | def __init__(self, s: $Str): 4 | self.s = s 5 | 6 | def test(s: $Str): 7 | print(s) 8 | 9 | def main(): 10 | t = Test('a') 11 | test(t.s) 12 | -------------------------------------------------------------------------------- /tests/pos-after-named.err: -------------------------------------------------------------------------------- 1 | pos-after-named.rns [6.15]: positional arguments must come before named arguments 2 | test(b=2, 1) 3 | ^ 4 | -------------------------------------------------------------------------------- /tests/pos-after-named.rns: -------------------------------------------------------------------------------- 1 | def test(a: uint, b: uint): 2 | print(a) 3 | print(b) 4 | 5 | def main(): 6 | test(b=2, 1) 7 | -------------------------------------------------------------------------------- /tests/pretty.out: -------------------------------------------------------------------------------- 1 | def test.__init__(self [&test], v [uint]) -> void: 2 | 0: # entry 3 | {00} self [&test] . val [uint] = v [uint] 4 | {01} Return 5 | 6 | def maybe(i [int]) -> ?$test: 7 | 0: # entry 8 | {00} $1 [int] = BWAnd i [int] 2 [int] 9 | {01} CondBranch $1 [int] ? 1 : 2 10 | 1: # ternary-left 11 | {00} $2 [$test] = Runa.__main__.test.__init__(Init $test, 1 [uint]) [$test] 12 | {01} Branch 3 13 | 2: # ternary-right 14 | {00} Branch 3 15 | 3: # ternary-exit 16 | {00} $0 [?$test] = Phi 1:$2 [$test], 2:NoneVal [?$test] 17 | {01} Return $0 [?$test] 18 | 19 | def multi() -> tuple[uint, uint]: 20 | 0: # entry 21 | {00} $0 [tuple[uint, uint]] = (4 [int], 5 [int]) 22 | {01} Return $0 [tuple[uint, uint]] 23 | 24 | def cast(a [&byte]) -> void: 25 | 0: # entry 26 | {00} p [&i8] = As a [&byte] &i8 27 | {01} Return 28 | 29 | def opt_check() -> void: 30 | 0: # entry 31 | {00} obj [?$test] = Runa.__main__.maybe(1 [int]) [?$test] 32 | {01} $0 [bool] = Is obj [?$test] NoneVal [NoType] 33 | {02} CondBranch $0 [bool] ? 1 : 2 34 | 1: # if-suite 35 | {00} $1 [$Str] = 'no val' [&Str] 36 | {01} Runa.core.print($1 [$Str]) [void] 37 | {02} Branch 3 38 | 2: # if-suite 39 | {00} Runa.core.print(False [bool]) [void] 40 | {01} Branch 3 41 | 3: # if-exit 42 | {00} obj [?$test] = Runa.__main__.maybe(3 [int]) [?$test] 43 | {01} $2 [bool] = Is obj [?$test] NoneVal [NoType] 44 | {02} CondBranch $2 [bool] ? 4 : 5 45 | 4: # if-suite 46 | {00} $3 [$Str] = 'no val' [&Str] 47 | {01} Runa.core.print($3 [$Str]) [void] 48 | {02} Branch 6 49 | 5: # if-suite 50 | {00} $4 [uint] = obj [$test] . val [uint] 51 | {01} Runa.core.print($4 [uint]) [void] 52 | {02} Branch 6 53 | 6: # if-exit 54 | {00} Raise Runa.core.Exception.__init__(Init $Exception, 'fail!' [$Str:E]) [$Exception] 55 | 56 | def math() -> void: 57 | 0: # entry 58 | {00} $1 [int] = Add 3 [int] 4 [int] 59 | {01} $3 [int] = Mul 5 [int] 6 [int] 60 | {02} $2 [int] = Div $3 [int] 7 [int] 61 | {03} $0 [int] = Sub $1 [int] $2 [int] 62 | {04} Runa.core.print($0 [int]) [void] 63 | {05} Runa.core.print(0.1 [float]) [void] 64 | {06} $4 [bool] = GT 5 [int] 3 [int] 65 | {07} Runa.core.print($4 [bool]) [void] 66 | {08} $5 [bool] = LT 6 [int] 4 [int] 67 | {09} Runa.core.print($5 [bool]) [void] 68 | {10} $6 [int] = Mod 4 [int] 3 [int] 69 | {11} Runa.core.print($6 [int]) [void] 70 | {12} Runa.__main__.opt_check() [void] => 4, 1 71 | 1: # landing-pad 72 | {00} LPad: $8 {Exception: 2} 73 | 2: # catch 74 | {00} $7 [$Str] = 'caught' [&Str] 75 | {01} Runa.core.print($7 [$Str]) [void] 76 | {02} Branch 4 77 | 3: # caught-no-match 78 | {00} Resume: $8 79 | 4: # try-exit 80 | {00} Return 81 | 82 | def binary() -> void: 83 | 0: # entry 84 | {00} $0 [int] = BWAnd 3 [int] 1 [int] 85 | {01} Runa.core.print($0 [int]) [void] 86 | {02} $1 [int] = BWOr 2 [int] 6 [int] 87 | {03} Runa.core.print($1 [int]) [void] 88 | {04} $2 [int] = BWXor 3 [int] 2 [int] 89 | {05} Runa.core.print($2 [int]) [void] 90 | {06} $5 [$Str] = '' [&Str] 91 | {07} $6 [$Str] = 'b' [&Str] 92 | {08} $4 [$Str] = And $5 [$Str] $6 [$Str] 93 | {09} $7 [$Str] = 'c' [&Str] 94 | {10} $3 [$Str] = Or $4 [$Str] $7 [$Str] 95 | {11} Runa.core.print($3 [$Str]) [void] 96 | {12} $9 [$Str] = 'a' [&Str] 97 | {13} $10 [$Str] = 'b' [&Str] 98 | {14} $8 [bool] = NE $9 [$Str] $10 [$Str] 99 | {15} Runa.core.print($8 [bool]) [void] 100 | {16} $12 [$Str] = 'c' [&Str] 101 | {17} $13 [$Str] = 'c' [&Str] 102 | {18} $11 [bool] = EQ $12 [$Str] $13 [$Str] 103 | {19} Runa.core.print($11 [bool]) [void] 104 | {20} $15 [$Str] = '' [&Str] 105 | {21} $14 [bool] = Not $15 [$Str] 106 | {22} Runa.core.print($14 [bool]) [void] 107 | {23} CondBranch 7 [int] ? 1 : 2 108 | 1: # if-suite 109 | {00} Pass 110 | {01} Branch 5 111 | 2: # if-cond 112 | {00} CondBranch 0 [int] ? 3 : 4 113 | 3: # if-suite 114 | {00} Pass 115 | {01} Branch 5 116 | 4: # if-suite 117 | {00} Pass 118 | {01} Branch 5 119 | 5: # if-exit 120 | {00} Free($3 [$Str]) 121 | {01} Free($4 [$Str]) 122 | {02} Return 123 | 124 | def range(end [int]) -> iter[int]: 125 | 0: # entry 126 | {00} i [int] = As 0 [int] int 127 | {01} Branch 1 128 | 1: # while-head 129 | {00} $0 [bool] = LT i [int] end [int] 130 | {01} CondBranch $0 [bool] ? 2 : 4 131 | 2: # while-body 132 | {00} Yield i [int] 133 | 3: # yield-to 134 | {00} IAdd i [int] 1 [int] 135 | {01} Branch 1 136 | 4: # while-exit 137 | {00} Return 138 | 139 | def loop(args [Array[Str]]) -> void: 140 | 0: # entry 141 | {00} $0 [Str] = Elem(args [Array[Str]], 0 [int]) [Str] 142 | {01} Runa.core.print($0 [Str]) [void] 143 | {02} $1 [Runa.__main__.range$ctx] = LoopSetup i [int] <- Runa.__main__.range(3 [int]) [iter[int]] 144 | {03} Branch 1 145 | 1: # for-head 146 | {00} LoopHeader ctx:$1 [Runa.__main__.range$ctx] lvar:i [int] 2:3 147 | 2: # for-body 148 | {00} Runa.core.print(i [int]) [void] 149 | {01} Branch 1 150 | 3: # for-exit 151 | {00} Return 152 | 153 | def raises() -> void: 154 | 0: # entry 155 | {00} Raise Runa.core.Exception.__init__(Init $Exception, 'foo' [$Str:E]) [$Exception] 156 | 157 | -------------------------------------------------------------------------------- /tests/pretty.rns: -------------------------------------------------------------------------------- 1 | # test: {"type": "show"} 2 | 3 | class test: 4 | val: uint 5 | def __init__(self, v: uint): 6 | self.val = v 7 | 8 | def maybe(i: int) -> ?$test: 9 | return test(1) if i & 2 else None 10 | 11 | def multi() -> (uint, uint): 12 | return 4, 5 13 | 14 | def cast(a: &byte): 15 | p = a as &i8 16 | 17 | def opt_check(): 18 | 19 | obj = maybe(1) 20 | if obj is None: 21 | print('no val') 22 | else: 23 | print(False) 24 | 25 | obj = maybe(3) 26 | if obj is None: 27 | print('no val') 28 | else: 29 | print(obj.val) 30 | 31 | raise Exception('fail!') 32 | 33 | def math(): 34 | print(3 + 4 - 5 * 6 / 7) 35 | print(0.1) 36 | print(5 > 3) 37 | print(6 < 4) 38 | print(4 % 3) 39 | try: 40 | opt_check() 41 | except Exception: 42 | print('caught') 43 | 44 | def binary(): 45 | print(3 & 1) 46 | print(2 | 6) 47 | print(3 ^ 2) 48 | print('' and 'b' or 'c') 49 | print('a' != 'b') 50 | print('c' == 'c') 51 | print(not '') 52 | if 7: 53 | pass 54 | elif 0: 55 | pass 56 | else: 57 | pass 58 | 59 | def range(end: int) -> iter[int]: 60 | i = 0 as int 61 | while i < end: 62 | yield i 63 | i += 1 64 | 65 | def loop(args: Array[Str]): 66 | print(args[0]) 67 | for i in range(3): 68 | print(i) 69 | 70 | def raises(): 71 | raise Exception('foo') 72 | -------------------------------------------------------------------------------- /tests/print-var.out: -------------------------------------------------------------------------------- 1 | 29 2 | birthday 3 | tests/print-var.test 4 | cold 5 | -------------------------------------------------------------------------------- /tests/print-var.rns: -------------------------------------------------------------------------------- 1 | # test: {"args": ["cold"]} 2 | def main(name: &Str, args: &Array[Str]): 3 | x = 29 4 | print(Str(x)) 5 | y = 'birthday' 6 | print(y) 7 | print(name) 8 | print(args[0]) 9 | -------------------------------------------------------------------------------- /tests/retval.out: -------------------------------------------------------------------------------- 1 | failing 2 | -------------------------------------------------------------------------------- /tests/retval.rns: -------------------------------------------------------------------------------- 1 | # test: {"ret": 5} 2 | def main() -> i32: 3 | print('failing') 4 | return 5 5 | -------------------------------------------------------------------------------- /tests/retype.err: -------------------------------------------------------------------------------- 1 | retype.rns [4.7]: reassignment with different type ('anyint' vs '$Str') 2 | a = 'x' 3 | ^ 4 | -------------------------------------------------------------------------------- /tests/retype.rns: -------------------------------------------------------------------------------- 1 | def main(): 2 | a = 1 3 | print(a) 4 | a = 'x' 5 | print(a) 6 | -------------------------------------------------------------------------------- /tests/rtype.err: -------------------------------------------------------------------------------- 1 | rtype.rns [2.12]: return value type does not match declared return type 2 | '$Str' vs 'int' 3 | return 's' 4 | ^ 5 | -------------------------------------------------------------------------------- /tests/rtype.rns: -------------------------------------------------------------------------------- 1 | def do() -> int: 2 | return 's' 3 | -------------------------------------------------------------------------------- /tests/self-type.err: -------------------------------------------------------------------------------- 1 | self-type.rns [2.24]: first method argument must be of type 'test' 2 | def __init__(self: int): 3 | ^ 4 | -------------------------------------------------------------------------------- /tests/self-type.rns: -------------------------------------------------------------------------------- 1 | class test: 2 | def __init__(self: int): 3 | return 4 | -------------------------------------------------------------------------------- /tests/str-ops.out: -------------------------------------------------------------------------------- 1 | X 2 | X 3 | X 4 | X 5 | X 6 | ab 7 | test 8 | -------------------------------------------------------------------------------- /tests/str-ops.rns: -------------------------------------------------------------------------------- 1 | def main(): 2 | print('' if 'a' == 'b' else 'X') 3 | print('' if 'aa' == 'a' else 'X') 4 | print('X' if 'aaa' == 'aaa' else '') 5 | print('X' if 'a' < 'b' else '') 6 | print('' if 'b' < 'a' else 'X') 7 | print('a' + 'b') 8 | print('test'.__copy__()) 9 | -------------------------------------------------------------------------------- /tests/ternary.out: -------------------------------------------------------------------------------- 1 | 2 2 | 2 3 | true 4 | false 5 | 1 6 | 0 7 | 1 8 | -------------------------------------------------------------------------------- /tests/ternary.rns: -------------------------------------------------------------------------------- 1 | def main(): 2 | 3 | print(Str(1 if 0 else 2)) 4 | print(Str(2 if 111 else 1)) 5 | 6 | print('true' if 111 else 'false') 7 | print('true' if 0 else 'false') 8 | 9 | print(Str(1 if 'a' else 0)) 10 | print(Str(1 if '' else 0)) 11 | print(Str(1 if not '' else 0)) 12 | -------------------------------------------------------------------------------- /tests/ternop-err.err: -------------------------------------------------------------------------------- 1 | ternop-err.rns [2.11]: unmatched types 'anyint', '$Str' 2 | a = 1 if True else 'a' 3 | ^ 4 | -------------------------------------------------------------------------------- /tests/ternop-err.rns: -------------------------------------------------------------------------------- 1 | def main(): 2 | a = 1 if True else 'a' 3 | -------------------------------------------------------------------------------- /tests/type-diff.err: -------------------------------------------------------------------------------- 1 | type-diff.rns [7.11]: unmatched types '$Str', 'anyint' on incoming branches 2 | print(bar) 3 | ^ 4 | -------------------------------------------------------------------------------- /tests/type-diff.rns: -------------------------------------------------------------------------------- 1 | def main(): 2 | foo = 1 3 | if foo == 2: 4 | bar = 3 5 | else: 6 | bar = 'foo' 7 | print(bar) 8 | 9 | -------------------------------------------------------------------------------- /tests/undefined.err: -------------------------------------------------------------------------------- 1 | undefined.rns [2.11]: undefined name 'x' 2 | print(x) 3 | ^ 4 | -------------------------------------------------------------------------------- /tests/undefined.rns: -------------------------------------------------------------------------------- 1 | def main(): 2 | print(x) 3 | return 0 4 | -------------------------------------------------------------------------------- /tests/unhandled.err: -------------------------------------------------------------------------------- 1 | Unhandled Exception: error 2 | -------------------------------------------------------------------------------- /tests/unhandled.rns: -------------------------------------------------------------------------------- 1 | # test: {"ret": 1} 2 | def func(): 3 | raise Exception('error') 4 | 5 | def main(): 6 | func() 7 | -------------------------------------------------------------------------------- /tests/unmatched.err: -------------------------------------------------------------------------------- 1 | unmatched.rns [2.13]: value of type 'anyint' may only be compared to integer type 2 | print(1 == 'a') 3 | ^ 4 | -------------------------------------------------------------------------------- /tests/unmatched.rns: -------------------------------------------------------------------------------- 1 | def main(): 2 | print(1 == 'a') 3 | -------------------------------------------------------------------------------- /tests/void-print.err: -------------------------------------------------------------------------------- 1 | void-print.rns [5.10]: arguments (void) cannot be passed as (&ToStr) 2 | print(jtharper()) 3 | ^ 4 | -------------------------------------------------------------------------------- /tests/void-print.rns: -------------------------------------------------------------------------------- 1 | def jtharper(): 2 | print('...') 3 | 4 | def main(): 5 | print(jtharper()) 6 | -------------------------------------------------------------------------------- /tests/while.out: -------------------------------------------------------------------------------- 1 | 0 2 | 1 3 | 2 4 | 3 5 | 4 6 | 3 7 | 2 8 | 1 9 | -------------------------------------------------------------------------------- /tests/while.rns: -------------------------------------------------------------------------------- 1 | def main(): 2 | 3 | i = 0 4 | while i < 5: 5 | print(i) 6 | i = i + 1 # incr 7 | 8 | i = 3 9 | while i: 10 | print(i) 11 | i = i - 1 12 | -------------------------------------------------------------------------------- /tests/yield-type.err: -------------------------------------------------------------------------------- 1 | yield-type.rns [2.11]: yield value type does not match declared type 2 | 'anyint' vs 'bool' 3 | yield 0 4 | ^ 5 | -------------------------------------------------------------------------------- /tests/yield-type.rns: -------------------------------------------------------------------------------- 1 | def start() -> iter[bool]: 2 | yield 0 3 | yield 1 4 | yield 2 5 | -------------------------------------------------------------------------------- /tests/zero.out: -------------------------------------------------------------------------------- 1 | 0 2 | 1 3 | -1 4 | a 5 | -------------------------------------------------------------------------------- /tests/zero.rns: -------------------------------------------------------------------------------- 1 | def main(): 2 | print(0) 3 | print(1 + 0) 4 | print(0 - 1) 5 | print('a' + '') 6 | --------------------------------------------------------------------------------
46 | 47 |