├── mypy.ini ├── .env ├── aki ├── core │ ├── __init__.py │ ├── constants.py │ ├── compiler.py │ ├── error.py │ ├── grammar │ │ ├── grammar.lark │ │ └── __init__.py │ ├── astree.py │ ├── repl │ │ └── __init__.py │ └── akitypes.py ├── tests │ ├── __init__.py │ ├── t_00_compiler.py │ ├── t_01_lexer.py.bak │ ├── t_03_codeexec.py │ └── t_02_parser.py.bak ├── examples │ ├── a.aki │ ├── test_1.aki │ ├── test_2.aki │ └── l.aki ├── aki.py └── stdlib │ └── nt │ ├── layer_0.aki │ └── layer_1.aki ├── requirements.txt ├── CONTRIBUTING.md ├── LICENSE ├── FAQ.md ├── .gitignore ├── code-of-conduct.md ├── whats-next.md ├── readme.md └── language.md /mypy.ini: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | PYTHONPATH=aki -------------------------------------------------------------------------------- /aki/core/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /aki/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | llvmlite==0.29.0 2 | colorama==0.4.1 3 | termcolor==1.1.0 4 | lark-parser==0.7.8 5 | -------------------------------------------------------------------------------- /aki/examples/a.aki: -------------------------------------------------------------------------------- 1 | def main(){ 2 | # Loops auto-increment by 1 if no incrementor is specified 3 | loop (var x = 1, x < 101) { 4 | if x % 15 == 0 print ("FizzBuzz") 5 | else if x % 3 == 0 print ("Fizz") 6 | else if x % 5 == 0 print ("Buzz") 7 | else printf_s(c_data('%i\n'),x) 8 | } 9 | 0 10 | } -------------------------------------------------------------------------------- /aki/examples/test_1.aki: -------------------------------------------------------------------------------- 1 | # Comment example 2 | 3 | @inline 4 | def g1 (z1=0) { 5 | var q=z1 6 | loop (var x=1, x<20, x+1) { 7 | q+=1 8 | } 9 | q 10 | } 11 | 12 | # Another comment example 13 | 14 | @noinline 15 | def g2 (z2,z3=0) { 16 | loop (var x=1, x<20, x+1) { 17 | z2+=1 18 | } 19 | z2 20 | } 21 | 22 | # A third comment example 23 | 24 | def g3(z4){ 25 | loop () { 26 | when z4>=20 break 27 | z4+=1 28 | } 29 | z4 30 | } -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Pull requests are welcome. 4 | 5 | We most need help with the following: 6 | 7 | * Porting to Linux and Mac OS. The project has been constructed so that any dependencies on Windows and its runtime have a level of abstraction between them to make this possible. 8 | * Exporting to and running WebAssembly or other LLVM targets. 9 | * Additions to core functionality and standard library: 10 | * String handling. 11 | * Memory management / garbage collection. 12 | 13 | # Code Standards 14 | 15 | * We use `black` as the standard code formatter. 16 | * We use `mypy` as the linter. (Note that not all the code currently conforms to `mypy` standards; we use it as a guide, not dogma.) 17 | 18 | # Code Of Conduct 19 | 20 | See the [Code of Conduct](code-of-conduct.md) for rules about how to interact with and participate in this project. 21 | -------------------------------------------------------------------------------- /aki/aki.py: -------------------------------------------------------------------------------- 1 | if __name__ == "__main__": 2 | #import cProfile, pstats, io 3 | #pr = cProfile.Profile() 4 | #pr.enable() 5 | import sys 6 | init_modules = set(sys.modules.keys()) 7 | while True: 8 | from core import repl 9 | from core.error import ReloadException, QuitException 10 | try: 11 | repl.Repl().run(True) 12 | except ReloadException: 13 | for m in reversed(list(sys.modules.keys())): 14 | if m not in init_modules: 15 | del sys.modules[m] 16 | continue 17 | except QuitException: 18 | #pr.disable() 19 | #s = io.StringIO() 20 | #sortby = pstats.SortKey.CUMULATIVE 21 | #ps = pstats.Stats(pr, stream=s).sort_stats(sortby) 22 | #ps.print_stats() 23 | #with open ('stats.txt','w') as file: 24 | #file.write(s.getvalue()) 25 | break -------------------------------------------------------------------------------- /aki/stdlib/nt/layer_0.aki: -------------------------------------------------------------------------------- 1 | # Layer 0 stdlib 2 | # This contains the platform-specific system calls we need 3 | # to create the primitives for each stdlib implementation. 4 | 5 | # Memory management 6 | 7 | extern GetProcessHeap():ptr u_size 8 | 9 | extern HeapAlloc( 10 | hHeap: ptr u_size, 11 | dwFlags: i32, 12 | dwBytes: u_size 13 | ):ptr u_mem 14 | 15 | extern HeapFree( 16 | hHeap: ptr u_size, 17 | dwFlags: i32, 18 | lpMem: ptr u_mem 19 | ):i32 20 | 21 | # Output 22 | 23 | extern GetStdHandle(nStdHandle:i32):ptr u_size 24 | 25 | extern WriteConsoleA( 26 | hConsoleOutput:ptr u_size, 27 | lpBuffer: ptr u_mem, 28 | nNumberOfCharsToWrite: i32, 29 | lpNumberOfCharsWritten: ptr i32, 30 | lpReserved: u64 = 0:u64 31 | ):i32 32 | 33 | extern _snprintf( 34 | buffer: ptr u_mem, 35 | count: u_size, 36 | format: ptr u8, 37 | *args 38 | ):i32 39 | 40 | # Etc 41 | 42 | extern Sleep(dwMilliseconds:i32):i32 43 | 44 | extern printf_s(_format:ptr u8, *args):i32 -------------------------------------------------------------------------------- /aki/stdlib/nt/layer_1.aki: -------------------------------------------------------------------------------- 1 | # Layer 1 stdlib 2 | # These are the primitives we actually use in Aki, 3 | # which are wrappers for the platform primitives. 4 | # All of this will eventually be namespaced. 5 | 6 | def alloc(bytes:u_size):ptr u_mem { 7 | HeapAlloc( 8 | GetProcessHeap(), 9 | 0x00000008, 10 | bytes 11 | ) 12 | } 13 | 14 | def free(ptr_to_free:ptr u_mem){ 15 | HeapFree( 16 | GetProcessHeap(), 17 | 0x00000000, 18 | ptr_to_free 19 | ) 20 | } 21 | 22 | def sleep(msecs:i32):i32 { 23 | Sleep(msecs) 24 | } 25 | 26 | def print(_str:str):i32 { 27 | 28 | var bytes_written:i32=0 29 | var _size = 4096:u_size 30 | var buffer = alloc(_size) 31 | 32 | var len = _snprintf( 33 | buffer, 34 | _size, 35 | c_data('%s\n'), 36 | c_data(_str) 37 | ) 38 | 39 | WriteConsoleA( 40 | GetStdHandle(-11), 41 | buffer, 42 | len, 43 | ref(bytes_written) 44 | ) 45 | 46 | free(buffer) 47 | bytes_written 48 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018-2019 Serdar Yegulalp 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /aki/tests/t_00_compiler.py: -------------------------------------------------------------------------------- 1 | # Test compiler by having it compile all example apps. 2 | # This is a good way to fail fast if we break something major, 3 | # especially since our examples all need to compile. 4 | 5 | # This should also include module load and name cross-linking tests, 6 | # e.g. the `g1()+g1()` test 7 | 8 | import unittest 9 | 10 | 11 | class TestLexer(unittest.TestCase): 12 | from core.repl import Repl 13 | #from core.akitypes import AkiTypeMgr 14 | 15 | #mgr = AkiTypeMgr() 16 | #types = mgr.types 17 | #r = Repl(typemgr=mgr) 18 | r = Repl() 19 | i = r.interactive 20 | 21 | def e(self, test, result): 22 | _ = [_ for _ in self.i(test, False)][0] 23 | self.assertEqual(_, result) 24 | 25 | def test_load_1(self): 26 | self.r.load_file("test_1", ignore_cache=True) 27 | self.e("g1()+g1()", 38) 28 | 29 | def test_load_2(self): 30 | self.r.load_file("test_2", ignore_cache=True) 31 | self.e('print("Hello world!")', 13) 32 | 33 | def test_load_3(self): 34 | # Right now we're just trying to see if the Life file compiles 35 | self.r.load_file("l", ignore_cache=True) 36 | 37 | -------------------------------------------------------------------------------- /aki/core/constants.py: -------------------------------------------------------------------------------- 1 | PRODUCT = "Aki" 2 | VERSION = "0.1.2019.11.07" 3 | COPYRIGHT = "2019" 4 | 5 | WELCOME = f"{PRODUCT} v.{VERSION}" 6 | 7 | ABOUT = f"""{WELCOME} 8 | © {COPYRIGHT} Serdar Yegulalp 9 | 10 | Based on code created by: 11 | - Frédéric Guérin (https://github.com/frederickjeanguerin/pykaleidoscope) 12 | - Eli Bendersky (https://github.com/eliben/pykaleidoscope)""" 13 | 14 | 15 | def defaults(): 16 | return { 17 | "paths": { 18 | "source_dir": "examples", 19 | "output_dir": "output", 20 | "dump_dir": ".", 21 | "nt_compiler": "C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community\\VC\\Auxiliary\\Build\\vcvarsall.bat", 22 | "stdlib": "stdlib" 23 | }, 24 | "settings": { 25 | "write_main_to_file": ( 26 | 'Dumps loaded module LLVM IR to "{settings.paths.output_dir}" on load.', 27 | True, 28 | ), 29 | "write_repl_to_file": ( 30 | 'Dumps REPL LLVM IR to "{setting.paths.output_dir}" on load.', 31 | True, 32 | ), 33 | "compile_on_load": ("Compile immediately when a file is loaded.", True), 34 | "cache_compilation": ("Cache compiled files for reuse", True), 35 | "ignore_cache": ("Ignore cached files when recompiling", False), 36 | }, 37 | } 38 | 39 | -------------------------------------------------------------------------------- /FAQ.md: -------------------------------------------------------------------------------- 1 | # What is Akilang? What is Aki? 2 | 3 | Aki, or Akilang, is a simple programming language. The first compiler for that language is written in Python, and using LLVM by way of the `llvmlite` library. 4 | 5 | # What's your goal for the language? 6 | 7 | Aki isn't meant to "replace" any existing languages or be a "challenge" to them. It's mainly a learning experience for the author and whoever else wants to help implement it. If in time it becomes something truly useful, even a challenge to existing languages, great. But the main goal is to learn compiler theory and programming language design by doing. 8 | 9 | # Is Aki interpreted? 10 | 11 | Aki compiles to native machine code. By default it compiles to the machine the compiler is running on, but in time we'll support emitting code for other targets. 12 | 13 | # Can I run Aki programs on (insert platform here)? 14 | 15 | In theory Aki programs can run anywhere LLVM has a target. Aki emits LLVM IR and bitcode, which can be compiled more or less independently of Aki itself. However, the only implementation of the compiler right now is for Microsoft Windows 10, 64-bit edition, although since it's written in Python it can also in theory be ported pretty easily to other platforms. (Windows is the chief developer's main platform.) 16 | 17 | # What kinds of programs can I write with Aki? 18 | 19 | At first, Aki will not be very useful. I don't expect to be able to support programs that do more than perform basic math, and print to and read from the console. In time, though, I hope to make its core as useful as, say, Python without its standard library. 20 | 21 | # Will Aki have garbage collected memory management (e.g., Python, Go) or scope/lifetime-based memory management (e.g., Rust)? 22 | 23 | I'm hoping to allow both where possible. Right now it doesn't have any functional memory management, as it is still in the very early stages. 24 | 25 | # Can I contribute a feature or a bugfix? 26 | 27 | Absolutely. See [CONTRIBUTING](CONTRIBUTING.md) for more details. -------------------------------------------------------------------------------- /aki/examples/test_2.aki: -------------------------------------------------------------------------------- 1 | # Layer 0: Platform-specific functions 2 | # Use the platform version of a function when we can. 3 | 4 | extern GetStdHandle(nStdHandle:i32):ptr u_size 5 | 6 | extern WriteConsoleA( 7 | hConsoleOutput:ptr u_size, 8 | lpBuffer: ptr u_mem, 9 | nNumberOfCharsToWrite: i32, 10 | lpNumberOfCharsWritten: ptr i32, 11 | lpReserved: u64 = 0:u64 12 | ):i32 13 | 14 | extern GetProcessHeap():ptr u_size 15 | 16 | extern HeapAlloc( 17 | hHeap: ptr u_size, 18 | dwFlags: i32, 19 | dwBytes: u_size 20 | ):ptr u_mem 21 | 22 | extern HeapFree( 23 | hHeap: ptr u_size, 24 | dwFlags: i32, 25 | lpMem: ptr u_mem 26 | ):i32 27 | 28 | extern Sleep(dwMilliseconds:i32):i32 29 | 30 | # Layer 0a: Non-platform specific functions 31 | # We can use portable versions of functions if it's easier. 32 | 33 | extern _snprintf( 34 | buffer: ptr u_mem, 35 | count: u_size, 36 | format: ptr u8, 37 | *args 38 | ):i32 39 | 40 | # Layer 1 41 | # Platform-level functions we need to have cross-platform interfaces for. 42 | # Used mainly in libraries but accessible to the end user. 43 | 44 | def alloc(bytes:u_size):ptr u_mem { 45 | HeapAlloc( 46 | GetProcessHeap(), 47 | 0x00000008, 48 | bytes 49 | ) 50 | } 51 | 52 | def free(ptr_to_free:ptr u_mem){ 53 | HeapFree( 54 | GetProcessHeap(), 55 | 0x00000000, 56 | ptr_to_free 57 | ) 58 | } 59 | 60 | # Layer 2 61 | # Library functions for all users. 62 | # Anything that requires these imports the stdlib 63 | 64 | def sleep(msecs:i32):i32 { 65 | Sleep(msecs) 66 | } 67 | 68 | def print(_str:str):i32 { 69 | 70 | var bytes_written:i32=0 71 | var _size = 4096:u_size 72 | var buffer = alloc(_size) 73 | 74 | var len = _snprintf( 75 | buffer, 76 | _size, 77 | c_data('%s\n'), 78 | c_data(_str) 79 | ) 80 | 81 | WriteConsoleA( 82 | GetStdHandle(-11), 83 | buffer, 84 | len, 85 | ref(bytes_written) 86 | ) 87 | 88 | free(buffer) 89 | bytes_written 90 | } 91 | 92 | # ----------------------- 93 | 94 | def g(x:str="Hi there"){ 95 | print(x) 96 | } 97 | 98 | def main(){ 99 | print ("Hello world!") 100 | sleep(1000) 101 | print ("Goodbye world!") 102 | } 103 | # test -------------------------------------------------------------------------------- /aki/core/compiler.py: -------------------------------------------------------------------------------- 1 | import llvmlite.binding as llvm 2 | from llvmlite import ir 3 | import datetime 4 | 5 | llvm.initialize() 6 | llvm.initialize_native_target() 7 | llvm.initialize_native_asmprinter() 8 | 9 | import os 10 | 11 | 12 | class AkiCompiler: 13 | def __init__(self): 14 | """ 15 | Create execution engine. 16 | """ 17 | 18 | # Create a target machine representing the host 19 | self.target = llvm.Target.from_default_triple() 20 | self.target_machine = self.target.create_target_machine() 21 | 22 | # Prepare the engine with an empty module 23 | self.backing_mod = llvm.parse_assembly("") 24 | self.engine = llvm.create_mcjit_compiler(self.backing_mod, self.target_machine) 25 | self.mod_ref = None 26 | 27 | # Not used yet 28 | # self.engine.set_object_cache(export,None) 29 | 30 | def compile_ir(self, llvm_ir): 31 | """ 32 | Compile a module from an LLVM IR string. 33 | """ 34 | mod = llvm.parse_assembly(llvm_ir) 35 | return self.finalize_compilation(mod) 36 | 37 | def compile_bc(self, bc): 38 | """ 39 | Compile a module from LLVM bitcode. 40 | """ 41 | mod = llvm.parse_bitcode(bc) 42 | return self.finalize_compilation(mod) 43 | 44 | def finalize_compilation(self, mod): 45 | mod.verify() 46 | self.engine.add_module(mod) 47 | self.engine.finalize_object() 48 | self.engine.run_static_constructors() 49 | self.mod_ref = mod 50 | return mod 51 | 52 | def compile_module(self, module, filename="output"): 53 | """ 54 | JIT-compiles the module for immediate execution. 55 | """ 56 | 57 | llvm_ir = str(module) 58 | 59 | # Write IR to file for debugging 60 | 61 | if filename: 62 | if not os.path.exists("output"): 63 | os.mkdir("output") 64 | with open(os.path.join("output", f"{filename}.akil"), "w") as file: 65 | file.write(f"; File written at {datetime.datetime.now()}\n") 66 | file.write(llvm_ir) 67 | 68 | mod = self.compile_ir(llvm_ir) 69 | 70 | # Write bitcode 71 | 72 | if filename: 73 | with open(os.path.join("output", f"{filename}.akib"), "wb") as file: 74 | file.write(mod.as_bitcode()) 75 | 76 | def get_addr(self, func_name="main"): 77 | # Obtain module entry point 78 | func_ptr = self.engine.get_function_address(func_name) 79 | return func_ptr 80 | -------------------------------------------------------------------------------- /aki/core/error.py: -------------------------------------------------------------------------------- 1 | from core.repl import RED, REP, CMD, MAG 2 | 3 | 4 | class ReloadException(Exception): 5 | """ 6 | Used to signal that the REPL needs to reload. 7 | """ 8 | 9 | pass 10 | 11 | 12 | class QuitException(Exception): 13 | """ 14 | Used to signal the REPL is quitting normally. 15 | """ 16 | 17 | pass 18 | 19 | 20 | class LocalException(Exception): 21 | """ 22 | Used for control flow in functions. 23 | """ 24 | 25 | pass 26 | 27 | 28 | class AkiBaseErr(Exception): 29 | """ 30 | Base Aki error type. This should not be used directly. 31 | """ 32 | 33 | _errtype = "0 (General error)" 34 | 35 | def __init__(self, index, txt, msg): 36 | if txt is None: 37 | txt = "" 38 | 39 | if index is None: 40 | index = 1 41 | elif not isinstance(index, int): 42 | index = index.index 43 | elif index == 0: 44 | index = 1 45 | 46 | last_newline = txt.rfind(f"\n", 0, index) 47 | 48 | if last_newline == -1: 49 | last_newline = 0 50 | self.col = index 51 | else: 52 | self.col = index - last_newline 53 | 54 | end = txt.find(f"\n", index + 1) 55 | if end == -1: 56 | self.extract = txt[last_newline:] 57 | else: 58 | self.extract = txt[last_newline:end] 59 | 60 | if last_newline == 0: 61 | self.lineno = 1 62 | else: 63 | self.lineno = txt.count(f"\n", 0, last_newline + 1) + 1 64 | 65 | self.msg = msg 66 | 67 | def __str__(self): 68 | return f"{'-'*72}\n{RED}Error: {self._errtype}\n{REP}Line {self.lineno}:{self.col}\n{self.msg}\n{'-'*72}\n{CMD}{self.extract}\n{MAG}{'-'*(self.col-1)}^{REP}" 69 | 70 | 71 | class AkiSyntaxErr(AkiBaseErr): 72 | """ 73 | Thrown when we encounter syntax or parsing issues. 74 | """ 75 | 76 | _errtype = "1 (Syntax error)" 77 | 78 | 79 | class AkiNameErr(AkiBaseErr): 80 | """ 81 | Thrown when a name reference is not recognized. 82 | """ 83 | 84 | _errtype = "2 (Name error)" 85 | 86 | 87 | class AkiTypeErr(AkiBaseErr): 88 | """ 89 | Thrown when types are not compatible or other type-related errors arise. 90 | """ 91 | 92 | _errtype = "3 (Type error)" 93 | 94 | 95 | class AkiOpError(AkiBaseErr): 96 | """ 97 | Thrown when the op for a function does not exist or is incompatible. 98 | """ 99 | 100 | _errtype = "4 (Operator error)" 101 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Project-specific 2 | 3 | .obsolete 4 | output 5 | dump 6 | _work 7 | _dist 8 | _build 9 | .sync 10 | .vscode 11 | src/test 12 | *.profile 13 | stats.txt 14 | other.md 15 | next.md 16 | config.ini 17 | vv.bat 18 | a2.bat 19 | issues 20 | parser.out 21 | in.aki 22 | **/*.akic 23 | **/*.akib 24 | **/*.akil 25 | /a.bat 26 | .mypy_cache 27 | 28 | # Byte-compiled / optimized / DLL files 29 | **/__pycache__/ 30 | __dump__* 31 | *.py[cod] 32 | *$py.class 33 | **/*.dll 34 | dump.ll 35 | 36 | # # C extensions 37 | # *.so 38 | 39 | # # Distribution / packaging 40 | __build 41 | _build 42 | _dist 43 | # .Python 44 | # env/ 45 | # build/ 46 | # develop-eggs/ 47 | # dist/ 48 | # downloads/ 49 | # eggs/ 50 | # .eggs/ 51 | # lib64/ 52 | # parts/ 53 | # sdist/ 54 | # var/ 55 | # *.egg-info/ 56 | # .installed.cfg 57 | # *.egg 58 | 59 | # # PyInstaller 60 | # # Usually these files are written by a python script from a template 61 | # # before PyInstaller builds the exe, so as to inject date/other infos into it. 62 | # *.manifest 63 | 64 | # # Installer logs 65 | # pip-log.txt 66 | # pip-delete-this-directory.txt 67 | 68 | # # Unit test / coverage reports 69 | # htmlcov/ 70 | # .tox/ 71 | # .coverage 72 | # .coverage.* 73 | # .cache 74 | # nosetests.xml 75 | # coverage.xml 76 | # *,cover 77 | # .hypothesis/ 78 | 79 | # # Translations 80 | # *.mo 81 | # *.pot 82 | 83 | # # Django stuff: 84 | # *.log 85 | # local_settings.py 86 | 87 | # # Flask instance folder 88 | # instance/ 89 | 90 | # # Scrapy stuff: 91 | # .scrapy 92 | 93 | # # Sphinx documentation 94 | # docs/_build/ 95 | 96 | # # PyBuilder 97 | # target/ 98 | 99 | # # IPython Notebook 100 | # .ipynb_checkpoints 101 | 102 | # # pyenv 103 | # .python-version 104 | 105 | # # celery beat schedule file 106 | # celerybeat-schedule 107 | 108 | # # dotenv 109 | # .env 110 | 111 | # # virtualenv 112 | # venv/ 113 | # ENV/ 114 | 115 | # # Spyder project settings 116 | # .spyderproject 117 | 118 | # # Rope project settings 119 | # .ropeproject 120 | 121 | # # ========================= 122 | # # Operating System Files 123 | # # ========================= 124 | 125 | # # OSX 126 | # # ========================= 127 | 128 | # .DS_Store 129 | # .AppleDouble 130 | # .LSOverride 131 | 132 | # # Thumbnails 133 | # ._* 134 | 135 | # # Files that might appear in the root of a volume 136 | # .DocumentRevisions-V100 137 | # .fseventsd 138 | # .Spotlight-V100 139 | # .TemporaryItems 140 | # .Trashes 141 | # .VolumeIcon.icns 142 | 143 | # # Directories potentially created on remote AFP share 144 | # .AppleDB 145 | # .AppleDesktop 146 | # Network Trash Folder 147 | # Temporary Items 148 | # .apdisk 149 | 150 | # # Windows 151 | # # ========================= 152 | 153 | # # Windows image file caches 154 | # Thumbs.db 155 | # ehthumbs.db 156 | 157 | # # Folder config file 158 | # Desktop.ini 159 | 160 | # # Recycle Bin used on file shares 161 | # $RECYCLE.BIN/ 162 | 163 | # # Windows Installer files 164 | # *.cab 165 | # *.msi 166 | # *.msm 167 | # *.msp 168 | 169 | # # Windows shortcuts 170 | # *.lnk 171 | 172 | -------------------------------------------------------------------------------- /code-of-conduct.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | education, socio-economic status, nationality, personal appearance, race, 10 | religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at serdar@genjipress.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | -------------------------------------------------------------------------------- /aki/tests/t_01_lexer.py.bak: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from core import lex 4 | from sly.lex import Token 5 | 6 | 7 | class TestLexer(unittest.TestCase): 8 | l = lex._AkiLexer.tokenize 9 | 10 | def _render(self): 11 | """ 12 | Used to render the confirmed output of the token list to our test case. 13 | """ 14 | 15 | t = self._generate_tokens() 16 | 17 | print("[") 18 | for _ in t: 19 | print([getattr(_, x) for x in _.__slots__], ",") 20 | print("]") 21 | 22 | def _generate_tokens(self): 23 | t = self.l( 24 | "1+2.0 hello:i32 (_f16) {-32,64} s=t r==1 def x(a:i32) var s=1 2+1 3-2 4*3 5/4 var x=8.0//2 if not x y else z a:ptr i32" 25 | ) 26 | 27 | t = [_ for _ in t] 28 | return t 29 | 30 | def test_tokens_and_values(self): 31 | t = self._generate_tokens() 32 | # self._render() 33 | 34 | slots = Token.__slots__ 35 | 36 | tests = [ 37 | ["INTEGER", 1, 1, 0], 38 | ["PLUS", "+", 1, 1], 39 | ["FLOAT", 2.0, 1, 2], 40 | ["NAME", "hello", 1, 6], 41 | ["COLON", ":", 1, 11], 42 | ["NAME", "i32", 1, 12], 43 | ["LPAREN", "(", 1, 16], 44 | ["NAME", "_f16", 1, 17], 45 | ["RPAREN", ")", 1, 21], 46 | ["LBRACE", "{", 1, 23], 47 | ["MINUS", "-", 1, 24], 48 | ["INTEGER", 32, 1, 25], 49 | ["COMMA", ",", 1, 27], 50 | ["INTEGER", 64, 1, 28], 51 | ["RBRACE", "}", 1, 30], 52 | ["NAME", "s", 1, 32], 53 | ["ASSIGN", "=", 1, 33], 54 | ["NAME", "t", 1, 34], 55 | ["NAME", "r", 1, 36], 56 | ["EQ", "==", 1, 37], 57 | ["INTEGER", 1, 1, 39], 58 | ["DEF", "def", 1, 41], 59 | ["NAME", "x", 1, 45], 60 | ["LPAREN", "(", 1, 46], 61 | ["NAME", "a", 1, 47], 62 | ["COLON", ":", 1, 48], 63 | ["NAME", "i32", 1, 49], 64 | ["RPAREN", ")", 1, 52], 65 | ["VAR", "var", 1, 54], 66 | ["NAME", "s", 1, 58], 67 | ["ASSIGN", "=", 1, 59], 68 | ["INTEGER", 1, 1, 60], 69 | ["INTEGER", 2, 1, 62], 70 | ["PLUS", "+", 1, 63], 71 | ["INTEGER", 1, 1, 64], 72 | ["INTEGER", 3, 1, 66], 73 | ["MINUS", "-", 1, 67], 74 | ["INTEGER", 2, 1, 68], 75 | ["INTEGER", 4, 1, 70], 76 | ["TIMES", "*", 1, 71], 77 | ["INTEGER", 3, 1, 72], 78 | ["INTEGER", 5, 1, 74], 79 | ["DIV", "/", 1, 75], 80 | ["INTEGER", 4, 1, 76], 81 | ["VAR", "var", 1, 78], 82 | ["NAME", "x", 1, 82], 83 | ["ASSIGN", "=", 1, 83], 84 | ["FLOAT", 8.0, 1, 84], 85 | ["INT_DIV", "//", 1, 87], 86 | ["INTEGER", 2, 1, 89], 87 | ["IF", "if", 1, 91], 88 | ["NOT", "not", 1, 94], 89 | ["NAME", "x", 1, 98], 90 | ["NAME", "y", 1, 100], 91 | ["ELSE", "else", 1, 102], 92 | ["NAME", "z", 1, 107], 93 | ["NAME", "a", 1, 109], 94 | ["COLON", ":", 1, 110], 95 | ["PTR", "ptr", 1, 111], 96 | ["NAME", "i32", 1, 115], 97 | ] 98 | 99 | for tok, test in zip(t, tests): 100 | for id, x in enumerate(slots): 101 | self.assertEqual(test[id], getattr(tok, slots[id])) 102 | -------------------------------------------------------------------------------- /aki/examples/l.aki: -------------------------------------------------------------------------------- 1 | # Conway's Game Of Life in Aki 2 | 3 | # Stuff to eventually be turned into libraries 4 | # -------------------------------- 5 | 6 | # non-macro print 7 | 8 | extern printf_s(_format:ptr u8, *args):i32 9 | 10 | @inline 11 | def print(_str:str):i32 { 12 | printf_s(c_data(_str)) 13 | } 14 | 15 | # rand 16 | 17 | extern rand():i32 18 | 19 | @inline 20 | def rnd(_min:i32, _max:i32):i32 { 21 | rand() / (32767 / ((_max+1) - _min)) +_min 22 | } 23 | 24 | # inkey 25 | 26 | extern _getch():u8 27 | 28 | @inline 29 | def inkey():u8 { 30 | _getch() 31 | } 32 | 33 | # Main program 34 | # -------------------------------- 35 | 36 | const { 37 | HEIGHT = 40, 38 | WIDTH = 80, 39 | GENESIS_FACTOR = 30, 40 | MUTATION_FACTOR = 5, 41 | DISPLAY_SIZE = ((WIDTH+1) * HEIGHT) + 1 42 | } 43 | 44 | uni { 45 | world:array u8[2,HEIGHT,WIDTH], 46 | output:array u8[DISPLAY_SIZE], 47 | current_world = 0, 48 | population = 0, 49 | generation = 0 50 | # TODO: allow trailing comma in these types of definitions 51 | } 52 | 53 | def setup(){ 54 | var output_index=0 55 | loop (var x=0, x, <=, >=, and, or`) 24 | * [x] also bitwise and/or by way of `&` and `|` ops 25 | * [x] Unary ops 26 | * [x] (`-`) - numerical (bitwise) 27 | * [x] (`not`) - logical (`i1` result) 28 | * [x] Variable declarations, assignment, retrieval 29 | * [x] Declaration/init 30 | * [x] Retrieval 31 | * [x] Assignment 32 | * [x] Loop constructions 33 | * [x] Var inside and outside of loops 34 | * [x] `break` to exit from blocks 35 | * [ ] Autoincrement/assume infinite loop 36 | * [x] Loaded programs and REPL use different JITs to allow efficient code reuse 37 | * [x] `with` context for variables 38 | * [x] Test suite 39 | * [x] Lexer 40 | * [x] Parser 41 | * [x] Compiler 42 | * [ ] Compile to standalone binary 43 | * [x] Default function argument values 44 | * [x] Color-coded output in REPL 45 | * [x] Store Aki-native type signatures as LLVM metadata 46 | * [x] Pointers and pointer types, mainly for compatibility with C right now 47 | * [x] `ref`/`deref` (to create and dereference pointers) 48 | * [ ] Pointer comparisons 49 | * [ ] Pointer math (may require its own type) 50 | * [x] Function type (for function pointers) 51 | * [x] Function pointer type 52 | * [x] String constants 53 | * [x] Comments 54 | * [x] Extract C-compatible data from Aki structures 55 | * [x] External function references (`extern` keyword) 56 | * [x] `cast` 57 | * [x] Unsafe `cast` 58 | * [x] Casting int to pointer and back (`unsafe` only) 59 | * [x] Inline typing for constants 60 | * [x] `True/False` for `bool` values 61 | * [x] `0x00` and `0h00` for unsigned and signed values, respectively 62 | * [x] Inline constant type declarations (e.g., `4096:u_size`) 63 | * [x] `unsafe` context block for certain operations 64 | * [x] Variable argument functions (for compatibility with C calls) 65 | * [x] String escape characters (e.g., `\x00` for inline hex) 66 | * [x] Escaped quoting (`'It\'s a quote!'`) 67 | * [x] Escape slashes (`\\` = \\) 68 | * [x] Array type 69 | * [x] Support for objects in arrays 70 | * [x] N-dimensional arrays of scalars 71 | * [x] `uni` for globals 72 | * [x] `case` statement for matching against constant enums (including type enumerators) 73 | * [x] `while` expressions 74 | * [x] `const` for constants 75 | * [x] Decorators by way of the `@` symbol 76 | * [x] `inline`/`noinline` for functions 77 | 78 | ## In progress 79 | * [ ] Compile-time computation of constants and values for `uni` assignments 80 | * [ ] Classes and object structures 81 | * [ ] Object methods and method calls 82 | * [ ] Type declarations (aliases) 83 | * [ ] `print` builtin 84 | * [ ] `meta` keyword for program metadata (compiler directives, etc.) 85 | * [ ] Complete REPL command set (as per earlier incarnation of project) 86 | * [ ] CLI command support 87 | * [ ] Compile to standalone binary, maybe with `lld` instead of the platform linker 88 | * [ ] Type conversions 89 | 90 | # Stage 1: Advanced variables and structures 91 | * [ ] Slices 92 | * [ ] Enums 93 | * [ ] Iterables by way of object methods 94 | * [ ] String operations 95 | * [ ] String slices 96 | * [ ] Call chains 97 | 98 | # Stage 2: Advanced error handling 99 | 100 | * [ ] Error/option types 101 | 102 | # Stage 3: Modules 103 | 104 | * [ ] Module imports 105 | 106 | # Stage 4: Other stuff 107 | 108 | * [ ] Container objects 109 | * [ ] Lists 110 | * [ ] Prereq for functions that take arbitrary numbers of arguments (since they are just list objects) 111 | * [ ] Dictionaries/hashmaps 112 | * [ ] Prereq for functions that take named arguments (since they are just dicts with string keys) 113 | * [ ] Sets 114 | * [ ] Auto-conversion rules for scalars? 115 | 116 | # Stage 5: Beyond The Impossible (for me right now, anyway) 117 | 118 | * [ ] Garbage collection 119 | * [ ] Use existing C headers as-is 120 | * [ ] Threading, actors, coroutines, etc. -------------------------------------------------------------------------------- /aki/core/grammar/grammar.lark: -------------------------------------------------------------------------------- 1 | %import common.WS 2 | %ignore WS 3 | 4 | start: toplevel* 5 | 6 | toplevel: function_declaration 7 | |expression 8 | |external_declaration 9 | |const_declaration_block 10 | |uni_declaration_block 11 | |toplevel_decorator 12 | 13 | function_declaration: DEF name LPAREN opt_arglist RPAREN opt_vartype expression 14 | external_declaration: EXTERN name LPAREN opt_arglist RPAREN opt_vartype 15 | 16 | function_multi_declaration: DEF name LBRACE multi_dec_list RBRACE 17 | multi_dec_list: multi_dec+ 18 | multi_dec: (WITH|DEFAULT) LPAREN opt_arglist RPAREN opt_vartype expression 19 | 20 | const_declaration_block: CONST LBRACE varassignments RBRACE 21 | uni_declaration_block: UNI LBRACE varassignments RBRACE 22 | 23 | toplevel_decorator: decorators function_declaration 24 | inline_decorator: decorators expression 25 | decorators: decorator+ 26 | decorator: DECORATOR NAME opt_args 27 | 28 | // An EXPRESSION can only PRODUCE a value 29 | 30 | expression: test 31 | | assignment 32 | | variable_declaration_block 33 | | terminal 34 | | inline_decorator 35 | | with_expr 36 | | while_expr 37 | | when_expr 38 | | if_expr 39 | | break_expr 40 | | loop_expr | infinite_loop_expr 41 | | select_expr 42 | | return_expr 43 | 44 | terminal: SEMI 45 | 46 | ?test: or_test 47 | ?or_test: and_test (OR and_test)* 48 | ?and_test: not_test (AND not_test)* 49 | ?not_test: NOT not_test | comparison 50 | ?comparison: add_ops (comp_op add_ops)* 51 | ?add_ops: mult_ops (add_op mult_ops)* 52 | ?mult_ops: factor_ops (mult_op factor_ops)* 53 | ?factor_ops: factor_op factor_ops | atom_expr 54 | 55 | // Atom-expressions are the only things that can both receive and produce a value. 56 | // However, only some atom types can receive a value (name, slice, dot ref) 57 | 58 | ?atom_expr: atom_expr LPAREN opt_call_args RPAREN -> func_call 59 | | atom_expr LBRACKET dimensions RBRACKET -> array_ref 60 | | atom 61 | 62 | atom: number 63 | | name 64 | | constant 65 | | subexpression 66 | | parenthetical 67 | | vartype 68 | | string 69 | | unsafe_block 70 | 71 | unsafe_block: UNSAFE expression 72 | 73 | single_variable_declaration_block: VAR varassignment 74 | variable_declaration_block: VAR (varassignments | alt_open varassignments alt_close) 75 | opt_varassignments: [varassignments] 76 | varassignments: varassignment ("," varassignment)* 77 | varassignment: name opt_vartype opt_assignment 78 | opt_assignment: [ASSIGN expression] 79 | 80 | opt_args: [LPAREN arglist RPAREN] 81 | opt_arglist: [arglist] 82 | arglist: argument ("," argument)* 83 | argument: stararg NAME opt_vartype opt_assignment 84 | stararg: [TIMES] 85 | 86 | opt_call_args: [call_args] 87 | call_args: call_arg ("," call_arg)* 88 | call_arg: expression 89 | 90 | assignment: atom_expr assignment_op expression 91 | assignment_op: ASSIGN|SM_PLUS|SM_MINUS|SM_TIMES|SM_DIV 92 | 93 | opt_vartype: [COLON vartype] 94 | // mandatory_vartype: COLON vartype 95 | // opt_bare_vartype: [vartype] 96 | // mandatory_bare_vartype: vartype 97 | 98 | vartype: ptr_list (NAME|functypedef|arraytypedef) 99 | ptr_list: PTR* 100 | functypedef: FUNC LPAREN vartypelist RPAREN opt_vartype 101 | //vartypelist: opt_vartype ("," mandatory_vartype)* 102 | vartypelist: [vartype ("," vartype)*] 103 | arraytypedef: ARRAY vartype LBRACKET dimensions RBRACKET 104 | dimensions: [dimension ("," dimension)*] 105 | dimension: expression 106 | 107 | // with 108 | 109 | with_expr: WITH (variable_declaration_block | alt_open variable_declaration_block alt_close) expression 110 | 111 | // while 112 | 113 | while_expr: WHILE expression expression 114 | 115 | // if/when 116 | 117 | if_expr: IF expression expression optional_else 118 | when_expr: WHEN expression expression optional_else 119 | optional_else: [ELSE expression] 120 | 121 | // loop 122 | 123 | loop_expr: LOOP LPAREN (assignment|single_variable_declaration_block) "," expression ["," expression] RPAREN expression 124 | 125 | infinite_loop_expr: LOOP LPAREN RPAREN expression 126 | 127 | // select 128 | 129 | select_expr: SELECT expression LBRACE cases RBRACE 130 | cases: case+ 131 | case: (CASE expression expression)|(DEFAULT expression) 132 | 133 | // break 134 | 135 | break_expr: BREAK 136 | 137 | // return 138 | 139 | return_expr: RETURN expression 140 | 141 | // -------------------- 142 | 143 | parenthetical: LPAREN test RPAREN 144 | subexpression: LBRACE expression* RBRACE 145 | 146 | ?comp_op: EQ|NEQ|GT|LT|GEQ|LEQ 147 | ?add_op: PLUS|MINUS 148 | ?mult_op: MOD|TIMES|DIV 149 | ?factor_op: NEG 150 | 151 | constant: TRUE|FALSE 152 | 153 | number: decimal_number 154 | |float_number 155 | |hex_number 156 | 157 | // TODO: move opt_vartype to number node, process it there 158 | 159 | decimal_number: DEC_NUMBER opt_vartype 160 | float_number: FLOAT_NUMBER opt_vartype 161 | hex_number: HEX_NUMBER opt_vartype 162 | 163 | ?alt_open: ["("|"{"] 164 | ?alt_close: ["}"|")"] 165 | 166 | DEC_NUMBER: /\d+/ 167 | FLOAT_NUMBER: /\d+[.]\d+/ 168 | HEX_NUMBER: /0[hx][a-fA-F0-9]*/ 169 | 170 | name: NAME 171 | NAME: /[a-zA-Z_][a-zA-Z0-9_]*/ 172 | 173 | string: TEXT1 174 | |TEXT2 175 | 176 | TEXT1: /'(?:[^'\\]|\\.)*'/ 177 | TEXT2: /"(?:[^"\\]|\\.)*"/ 178 | 179 | VAR: "var" 180 | PTR: "ptr" 181 | ARRAY: "array" 182 | FUNC: "func" 183 | 184 | BREAK: "break" 185 | CASE: "case" 186 | CONST: "const" 187 | DEF: "def" 188 | DEFAULT: "default" 189 | ELSE: "else" 190 | EXTERN: "extern" 191 | IF: "if" 192 | LOOP: "loop" 193 | RETURN: "return" 194 | SELECT: "select" 195 | UNI: "uni" 196 | UNSAFE: "unsafe" 197 | WITH: "with" 198 | WHEN: "when" 199 | WHILE: "while" 200 | 201 | ASSIGN: "=" 202 | STORE: "<-" 203 | 204 | OR: "or" 205 | AND: "and" 206 | NOT: "not" 207 | 208 | PLUS: "+" 209 | MINUS: "-" 210 | TIMES: "*" 211 | DIV: "/" 212 | MOD: "%" 213 | 214 | SM_PLUS: "+=" 215 | SM_MINUS: "-=" 216 | SM_TIMES: "*=" 217 | SM_DIV: "/=" 218 | 219 | EQ: "==" 220 | NEQ: "!=" 221 | GT: ">" 222 | LT: "<" 223 | GEQ: ">=" 224 | LEQ: "<=" 225 | 226 | NEG: "-" 227 | 228 | LPAREN: "(" 229 | RPAREN: ")" 230 | LBRACE: "{" 231 | RBRACE: "}" 232 | LBRACKET: "[" 233 | RBRACKET: "]" 234 | SEMI: ";" 235 | COLON: ":" 236 | DECORATOR: "@" 237 | 238 | TRUE: "True" 239 | FALSE: "False" 240 | 241 | COMMENT: /#[^\n]*/ 242 | 243 | %ignore COMMENT -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # **Aki**: a compiler for a simple language, built with Python 3.6+ and the [LLVM framework](https://www.llvm.org) by way of the [llvmlite](http://llvmlite.pydata.org/en/latest/) library 2 | 3 | > ⚠ This project is currently very unstable and should not be used in production. However, you should always be able to pull from `master`, run the demos, and pass the test suite. (The test suite compiles the demos internally as well.) 4 | 5 | This project is an attempt to create a compiler, language server, and REPL in Python for a simple language that's compiled to native machine code. 6 | 7 | Eventually, this might become something useful for real work. Right now, it's strictly proof-of-concept -- a fun toy for me to hack on and to learn about compiler theory and programming language construction in the process. 8 | 9 | * [This document](language.md) is a work-in-progress tour of the language's syntax. 10 | * [This document](whats-next.md) gives you an idea of what's being worked on right now and in the near future. 11 | * [The official blog](https://aki.genjipress.com) tracks development and discussion of the project. (Not updated often) 12 | * You can see a demo movie (now outdated) of the language in action [here.](https://www.youtube.com/watch?v=9vZ4oFCFOl8) 13 | 14 | **If you're a fan of Python, LLVM, compilers, or any combination of the above, [learn how to contribute.](CONTRIBUTING.md)** 15 | 16 | # Features, goals, and ideals 17 | 18 | The language's syntax and goals are in heavy flux, but this is the basic idea I want to aim for: 19 | 20 | * Compile to compact machine-native code with a minimal runtime and as few external dependencies as possible. 21 | * Use LLVM as our code generation system, so we can theoretically compile to any target LLVM supports, like WebAssembly. 22 | * Strong typing, eventually to be made dynamic by way of an object system a la Python. 23 | * Keep the core of the language small, but provide useful native constructs a la Python (Unicode strings, lists, dictionaries, tuples, sets, etc.). 24 | * Your choice of memory management methods as need dictates. Use garbage collected memory management when you need to throw together a quick script; use static memory management (by way of syntactical constructions) when you want speed and performance. 25 | * A batteries-included standard library, yet again like Python. 26 | * Good tooling for package and project management, including out-of-the-box code formatting tools, akin to Rust/Go/C#. 27 | * Integrated support for C libraries; the ability to import a C header file for a (compiled) library and use it as-is. 28 | * While we're dreaming: Make the language self-hosting. 29 | 30 | Even if any of these features are only implemented in miniature, the idea would be to polish the implementations as completely as possible, so they could set the best possible example. For instance, if the standard library only had a proof-of-concept number of modules, the *way* they worked would be refined so that the practices around them could support a larger standard library. 31 | 32 | By deliberately keeping the scope of the implementation at PoC level (or, rather, by accepting such a scope as a by-product of the fact that this is a hobby project for now), it ought to be easier to keep a good grasp on the basics, and to avoid conceptual problems that could bite us later. It also makes it easier to tear down and rebuild in the event we find we've written ourselves into a conceptual corner. 33 | 34 | Another likely goal would be to make this a language that encourages quick interactive development by way of its workflow. For instance, invoking the compiler with a certain set of command-line switches would fire up an editor for a new or existing project, and preconfigure the REPL to reload-and-run that project whenever you input the `.rlr` command. 35 | 36 | Finally, I'm not looking at this as a "replacement" for any existing language, just something that takes good cues from the galaxy of other languages out there. 37 | 38 | # "Aki's Way" 39 | 40 | Some guidelines about what we want Aki to be, by way of aphorisms (also in flux). These are not dogma, just directions to lean in: 41 | 42 | * Fast is good, easy is better, useful is best -- and sometimes also fastest, too. 43 | * Just enough is more. 44 | * You should be able to lift the hood if you *need* to, but the vast majority of the time you shouldn't *have* to. 45 | * Solve the right problems at the right level, and in a way that helps as many others as possible. 46 | * Don't include *now* what we don't need *now*. 47 | * Eschew ambiguity, but embrace practical brevity. 48 | 49 | # Code examples 50 | 51 | > Note that these do not work yet. This message will go away when they do. 52 | 53 | ## Hello, world! 54 | 55 | ``` 56 | def main() { 57 | 58 | print('Hello world!') 59 | # you can use "" or '' to delineate strings as long as they match 60 | 61 | print("こんにちは世界!") 62 | # Unicode is allowed 63 | 64 | print("Goodbye 65 | world!") # so are multi-line strings, as linebreaks are arbitrary 66 | 67 | 0 68 | 69 | # every statement is an expression, 70 | # and the last statement of a function is an implicit return, 71 | # but we do have a `return` keyword for early returns, 72 | # so the last line could also be: `return 0` 73 | } 74 | ``` 75 | 76 | (Eventually, for quick-and-dirty scripting, we'll ditch the need for a `main()` function.) 77 | 78 | ## Fibonacci sequence 79 | 80 | ``` 81 | def main() { 82 | var a = 0:u64, b = 1:u64, c = 0:u64 83 | # :u64 = unsigned 64-bit integer 84 | loop (x = 0, x < 50) { 85 | # by default loops increment by one 86 | print (a) 87 | c = a + b 88 | a = b 89 | b = c 90 | } 91 | return 0 92 | } 93 | ``` 94 | 95 | # Quickstart 96 | 97 | You'll need Python 3.6 and Windows 10 64-bit. 98 | 99 | 1. Clone or download the repo. 100 | 2. `pip install -r requirements.txt` to ensure you have all the requirements. 101 | 3. Run `python aki.py` to start the REPL. 102 | 4. Enter `.l.` to load the Conway's Life demo from the `examples` directory. 103 | 5. Enter `.r` to run the demo. 104 | 6. Hit `q` to exit Conway's Life. Enter `.` to see all commands. 105 | 106 | 107 | There's also going to be a standalone binary version of the compiler, most likely by way of `pyinstaller`. 108 | 109 | 110 | # Limitations 111 | 112 | (Apart from the language itself being a minimal affair, that is.) 113 | 114 | * Currently only Windows 10 64-bit is supported. We'd like to add support for other systems in time, though. 115 | * We have no plans to support any text encoding other than UTF-8, plus whatever encodings are used in the kernel of a given target platform (e.g., UTF-16 for Windows). 116 | 117 | # Derivation and licensing 118 | 119 | This project is based (very loosely) on the 120 | [Pykaleidoscope project](https://github.com/frederickjeanguerin/pykaleidoscope) by [Frédéric Guérin](https://github.com/frederickjeanguerin), 121 | derived in turn from the [Pykaleidoscope project](https://github.com/eliben/pykaleidoscope) by [Eli Bendersky](https://github.com/eliben). (An earlier version of this project used their code directly; this version is a complete rewrite from scratch.) 122 | 123 | The original Pykaleidoscope project was made available under the [Unlicense](https://github.com/eliben/pykaleidoscope/blob/master/LICENSE). This version is distributed under the [MIT license](LICENSE). 124 | 125 | Licensing for included and to-be-included modules: 126 | 127 | * [llvmlite](http://llvmlite.pydata.org/en/latest/): [BSD 2-Clause "Simplified" License](https://github.com/numba/llvmlite/blob/master/LICENSE) 128 | * [termcolor](https://pypi.org/project/termcolor/): MIT License 129 | * [colorama](https://pypi.org/project/colorama/): [BSD License](https://github.com/tartley/colorama/blob/master/LICENSE.txt) 130 | * [lark-parser](https://github.com/lark-parser/lark/): [MIT license](https://github.com/lark-parser/lark/blob/master/LICENSE) 131 | -------------------------------------------------------------------------------- /aki/tests/t_03_codeexec.py: -------------------------------------------------------------------------------- 1 | # Test all code generation functions. 2 | 3 | import unittest 4 | from core.error import AkiTypeErr, AkiSyntaxErr, AkiBaseErr, AkiOpError 5 | 6 | 7 | class TestLexer(unittest.TestCase): 8 | from core.repl import Repl 9 | from core.akitypes import AkiTypeMgr 10 | 11 | mgr = AkiTypeMgr() 12 | types = mgr.types 13 | r = Repl(typemgr=mgr) 14 | i = r.interactive 15 | 16 | def e(self, test, result): 17 | _ = [_ for _ in self.i(test, True)][0] 18 | self.assertEqual(_, result) 19 | 20 | def ex(self, err_type, test): 21 | with self.assertRaises(err_type): 22 | [_ for _ in self.i(test, True)] 23 | 24 | def test_constants(self): 25 | self.e(r"2", 2) 26 | self.e(r"2.0", 2.0) 27 | self.e(r"0hff", -1) 28 | self.e(r"0xff", 255) 29 | self.e(r"type(0x00:u64)", "") 30 | 31 | def test_math_integer(self): 32 | self.e(r"2+2", 4) 33 | self.e(r"2-2", 0) 34 | self.e(r"4-5", -1) 35 | self.e(r"2*2", 4) 36 | self.e(r"2*-2", -4) 37 | self.e(r"8/4", 2) 38 | 39 | def test_math_float(self): 40 | self.e(r"2.0+3.0", 5) 41 | self.e(r"2.0-3.0", -1) 42 | self.e(r"2.0*3.0", 6) 43 | self.e(r"6.0/3.0", 2) 44 | 45 | def test_nested_math(self): 46 | self.e(r"(2+2)/2", 2) 47 | self.e(r"(2+2)+(2*2)", 8) 48 | self.e(r"(2.0+2.5)/2.0", 2.25) 49 | self.e(r"(2.0+2.5)+(2.0*2.0)", 8.5) 50 | 51 | def test_andor_ops(self): 52 | self.e(r"2 and 3", 3) 53 | self.e(r"3 and 3", 3) 54 | self.e(r"2 and 0", 0) 55 | self.e(r"0 and 2", 0) 56 | self.e(r"0 and 0", 0) 57 | self.e(r"2 or 3", 2) 58 | self.e(r"3 or 3", 3) 59 | self.e(r"2 or 0", 2) 60 | self.e(r"0 or 2", 2) 61 | self.e(r"0 or 0", 0) 62 | 63 | def test_neg_ops(self): 64 | self.e(r"-1", -1) 65 | self.e(r"-1.0", -1.0), 66 | self.e(r"-0", 0) 67 | self.e(r"--1", 1) 68 | self.e(r"--1.0", 1.0) 69 | 70 | def test_not_ops(self): 71 | self.e(r"not 1", False) 72 | self.e(r"not 0", True) 73 | self.e(r"not 32", False) 74 | self.e(r"not 0.0", True) 75 | self.e(r"not 32.1", False) 76 | 77 | def test_comparisons_int(self): 78 | self.e(r"2>1", 1) 79 | self.e(r"2<1", 0) 80 | self.e(r"2>=1", 1) 81 | self.e(r"2<=1", 0) 82 | self.e(r"2!=1", 1) 83 | self.e(r"2==1", 0) 84 | self.e(r"2==2", 1) 85 | self.e(r"True == True", 1) 86 | self.e(r"True == False", 0) 87 | self.e(r"True != False", 1) 88 | self.e(r"False == False", 1) 89 | self.e(r"False != False", 0) 90 | 91 | self.ex(AkiTypeErr, r"False == 0") 92 | self.ex(AkiTypeErr, r"True == 1") 93 | 94 | def test_comparisons_float(self): 95 | self.e(r"2.0>1.0", 1) 96 | self.e(r"2.0<1.0", 0) 97 | self.e(r"2.0>=1.0", 1) 98 | self.e(r"2.0<=1.0", 0) 99 | self.e(r"2.0!=1.0", 1) 100 | self.e(r"2.0==1.0", 0) 101 | self.e(r"2.0==2.0", 1) 102 | 103 | self.ex(AkiTypeErr, r"False == 2.0") 104 | self.ex(AkiTypeErr, r"True == 0.0") 105 | 106 | def test_basic_control_flow(self): 107 | self.e(r"if 1 2 else 3", 2) 108 | self.e(r"if 0 2 else 3", 3) 109 | self.e(r"when 1 2 else 3", 1) 110 | self.e(r"when 0 2 else 3", 0) 111 | self.e(r"var x=1, y=2 if x==2 or y==1 3 else 4", 4) 112 | self.e(r"var x=1, y=2 if x==1 or y==2 3 else 4", 3) 113 | self.e(r"var x=1, y=2 if x==1 and y==2 3 else 4", 3) 114 | self.e(r"var x=1, y=2 if x==2 and y==1 3 else 4", 4) 115 | 116 | def test_expressionblock_syntax(self): 117 | self.e(r"var x=1,y=2 x+y", 3) 118 | 119 | def test_var_assignment(self): 120 | # Default type 121 | self.e(r"var x=1 x", 1) 122 | # Explicit type 123 | self.e(r"var x:f64=2.2 x", 2.2) 124 | # Implicit type 125 | self.e(r"var x=3.3 x", 3.3) 126 | self.e(r"var x,y,z x=y=z=1", 1) 127 | self.e(r"var x,y,z x=y=z=1 x", 1) 128 | self.e(r"var x,y,z x=y=z=1 y", 1) 129 | self.e(r"var x,y,z x=y=z=1 z", 1) 130 | 131 | def test_type_trapping(self): 132 | self.ex(AkiTypeErr, r"var x:f32=1") 133 | self.ex(AkiTypeErr, r"var x:i32=1.0") 134 | self.ex(AkiTypeErr, r"3+32.0") 135 | self.ex(AkiTypeErr, r"3.0+32") 136 | 137 | def test_function_defs(self): 138 | self.e(r"def m0(){1} m0()", 1) 139 | self.e(r"def m1(){1} def m2(){2} m1()+m2()", 3) 140 | 141 | def test_with(self): 142 | self.e(r"def m1(z){with var q:i32 loop (q=0, q<20, q+1) {z+=1} z} m1(0)", 20) 143 | 144 | def test_break(self): 145 | self.e(r"def m1(z){var q=0 loop () {q+=1 when q==20 break} q} m1(0)", 20) 146 | 147 | def test_default_function_arguments(self): 148 | self.e(r"def m1(z=1){z} m1()", 1) 149 | self.e(r"def m2(y,z=1){y+z} m2(2)", 3) 150 | self.e(r"def m3(y=2,z=1){y+z} m3()", 3) 151 | 152 | self.ex(AkiSyntaxErr, r"def m1(z=1,y){z+y") 153 | 154 | def test_func_arg_type_trapping(self): 155 | self.ex(AkiTypeErr, r"def m1(x:i32){x} m1(1.0)") 156 | self.ex(AkiTypeErr, r"def m2(x:f32){x} m2(3)") 157 | 158 | def test_func_arg_count_trapping(self): 159 | self.ex(AkiSyntaxErr, r"def m1(x=1,y=2){x+y} m1(1,2,3)") 160 | self.ex(AkiSyntaxErr, r"def m2(x,y,z){x+y} m2(1,2)") 161 | self.ex(AkiSyntaxErr, r"def m3(){0} m3(1,2)") 162 | 163 | def test_inline_type_declarations(self): 164 | self.e(r"var x:i32=1 x==1:i32", True) 165 | 166 | self.ex(AkiTypeErr, r"var x:i32=1 x==1:i64") 167 | 168 | def test_type_comparison(self): 169 | self.e(r"var x=1,y=2 type(x)==type(y)", True) 170 | self.e(r"i32==i32", True) 171 | self.e(r"i32==i64", False) 172 | self.e(r"var x=1,y=2 type(x)!=type(y)", False) 173 | self.e(r"type(i32)", "") 174 | self.e(r"i32", "") 175 | self.e(r"def x(){} type(x)", "") 176 | self.e(r"def y(z:i32){z} type(y)", "") 177 | self.e(r"def q(z:i64):i64{z} type(q)", "") 178 | 179 | self.ex(AkiOpError, r"var x=1,y=2 type(x)") 183 | 184 | def test_function_pointer(self): 185 | self.e(r"var x:func():i32 x", "") 186 | self.e(r"var x:func(i32):i32 x", "") 187 | self.e(r"var x:func(i32, i64):i32 x", "") 188 | self.e(r"def g0(){32} var x=g0 x()", 32) 189 | self.e(r"def g1(){32} def g2(x){32+x} var x=g1,y=g2 x()+y(1)", 65) 190 | self.e( 191 | r"def g3(){32} def g4(x){32+x} var x=g3 var y=x var z=g4 x()+y()+z(1)", 97 192 | ) 193 | 194 | def test_string_constant(self): 195 | self.e(r'"hi"', '"hi"') 196 | self.e(r'{var x="hi" x}', '"hi"') 197 | self.e(r'{var x:str x="hi" x}', '"hi"') 198 | self.e(r"var x:str x", '""') 199 | self.e(r"if 1 'hi' else 'bye'", '"hi"') 200 | 201 | # Trap expressions that return no value, like `var` 202 | 203 | def test_nonyielding_expression_trap(self): 204 | self.ex(AkiSyntaxErr, r"if {var x:i32=1} 2 else 3") 205 | self.ex(AkiSyntaxErr, r"{var x:i32=1}==1") 206 | 207 | def test_function_default_trap(self): 208 | self.ex(AkiSyntaxErr, r"def x1(x={var x:i32=1 x}){}") 209 | 210 | def test_ref_deref(self): 211 | # This first test ensures the original `x` is not clobbered 212 | self.e(r"var x=32 var y=ref(x) var z=deref(y) x", 32) 213 | self.e(r"var x=32 var y=ref(x) var z=deref(y) z", 32) 214 | self.e( 215 | r"var x=32 var y=ref(x) var q=ref(y) var t=deref(q) var z=deref(t) z", 32 216 | ) 217 | self.e( 218 | r"var x=32 var y=ref(x) var q=ref(y) var t=deref(q) var z=deref(t) x", 32 219 | ) 220 | self.e(r"def g1(){32} var y=ref(g1) var z=deref(y) z()", 32) 221 | self.e(r"def g1(){32} var x=g1 var y=ref(x) var z=deref(y) z()", 32) 222 | self.e(r"def g1(){32} var x=g1 var y=ref(x) var z=deref(y) x()", 32) 223 | self.e(r"def g1(){32} var x=ref(g1) var y=deref(x) y()", 32) 224 | self.e( 225 | r"def g1(){32} var x=g1 var y=ref(x) var q=ref(y) var t=deref(q) var z=deref(t) z()", 226 | 32, 227 | ) 228 | self.e( 229 | r"def g1(){32} var x=g1 var y=ref(x) var q=ref(y) var t=deref(q) var z=deref(t) x()", 230 | 32, 231 | ) 232 | self.e(r"def b1(x:ptr i32){x} var q=ref(b1)", 0) 233 | self.e(r"var x:array i32[10] x[1]=10 var y=ref(x[1]) deref(y)", 10) 234 | 235 | # `y()` is a pointer, not a callable 236 | self.ex(AkiTypeErr, r"def g1(){32} var x=g1 var y=ref(x) var z=deref(y) y()") 237 | # you can't `ref` or `deref` anything not a variable 238 | self.ex(AkiTypeErr, r"ref(32)") 239 | self.ex(AkiTypeErr, r"deref(32)") 240 | # `x` is not a reference 241 | self.ex(AkiTypeErr, r"var x=1 deref(x)") 242 | 243 | def test_cast(self): 244 | # truncation 245 | self.e(r"unsafe cast(0x000000ff,u8)", 255) 246 | self.e(r"type(unsafe cast(0x000000ff,u8))=={var x:u8 type(x)}", True) 247 | # zero extend 248 | self.e(r"unsafe cast(0xff,u64)", 255) 249 | self.e(r"type(unsafe cast(0xff,u64))=={var x:u64 type(x)}", True) 250 | # signed to unsigned 251 | self.e(r"unsafe cast(0hff,i8)", -1) 252 | self.e(r"type(unsafe cast(0hff,i8))=={var x:i8 type(x)}", True) 253 | # int to ptr 254 | self.e(r"var x:u_size var y=unsafe cast(x, ptr u_mem) type(y)", "") 255 | # ptr to int 256 | self.e(r"var x:ptr u_mem var y=unsafe cast(x, u_size) type(y)", "") 257 | 258 | def test_incorrect_casting(self): 259 | self.ex(AkiTypeErr, r"var x:ptr u_mem var y=unsafe cast(x, i32) type(y)") 260 | self.ex(AkiTypeErr, r"var x:i32 var y=unsafe cast(x, ptr u_mem) type(y)") 261 | self.ex(AkiTypeErr, r"unsafe cast(32,str)") 262 | self.ex(AkiTypeErr, r'unsafe cast("Hello",i32)') 263 | 264 | def test_unsafe_trapping(self): 265 | # `cast` operations are unsafe 266 | self.ex(AkiSyntaxErr, r"cast(2,f64)") 267 | 268 | def test_array(self): 269 | self.e( 270 | r"var x:array i32[2,2] x[0,0]=32 x[0,1]=12 x[1,0]=8 x[0,1]+x[0,0]+x[1,0]", 271 | 52, 272 | ) 273 | self.e(r"var x:array str[20] x[1]='Hi' x[1]", '"Hi"') 274 | self.e( 275 | r"def a(){30} def b(){64} var x:array func():i32[2,2] x[1,2]=a x[2,1]=b var c=x[1,2] var d=x[2,1] d()-c()", 276 | 34, 277 | ) 278 | 279 | # NOT FUNCTIONAL YET. We don't have a repr for arrays 280 | 281 | # self.e(r"var x: array i32[20] x", None) 282 | 283 | # We also don't yet have a direct array assignment mechanism 284 | 285 | # self.e(r"var x: array i32[2]=[8,16] x", None) 286 | 287 | def test_array_trapping(self): 288 | self.ex(AkiTypeErr, r"var x:array i32[20]=0") 289 | self.ex(AkiTypeErr, r"var x:array i32[20]=0") 290 | 291 | def test_pointer_comparison(self): 292 | self.e(r"def a1(x:ptr i32){x} var x=32 var y=ref(x) var z=a1(y) z==y", True) 293 | 294 | def test_autoset_return_type(self): 295 | self.e(r"def a(){32} type(a())==i32", True) 296 | self.e(r"def a(){32} type(a)==func():i32", True) 297 | 298 | def test_c_size(self): 299 | self.e(r"c_size('Hello there')", 12) 300 | self.e(r"c_size(1)", 4) 301 | self.e(r"c_size(1:u64)", 8) 302 | 303 | def test_size(self): 304 | self.e(r"size('Hello there')", 8) 305 | self.e(r"size(1)", 4) 306 | self.e(r"size(1:u64)", 8) 307 | 308 | def test_while(self): 309 | self.e(r"var x=1,q=20 while x<20 {x+=1} q+x", 40) 310 | self.e(r"var x=1 while x<20 {x+=1 if x==10 break} x", 10) 311 | 312 | def test_const(self): 313 | self.e(r"const {x=1} x", 1) 314 | self.e(r"const {x='hi'} x", '"hi"') 315 | self.ex(AkiTypeErr, r"const {x=1} x=2") 316 | 317 | def test_decorator(self): 318 | self.e(r"@noinline def m1(){32} m1()", 32) 319 | self.e(r"@inline def m1(){32} m1()", 32) 320 | self.ex(AkiSyntaxErr, r"@bogus def m1(){32} m1()") 321 | 322 | def test_return(self): 323 | self.e(r"def m1(){return 32} m1()",32) 324 | self.e(r"def m1():u64{return 32:u64} m1()",32) 325 | self.e(r"def m1(x){if x==1 return 32; 64} m1(1)",32) 326 | self.e(r"def m1(x){if x==1 return 32; 64} m1(0)",64) 327 | self.e(r"def m1(x){if x==1 return 32 else return 64} m1(1)",32) 328 | self.e(r"def m1(x){if x==1 return 32 else return 64} m1(0)",64) 329 | self.ex(AkiTypeErr, r"def m1():u64{return 32} m1()") 330 | 331 | def test_select(self): 332 | p0 = r""" 333 | def main(){ 334 | var x="Hello" 335 | var y:i64 336 | select type(x) { 337 | case i32{ 338 | y=1:i64 339 | } 340 | case type(y){ 341 | y=2:i64 342 | } 343 | default { 344 | y=3:i64 345 | } 346 | } 347 | y 348 | } 349 | main()""" 350 | p1 = r""" 351 | def main(){ 352 | var x=2 353 | var y:i64 354 | select type(x) { 355 | case i32{ 356 | y=1:i64 357 | } 358 | case type(y){ 359 | y=2:i64 360 | } 361 | } 362 | y 363 | } 364 | main()""" 365 | p2 = r""" 366 | def main(){ 367 | var x=2 368 | var y:i64 369 | select x { 370 | case type(y){ 371 | y=2:i64 372 | } 373 | } 374 | y 375 | } 376 | main()""" 377 | 378 | p3 = r""" 379 | def main(){ 380 | var x=2, y=0 381 | select x { 382 | case 3 383 | y=0 384 | case 4 385 | y=1 386 | default 387 | y=2 388 | default 389 | y=3 390 | } 391 | } 392 | """ 393 | self.e(p0, 3) 394 | self.e(p1, 1) 395 | self.ex(AkiTypeErr, p2) 396 | self.ex(AkiSyntaxErr, p3) 397 | 398 | -------------------------------------------------------------------------------- /language.md: -------------------------------------------------------------------------------- 1 | - [Aki language basics](#aki-language-basics) 2 | - [Introduction](#introduction) 3 | - [Expressions](#expressions) 4 | - [Functions and function calls](#functions-and-function-calls) 5 | - [Variables and variable typing](#variables-and-variable-typing) 6 | - [Symbols](#symbols) 7 | - [Operators](#operators) 8 | - [`=` Assignment](#-assignment) 9 | - [`==` Equality test](#-equality-test) 10 | - [`!=` Negative equality test](#-negative-equality-test) 11 | - [`>`/`>=` Greater than / or equal to test](#-greater-than--or-equal-to-test) 12 | - [`<`/`<=` Less than / or equal to test](#-less-than--or-equal-to-test) 13 | - [`+` Addition operator](#-addition-operator) 14 | - [`-` Subtraction operator](#--subtraction-operator) 15 | - [`*` Multiplication operator](#-multiplication-operator) 16 | - [`/` Division operator](#-division-operator) 17 | - [`and`/`or`/`xor`/`not` operators](#andorxornot-operators) 18 | - [Parentheses `()`](#parentheses-) 19 | - [Curly braces `{}`](#curly-braces-) 20 | - [Hash symbol `#`](#hash-symbol-) 21 | - [Decorator symbol `@`](#decorator-symbol-) 22 | - [Top-level keywords](#top-level-keywords) 23 | - [`const`](#const) 24 | - [`def`](#def) 25 | - [`extern`](#extern) 26 | - [`uni`](#uni) 27 | - [Keywords](#keywords) 28 | - [`break`](#break) 29 | - [`default`](#default) 30 | - [`if` / `else`](#if--else) 31 | - [`loop`](#loop) 32 | - [`not`](#not) 33 | - [`return`](#return) 34 | - [`select`/`case`](#selectcase) 35 | - [`unsafe`](#unsafe) 36 | - [`var`](#var) 37 | - [`while`](#while) 38 | - [`with`](#with) 39 | - [`when`](#when) 40 | - [Types:](#types) 41 | - [`bool (u1)`](#bool-u1) 42 | - [`byte (u8)`](#byte-u8) 43 | - [`i8/32/64`](#i83264) 44 | - [`u8/32/64`](#u83264) 45 | - [`f32/64`](#f3264) 46 | - [`array`](#array) 47 | - [`str`](#str) 48 | 49 | # Aki language basics 50 | 51 | This is a document of Aki syntax and usage. 52 | 53 | > ⚠ This document is incomplete and is being revised continuously. 54 | 55 | # Introduction 56 | 57 | ## Expressions 58 | 59 | Expressions are the basic unit of computation in Aki. Each expression returns a value of a specific type. Every statement is itself an expression: 60 | 61 | ``` 62 | 5 63 | ``` 64 | 65 | is an expression that returns the value `5`. (The type of this value is the default type for numerical values in Aki, which is a signed 32-bit integer.) 66 | 67 | ``` 68 | print (if x==1 'Yes' else 'No') 69 | ``` 70 | 71 | Here, the argument to `print` is an expression that returns one of two compile-time strings depending on the variable `x`. (`print` itself is a wrapper for `printf` and so returns the number of bytes printed.) 72 | 73 | The divisions between expressions are normally deduced automatically by the compiler. You can use parentheses to explicitly set expressions apart: 74 | 75 | ``` 76 | (x+y)/(a+b) 77 | ``` 78 | 79 | The whole of the above is also considered a single expression. 80 | 81 | You can also use a semicolon to separate expressions: 82 | 83 | ``` 84 | x+y; a+b; 85 | ``` 86 | 87 | Function bodies are considered expressions: 88 | 89 | ``` 90 | def myfunc() 5 91 | ``` 92 | 93 | This defines a function, `myfunc`, which takes no arguments, and returns `5` when invoked by `myfunc()` elsewhere in the code. 94 | 95 | To group together one or more expressions procedurally, for instance as a clause in an expression or in the body of a function, use curly braces to form a *block*: 96 | 97 | ``` 98 | def main(){ 99 | print ("Hello!") 100 | var x=invoke() 101 | x+=1 102 | x 103 | } 104 | ``` 105 | 106 | With any expression block, the last expression is the one returned from the block. To that end, the `x` as the last line of the function body here works as an *implicit return.* You could also say: 107 | 108 | ``` 109 | def main(){ 110 | print ("Hello!") 111 | var x=invoke() 112 | x+=1 113 | return x 114 | } 115 | ``` 116 | 117 | For the most part, Aki does not care about where you place linebreaks, and is insensitive to indentation. Expressions and strings can span multiple lines. Again, if you want to forcibly separate lines into expressions, you can use semicolons. 118 | 119 | However, comments (anything starting with a `#`) always end with a linebreak. 120 | 121 | ## Functions and function calls 122 | 123 | Use the `def` top-level keyword to define a function: 124 | 125 | ``` 126 | def f1(x:i32):i32 x 127 | ``` 128 | 129 | Function definitions need to have: 130 | 131 | * a name that does not shadow any existing name or keyword (except for functions with varying type signatures, where the same name can be reused, or where a bare prototype is redefined with the same type signature and an actual function body) 132 | * zero or more explicitly-typed arguments 133 | * a return type 134 | * and a function body. 135 | 136 | In the above example: 137 | * the name is `f1` 138 | * the single argument is `x`, with an explicit type of `i32` 139 | * the return type is `i32` 140 | * and the body is simply the expression `x`. 141 | 142 | A more complex function body example: 143 | 144 | ``` 145 | def f1(x:i32):i32 { 146 | var x = (if x>0 1 else -1) 147 | x * 5 148 | } 149 | ``` 150 | 151 | Functions can also take optional arguments with defaults: 152 | 153 | ``` 154 | def f1(x:i32, y:i32=1) x+y 155 | ``` 156 | 157 | Invoking this with `f1(0,32)` would return `32`. With `f1(1)`, you'd get `2`. 158 | 159 | Note that optional arguments must always follow mandatory arguments. 160 | 161 | 162 | ## Variables and variable typing 163 | 164 | There is no shadowing of variable names permitted anywhere. You cannot have the same name for a variable in both the universal and current scope. 165 | 166 | Scalar types -- integers, floats, booleans -- are passed by value. All other objects (classes, strings, etc.) are automatically passed by reference, by way of a pointer to the object. 167 | 168 | # Symbols 169 | 170 | ## Operators 171 | 172 | The following operator symbols are predefined: 173 | 174 | ### `=` Assignment 175 | 176 | `var x:i32=5` 177 | 178 | ### `==` Equality test 179 | 180 | `if x==5 then print("OK") else print ("No)` 181 | 182 | ### `!=` Negative equality test 183 | 184 | `if x!=5 then print("No") else print ("OK")` 185 | 186 | ### `>`/`>=` Greater than / or equal to test 187 | 188 | `if x>=5 then print ("OK") else print ("No")` 189 | 190 | ### `<`/`<=` Less than / or equal to test 191 | 192 | `if x<=5 then print ("OK") else print ("No")` 193 | 194 | ### `+` Addition operator 195 | 196 | `x=x+1` 197 | 198 | ### `-` Subtraction operator 199 | 200 | `x=x-1` 201 | 202 | ### `*` Multiplication operator 203 | 204 | `x=x*5` 205 | 206 | ### `/` Division operator 207 | 208 | `x=x/2` 209 | 210 | ### `and`/`or`/`xor`/`not` operators 211 | 212 | Logical `and`, `or`, `xor`, and `not`. 213 | 214 | ## Parentheses `()` 215 | 216 | Parentheses are used to set aside clauses in expressions, and to identify the arguments for a function. 217 | 218 | ## Curly braces `{}` 219 | 220 | Curly braces are used to set aside expression blocks. 221 | 222 | ## Hash symbol `#` 223 | 224 | The hash symbol is a comment to the end of a line. (This is one of the few cases where linebreaks are honored as syntax.) 225 | 226 | ``` 227 | def main(){ 228 | # This is a comment. 229 | do_something() # Comment after statement. 230 | } 231 | ``` 232 | 233 | There is no block comment syntax. However, an inline string can span multiple lines, and is discarded at compile time if it isn't assigned to anything. This can be used for multiline comments. 234 | 235 | ``` 236 | def main(){ 237 | 'This is a multiline 238 | string that could be a comment.' 239 | do_something() 240 | } 241 | ``` 242 | 243 | ## Decorator symbol `@` 244 | 245 | The `@` symbol is used to indicate a [decorator](#decorators). 246 | 247 | 248 | # Top-level keywords 249 | 250 | 251 | "Top-level" keywords can only appear as the first level of keywords encountered by the compiler in a module. E.g., you can have a `def` as a top-level keyword, but you cannot enclose another `def` or a `const` block inside a `def`. (At least, not yet!) 252 | 253 | ## `const` 254 | 255 | A `const` block is used to define compile-time constants for a module. 256 | 257 | 258 | ``` 259 | const { 260 | x=10, 261 | y=20 262 | } 263 | 264 | def main(){ 265 | print (x+5) # x becomes 10 at compile time, so this is always 15 266 | } 267 | ``` 268 | 269 | There can be more than one `const` block per module. 270 | 271 | It's also possible for constants to be defined at compile time based on previous constants: 272 | 273 | ``` 274 | const { 275 | WIDTH=80, 276 | HEIGHT=32, 277 | FIELD_SIZE = (WIDTH+1)*(HEIGHT+1) 278 | DIVIDER_WIDTH = WIDTH+1 279 | } 280 | ``` 281 | 282 | In this example, `FIELD_SIZE` would be defined as `2673`, and `DIVIDER_WIDTH` would be `81`. 283 | 284 | ## `def` 285 | 286 | Define a function signature and its body. 287 | 288 | ``` 289 | def add(a,b){ 290 | return a+b 291 | } 292 | 293 | ``` 294 | 295 | The default type for function signatures and return types, as with variables, is `i32`. 296 | 297 | Function signatures can also take explicitly specified types and a return type: 298 | 299 | ``` 300 | def add(a:u64, b:u64):u64{ 301 | return a+b 302 | } 303 | ``` 304 | 305 | ## `extern` 306 | 307 | Defines an external function with a C calling interface to be linked in at compile time. 308 | 309 | An example, on Win32, that uses the `MessageBoxA` system call: 310 | 311 | ``` 312 | extern MessageBoxA(hwnd:i32, msg:ptr i8, caption:ptr i8, msg_type: i8):i32 313 | 314 | def main(){ 315 | MessageBoxA(0,c_data('Hi'),c_data('Yo there'),0:byte) 316 | } 317 | 318 | ``` 319 | 320 | ## `uni` 321 | 322 | A `uni` block defines *universals*, or variables available throughout a module. The syntax is the same as a `var` assignment. 323 | 324 | There can be more than one `uni` block per module, typically after any `const` module. This rule is not enforced by the compiler, but it's a good idea, since `uni` declarations may depend on previous `const` declarations. 325 | 326 | ``` 327 | uni { 328 | x=32, 329 | # defines an i32, the default variable type 330 | 331 | y=64:u64, 332 | # unsigned 64-bit integer 333 | 334 | z:byte=1B 335 | # explicitly defined byte variable, 336 | # set to an explicitly defined byte value 337 | 338 | } 339 | ``` 340 | 341 | # Keywords 342 | 343 | These keywords are valid within the body of a function. 344 | 345 | ## `break` 346 | 347 | Exit a `loop` manually. 348 | 349 | ``` 350 | x=1 351 | loop { 352 | x+=1 353 | if x>10 break 354 | } 355 | x 356 | 357 | [output:] 358 | 359 | 11 360 | ``` 361 | 362 | ## `default` 363 | 364 | See [`match`](#match). 365 | 366 | ## `if` / `else` 367 | 368 | If a given expression yields `True`, then yield the value of one expression; if `False`, yield the value of another. Each `then` clause is an expression. 369 | 370 | ``` 371 | var y = 0 372 | var t = {if y == 1 2 else 3} 373 | 374 | ``` 375 | 376 | **Each branch of an `if` must yield the same type.** For operations where the types of each decision might mismatch, or where some possible decisions might not yield a result at all, use [`when/then/else`](#when). 377 | 378 | `if` constructions can also be used for expressions where the value of the expression is to be discarded. 379 | 380 | ``` 381 | # FizzBuzz 382 | 383 | def main(){ 384 | # Loops auto-increment by 1 if no incrementor is specified 385 | loop (var x = 1, x < 101) { 386 | if x % 15 == 0 print ("FizzBuzz") 387 | else if x % 3 == 0 print ("Fizz") 388 | else if x % 5 == 0 print ("Buzz") 389 | else printf_s(c_data('%i\n'),x) 390 | } 391 | 0 392 | } 393 | ``` 394 | 395 | Note that if we didn't have the `return 0` at the bottom of `main`, the last value yielded by the `if` would be the value returned from `main`. 396 | 397 | 398 | ## `loop` 399 | 400 | Defines a loop operation. The default is a loop that is infinite and needs to be exited manually with a `break`. 401 | 402 | ``` 403 | x=1 404 | loop { 405 | x = x + 1 406 | if x>10: break 407 | } 408 | ``` 409 | 410 | A loop can also specify a counter variable and a while-true condition: 411 | 412 | ``` 413 | loop (x = 1, x < 11){ 414 | ... 415 | } 416 | ``` 417 | 418 | The default incrementor for a loop is +1, but you can make the increment operation any valid operator for that variable. 419 | 420 | ``` 421 | loop (x = 100, x > 0, x - 5) { 422 | ... 423 | } 424 | ``` 425 | 426 | If the loop variable is already defined in the current or universal scope, it is re-used. If it doesn't exist, it will be created and added to the current scope, and will continue to be available in the current scope after the loop exits. 427 | 428 | If you want to constrain the use of the loop variable to only the loop, use `with`: 429 | 430 | ``` 431 | with x loop (x = 1, x < 11) { 432 | ... 433 | } # x is not valid outside of this block 434 | ``` 435 | 436 | ## `not` 437 | 438 | A built-in unary for negating values. 439 | 440 | ``` 441 | x = 1 442 | y = not x # 0 443 | ``` 444 | 445 | ## `return` 446 | 447 | Exits from a function early and returns a value. 448 | 449 | ``` 450 | def f1(x):str { 451 | if x == 1 return "Yes" 452 | # else ... 453 | "No" 454 | } 455 | ``` 456 | 457 | Note that the return value's type must match the function's overall type, and that all returns must have the same type. This is not permitted: 458 | 459 | ``` 460 | def f1(x) { 461 | if x == 1 return "Yes" 462 | # else ... 463 | 5 464 | } 465 | ``` 466 | 467 | This, however, is okay. Note how the function has no explicit return type, but all the returned values have matching types. 468 | 469 | ``` 470 | def f1(x) { 471 | if x == 1 return "Yes" 472 | # else ... 473 | "No" 474 | } 475 | ``` 476 | 477 | 478 | ## `select`/`case` 479 | 480 | Evaluates an expression based on whether or not a value is equal to one of a given set of compile-time constants (*not* expressions). 481 | 482 | The value returned from this expression is the selected value, *not any value returned by the expressions themselves.* 483 | 484 | There is no "fall-through" between cases 485 | 486 | The `default` keyword indicates which expression to use if no other match can be found. 487 | 488 | ``` 489 | select t { 490 | case 0: break 491 | case 1: { 492 | t+=1 493 | s=1 494 | } 495 | case 2: { 496 | t=0 497 | s=0 498 | } 499 | default: t-=1 500 | } 501 | ``` 502 | 503 | ## `unsafe` 504 | 505 | Designates an expression or block where direct manipulation of memory or some other potentially dangerous action is performed. 506 | 507 | This is not widely used yet. 508 | 509 | 510 | ## `var` 511 | 512 | Defines a variable for use within the scope of a function. 513 | 514 | ``` 515 | def main(){ 516 | var x = 1, y = 2 517 | # implicit i32 variable and value 518 | # multiple variables are separated by commas 519 | 520 | var y = 2:byte 521 | # explicitly defined value: byte 2 522 | # variable type is set automatically by value type 523 | 524 | var z:u64 = 4:u64 525 | # explicitly defined variable: unsigned 64 526 | # with an explicitly defined value assigned to it 527 | } 528 | ``` 529 | 530 | For a variable that only is valid within a specific scope in a function, use `with`. 531 | 532 | ## `while` 533 | 534 | Defines a loop condition that continues as long as a given condition is true. 535 | 536 | ``` 537 | var x=0 538 | while x<100 { 539 | x+=1 540 | } 541 | ``` 542 | 543 | ## `with` 544 | 545 | Provides a context, or closure, for variable assignments. 546 | 547 | ``` 548 | y=1 # y is valid from here on down 549 | 550 | with var x = 32 { 551 | y+x 552 | # but use of x is only valid in this block 553 | } 554 | ``` 555 | 556 | As with variables generally, a variable name in a `with` block cannot "shadow" one outside. 557 | 558 | ``` 559 | y=1 560 | with var y = 2 { # this is invalid 561 | ... 562 | } 563 | ``` 564 | 565 | ## `when` 566 | 567 | If a given expression yields `True`, then use the value of one expression; if `False`, use the value of another. 568 | 569 | Differs from `if/then/else` in that the `else` clause is optional, and that the value yielded is that of the *deciding expression*, not the `then/else` expressions. This way, the values of the `then/else` expressions can be of entirely different types if needed. 570 | 571 | ``` 572 | when x=1 do_something() # this function returns an u64 573 | else if x=2 do_something_else() # this function returns an i32 574 | else do_yet_another_thing() # this function returns an i8 575 | ``` 576 | 577 | In all cases the above expression would return the value of whatever `x` was, not the value of any of the called functions. 578 | 579 | # Types: 580 | 581 | ## `bool (u1)` 582 | 583 | An unsigned true or false value. 584 | 585 | Constant representation of 1: `1:bool` 586 | 587 | ## `byte (u8)` 588 | 589 | An unsigned byte. 590 | 591 | Constant representation of 1: `1:byte` 592 | 593 | ## `i8/32/64` 594 | 595 | Signed integers of 8, 32, or 64 bit widths. 596 | 597 | Constant representation of 1: `1:i8, 1:i32, 1:i64` 598 | 599 | The default variable type is a 32-bit signed integer (`1:i32`). 600 | 601 | ## `u8/32/64` 602 | 603 | Unsigned integers of 8, 32, or 64 bit widths. 604 | 605 | Constant representation of 1: `1:u8, 1:u32, 1:u64` 606 | 607 | ## `f32/64` 608 | 609 | Floats of 32 or 64 bit widths: `3.2:f32`/ `3.2:f64`. 610 | 611 | Constant representation of 1: `1.` or `1.0`. 612 | 613 | ## `array` 614 | 615 | An array of scalar (integer or float) types. 616 | 617 | For a one-dimensional array of bytes: 618 | 619 | `var x:array byte[100]` 620 | 621 | For a multidimensional array of bytes: 622 | 623 | `var x:array byte[32,32]` 624 | 625 | > ⚠ There is as yet no way to define array members on creation. They have to be assigned individually. 626 | 627 | > ⚠ There is as yet no way to nest different scalars in different array dimensions. 628 | 629 | > ⚠ There is as yet no way to perform array slicing or concatenation. 630 | 631 | ## `str` 632 | 633 | A string of characters, defined either at compile time or runtime. 634 | 635 | ``` 636 | hello = "Hello World!" 637 | hello_also = 'Hello world!' 638 | hello_again = 'Hello 639 | world!' 640 | ``` 641 | 642 | Linebreaks inside strings are permitted. Single or double quotes can be used as long as they match. 643 | 644 | String escaping functions are not yet robustly defined, but you can use `\n` in a string for a newline, and you can escape quotes as needed with a backslash as well: 645 | 646 | ``` 647 | hello = "Hello \"world\"! \n" 648 | ``` 649 | 650 | > ⚠ There is as yet no way to perform string slicing or concantenation. -------------------------------------------------------------------------------- /aki/core/astree.py: -------------------------------------------------------------------------------- 1 | from core.error import AkiSyntaxErr 2 | from llvmlite import ir 3 | from typing import Optional 4 | 5 | 6 | class ASTNode: 7 | """ 8 | Base type for all AST nodes, with helper functions. 9 | """ 10 | 11 | def __init__(self, index): 12 | self.child = None 13 | self.index = index 14 | 15 | def __eq__(self, other): 16 | raise NotImplementedError 17 | 18 | def flatten(self): 19 | return [self.__class__.__name__, "flatten unimplemented"] 20 | 21 | 22 | class Expression(ASTNode): 23 | """ 24 | Base type for all expressions. 25 | """ 26 | 27 | pass 28 | 29 | 30 | class Keyword(ASTNode): 31 | """ 32 | Base type for keywords. 33 | """ 34 | 35 | pass 36 | 37 | 38 | class TopLevel(ASTNode): 39 | """ 40 | Mixin type for top-level AST nodes. 41 | """ 42 | 43 | pass 44 | 45 | 46 | class VarTypeNode(Expression): 47 | name: Optional[str] = None 48 | 49 | 50 | class VarTypeName(VarTypeNode): 51 | def __init__(self, p, name: str): 52 | super().__init__(p) 53 | self.name = name 54 | 55 | def __eq__(self, other): 56 | return self.name == other.name 57 | 58 | def flatten(self): 59 | return [self.__class__.__name__, self.name] 60 | 61 | 62 | class VarTypePtr(VarTypeNode): 63 | def __init__(self, p, pointee: VarTypeNode): 64 | super().__init__(p) 65 | self.pointee = pointee 66 | self.name = f"ptr {pointee.name}" 67 | 68 | def __eq__(self, other): 69 | return self.pointee == other.pointee 70 | 71 | def flatten(self): 72 | return [self.__class__.__name__, self.pointee.flatten()] 73 | 74 | 75 | class VarTypeFunc(VarTypeNode): 76 | def __init__(self, p, arguments, return_type: VarTypeNode): 77 | super().__init__(p) 78 | self.arguments = arguments 79 | self.return_type = return_type 80 | 81 | def __eq__(self, other): 82 | return ( 83 | self.arguments == other.arguments and self.return_type == other.return_type 84 | ) 85 | 86 | def flatten(self): 87 | return [ 88 | self.__class__.__name__, 89 | self.arguments.flatten() if self.arguments else [], 90 | self.return_type.flatten() if self.return_type else None, 91 | ] 92 | 93 | 94 | class VarTypeAccessor(VarTypeNode): 95 | def __init__(self, p, vartype: VarTypeNode, accessors: list): 96 | super().__init__(p) 97 | self.vartype = vartype 98 | self.accessors = accessors 99 | # self.name = f"array({vartype.name})[{','.join([str(_[0]) for _ in accessors.accessors])}]" 100 | 101 | def __eq__(self, other): 102 | return self.vartype == other.vartype and self.accessors == other.accessors 103 | 104 | def flatten(self): 105 | return [ 106 | self.__class__.__name__, 107 | self.vartype.flatten(), 108 | self.accessors.flatten() if self.accessors else [], 109 | ] 110 | 111 | 112 | class Name(Expression): 113 | """ 114 | Variable reference. 115 | """ 116 | 117 | def __init__(self, p, name, val=None, vartype=None): 118 | super().__init__(p) 119 | self.name = name 120 | self.val = val 121 | # `val` is only used in variable assignment form 122 | self.vartype = vartype 123 | 124 | def __eq__(self, other): 125 | return self.name == other.name 126 | 127 | def flatten(self): 128 | return [ 129 | self.__class__.__name__, 130 | self.name, 131 | self.val.flatten() if self.val else None, 132 | self.vartype.flatten() if self.vartype else None, 133 | ] 134 | 135 | 136 | class VarList(Expression): 137 | """ 138 | `var` declaration with one or more variables. 139 | """ 140 | 141 | def __init__(self, p, vars): 142 | super().__init__(p) 143 | self.vars = vars 144 | 145 | def __eq__(self, other): 146 | return self.vars == other.vars 147 | 148 | def flatten(self): 149 | return [ 150 | self.__class__.__name__, 151 | [_.flatten() for _ in self.vars] if self.vars else [], 152 | ] 153 | 154 | 155 | class UniList(TopLevel, VarList): 156 | pass 157 | 158 | 159 | class ConstList(TopLevel, VarList): 160 | pass 161 | 162 | 163 | class Argument(ASTNode): 164 | """ 165 | Function argument, with optional type declaration. 166 | """ 167 | 168 | def __init__(self, p, name, vartype=None, default_value=None): 169 | super().__init__(p) 170 | self.name = name 171 | self.vartype = vartype 172 | self.default_value = default_value 173 | 174 | def __eq__(self, other): 175 | return self.name == other.name and self.vartype == other.vartype 176 | 177 | def flatten(self): 178 | return [ 179 | self.__class__.__name__, 180 | self.name, 181 | self.vartype.flatten() if self.vartype else None, 182 | self.default_value.flatten() if self.default_value else None, 183 | ] 184 | 185 | 186 | class StarArgument(Argument): 187 | pass 188 | 189 | 190 | class Constant(Expression): 191 | """ 192 | LLVM constant value. 193 | """ 194 | 195 | def __init__(self, p, val, vartype): 196 | super().__init__(p) 197 | self.val = val 198 | self.vartype = vartype 199 | 200 | def __eq__(self, other): 201 | return self.val == other.val and self.vartype == other.vartype 202 | 203 | def flatten(self): 204 | return [self.__class__.__name__, self.val, self.vartype.flatten() if self.vartype is not None else None] 205 | 206 | 207 | class String(Expression): 208 | """ 209 | String constant. 210 | """ 211 | 212 | def __init__(self, p, val, vartype): 213 | super().__init__(p) 214 | self.val = val 215 | self.vartype = vartype 216 | self.name = f'"{val}"' 217 | 218 | def __eq__(self, other): 219 | return self.val == other.val 220 | 221 | def flatten(self): 222 | return [self.__class__.__name__, self.val] 223 | 224 | 225 | class UnOp(Expression): 226 | """ 227 | Unary operator expression. 228 | """ 229 | 230 | def __init__(self, p, op, lhs): 231 | super().__init__(p) 232 | self.op = op 233 | self.lhs = lhs 234 | 235 | def __eq__(self, other): 236 | return self.op == other.op and self.lhs == other.lhs 237 | 238 | def flatten(self): 239 | return [self.__class__.__name__, self.op, self.lhs.flatten()] 240 | 241 | 242 | class RefExpr(Expression): 243 | """ 244 | Reference expression (obtaining a pointer to an object) 245 | """ 246 | 247 | def __init__(self, p, ref): 248 | super().__init__(p) 249 | self.ref = ref 250 | 251 | def __eq__(self, other): 252 | return self.ref == other.ref 253 | 254 | def flatten(self): 255 | return [self.__class__.__name__, self.ref.flatten()] 256 | 257 | 258 | class DerefExpr(RefExpr): 259 | pass 260 | 261 | 262 | class BinOp(Expression): 263 | """ 264 | Binary operator expression. 265 | """ 266 | 267 | def __init__(self, p, op, lhs, rhs): 268 | super().__init__(p) 269 | self.op = op 270 | self.lhs = lhs 271 | self.rhs = rhs 272 | 273 | def __eq__(self, other): 274 | return self.op == other.op and self.lhs == other.lhs and self.rhs == other.rhs 275 | 276 | def flatten(self): 277 | return [ 278 | self.__class__.__name__, 279 | self.op, 280 | self.lhs.flatten(), 281 | self.rhs.flatten(), 282 | ] 283 | 284 | 285 | class Assignment(BinOp): 286 | pass 287 | 288 | 289 | class BinOpComparison(BinOp): 290 | pass 291 | 292 | 293 | class IfExpr(ASTNode): 294 | def __init__(self, p, if_expr, then_expr, else_expr=None): 295 | super().__init__(p) 296 | self.if_expr = if_expr 297 | self.then_expr = then_expr 298 | self.else_expr = else_expr 299 | 300 | def __eq__(self, other): 301 | return ( 302 | self.if_expr == other.if_expr 303 | and self.then_expr == other.then_expr 304 | and self.else_expr == other.else_expr 305 | ) 306 | 307 | def flatten(self): 308 | return [ 309 | self.__class__.__name__, 310 | self.if_expr.flatten(), 311 | self.then_expr.flatten(), 312 | self.else_expr.flatten() if self.else_expr else None, 313 | ] 314 | 315 | 316 | class WhenExpr(IfExpr): 317 | pass 318 | 319 | class Return(ASTNode): 320 | def __init__(self, p, return_val): 321 | super().__init__(p) 322 | self.return_val = return_val 323 | 324 | def __eq__(self, other): 325 | return ( 326 | self.return_val == other.return_val 327 | ) 328 | 329 | def flatten(self): 330 | return [ 331 | self.__class__.__name__, 332 | self.return_val.flatten() 333 | ] 334 | 335 | class Prototype(ASTNode): 336 | """ 337 | Function prototype. 338 | """ 339 | 340 | def __init__( 341 | self, 342 | p, 343 | name: str, 344 | arguments: list, 345 | return_type: VarTypeNode, 346 | is_declaration=False, 347 | ): 348 | super().__init__(p) 349 | self.name = name 350 | self.arguments = arguments 351 | self.return_type = return_type 352 | self.is_declaration = is_declaration 353 | 354 | def __eq__(self, other): 355 | return ( 356 | self.arguments == other.arguments and self.return_type == other.return_type 357 | ) 358 | 359 | def flatten(self): 360 | return [ 361 | self.__class__.__name__, 362 | self.name, 363 | [_.flatten() for _ in self.arguments] if self.arguments else [], 364 | self.return_type.flatten() if self.return_type else None, 365 | ] 366 | 367 | 368 | class Function(TopLevel, ASTNode): 369 | """ 370 | Function body. 371 | """ 372 | 373 | def __init__(self, p, prototype, body): 374 | super().__init__(p) 375 | self.prototype = prototype 376 | self.body = body 377 | 378 | def __eq__(self, other): 379 | return self.prototype == other.prototype and self.body == other.body 380 | 381 | def flatten(self): 382 | return [ 383 | self.__class__.__name__, 384 | self.prototype.flatten(), 385 | [_.flatten() for _ in self.body], 386 | ] 387 | 388 | 389 | class External(Function): 390 | pass 391 | 392 | 393 | class Call(Expression, Prototype): 394 | """ 395 | Function call. 396 | Re-uses Prototype since it has the same basic structure. 397 | Arguments contains a list of Expression-class ASTs. 398 | """ 399 | 400 | pass 401 | 402 | 403 | class ExpressionBlock(Expression): 404 | """ 405 | {}-delimeted set of expressions, stored as a list in `body`. 406 | """ 407 | 408 | def __init__(self, p, body): 409 | super().__init__(p) 410 | self.body = body 411 | 412 | def __eq__(self, other): 413 | return self.body == other.body 414 | 415 | def flatten(self): 416 | return [self.__class__.__name__, [_.flatten() for _ in self.body]] 417 | 418 | 419 | class LLVMNode(Expression): 420 | """ 421 | Repackages an LLVM op as if it were an unprocessed AST node. 422 | You should use this if: 423 | a) you have an op that has been produced by a previous codegen action 424 | b) you're going to codegen a synthetic AST node using that result as a parameter 425 | """ 426 | 427 | def __init__(self, node, vartype, llvm_node): 428 | super().__init__(node.index) 429 | 430 | # Aki node, for position information 431 | self.node = node 432 | 433 | # Vartype (an AST vartype node provided by the caller) 434 | # This can also also be an .akitype node 435 | # so it can just be copied from the last instruction 436 | 437 | self.vartype = vartype 438 | 439 | # LLVM node 440 | # This MUST have .akitype and .akinode data 441 | 442 | assert isinstance(self.llvm_node, ir.Instruction) 443 | 444 | self.llvm_node = llvm_node 445 | assert self.llvm_node.akinode 446 | assert self.llvm_node.akitype 447 | 448 | # Name (optional) 449 | self.name = None 450 | 451 | 452 | class LoopExpr(Expression): 453 | def __init__(self, p, conditions, body): 454 | super().__init__(p) 455 | self.conditions = conditions 456 | self.body = body 457 | 458 | def __eq__(self, other): 459 | return self.conditions == other.conditions and self.body == other.body 460 | 461 | def flatten(self): 462 | return [ 463 | self.__class__.__name__, 464 | [_.flatten() for _ in self.conditions], 465 | self.body.flatten(), 466 | ] 467 | 468 | 469 | class Break(Expression): 470 | def __init__(self, p): 471 | super().__init__(p) 472 | 473 | def flatten(self): 474 | return [self.__class__.__name__] 475 | 476 | def __eq__(self, other): 477 | return self.__class__ == other.__class__ 478 | 479 | 480 | class WithExpr(Expression): 481 | def __init__(self, p, varlist: VarList, body: ExpressionBlock): 482 | super().__init__(p) 483 | self.varlist = varlist 484 | self.body = body 485 | 486 | def __eq__(self, other): 487 | return self.varlist == other.varlist and self.body == other.body 488 | 489 | def flatten(self): 490 | return [ 491 | self.__class__.__name__, 492 | [_.flatten() for _ in self.varlist.vars], 493 | self.body.flatten(), 494 | ] 495 | 496 | 497 | class ChainExpr(Expression): 498 | def __init__(self, p, expr_chain: list): 499 | super().__init__(p) 500 | self.expr_chain = expr_chain 501 | 502 | def __eq__(self, other): 503 | return self.expr_chain == other.expr_chain 504 | 505 | def flatten(self): 506 | return [self.__class__.__name__, [_.flatten() for _ in self.expr_chain]] 507 | 508 | 509 | class UnsafeBlock(Expression): 510 | def __init__(self, p, expr_block): 511 | super().__init__(p) 512 | self.expr_block = expr_block 513 | 514 | def __eq__(self, other): 515 | return self.expr_block == other.expr_block 516 | 517 | def flatten(self): 518 | return [self.__class__.__name__, [_.flatten() for _ in self.expr_block]] 519 | 520 | 521 | class Accessor(Expression): 522 | def __init__(self, p, accessors): 523 | super().__init__(p) 524 | self.accessors = accessors 525 | 526 | def __eq__(self, other): 527 | return self.accessors == other.accessors 528 | 529 | def flatten(self): 530 | return [self.__class__.__name__, [_.flatten() for _ in self.accessors]] 531 | 532 | 533 | class AccessorExpr(Expression): 534 | def __init__(self, p, expr, accessors): 535 | super().__init__(p) 536 | self.expr = expr 537 | self.accessors = accessors 538 | 539 | def __eq__(self, other): 540 | return self.expr == other.expr and self.accessors == other.accessors 541 | 542 | def flatten(self): 543 | return [ 544 | self.__class__.__name__, 545 | self.expr.flatten(), 546 | [_.flatten() for _ in self.accessors], 547 | ] 548 | 549 | 550 | class ObjectRef(Expression): 551 | """ 552 | Target of an assignment operation. 553 | The expr in question is a name, etc. 554 | In every case we want to return a reference to the object, 555 | not its value. 556 | """ 557 | 558 | def __init__(self, p, expr): 559 | super().__init__(p) 560 | self.expr = expr 561 | 562 | def __eq__(self, other): 563 | return self.expr == other.expr 564 | 565 | def flatten(self): 566 | return [self.__class__.__name__, self.expr.flatten()] 567 | 568 | 569 | class ObjectValue(Expression): 570 | """ 571 | Extracted value. 572 | """ 573 | 574 | def __init__(self, p, expr): 575 | super().__init__(p) 576 | self.expr = expr 577 | 578 | def __eq__(self, other): 579 | return self.expr == other.expr 580 | 581 | def flatten(self): 582 | return [self.__class__.__name__, self.expr.flatten()] 583 | 584 | 585 | class SelectExpr(Expression): 586 | """ 587 | `select` expression. 588 | """ 589 | 590 | def __init__(self, p, select_expr, case_list: list, default_case=None): 591 | super().__init__(p) 592 | self.select_expr = select_expr 593 | self.case_list = case_list 594 | self.default_case = default_case 595 | 596 | def __eq__(self, other): 597 | return ( 598 | self.select_expr == other.select_expr 599 | and self.case_list == other.case_list 600 | and self.default_case == other.default_case 601 | ) 602 | 603 | def flatten(self): 604 | return [ 605 | self.__class__.__name__, 606 | self.select_expr.flatten(), 607 | [_.flatten() for _ in self.case_list], 608 | ] 609 | 610 | 611 | class CaseExpr(Expression): 612 | """ 613 | `case` expression. 614 | """ 615 | 616 | def __init__(self, p, case_value, case_expr): 617 | super().__init__(p) 618 | self.case_value = case_value 619 | self.case_expr = case_expr 620 | 621 | def __eq__(self, other): 622 | return self.case_value == other.case_value and self.case_expr == other.case_expr 623 | 624 | def flatten(self): 625 | return [ 626 | self.__class__.__name__, 627 | self.case_value.flatten(), 628 | self.case_expr.flatten(), 629 | ] 630 | 631 | class DefaultExpr(CaseExpr): 632 | pass 633 | 634 | 635 | class WhileExpr(Expression): 636 | """ 637 | `while` expression. 638 | """ 639 | 640 | def __init__(self, p, while_value, while_expr): 641 | super().__init__(p) 642 | self.while_value = while_value 643 | self.while_expr = while_expr 644 | 645 | def __eq__(self, other): 646 | return ( 647 | self.while_value == other.while_value 648 | and self.while_expr == other.while_expr 649 | ) 650 | 651 | def flatten(self): 652 | return [ 653 | self.__class__.__name__, 654 | self.while_value.flatten(), 655 | self.while_expr.flatten(), 656 | ] 657 | 658 | 659 | class BaseDecorator(Expression): 660 | def __init__(self, p, name, args, expr_block): 661 | super().__init__(p) 662 | self.name = name 663 | self.args = args 664 | self.expr_block = expr_block 665 | 666 | def __eq__(self, other): 667 | return self.name == other.name and self.args == other.args 668 | 669 | def flatten(self): 670 | return [ 671 | self.__class__.__name__, 672 | self.name, 673 | self.args.flatten() if self.args else [], 674 | self.expr_block.flatten(), 675 | ] 676 | 677 | 678 | class Decorator(BaseDecorator, TopLevel): 679 | pass 680 | 681 | 682 | class InlineDecorator(Decorator): 683 | pass 684 | -------------------------------------------------------------------------------- /aki/core/grammar/__init__.py: -------------------------------------------------------------------------------- 1 | from lark import Lark, Transformer, Tree, exceptions 2 | from core import error 3 | 4 | from core.astree import ( 5 | Constant, 6 | VarTypeName, 7 | ExpressionBlock, 8 | Name, 9 | VarList, 10 | Assignment, 11 | ObjectRef, 12 | Argument, 13 | StarArgument, 14 | Prototype, 15 | Function, 16 | Call, 17 | BinOp, 18 | WithExpr, 19 | BinOpComparison, 20 | LoopExpr, 21 | WhileExpr, 22 | IfExpr, 23 | Break, 24 | WhenExpr, 25 | String, 26 | SelectExpr, 27 | CaseExpr, 28 | DefaultExpr, 29 | VarTypePtr, 30 | VarTypeFunc, 31 | VarTypeAccessor, 32 | VarTypeNode, 33 | Accessor, 34 | UnOp, 35 | UnsafeBlock, 36 | Decorator, 37 | InlineDecorator, 38 | ConstList, 39 | External, 40 | AccessorExpr, 41 | UniList, 42 | Return, 43 | ) 44 | 45 | 46 | class AkiTransformer(Transformer): 47 | def start(self, node): 48 | """ 49 | Returns a list of AST nodes to evaluate. 50 | """ 51 | return node 52 | 53 | def toplevel(self, node): 54 | return node[0] 55 | 56 | def expression(self, node): 57 | """ 58 | Any single expression. 59 | """ 60 | return node[0] 61 | 62 | def terminal(self, node): 63 | """ 64 | A semicolon, which forces an end-of-expression. 65 | """ 66 | return ExpressionBlock(node[0].pos_in_stream, []) 67 | 68 | def atom(self, node): 69 | """ 70 | Any *single* reference that returns a value: 71 | - a constant 72 | - a variable 73 | - a function call 74 | - a slice 75 | - a parenthetical 76 | - an expression block 77 | """ 78 | return node[0] 79 | 80 | def parenthetical(self, node): 81 | """ 82 | One or more atoms enclosed in a parenthetical. 83 | """ 84 | return node[1] 85 | 86 | def subexpression(self, node): 87 | """ 88 | Braces enclosing one or more expressions. 89 | """ 90 | return ExpressionBlock(node[0].pos_in_stream, node[1:-1]) 91 | 92 | def toplevel_decorator(self, node): 93 | """ 94 | A decorator in a top-level context. 95 | """ 96 | body = node[1] 97 | for _ in node[0]: 98 | pos = _[0] 99 | name = _[1].value 100 | args = _[2] 101 | ret = Decorator(pos.pos_in_stream, name, args, body) 102 | body = ret 103 | return ret 104 | 105 | def decorators(self, node): 106 | """ 107 | One or more decorators. 108 | """ 109 | return node 110 | 111 | def inline_decorator(self, node): 112 | raise NotImplementedError 113 | 114 | def decorator(self, node): 115 | """ 116 | A single decorator. 117 | """ 118 | return node 119 | 120 | def opt_args(self, node): 121 | """ 122 | An optional argument list for a decorator. 123 | """ 124 | return node 125 | 126 | def opt_arglist(self, node): 127 | """ 128 | The arguments themselves within an optional argument list. 129 | """ 130 | if not node: 131 | return node 132 | return node[0] 133 | 134 | def arglist(self, node): 135 | """ 136 | An argument list. 137 | """ 138 | return node 139 | 140 | def argument(self, node): 141 | """ 142 | Argument for an argument list. 143 | """ 144 | stararg, name, vartype, default_value = node 145 | argtype = StarArgument if stararg else Argument 146 | return argtype(name.pos_in_stream, name.value, vartype, default_value) 147 | 148 | def stararg(self, node): 149 | """ 150 | A starred argument. 151 | """ 152 | return node 153 | 154 | def dimensions(self, node): 155 | """ 156 | Dimensions for an array variable type descriptor. 157 | """ 158 | return node 159 | 160 | def dimension(self, node): 161 | """ 162 | Single dimension in a dimension list. 163 | """ 164 | return node[0] 165 | 166 | def mandatory_vartype(self, node): 167 | """ 168 | A vartype that is required (with a preceding colon). 169 | """ 170 | return node[1] 171 | 172 | def arraytypedef(self, node): 173 | """ 174 | Type definition for an array. 175 | """ 176 | pos, vartype, pos2, dimensions, _ = node 177 | return VarTypeAccessor( 178 | pos.pos_in_stream, vartype, Accessor(pos2.pos_in_stream, dimensions) 179 | ) 180 | 181 | def func_call(self, node): 182 | """ 183 | Function call. 184 | """ 185 | return Call(node[0].index, node[0].name, node[2], None) 186 | 187 | def opt_call_args(self, node): 188 | """ 189 | Optional function call arguments. 190 | """ 191 | if not node: 192 | return [] 193 | return node[0] 194 | 195 | def call_arg(self, node): 196 | """ 197 | Single call argument. 198 | """ 199 | return node[0] 200 | 201 | def call_args(self, node): 202 | """ 203 | Set of function call arguments. 204 | """ 205 | return node 206 | 207 | def function_declaration(self, node): 208 | """ 209 | Function declaration. 210 | """ 211 | pos = node[0] 212 | name = node[1].name 213 | args = node[3] 214 | vartype = node[5] 215 | body = node[6] 216 | proto = Prototype(pos.pos_in_stream, name, args, vartype) 217 | func = Function(pos.pos_in_stream, proto, body) 218 | return func 219 | 220 | def external_declaration(self, node): 221 | """ 222 | External function declaration. 223 | """ 224 | pos = node[0] 225 | name = node[1].name 226 | args = node[3] 227 | vartype = node[5] 228 | proto = Prototype(pos.pos_in_stream, name, args, vartype) 229 | func = External(pos.pos_in_stream, proto, None) 230 | return func 231 | 232 | def unsafe_block(self, node): 233 | """ 234 | Unsafe block declaration. 235 | """ 236 | return UnsafeBlock( 237 | node[0].pos_in_stream, ExpressionBlock(node[0].pos_in_stream, [node[1]]) 238 | ) 239 | 240 | def opt_varassignments(self, node): 241 | """ 242 | Optional variable assignments. 243 | """ 244 | if not node: 245 | return [] 246 | return node[0] 247 | 248 | def variable_declaration_block(self, node): 249 | """ 250 | Variable declaration block. 251 | """ 252 | pos = node[0] 253 | if isinstance(node[1], Tree): 254 | vlist = node[2] 255 | else: 256 | vlist = node[1] 257 | return VarList(pos.pos_in_stream, vlist) 258 | 259 | def varassignment(self, node): 260 | """ 261 | Variable assignment block. 262 | """ 263 | name = node[0] 264 | vartype = node[1] 265 | val = node[2] 266 | name.vartype = vartype 267 | name.val = val 268 | return name 269 | 270 | def varassignments(self, node): 271 | """ 272 | Variable assignments. 273 | """ 274 | return node 275 | 276 | def assignments(self, node): 277 | """ 278 | Assignments list. 279 | """ 280 | return node 281 | 282 | def opt_assignment(self, node): 283 | """ 284 | Optional assignment. 285 | """ 286 | if not node: 287 | return None 288 | return node[1] 289 | 290 | def assignment_op(self, node): 291 | """ 292 | Assignment operation. 293 | """ 294 | return node[0] 295 | 296 | def single_variable_declaration_block(self, node): 297 | """ 298 | Block with a single variable declaration, for instance in a loop preamble. 299 | """ 300 | return VarList(node[0].pos_in_stream, [node[1]]) 301 | 302 | def assignment(self, node): 303 | """ 304 | Variable assignment. 305 | """ 306 | name = node[0] 307 | op = node[1] 308 | if op.type.startswith("SM_"): 309 | return Assignment( 310 | op.pos_in_stream, 311 | "=", 312 | ObjectRef(op.pos_in_stream, node[0]), 313 | BinOp(op.pos_in_stream, op.value[0], node[0], node[2]), 314 | ) 315 | return Assignment( 316 | op.pos_in_stream, op.value, ObjectRef(name.index, name), node[2] 317 | ) 318 | 319 | def test(self, node): 320 | """ 321 | Any binop or unop. 322 | """ 323 | _p("test", node) 324 | return node 325 | 326 | def or_test(self, node): 327 | """ 328 | OR test. 329 | """ 330 | op = node[1] 331 | return BinOp(op.pos_in_stream, op.value, node[0], node[2]) 332 | 333 | def not_test(self, node): 334 | """ 335 | NOT test. 336 | """ 337 | x = UnOp(node[0].pos_in_stream, "not", node[1]) 338 | return x 339 | 340 | def fold_binop(self, node): 341 | """ 342 | Turn multiple binops into a single binop. 343 | """ 344 | lhs = None 345 | while len(node) > 0: 346 | lhs = node.pop(0) if lhs is None else binop 347 | op = node.pop(0) 348 | rhs = node.pop(0) 349 | binop = BinOp(op.pos_in_stream, op.value, lhs, rhs) 350 | return binop 351 | 352 | and_test = add_ops = mult_ops = fold_binop 353 | 354 | def comparison(self, node): 355 | lhs = None 356 | while len(node) > 0: 357 | lhs = node.pop(0) if lhs is None else binop 358 | op = node.pop(0) 359 | rhs = node.pop(0) 360 | binop = BinOpComparison(op.pos_in_stream, op.value, lhs, rhs) 361 | return binop 362 | 363 | # TODO: allow multi-way comparison: 364 | # take the first three elements 365 | # save the last element (rhs) 366 | # pack them together into a binop 367 | # if there are more elements: 368 | # take the rhs, read the next two, pack those into another binopcomp, 369 | # pack the two binops as an AND, and preserve that 370 | # etc. 371 | 372 | def factor_ops(self, node): 373 | """ 374 | Negation or factoring ops. 375 | """ 376 | return UnOp(node[0].pos_in_stream, node[0].value, node[1]) 377 | 378 | def with_expr(self, node): 379 | """ 380 | With expression. 381 | """ 382 | if isinstance(node[1], Tree): 383 | varlist = node[2] 384 | body = node[4] 385 | else: 386 | varlist = node[1] 387 | body = node[2] 388 | return WithExpr(node[0].pos_in_stream, varlist, body) 389 | 390 | def while_expr(self, node): 391 | """ 392 | While expressiion. 393 | """ 394 | return WhileExpr(node[0].pos_in_stream, node[1], node[2]) 395 | 396 | def if_expr(self, node): 397 | """ 398 | If expression. 399 | """ 400 | if node[3] is None: 401 | return WhenExpr(node[0].pos_in_stream, node[1], node[2], None) 402 | return IfExpr(node[0].pos_in_stream, node[1], node[2], node[3]) 403 | 404 | def return_expr(self, node): 405 | """ 406 | Return. 407 | """ 408 | return Return(node[0].pos_in_stream, node[1]) 409 | 410 | def when_expr(self, node): 411 | """ 412 | When expression. 413 | """ 414 | if len(node) < 4: 415 | return WhenExpr(node[0].pos_in_stream, node[1], node[2], None) 416 | return WhenExpr(node[0].pos_in_stream, node[1], node[2], node[3]) 417 | 418 | def loop_expr(self, node): 419 | """ 420 | Loop expression. 421 | """ 422 | pos = node[0].pos_in_stream 423 | var = node[2] 424 | if isinstance(var, Assignment): 425 | v = var.lhs.expr 426 | else: 427 | v = var.vars[0] 428 | if len(node) > 6: 429 | return LoopExpr(pos, [node[2], node[3], node[4]], node[6]) 430 | else: 431 | return LoopExpr( 432 | pos, 433 | [node[2], node[3], BinOp(pos, "+", v, Constant(pos, "1", v.vartype))], 434 | node[5], 435 | ) 436 | 437 | def infinite_loop_expr(self, node): 438 | """ 439 | Loop with no preamble. 440 | """ 441 | return LoopExpr(node[0].pos_in_stream, [], node[3]) 442 | 443 | def select_expr(self, node): 444 | """ 445 | Select expression. 446 | """ 447 | caselist = [] 448 | default_case = None 449 | for _ in node[3]: 450 | if isinstance(_, DefaultExpr): 451 | if default_case is not None: 452 | raise error.AkiSyntaxErr( 453 | _.index, self.text, "Multiple default cases specified in select" 454 | ) 455 | default_case = _ 456 | else: 457 | caselist.append(_) 458 | return SelectExpr(node[0].pos_in_stream, node[1], caselist, default_case) 459 | 460 | def cases(self, node): 461 | """ 462 | Cases for select expression. 463 | """ 464 | return node 465 | 466 | def case(self, node): 467 | """ 468 | Case expression. 469 | """ 470 | if node[0].value == "default": 471 | return DefaultExpr(node[0].pos_in_stream, None, node[1]) 472 | return CaseExpr(node[0].pos_in_stream, node[1], node[2]) 473 | 474 | def optional_else(self, node): 475 | """ 476 | Optional Else expression. 477 | """ 478 | if node: 479 | return node[1] 480 | return None 481 | 482 | def break_expr(self, node): 483 | """ 484 | Break expression. 485 | """ 486 | return Break(node[0].pos_in_stream) 487 | 488 | def array_ref(self, node): 489 | """ 490 | Array reference. 491 | """ 492 | pos = node[1] 493 | accessor = node[2] 494 | return AccessorExpr( 495 | pos.pos_in_stream, node[0], Accessor(pos.pos_in_stream, accessor) 496 | ) 497 | 498 | def vartype(self, node): 499 | """ 500 | Variable type expression. 501 | """ 502 | name = node[1] 503 | if isinstance(name, VarTypeNode): 504 | vt = name 505 | elif name is None: 506 | return None 507 | else: 508 | vt = VarTypeName(name.index, name.value) 509 | for _ in node[0]: 510 | vt = VarTypePtr(name.index, vt) 511 | return vt 512 | 513 | def opt_vartype(self, node): 514 | """ 515 | Optional variable type." 516 | """ 517 | if not node: 518 | return None 519 | return node[1] 520 | 521 | def vartypelist(self, node): 522 | """ 523 | Variable type list. 524 | """ 525 | if not node: 526 | return [] 527 | if len(node) == 1 and node[0] is None: 528 | return [] 529 | return node 530 | 531 | def functypedef(self, node): 532 | """ 533 | Vartype for function type definition. 534 | """ 535 | typelist = node[2] 536 | return_type = node[4] 537 | return VarTypeFunc(node[0].pos_in_stream, typelist, return_type) 538 | 539 | def ptr_list(self, node): 540 | """ 541 | Ptr node in var type def. 542 | """ 543 | return node 544 | 545 | ctrl_chars = { 546 | # Bell 547 | "a": "\a", 548 | # Backspace 549 | "b": "\b", 550 | # Form feed 551 | "f": "\f", 552 | # Line feed/newline 553 | "n": "\n", 554 | # Carriage return 555 | "r": "\r", 556 | # Horizontal tab 557 | "t": "\t", 558 | # vertical tab 559 | "v": "\v", 560 | } 561 | 562 | char_code = { 563 | # 8-bit ASCII 564 | "x": 3, 565 | # 16-bit Unicode 566 | "u": 5, 567 | # 32-bit Unicode 568 | "U": 9, 569 | } 570 | 571 | def string(self, node): 572 | """ 573 | String constant. 574 | """ 575 | node = node[0] 576 | pos = node.pos_in_stream 577 | raw_str = node.value[1:-1] 578 | new_str = [] 579 | subs = raw_str.split("\\") 580 | # the first one will never be a control sequence 581 | new_str.append(subs.pop(0)) 582 | _subs = iter(subs) 583 | strlen = 0 584 | for n in _subs: 585 | # a \\ generates an empty sequence 586 | # so we consume that and generate a single \ 587 | if not n: 588 | new_str.append("\\") 589 | n = next(_subs) 590 | new_str.append(n) 591 | continue 592 | s = n[0] 593 | strlen += len(n) 594 | if s in self.ctrl_chars: 595 | new_str.append(self.ctrl_chars[s]) 596 | new_str.append(n[1:]) 597 | elif s in self.char_code: 598 | r = self.char_code[s] 599 | hexval = n[1:r] 600 | try: 601 | new_str.append(chr(int(hexval, 16))) 602 | except ValueError: 603 | raise AkiSyntaxErr( 604 | Pos(p), self.text, f"Unrecognized hex sequence for character" 605 | ) 606 | new_str.append(n[r:]) 607 | elif s in ('"', "'"): 608 | new_str.append(n[0:]) 609 | else: 610 | # print(n) 611 | raise AkiSyntaxErr( 612 | Errpos(p, strlen + 2), 613 | self.text, 614 | f"Unrecognized control sequence in string", 615 | ) 616 | 617 | return String(pos, "".join(new_str), VarTypeName(pos, "str")) 618 | 619 | def const_declaration_block(self, node): 620 | """ 621 | Constant declaration block. 622 | """ 623 | return ConstList(node[0].pos_in_stream, node[2]) 624 | 625 | def uni_declaration_block(self, node): 626 | """ 627 | Uni declaration block. 628 | """ 629 | return UniList(node[0].pos_in_stream, node[2]) 630 | 631 | def constant(self, node): 632 | """ 633 | True/False. 634 | """ 635 | pos = node[0].pos_in_stream 636 | return Constant( 637 | pos, 1 if node[0].value == "True" else 0, VarTypeName(pos, "bool") 638 | ) 639 | 640 | def number(self, node): 641 | """ 642 | Any number. 643 | """ 644 | return node[0] 645 | 646 | def name(self, node): 647 | """ 648 | Name reference. 649 | """ 650 | return Name(node[0].pos_in_stream, node[0].value) 651 | 652 | def decimal_number(self, node): 653 | """ 654 | Decimal number. 655 | """ 656 | number = node[0] 657 | vartype = node[1] 658 | if vartype is None: 659 | vartype = VarTypeName(number.pos_in_stream, "i32") 660 | return Constant(number.pos_in_stream, int(number.value), vartype) 661 | 662 | def hex_number(self, node): 663 | """ 664 | Hex number. 665 | """ 666 | pos = node[0].pos_in_stream 667 | hex_str = node[0].value 668 | vartype = node[1] 669 | 670 | if hex_str[1] == "x": 671 | # 0x00 = unsigned 672 | sign = "u" 673 | else: 674 | # 0h00 = signed 675 | sign = "i" 676 | value = int(hex_str[2:], 16) 677 | if vartype: 678 | return Constant(pos, value, vartype) 679 | bytelength = (len(hex_str[2:])) * 4 680 | if bytelength < 8: 681 | if value > 1: 682 | bytelength = 8 683 | typestr = f"{sign}{bytelength}" 684 | else: 685 | typestr = "bool" 686 | else: 687 | typestr = f"{sign}{bytelength}" 688 | 689 | return Constant(pos, value, VarTypeName(pos, typestr)) 690 | 691 | def float_number(self, node): 692 | """ 693 | Floating point number. 694 | """ 695 | number = node[0] 696 | vartype = node[1] 697 | if vartype is None: 698 | vartype = VarTypeName(number.pos_in_stream, "f64") 699 | return Constant(number.pos_in_stream, float(number.value), vartype) 700 | 701 | 702 | with open("core/grammar/grammar.lark") as file: 703 | grammar = file.read() 704 | 705 | AkiParser = Lark( 706 | grammar, 707 | transformer=AkiTransformer(), 708 | parser="lalr", 709 | debug=False, 710 | ambiguity="explicit", 711 | ) 712 | 713 | 714 | def parse(text, *a, **ka): 715 | try: 716 | AkiParser.options.transformer.text = text 717 | result = AkiParser.parse(text, *a, **ka) 718 | except exceptions.UnexpectedCharacters as e: 719 | raise error.AkiSyntaxErr(e.pos_in_stream, text, "Unexpected character") 720 | except exceptions.UnexpectedToken as e: 721 | raise error.AkiSyntaxErr(e.pos_in_stream, text, "Unexpected token or keyword") 722 | return result 723 | -------------------------------------------------------------------------------- /aki/tests/t_02_parser.py.bak: -------------------------------------------------------------------------------- 1 | # Test generation of all ASTs in parser. 2 | # We might want to find a way to take the debug output from the parser, 3 | # and use that to auto-generate this test suite. 4 | 5 | import unittest 6 | from core.error import AkiSyntaxErr 7 | 8 | 9 | class TestLexer(unittest.TestCase): 10 | 11 | from core import lex, parse 12 | 13 | #l = lex._AkiLexer.tokenize 14 | p = parse._AkiParser.parse 15 | 16 | def __parse(self, text): 17 | return self._parse(text, True) 18 | 19 | def _parse(self, text, display=False): 20 | tokens = self.l(text) 21 | asts = self.p(tokens, text) 22 | stream = [_.flatten() for _ in asts] 23 | if display: 24 | print(stream) 25 | return stream 26 | 27 | def _e(self, tests): 28 | for text, result in tests: 29 | self.assertEqual(self._parse(text), result) 30 | 31 | def __e(self, tests): 32 | for text, result in tests: 33 | self.assertEqual(self.__parse(text), result) 34 | 35 | def _ex(self, err_type, tests): 36 | for text in tests: 37 | with self.assertRaises(err_type): 38 | self._parse(text) 39 | 40 | def test_string(self): 41 | self._e( 42 | ( 43 | (r'"Hello world"', [["String", "Hello world"]]), 44 | (r'"Hello \" world"', [["String", 'Hello " world']]), 45 | (r"'Hello \' world'", [["String", "Hello ' world"]]), 46 | (r"'Hello \\ world'", [["String", "Hello \\ world"]]), 47 | (r"'\x40'", [["String", "@"]]), 48 | (r"'\u0040'", [["String", "@"]]), 49 | (r"'\U00000040'", [["String", "@"]]), 50 | (r"'\x40_'", [["String", "@_"]]), 51 | (r"'\u0040_'", [["String", "@_"]]), 52 | (r"'\U00000040_'", [["String", "@_"]]), 53 | ) 54 | ) 55 | self._ex(AkiSyntaxErr, ((r"'\xq5'"),)) 56 | 57 | def test_constant(self): 58 | self._e( 59 | ( 60 | (r"32", [["Constant", 32, ["VarTypeName", "i32"]]]), 61 | (r"32.0", [["Constant", 32.0, ["VarTypeName", "f64"]]]), 62 | (r"0hff", [["Constant", 255, ["VarTypeName", "i8"]]]), 63 | (r"0xff", [["Constant", 255, ["VarTypeName", "u8"]]]), 64 | (r"True", [["Constant", True, ["VarTypeName", "bool"]]]), 65 | (r"False", [["Constant", False, ["VarTypeName", "bool"]]]), 66 | ) 67 | ) 68 | # These are syntactically valid dot expressions 69 | # self._ex(AkiSyntaxErr, ((r"32.x"), (r"x.32"))) 70 | 71 | def test_expr_names(self): 72 | self._e( 73 | ( 74 | (r"x", [["Name", "x", None, None]]), 75 | (r"_x", [["Name", "_x", None, None]]), 76 | ( 77 | r"var x:i32", 78 | [["VarList", [["Name", "x", None, ["VarTypeName", "i32"]]]]], 79 | ), 80 | ( 81 | r"var x:i32=1,y=2,z:i32", 82 | [ 83 | [ 84 | "VarList", 85 | [ 86 | [ 87 | "Name", 88 | "x", 89 | ["Constant", 1, ["VarTypeName", "i32"]], 90 | ["VarTypeName", "i32"], 91 | ], 92 | [ 93 | "Name", 94 | "y", 95 | ["Constant", 2, ["VarTypeName", "i32"]], 96 | None, 97 | ], 98 | ["Name", "z", None, ["VarTypeName", "i32"]], 99 | ], 100 | ] 101 | ], 102 | ), 103 | ) 104 | ) 105 | 106 | def test_binop(self): 107 | self._e( 108 | ( 109 | ( 110 | r"x+1", 111 | [ 112 | [ 113 | "BinOp", 114 | "+", 115 | ["Name", "x", None, None], 116 | ["Constant", 1, ["VarTypeName", "i32"]], 117 | ] 118 | ], 119 | ), 120 | ( 121 | r"x+1*5", 122 | [ 123 | [ 124 | "BinOp", 125 | "+", 126 | ["Name", "x", None, None], 127 | [ 128 | "BinOp", 129 | "*", 130 | ["Constant", 1, ["VarTypeName", "i32"]], 131 | ["Constant", 5, ["VarTypeName", "i32"]], 132 | ], 133 | ] 134 | ], 135 | ), 136 | ) 137 | ) 138 | 139 | def test_unop(self): 140 | self._e( 141 | ( 142 | (r"-5", [["UnOp", "-", ["Constant", 5, ["VarTypeName", "i32"]]]]), 143 | ( 144 | r"-{x*y}", 145 | [ 146 | [ 147 | "UnOp", 148 | "-", 149 | [ 150 | "ExpressionBlock", 151 | [ 152 | [ 153 | "BinOp", 154 | "*", 155 | ["Name", "x", None, None], 156 | ["Name", "y", None, None], 157 | ] 158 | ], 159 | ], 160 | ] 161 | ], 162 | ), 163 | ) 164 | ) 165 | 166 | def test_assignment(self): 167 | self._e( 168 | ( 169 | ( 170 | r"x=5", 171 | [ 172 | [ 173 | "Assignment", 174 | "=", 175 | ["ObjectRef", ["Name", "x", None, None]], 176 | ["Constant", 5, ["VarTypeName", "i32"]], 177 | ] 178 | ], 179 | ), 180 | ) 181 | ) 182 | 183 | def test_expr_paren(self): 184 | self._e( 185 | ( 186 | ( 187 | r"(x==1)", 188 | [ 189 | [ 190 | "BinOpComparison", 191 | "==", 192 | ["Name", "x", None, None], 193 | ["Constant", 1, ["VarTypeName", "i32"]], 194 | ] 195 | ], 196 | ), 197 | ( 198 | r"(x=1)", 199 | [ 200 | [ 201 | "Assignment", 202 | "=", 203 | ["ObjectRef", ["Name", "x", None, None]], 204 | ["Constant", 1, ["VarTypeName", "i32"]], 205 | ] 206 | ], 207 | ), 208 | ) 209 | ) 210 | 211 | with self.assertRaises(AkiSyntaxErr): 212 | self.assertEqual(self._parse(r"(x=1 x=2)"), []) 213 | 214 | def text_expr_block(self): 215 | self._e( 216 | ( 217 | ( 218 | r"{x=1 x==1}", 219 | [ 220 | [ 221 | "ExpressionBlock", 222 | [ 223 | [ 224 | "Assignment", 225 | "=", 226 | ["Name", "x", None, None], 227 | ["Constant", 1, ["VarTypeName", "i32"]], 228 | ], 229 | [ 230 | "BinOpComparison", 231 | "==", 232 | ["Name", "x", None, None], 233 | ["Constant", 1, ["VarTypeName", "i32"]], 234 | ], 235 | ], 236 | ] 237 | ], 238 | ) 239 | ) 240 | ) 241 | 242 | def test_toplevel_def(self): 243 | self._e( 244 | ( 245 | ( 246 | "def main(){0}", 247 | [ 248 | [ 249 | "Function", 250 | ["Prototype", "main", [], None], 251 | [["Constant", 0, ["VarTypeName", "i32"]]], 252 | ] 253 | ], 254 | ), 255 | ) 256 | ) 257 | 258 | def test_function_def(self): 259 | self._e( 260 | ( 261 | ( 262 | r"def main(x){x+=1 x}", 263 | [ 264 | [ 265 | "Function", 266 | [ 267 | "Prototype", 268 | "main", 269 | [["Argument", "x", None, None]], 270 | None, 271 | ], 272 | [ 273 | [ 274 | "Assignment", 275 | "=", 276 | ["ObjectRef", ["Name", "x", None, None]], 277 | [ 278 | "BinOp", 279 | "+", 280 | ["Name", "x", None, None], 281 | ["Constant", 1, ["VarTypeName", "i32"]], 282 | ], 283 | ], 284 | ["Name", "x", None, None], 285 | ], 286 | ] 287 | ], 288 | ), 289 | ) 290 | ) 291 | 292 | def test_loop(self): 293 | self._e( 294 | ( 295 | ( 296 | r"loop (x=0,x<20,x+1){x}", 297 | [ 298 | [ 299 | "LoopExpr", 300 | [ 301 | [ 302 | "Assignment", 303 | "=", 304 | ["ObjectRef", ["Name", "x", None, None]], 305 | ["Constant", 0, ["VarTypeName", "i32"]], 306 | ], 307 | [ 308 | "BinOpComparison", 309 | "<", 310 | ["Name", "x", None, None], 311 | ["Constant", 20, ["VarTypeName", "i32"]], 312 | ], 313 | [ 314 | "BinOp", 315 | "+", 316 | ["Name", "x", None, None], 317 | ["Constant", 1, ["VarTypeName", "i32"]], 318 | ], 319 | ], 320 | ["ExpressionBlock", [["Name", "x", None, None]]], 321 | ] 322 | ], 323 | ), 324 | ( 325 | r"loop (var x=0,x<20,x+1){x}", 326 | [ 327 | [ 328 | "LoopExpr", 329 | [ 330 | [ 331 | "VarList", 332 | [ 333 | [ 334 | "Name", 335 | "x", 336 | ["Constant", 0, ["VarTypeName", "i32"]], 337 | None, 338 | ] 339 | ], 340 | ], 341 | [ 342 | "BinOpComparison", 343 | "<", 344 | ["Name", "x", None, None], 345 | ["Constant", 20, ["VarTypeName", "i32"]], 346 | ], 347 | [ 348 | "BinOp", 349 | "+", 350 | ["Name", "x", None, None], 351 | ["Constant", 1, ["VarTypeName", "i32"]], 352 | ], 353 | ], 354 | ["ExpressionBlock", [["Name", "x", None, None]]], 355 | ] 356 | ], 357 | ), 358 | ) 359 | ) 360 | 361 | def test_if_else(self): 362 | self._e( 363 | ( 364 | ( 365 | r"if x>1 y else z", 366 | [ 367 | [ 368 | "IfExpr", 369 | [ 370 | "BinOpComparison", 371 | ">", 372 | ["Name", "x", None, None], 373 | ["Constant", 1, ["VarTypeName", "i32"]], 374 | ], 375 | ["Name", "y", None, None], 376 | ["Name", "z", None, None], 377 | ] 378 | ], 379 | ), 380 | ( 381 | r"if (x>1) (y) else (z)", 382 | [ 383 | [ 384 | "IfExpr", 385 | [ 386 | "BinOpComparison", 387 | ">", 388 | ["Name", "x", None, None], 389 | ["Constant", 1, ["VarTypeName", "i32"]], 390 | ], 391 | ["Name", "y", None, None], 392 | ["Name", "z", None, None], 393 | ] 394 | ], 395 | ), 396 | ( 397 | r"if {x>1} {y} else {z}", 398 | [ 399 | [ 400 | "IfExpr", 401 | [ 402 | "ExpressionBlock", 403 | [ 404 | [ 405 | "BinOpComparison", 406 | ">", 407 | ["Name", "x", None, None], 408 | ["Constant", 1, ["VarTypeName", "i32"]], 409 | ] 410 | ], 411 | ], 412 | ["ExpressionBlock", [["Name", "y", None, None]]], 413 | ["ExpressionBlock", [["Name", "z", None, None]]], 414 | ] 415 | ], 416 | ), 417 | ) 418 | ) 419 | 420 | def test_when(self): 421 | self._e( 422 | ( 423 | ( 424 | r"when x>1 y else z", 425 | [ 426 | [ 427 | "WhenExpr", 428 | [ 429 | "BinOpComparison", 430 | ">", 431 | ["Name", "x", None, None], 432 | ["Constant", 1, ["VarTypeName", "i32"]], 433 | ], 434 | ["Name", "y", None, None], 435 | ["Name", "z", None, None], 436 | ] 437 | ], 438 | ), 439 | ( 440 | r"when {x>1} {y} else {z}", 441 | [ 442 | [ 443 | "WhenExpr", 444 | [ 445 | "ExpressionBlock", 446 | [ 447 | [ 448 | "BinOpComparison", 449 | ">", 450 | ["Name", "x", None, None], 451 | ["Constant", 1, ["VarTypeName", "i32"]], 452 | ] 453 | ], 454 | ], 455 | ["ExpressionBlock", [["Name", "y", None, None]]], 456 | ["ExpressionBlock", [["Name", "z", None, None]]], 457 | ] 458 | ], 459 | ), 460 | ) 461 | ) 462 | 463 | def test_with(self): 464 | self._e( 465 | ( 466 | ( 467 | r"with var x=1 {x}", 468 | [ 469 | [ 470 | "WithExpr", 471 | [ 472 | [ 473 | "Name", 474 | "x", 475 | ["Constant", 1, ["VarTypeName", "i32"]], 476 | None, 477 | ] 478 | ], 479 | ["ExpressionBlock", [["Name", "x", None, None]]], 480 | ] 481 | ], 482 | ), 483 | ( 484 | r"with var x=1, y=2 {x}", 485 | [ 486 | [ 487 | "WithExpr", 488 | [ 489 | [ 490 | "Name", 491 | "x", 492 | ["Constant", 1, ["VarTypeName", "i32"]], 493 | None, 494 | ], 495 | [ 496 | "Name", 497 | "y", 498 | ["Constant", 2, ["VarTypeName", "i32"]], 499 | None, 500 | ], 501 | ], 502 | ["ExpressionBlock", [["Name", "x", None, None]]], 503 | ] 504 | ], 505 | ), 506 | ) 507 | ) 508 | 509 | def test_break(self): 510 | self._e( 511 | ((r"loop {break}", [["LoopExpr", [], ["ExpressionBlock", [["Break"]]]]]),) 512 | ) 513 | 514 | def test_call(self): 515 | self._e( 516 | ( 517 | ( 518 | r"x(1)", 519 | [["Call", "x", [["Constant", 1, ["VarTypeName", "i32"]]], None]], 520 | ), 521 | ) 522 | ) 523 | 524 | def test_advanced_type_names(self): 525 | self._e( 526 | ( 527 | ( 528 | "var x:ptr i32", 529 | [ 530 | [ 531 | "VarList", 532 | [ 533 | [ 534 | "Name", 535 | "x", 536 | None, 537 | ["VarTypePtr", ["VarTypeName", "i32"]], 538 | ] 539 | ], 540 | ] 541 | ], 542 | ), 543 | ( 544 | "var x:ptr ptr i32", 545 | [ 546 | [ 547 | "VarList", 548 | [ 549 | [ 550 | "Name", 551 | "x", 552 | None, 553 | [ 554 | "VarTypePtr", 555 | ["VarTypePtr", ["VarTypeName", "i32"]], 556 | ], 557 | ] 558 | ], 559 | ] 560 | ], 561 | ), 562 | ) 563 | ) 564 | 565 | -------------------------------------------------------------------------------- /aki/core/repl/__init__.py: -------------------------------------------------------------------------------- 1 | from llvmlite import ir, binding 2 | import pickle 3 | 4 | pickle.DEFAULT_PROTOCOL = pickle.HIGHEST_PROTOCOL 5 | import sys 6 | import colorama 7 | 8 | colorama.init() 9 | CMD = colorama.Fore.YELLOW 10 | REP = colorama.Fore.WHITE 11 | 12 | RED = colorama.Fore.RED 13 | XX = colorama.Fore.RESET 14 | 15 | GRN = colorama.Fore.GREEN 16 | MAG = colorama.Fore.MAGENTA 17 | 18 | 19 | class Comment(ir.values.Value): 20 | def __init__(self, parent, text): 21 | self.text = text 22 | 23 | def __repr__(self): 24 | return f"; {self.text}" 25 | 26 | 27 | ir.instructions.Comment = Comment 28 | 29 | 30 | def comment(self, txt): 31 | self._insert(ir.instructions.Comment(self.block, txt)) 32 | 33 | 34 | import time 35 | 36 | 37 | class Timer: 38 | def __init__(self): 39 | self.clock = time.clock 40 | 41 | def __enter__(self): 42 | self.begin = self.clock() 43 | return self 44 | 45 | def __exit__(self, exception_type, exception_value, traceback): 46 | self.end = self.clock() 47 | self.time = self.end - self.begin 48 | 49 | 50 | ir.builder.IRBuilder.comment = comment 51 | 52 | import ctypes 53 | import os 54 | 55 | from core import grammar as AkiParser 56 | from core.codegen import AkiCodeGen 57 | from core.compiler import AkiCompiler, ir 58 | from core.astree import ( 59 | Function, 60 | Call, 61 | Prototype, 62 | ExpressionBlock, 63 | TopLevel, 64 | Name, 65 | VarTypeName, 66 | External, 67 | ) 68 | from core.error import AkiBaseErr, ReloadException, QuitException, LocalException 69 | from core.akitypes import AkiTypeMgr, AkiObject 70 | from core import constants 71 | 72 | 73 | PROMPT = "A>" 74 | 75 | USAGE = f"""From the {PROMPT} prompt, type Aki code or enter special commands 76 | preceded by a dot sign: 77 | 78 | {CMD}.about|ab{REP} : About this program. 79 | {CMD}.compile|cp{REP} : Compile current module to executable. 80 | {CMD}.dump|dp {REP} 81 | : Dump current module IR to console. 82 | : Add to dump IR for a function. 83 | {CMD}.exit|quit|stop|q{REP} 84 | : Stop and exit the program. 85 | {CMD}.export|ex {REP} 86 | : Dump current module to file in LLVM assembler format. 87 | : Uses output.ll in current directory as default filename. 88 | {CMD}.help|.?|.{REP} : Show this message. 89 | {CMD}.rerun|..{REP} : Reload the Python code and restart the REPL. 90 | {CMD}.rl[c|r]{REP} : Reset the interpreting engine and reload the last .aki 91 | file loaded in the REPL. Add c to run .cp afterwards. 92 | Add r to run main() afterwards. 93 | {CMD}.reset|~{REP} : Reset the interpreting engine. 94 | {CMD}.run|r{REP} : Run the main() function (if present) in the current 95 | module. 96 | {CMD}.test|t{REP} : Run unit tests. 97 | {CMD}.version|ver|v{REP} 98 | : Print version information. 99 | {CMD}..{REP} : Load .aki from the src directory. 100 | For instance, .l. will load the Conway's Life demo. 101 | 102 | These commands are also available directly from the command line, for example: 103 | 104 | {CMD}aki 2 + 3 105 | aki test 106 | aki .myfile.aki{REP} 107 | 108 | On the command line, the initial dot sign can be replaced with a double dash: 109 | 110 | {CMD}aki --test 111 | aki --myfile.aki{REP} 112 | """ 113 | 114 | 115 | def cp(string): 116 | print(f"{REP}{string}") 117 | 118 | 119 | class Repl: 120 | VERSION = f"""Python :{sys.version} 121 | LLVM :{".".join((str(n) for n in binding.llvm_version_info))} 122 | pyaki :{constants.VERSION}""" 123 | 124 | def __init__(self, typemgr=None): 125 | self.reset(silent=True, typemgr=typemgr) 126 | 127 | def make_module(self, name, typemgr=None): 128 | if typemgr is None: 129 | typemgr = self.typemgr 130 | mod = ir.Module(name) 131 | mod.triple = binding.Target.from_default_triple().triple 132 | other_modules = [] 133 | if name != "stdlib": 134 | other_modules.append(self.stdlib_module) 135 | mod.codegen = AkiCodeGen(mod, typemgr, name, other_modules) 136 | return mod 137 | 138 | def compile_stdlib(self): 139 | stdlib_path = os.path.join(self.paths["stdlib"], "nt") 140 | stdlib = [] 141 | 142 | for _ in ("0", "1"): 143 | with open(os.path.join(stdlib_path, f"layer_{_}.aki")) as f: 144 | stdlib.append(f.read()) 145 | 146 | text = "\n".join(stdlib) 147 | ast = AkiParser.parse(text) 148 | 149 | self.dump_ast(os.path.join(stdlib_path, "stdlib.akic"), ast, text) 150 | 151 | return ast, text 152 | 153 | def load_stdlib(self): 154 | stdlib_path = os.path.join(self.paths["stdlib"], "nt") 155 | 156 | if not os.path.exists(os.path.join(stdlib_path, "stdlib.akic")): 157 | cp("Compiling stdlib") 158 | ast, text = self.compile_stdlib() 159 | else: 160 | with open(os.path.join(stdlib_path, "stdlib.akic"), "rb") as file: 161 | mod_in = pickle.load(file) 162 | ast, text = mod_in["ast"], mod_in["text"] 163 | 164 | self.stdlib_module = self.make_module("stdlib") 165 | 166 | codegen = AkiCodeGen(self.stdlib_module, module_name="stdlib").eval(ast) 167 | self.stdlib_module_ref = self.compiler.compile_module( 168 | self.stdlib_module, "stdlib" 169 | ) 170 | 171 | def run(self, initial_load=False): 172 | if initial_load: 173 | self.compile_stdlib() 174 | import shutil 175 | 176 | cols = shutil.get_terminal_size()[0] 177 | if cols < 80: 178 | warn = f"\n{RED}Terminal is less than 80 colums wide.\nOutput may not be correctly formatted." 179 | else: 180 | warn = "" 181 | 182 | cp( 183 | f"{GRN}{constants.WELCOME}{warn}\n{REP}Type {CMD}.help{REP} or a command to be interpreted" 184 | ) 185 | while True: 186 | try: 187 | print(f"{REP}{PROMPT}{CMD}", end="") 188 | text = input() 189 | self.cmd(text) 190 | except AkiBaseErr as e: 191 | print(e) 192 | except EOFError: 193 | break 194 | 195 | def cmd(self, text): 196 | if not text: 197 | return 198 | if text[0] == ".": 199 | if len(text) == 1: 200 | self.help() 201 | return 202 | if text[-1] == ".": 203 | if len(text) == 2: 204 | return self.reload() 205 | text = text[1:-1] 206 | self.load_file(text) 207 | return 208 | command = text[1:] 209 | else: 210 | print(f"{REP}", end="") 211 | for _ in self.interactive(text): 212 | print(_) 213 | return 214 | 215 | cmd_split = command.split(" ") 216 | cmd_name = cmd_split[0] 217 | if len(cmd_split) > 1: 218 | params = cmd_split[1:] 219 | else: 220 | params = None 221 | 222 | cmd_func = self.cmds.get(cmd_name) 223 | 224 | if cmd_func is None: 225 | cp(f'Unrecognized command "{CMD}{command}{REP}"') 226 | return 227 | 228 | return cmd_func(self, text, params=params) 229 | 230 | def dump_ast(self, filename, ast, text): 231 | with open(filename, "wb") as file: 232 | output = {"version": constants.VERSION, "ast": ast, "text": text} 233 | pickle.dump(output, file) 234 | 235 | def load_file(self, file_to_load, file_path=None, ignore_cache=False): 236 | 237 | if file_path is None: 238 | file_path = self.paths["source_dir"] 239 | 240 | # reset 241 | self.main_module = self.make_module(None) 242 | 243 | filepath = os.path.join(file_path, f"{file_to_load}.aki") 244 | self.last_file_loaded = file_to_load 245 | cache_path = f"{file_path}/__akic__/" 246 | 247 | # Attempt to load precomputed module from cache 248 | 249 | if self.settings["ignore_cache"] is True: 250 | ignore_cache = True 251 | 252 | if not ignore_cache: 253 | 254 | force_recompilation = False 255 | cache_file = f"{file_to_load}.akic" 256 | bitcode_file = f"{file_to_load}.akib" 257 | full_cache_path = cache_path + cache_file 258 | 259 | if not os.path.exists(full_cache_path): 260 | force_recompilation = True 261 | elif os.path.getmtime(filepath) > os.path.getmtime(full_cache_path): 262 | force_recompilation = True 263 | 264 | if not force_recompilation: 265 | try: 266 | with open(full_cache_path, "rb") as file: 267 | 268 | with Timer() as t1: 269 | mod_in = pickle.load(file) 270 | if mod_in["version"] != constants.VERSION: 271 | raise LocalException 272 | 273 | file_size = os.fstat(file.fileno()).st_size 274 | 275 | cp(f"Loaded {file_size} bytes from {CMD}{full_cache_path}{REP}") 276 | cp(f" Parse: {t1.time:.3f} sec") 277 | 278 | ast = mod_in["ast"] 279 | text = mod_in["text"] 280 | 281 | self.main_module.codegen.text = text 282 | 283 | with Timer() as t2: 284 | try: 285 | self.main_module.codegen.eval(ast) 286 | except Exception as e: 287 | for _ in ("l", "b"): 288 | del_path = cache_path + file_to_load + f".aki{_}" 289 | if os.path.exists(del_path): 290 | os.remove(del_path) 291 | self.main_module = self.make_module(None) 292 | raise e 293 | 294 | cp(f" Eval: {t2.time:.3f} sec") 295 | 296 | with Timer() as t3: 297 | 298 | self.main_ref = self.compiler.compile_module( 299 | self.main_module, file_to_load 300 | ) 301 | 302 | cp(f"Compile: {t3.time:.3f} sec") 303 | cp(f" Total: {t1.time+t2.time+t3.time:.3f} sec") 304 | 305 | return 306 | except LocalException: 307 | pass 308 | except Exception as e: 309 | cp(f"Error reading cached file: {e}") 310 | 311 | # If no cache, or cache failed, 312 | # load original file and compile from scratch 313 | 314 | try: 315 | with open(filepath) as file: 316 | text = file.read() 317 | file_size = os.fstat(file.fileno()).st_size 318 | except FileNotFoundError: 319 | raise AkiBaseErr( 320 | None, file_to_load, f"File not found: {CMD}{filepath}{REP}" 321 | ) 322 | 323 | with Timer() as t1: 324 | ast = AkiParser.parse(text) 325 | 326 | cp(f"Loaded {file_size} bytes from {CMD}{filepath}{REP}") 327 | cp(f" Parse: {t1.time:.3f} sec") 328 | 329 | # Write cached AST to file 330 | 331 | self.main_module.codegen.text = text 332 | 333 | if not ignore_cache and self.settings["cache_compilation"] == True: 334 | 335 | try: 336 | if not os.path.exists(cache_path): 337 | os.makedirs(cache_path) 338 | self.dump_ast(full_cache_path, ast, text) 339 | except LocalException: 340 | cp("Can't write cache file") 341 | os.remove(cache_path) 342 | 343 | with Timer() as t2: 344 | try: 345 | self.main_module.codegen.eval(ast) 346 | except Exception as e: 347 | for _ in ("l", "b"): 348 | del_path = cache_path + file_to_load + f".aki{_}" 349 | if os.path.exists(del_path): 350 | os.remove(del_path) 351 | self.main_module = self.make_module(None) 352 | raise e 353 | 354 | cp(f" Eval: {t2.time:.3f} sec") 355 | 356 | with Timer() as t3: 357 | 358 | try: 359 | self.main_ref = self.compiler.compile_module( 360 | self.main_module, file_to_load 361 | ) 362 | except Exception as e: 363 | for _ in ("l", "b"): 364 | del_path = cache_path + file_to_load + f".aki{_}" 365 | if os.path.exists(del_path): 366 | os.remove(del_path) 367 | self.main_module = self.make_module(None) 368 | raise e 369 | 370 | cp(f"Compile: {t3.time:.3f} sec") 371 | cp(f" Total: {t1.time+t2.time+t3.time:.3f} sec") 372 | 373 | # write compiled bitcode and IR 374 | # We will eventually reuse bitcode when it's appropriate 375 | 376 | if not ignore_cache: 377 | with open(cache_path + file_to_load + ".akil", "w") as file: 378 | file.write(str(self.main_module)) 379 | with open(cache_path + file_to_load + ".akib", "wb") as file: 380 | file.write(self.compiler.mod_ref.as_bitcode()) 381 | 382 | def interactive(self, text, immediate_mode=False): 383 | # Immediate mode processes everything in the repl compiler. 384 | # Nothing is retained. 385 | # Regular mode only uses the REPL to launch things. 386 | # But its typemgr is the same as the main repl. 387 | 388 | if immediate_mode: 389 | main_file = None 390 | repl_file = None 391 | self.typemgr = AkiTypeMgr() 392 | self.main_module = self.make_module(None) 393 | self.repl_module = self.make_module(".repl") 394 | main = self.repl_module 395 | else: 396 | main = self.main_module 397 | main_file = "main" 398 | repl_file = "repl" 399 | self.repl_module = self.make_module(".repl") 400 | 401 | # Tokenize input 402 | 403 | ast = AkiParser.parse(text) 404 | self.repl_module.codegen.text = text 405 | 406 | # Iterate through AST tokens. 407 | # When we encounter an expression node, 408 | # we add it to a stack of expression nodes. 409 | # When we encounter a top-level command, 410 | # we compile it immediately, and don't add it 411 | # to the stack. 412 | 413 | ast_stack = [] 414 | 415 | for _ in ast: 416 | 417 | if isinstance(_, TopLevel): 418 | main.codegen.eval([_]) 419 | self.main_ref = self.compiler.compile_module(main, main_file) 420 | continue 421 | 422 | ast_stack.append(_) 423 | 424 | # When we come to the end of the node list, 425 | # we take the expression node stack, turn it into 426 | # an anonymous function, and execute it. 427 | 428 | if ast_stack: 429 | 430 | res, return_type = self.anonymous_function( 431 | ast_stack, repl_file, immediate_mode 432 | ) 433 | 434 | yield return_type.format_result(res) 435 | 436 | def anonymous_function( 437 | self, ast_stack, repl_file, immediate_mode=False, call_name_prefix="_ANONYMOUS_" 438 | ): 439 | 440 | _ = ast_stack[-1] 441 | 442 | self.anon_counter += 1 443 | 444 | call_name = f"{call_name_prefix}{self.anon_counter}" 445 | proto = Prototype(_.index, call_name, (), None) 446 | func = Function(_.index, proto, ExpressionBlock(_.index, ast_stack)) 447 | 448 | if not immediate_mode: 449 | for k, v in self.main_module.codegen.module.globals.items(): 450 | if isinstance(v, ir.GlobalVariable): 451 | self.repl_module.codegen.module.globals[k] = v 452 | else: 453 | f_ = External(None, v.akinode, None) 454 | self.repl_module.codegen.eval([f_]) 455 | 456 | try: 457 | self.repl_module.codegen.eval([func]) 458 | except AkiBaseErr as e: 459 | # if not immediate_mode: 460 | # self.repl_cpl.codegen.other_modules.pop() 461 | del self.repl_module.globals[call_name] 462 | raise e 463 | 464 | first_result_type = self.repl_module.globals[call_name].return_value.akitype 465 | 466 | # If the result from the codegen is an object, 467 | # redo the codegen with an addition to the AST stack 468 | # that extracts the c_data value. 469 | 470 | # TODO: This should not be `c_data`, as it does not work 471 | # for things like arrays. It should be some other method 472 | # that extracts something appropriate. Maybe `repl_result` 473 | 474 | if isinstance(first_result_type, AkiObject): 475 | _ = ast_stack.pop() 476 | ast_stack = [] 477 | 478 | ast_stack.append( 479 | Call(_.index, "c_data", (Call(_.index, call_name, (), None),), None) 480 | ) 481 | 482 | call_name += "_WRAP" 483 | proto = Prototype(_.index, call_name, (), None) 484 | func = Function(_.index, proto, ExpressionBlock(_.index, ast_stack)) 485 | 486 | # It's unlikely that this codegen will ever throw an error, 487 | # but I'm keeping this anyway 488 | 489 | try: 490 | self.repl_module.codegen.eval([func]) 491 | except AkiBaseErr as e: 492 | del self.repl_module.module.globals[call_name] 493 | raise e 494 | 495 | final_result_type = self.repl_module.globals[call_name].return_value.akitype 496 | 497 | else: 498 | final_result_type = first_result_type 499 | 500 | self.repl_ref = self.compiler.compile_module(self.repl_module, "repl") 501 | 502 | # Retrieve a pointer to the function to execute 503 | func_ptr = self.compiler.get_addr(call_name) 504 | 505 | return_type = first_result_type 506 | return_type_ctype = final_result_type.c() 507 | 508 | # Get the function signature 509 | func_signature = [ 510 | _.aki.vartype.aki_type.c() for _ in self.repl_module.globals[call_name].args 511 | ] 512 | 513 | # Generate a result 514 | cfunc = ctypes.CFUNCTYPE(return_type_ctype, *[])(func_ptr) 515 | res = cfunc() 516 | 517 | return res, return_type 518 | 519 | def run_tests(self, *a, **ka): 520 | print(f"{REP}", end="") 521 | import unittest 522 | 523 | tests = unittest.defaultTestLoader.discover("tests", "*.py") 524 | unittest.TextTestRunner().run(tests) 525 | 526 | def quit(self, *a, **ka): 527 | print(XX) 528 | raise QuitException 529 | 530 | def reload(self, *a, **ka): 531 | print(XX) 532 | raise ReloadException 533 | 534 | def help(self, *a, **ka): 535 | cp(f"\n{USAGE}") 536 | 537 | def about(self, *a, **ka): 538 | print(f"\n{GRN}{constants.ABOUT}\n\n{self.VERSION}\n") 539 | 540 | def not_implemented(self, *a, **ka): 541 | cp(f"{RED}Not implemented yet") 542 | 543 | def version(self, *a, **ka): 544 | print(f"\n{GRN}{self.VERSION}\n") 545 | 546 | def reset(self, *a, typemgr=None, **ka): 547 | """ 548 | Reset the REPL and all of its objects. 549 | """ 550 | defaults = constants.defaults() 551 | self.paths = defaults["paths"] 552 | self.settings = {} 553 | self.settings_data = defaults["settings"] 554 | for k, v in self.settings_data.items(): 555 | self.settings[k] = v[1] 556 | 557 | if typemgr is not None: 558 | self.typemgr = typemgr 559 | else: 560 | self.typemgr = AkiTypeMgr() 561 | self.types = self.typemgr.types 562 | 563 | self.compiler = AkiCompiler() 564 | self.load_stdlib() 565 | self.main_module = self.make_module(None) 566 | self.repl_module = self.make_module(".repl") 567 | 568 | self.anon_counter = 0 569 | 570 | self.last_file_loaded = None 571 | if not "silent" in ka: 572 | cp(f"{RED}Workspace reset") 573 | 574 | def run_main(self, *a, **ka): 575 | self.cmd("main()") 576 | 577 | def dump(self, *a, params, **ka): 578 | if params is not None and len(params) > 0: 579 | function = params[0] 580 | else: 581 | function = None 582 | 583 | if function: 584 | to_print = self.main_module.globals.get(function, None) 585 | else: 586 | to_print = self.main_module 587 | 588 | if not to_print: 589 | cp("Function not found") 590 | else: 591 | cp(str(to_print)) 592 | 593 | def reload_file(self, *a, **ka): 594 | if self.last_file_loaded is None: 595 | cp("No file history to load") 596 | return 597 | self.load_file(self.last_file_loaded) 598 | 599 | cmds = { 600 | "t": run_tests, 601 | "q": quit, 602 | ".": reload, 603 | "ab": about, 604 | "about": about, 605 | "compile": not_implemented, 606 | "cp": not_implemented, 607 | "dump": dump, 608 | "dp": dump, 609 | "exit": quit, 610 | "quit": quit, 611 | "stop": quit, 612 | "q": quit, 613 | "export": not_implemented, 614 | "ex": not_implemented, 615 | "help": help, 616 | "?": help, 617 | "rerun": not_implemented, 618 | "rl": reload_file, 619 | "rlc": not_implemented, 620 | "rlr": not_implemented, 621 | "reset": reset, 622 | "~": reset, 623 | "run": run_main, 624 | "r": run_main, 625 | "test": run_tests, 626 | "version": version, 627 | "ver": version, 628 | "v": version, 629 | } 630 | 631 | -------------------------------------------------------------------------------- /aki/core/akitypes.py: -------------------------------------------------------------------------------- 1 | from llvmlite.ir import types 2 | from llvmlite import ir, binding 3 | import ctypes 4 | from core.astree import Constant, IfExpr, BinOp, VarTypeName, LLVMNode, String, Name 5 | from typing import Optional 6 | from core.error import AkiTypeErr, AkiSyntaxErr 7 | 8 | 9 | def _int(value: int): 10 | return ir.Constant(ir.IntType(32), value) 11 | 12 | 13 | class AkiType: 14 | """ 15 | Base type for all Aki types. 16 | """ 17 | 18 | llvm_type: ir.Type 19 | base_type: Optional["AkiType"] 20 | type_id: Optional[str] = None 21 | enum_id: Optional[int] = None 22 | comp_ins: Optional[str] = None 23 | original_function: Optional[ir.Function] = None 24 | literal_ptr: bool = False 25 | bits: int = 0 26 | 27 | comp_ops = { 28 | "==": ".eqop", 29 | "!=": ".neqop", 30 | "<=": ".leqop", 31 | ">=": ".geqop", 32 | "<": ".ltop", 33 | ">": ".gtop", 34 | } 35 | 36 | c_ref = { 37 | True: { 38 | 8: ctypes.c_byte, 39 | 16: ctypes.c_int16, 40 | 32: ctypes.c_int32, 41 | 64: ctypes.c_int64, 42 | }, 43 | False: { 44 | 1: ctypes.c_bool, 45 | 8: ctypes.c_ubyte, 46 | 16: ctypes.c_uint16, 47 | 32: ctypes.c_uint32, 48 | 64: ctypes.c_uint64, 49 | }, 50 | } 51 | 52 | def __str__(self): 53 | return f":{self.type_id}" 54 | 55 | def __eq__(self, other): 56 | return self.type_id == other.type_id 57 | 58 | def c(self): 59 | """ 60 | Return the ctypes representation of this type. 61 | """ 62 | return self.c_ref[self.signed][self.bits] 63 | 64 | def format_result(self, result): 65 | """ 66 | Return a __str__-compatible representation for this result, 67 | as used in the REPL. 68 | """ 69 | return result 70 | 71 | def c_data(self, codegen, node): 72 | return node 73 | 74 | def c_size(self, codegen, node, llvm_obj): 75 | size = llvm_obj.type.get_abi_size(codegen.typemgr.target_data()) 76 | return codegen._codegen(Constant(node, size, codegen.types["u_size"])) 77 | 78 | 79 | class AkiTypeRef(AkiType): 80 | """ 81 | Type reference type (essentially, an enum). 82 | """ 83 | 84 | comp_ops = {"==": ".eqop", "!=": ".neqop"} 85 | comp_ins = "icmp_unsigned" 86 | 87 | def __init__(self, module): 88 | self.module = module 89 | self.bits = 64 90 | self.llvm_type = types.IntType(self.bits) 91 | self.signed = False 92 | self.type_id = "type" 93 | self._default = 0 94 | 95 | def format_result(self, result): 96 | return f"" 97 | 98 | def default(self, codegen, node): 99 | return self._default 100 | 101 | 102 | class AkiPointer(AkiType): 103 | """ 104 | Takes in an Aki type reference, 105 | and returns a pointer of that type, 106 | """ 107 | 108 | signed = False 109 | llvm_type: Optional[ir.Type] = None 110 | 111 | comp_ops = {"==": ".eqop", "!=": ".neqop"} 112 | comp_ins = "icmp_unsigned" 113 | 114 | def __init__(self, module: ir.Module): 115 | self.module = module 116 | self.bits = module.typemgr._pointer_width 117 | 118 | def default(self, codegen, node): 119 | # Null value for pointer 120 | return None 121 | 122 | def new(self, base_type: AkiType, literal_ptr=False): 123 | """ 124 | Create a new pointer type from an existing type. 125 | """ 126 | 127 | new = AkiPointer(self.module) 128 | new.base_type = base_type 129 | new.llvm_type = base_type.llvm_type.as_pointer() 130 | new.type_id = f"ptr {base_type.type_id}" 131 | new.literal_ptr = literal_ptr 132 | return new 133 | 134 | def format_result(self, result): 135 | return f"<{self.type_id} @ {hex(result)}>" 136 | 137 | 138 | class AkiObject(AkiType): 139 | """ 140 | Type for objects in Aki. This is essentially a header, 141 | with the actual object in the following structure: 142 | [ 143 | [header], 144 | [rest of object] 145 | ] 146 | 147 | """ 148 | 149 | OBJECT_TYPE = 0 150 | LENGTH = 1 151 | IS_ALLOCATED = 2 152 | REFCOUNT = 3 153 | 154 | def __init__(self, module: ir.Module): 155 | self.module = module 156 | self.llvm_type = module.context.get_identified_type(".object") 157 | self.llvm_type.elements = [ 158 | # Type of object (enum) 159 | module.types["u_size"].llvm_type, 160 | # Length of object data, minus header 161 | module.types["u_size"].llvm_type, 162 | # Was this object allocated from the heap? 163 | module.types["bool"].llvm_type, 164 | # refcount for object, not used yet 165 | module.types["u_size"].llvm_type, 166 | ] 167 | # TODO: Have a dummy pointer at end that we use to calculate total size? 168 | 169 | # self.llvm_type.packed=True 170 | 171 | def new(self): 172 | pass 173 | 174 | 175 | class AkiFunction(AkiObject): 176 | """ 177 | Type for function pointers in Aki. 178 | """ 179 | 180 | signed = False 181 | 182 | def __init__(self, arguments: list, return_type: AkiType): 183 | # list of decorated AkiType nodes 184 | self.arguments = arguments 185 | # single decorated AkiType node 186 | self.return_type = return_type 187 | 188 | self.llvm_type = ir.PointerType( 189 | ir.FunctionType( 190 | self.return_type.llvm_type, [_.llvm_type for _ in self.arguments] 191 | ) 192 | ) 193 | self.type_id = f'func({",".join([str(_.akitype)[1:] for _ in self.arguments])}){self.return_type}' 194 | self.name = self.type_id 195 | 196 | def c(self): 197 | return ctypes.c_void_p 198 | 199 | def default(self, codegen, node): 200 | return None 201 | 202 | def format_result(self, result): 203 | if result is None: 204 | result = 0 205 | return f"" 206 | 207 | 208 | class AkiIntBoolMathOps: 209 | """ 210 | Math operations shared between integer and boolean types. 211 | """ 212 | 213 | bin_ops = { 214 | "+": "add", 215 | "-": "sub", 216 | "*": "mul", 217 | "/": "div", 218 | "&": "bin_and", 219 | "|": "bin_or", 220 | "and": "andor", 221 | "or": "andor", 222 | "%": "mod", 223 | } 224 | 225 | def binop_mod(self, codegen, node, lhs, rhs, op_name): 226 | f1 = codegen.builder.sdiv(lhs, rhs, f".{op_name}1") 227 | f2 = codegen.builder.mul(f1, rhs, f".{op_name}2") 228 | return codegen.builder.sub(lhs, f2, f".{op_name}3") 229 | 230 | def binop_add(self, codegen, node, lhs, rhs, op_name): 231 | return codegen.builder.add(lhs, rhs, f".{op_name}") 232 | 233 | def binop_sub(self, codegen, node, lhs, rhs, op_name): 234 | return codegen.builder.sub(lhs, rhs, f".{op_name}") 235 | 236 | def binop_mul(self, codegen, node, lhs, rhs, op_name): 237 | return codegen.builder.mul(lhs, rhs, f".{op_name}") 238 | 239 | def binop_div(self, codegen, node, lhs, rhs, op_name): 240 | return codegen.builder.sdiv(lhs, rhs, f".{op_name}") 241 | 242 | def binop_bin_and(self, codegen, node, lhs, rhs, op_name): 243 | return codegen.builder.and_(lhs, rhs, f".{op_name}") 244 | 245 | def binop_bin_or(self, codegen, node, lhs, rhs, op_name): 246 | return codegen.builder.or_(lhs, rhs, f".{op_name}") 247 | 248 | def binop_andor(self, codegen, node, lhs, rhs, op_name): 249 | # Handles both and and or operations 250 | 251 | if not isinstance(lhs.akitype, AkiBool): 252 | lhs_x = codegen._scalar_as_bool(node.lhs, lhs) 253 | else: 254 | lhs_x = lhs 255 | 256 | if not isinstance(rhs.akitype, AkiBool): 257 | rhs_x = codegen._scalar_as_bool(node.rhs, rhs) 258 | else: 259 | rhs_x = rhs 260 | 261 | lhs_x = LLVMNode(node.lhs, VarTypeName(node.lhs, lhs.akitype.type_id), lhs_x) 262 | rhs_x = LLVMNode(node.rhs, VarTypeName(node.rhs, rhs.akitype.type_id), rhs_x) 263 | 264 | if node.op == "and": 265 | result_test = BinOp(node, "&", lhs_x, rhs_x) 266 | true_result = LLVMNode( 267 | node.rhs, VarTypeName(node.rhs, rhs.akitype.type_id), rhs 268 | ) 269 | false_result = Constant( 270 | node, 271 | rhs.akitype.default(self, node.rhs), 272 | VarTypeName(node, rhs.akitype.type_id), 273 | ) 274 | 275 | result = IfExpr(node, result_test, true_result, false_result) 276 | 277 | else: 278 | 279 | # first, test if both values are false 280 | first_result_test = BinOp(node, "|", lhs_x, rhs_x) 281 | # if so, return 0 282 | first_false_result = Constant( 283 | node, 284 | lhs.akitype.default(self, node.lhs), 285 | VarTypeName(node, lhs.akitype.type_id), 286 | ) 287 | # if not, return the value 288 | first_true_result = LLVMNode( 289 | node.lhs, VarTypeName(node.lhs, lhs.akitype.type_id), lhs 290 | ) 291 | 292 | first_result = IfExpr( 293 | node, first_result_test, first_true_result, first_false_result 294 | ) 295 | 296 | # next, test if the lhs result is nonzero 297 | second_result_test = BinOp(node, "|", first_result, first_false_result) 298 | 299 | # if so, return that value 300 | second_true_result = first_true_result 301 | 302 | # if not, return the rhs 303 | second_false_result = LLVMNode( 304 | node.rhs, VarTypeName(node.rhs, rhs.akitype.type_id), rhs 305 | ) 306 | 307 | result = IfExpr( 308 | node, second_result_test, second_true_result, second_false_result 309 | ) 310 | 311 | result = codegen._codegen(result) 312 | 313 | return result 314 | 315 | 316 | class AkiBaseInt(AkiType, AkiIntBoolMathOps): 317 | """ 318 | Base type for integers. 319 | """ 320 | 321 | def __init__(self, bits, signed): 322 | self.bits = bits 323 | self.llvm_type = types.IntType(bits) 324 | self.signed = signed 325 | self.type_id = f'{"i" if signed else "u"}{bits}' 326 | 327 | def default(self, codegen, node): 328 | return 0 329 | 330 | 331 | class AkiBool(AkiType, AkiIntBoolMathOps): 332 | """ 333 | Aki Boolean type. Bit-compatible, but not type-compatible, 334 | with an i1. 335 | """ 336 | 337 | comp_ops = {"==": ".eqop", "!=": ".neqop"} 338 | comp_ins = "icmp_unsigned" 339 | 340 | def __init__(self): 341 | self.bits = 1 342 | self.llvm_type = types.IntType(1) 343 | self.signed = False 344 | self.type_id = "bool" 345 | 346 | def default(self, codegen, node): 347 | return False 348 | 349 | def unop_neg(self, codegen, node, operand): 350 | lhs = codegen._codegen(Constant(node, 1, operand.akitype)) 351 | return codegen.builder.xor(lhs, operand, "bnegop") 352 | 353 | def format_result(self, result): 354 | return True if result else False 355 | 356 | 357 | class AkiInt(AkiBaseInt): 358 | """ 359 | Signed Aki integer type. 360 | """ 361 | 362 | comp_ins = "icmp_signed" 363 | 364 | def __init__(self, bits): 365 | super().__init__(bits, True) 366 | 367 | def unop_neg(self, codegen, node, operand): 368 | lhs = codegen._codegen(Constant(node, 0, operand.akitype)) 369 | return codegen.builder.sub(lhs, operand, "negop") 370 | 371 | 372 | class AkiUnsignedInt(AkiBaseInt): 373 | """ 374 | Unsigned Aki integer type. 375 | """ 376 | 377 | comp_ins = "icmp_unsigned" 378 | 379 | def __init__(self, bits): 380 | super().__init__(bits, False) 381 | 382 | 383 | class AkiBaseFloat(AkiType): 384 | """ 385 | Base type for floating-point Aki types. 386 | """ 387 | 388 | bin_ops = {"+": "add", "-": "sub", "*": "mul", "/": "div"} 389 | 390 | def binop_add(self, codegen, node, lhs, rhs, op_name): 391 | return codegen.builder.fadd(lhs, rhs, f".f{op_name}") 392 | 393 | def binop_sub(self, codegen, node, lhs, rhs, op_name): 394 | return codegen.builder.fsub(lhs, rhs, f".f{op_name}") 395 | 396 | def binop_mul(self, codegen, node, lhs, rhs, op_name): 397 | return codegen.builder.fmul(lhs, rhs, f".f{op_name}") 398 | 399 | def binop_div(self, codegen, node, lhs, rhs, op_name): 400 | return codegen.builder.fdiv(lhs, rhs, f".f{op_name}") 401 | 402 | def unop_neg(self, codegen, node, operand): 403 | lhs = codegen._codegen( 404 | Constant(node, 0.0, VarTypeName(node, operand.akitype.type_id)) 405 | ) 406 | return codegen.builder.fsub(lhs, operand, "fnegop") 407 | 408 | signed = True 409 | comp_ins = "fcmp_ordered" 410 | 411 | def default(self, codegen, node): 412 | return 0.0 413 | 414 | 415 | class AkiFloat(AkiBaseFloat): 416 | """ 417 | 32-bit floating point Aki type. 418 | """ 419 | 420 | def __init__(self): 421 | super().__init__() 422 | self.llvm_type = types.FloatType() 423 | self.type_id = f"f32" 424 | 425 | def c(self): 426 | return ctypes.c_float 427 | 428 | 429 | class AkiDouble(AkiBaseFloat): 430 | """ 431 | 64-bit floating point Aki type. 432 | """ 433 | 434 | def __init__(self): 435 | super().__init__() 436 | self.llvm_type = types.DoubleType() 437 | self.type_id = f"f64" 438 | 439 | def c(self): 440 | return ctypes.c_double 441 | 442 | 443 | class AkiArray(AkiObject, AkiType): 444 | """ 445 | Aki array type. 446 | This is a raw array, not managed. 447 | Arrays should not be stack-allocated. 448 | """ 449 | 450 | signed = None 451 | name = None 452 | 453 | def __init__(self, module): 454 | self.module = module 455 | 456 | def new(self, codegen, node, base_type: AkiType, accessors: list): 457 | new = AkiArray(codegen.module) 458 | 459 | array_type = base_type.llvm_type 460 | array_type.akitype = base_type 461 | array_type.akinode = node 462 | 463 | array_aki_type = base_type 464 | subaccessors = [] 465 | 466 | for _ in reversed(accessors): 467 | accessor_dimension = None 468 | if isinstance(_, Constant): 469 | accessor_dimension = _.val 470 | elif isinstance(_, Name): 471 | 472 | # try: 473 | # t_val = codegen.eval_to_result(node,[_]) 474 | # except Exception as e: 475 | # print("err", e) 476 | 477 | name_val = codegen._name(node, _.name) 478 | try: 479 | accessor_dimension = name_val.initializer.constant 480 | except Exception: 481 | pass 482 | 483 | if not accessor_dimension: 484 | raise AkiSyntaxErr( 485 | _, 486 | codegen.text, 487 | f"Only constants (not computed values) allowed for array dimensions", 488 | ) 489 | 490 | array_type = ir.ArrayType(array_type, accessor_dimension) 491 | subaccessors.append(accessor_dimension) 492 | 493 | subakitype = AkiArray(codegen.module) 494 | subakitype.llvm_type = array_type 495 | subakitype.type_id = ( 496 | f"array({base_type})[{','.join([str(_) for _ in subaccessors])}]" 497 | ) 498 | 499 | array_type.akitype = subakitype 500 | array_type.akinode = node 501 | 502 | new.llvm_type = array_type 503 | new.type_id = f"array({base_type})[{','.join([str(_) for _ in subaccessors])}]" 504 | 505 | codegen.typemgr.add_type(new.type_id, new, codegen.module) 506 | return new 507 | 508 | def default(self, codegen, node): 509 | return None 510 | 511 | def op_index(self, codegen, node, expr): 512 | current = expr 513 | akitype_loc = current.type.pointee 514 | indices = [_int(0)] 515 | for _ in node.accessors.accessors: 516 | akitype_loc = akitype_loc.element 517 | akitype = akitype_loc.akitype 518 | index = codegen._codegen(_) 519 | indices.append(index) 520 | result = codegen.builder.gep(current, indices) 521 | result.akitype = akitype 522 | result.akinode = node 523 | return result 524 | 525 | def c_data(self, codegen, node): 526 | obj_ptr = codegen.builder.bitcast( 527 | node, codegen.types["u_mem"].llvm_type.as_pointer(0) 528 | ) 529 | obj_ptr.akitype = codegen.typemgr.as_ptr(codegen.types["u_mem"]) 530 | obj_ptr.akinode = node.akinode 531 | return obj_ptr 532 | 533 | 534 | class AkiString(AkiObject, AkiType): 535 | """ 536 | Type for Aki string constants. 537 | 538 | """ 539 | 540 | signed = False 541 | type_id = "str" 542 | 543 | bin_ops = {"+": "add"} 544 | 545 | def __init__(self, module): 546 | self.module = module 547 | self.llvm_type_base = module.context.get_identified_type(".str") 548 | self.llvm_type_base.elements = [ 549 | # Header block 550 | module.types["obj"].llvm_type, 551 | # Pointer to data 552 | module.typemgr.as_ptr(module.types["u_mem"]).llvm_type, 553 | ] 554 | 555 | # TODO: we may later use a zero length array as the type, 556 | # so that we can encode the data directly into the block 557 | # instead of following a pointer 558 | 559 | self.llvm_type = ir.PointerType(self.llvm_type_base) 560 | 561 | def c(self): 562 | return ctypes.c_void_p 563 | 564 | def default(self, codegen, node): 565 | null_str = codegen._codegen(String(node, "", None)).get_reference() 566 | return null_str 567 | 568 | def binop_add(self, codegen, node, lhs, rhs, op_name): 569 | pass 570 | # extract lengths of each string 571 | # c_size 572 | # compute new string length 573 | # (check for overflow?) 574 | # (later when we have result types) 575 | # allocate new header 576 | # malloc 577 | # set details 578 | # 579 | # allocate new string body 580 | # malloc 581 | # store string data 582 | # = 583 | 584 | def format_result(self, result): 585 | char_p = ctypes.POINTER(ctypes.c_char_p) 586 | result1 = ctypes.cast(result, char_p) 587 | result2 = f'"{str(ctypes.string_at(result1),"utf8")}"' 588 | return result2 589 | 590 | def data(self, text): 591 | data = bytearray((text + "\x00").encode("utf8")) 592 | data_array = ir.ArrayType(self.module.types["byte"].llvm_type, len(data)) 593 | return data, data_array 594 | 595 | def c_data(self, codegen, node): 596 | obj_ptr = codegen.builder.gep(node, [_int(0), _int(1)]) 597 | obj_ptr = codegen.builder.load(obj_ptr) 598 | obj_ptr.akitype = codegen.typemgr.as_ptr(codegen.types["u_mem"]) 599 | obj_ptr.akinode = node.akinode 600 | return obj_ptr 601 | 602 | def c_size(self, codegen, node, llvm_obj): 603 | obj_ptr = codegen.builder.gep( 604 | llvm_obj, [_int(0), _int(0), _int(AkiObject.LENGTH)] 605 | ) 606 | obj_ptr = codegen.builder.load(obj_ptr) 607 | obj_ptr.akitype = codegen.types["u_size"] 608 | obj_ptr.akinode = llvm_obj.akinode 609 | return obj_ptr 610 | 611 | 612 | class AkiTypeMgr: 613 | 614 | # These do not rely on any particular architecture, 615 | # and so are set once and never changed. 616 | 617 | base_types = { 618 | "bool": AkiBool(), 619 | "i1": AkiInt(1), 620 | "i8": AkiInt(8), 621 | "i16": AkiInt(16), 622 | "i32": AkiInt(32), 623 | "i64": AkiInt(64), 624 | "int": AkiInt(32), 625 | "u8": AkiUnsignedInt(8), 626 | "u16": AkiUnsignedInt(16), 627 | "u32": AkiUnsignedInt(32), 628 | "u64": AkiUnsignedInt(64), 629 | "uint": AkiUnsignedInt(32), 630 | "f32": AkiFloat(), 631 | "f64": AkiDouble(), 632 | "float": AkiDouble(), 633 | "func": AkiFunction, 634 | } 635 | 636 | def __init__(self, module: Optional[ir.Module] = None, bytesize=8): 637 | if module is None: 638 | module = ir.Module() 639 | module.triple = binding.Target.from_default_triple().triple 640 | 641 | # Set internal module reference so types can access each other 642 | 643 | # TODO: we might want to set these in the module, 644 | # back-decorating is an antipattern 645 | 646 | module.typemgr = self 647 | 648 | self.module = module 649 | 650 | # Obtain pointer size from LLVM target 651 | self._byte_width = ir.PointerType(ir.IntType(bytesize)).get_abi_size( 652 | self.target_data() 653 | ) 654 | self._pointer_width = self._byte_width * bytesize 655 | 656 | self.reset() 657 | 658 | self.const_enum = 0 659 | 660 | # Do not move this, otherwise we can't serialize the type mgr 661 | def target_data(self): 662 | return binding.create_target_data(self.module.data_layout) 663 | 664 | def reset(self): 665 | # Initialize the type map from the base type list, 666 | # which never changes 667 | 668 | self.custom_types = {} 669 | 670 | self.enum_id_ctr = 0 671 | self.enum_ids = {} 672 | 673 | self.types = dict(self.base_types) 674 | self.module.types = self.types 675 | 676 | # Set byte and pointer sizes 677 | self.types["byte"] = AkiUnsignedInt(self._byte_width) 678 | self.types["u_mem"] = self.types["byte"] 679 | self.types["u_size"] = AkiUnsignedInt(self._pointer_width) 680 | 681 | # TODO: u_mem and u_size should be registered types 682 | # they might not be u8 or u64 on all platforms! 683 | 684 | # Set types that depend on pointer sizes 685 | self._ptr = AkiPointer(self.module) 686 | self.types["obj"] = AkiObject(self.module) 687 | self.types["str"] = AkiString(self.module) 688 | self.types["type"] = AkiTypeRef(self.module) 689 | self.types["array"] = AkiArray(self.module) 690 | 691 | # Default type is a 32-bit signed integer 692 | self._default = self.types["i32"] 693 | 694 | for _ in self.types.values(): 695 | setattr(_, "enum_id", self.enum_id_ctr) 696 | self.enum_ids[self.enum_id_ctr] = _ 697 | self.enum_id_ctr += 1 698 | 699 | def as_ptr(self, *a, **ka): 700 | new = self._ptr.new(*a, **ka) 701 | # TODO: move this into the actual `new` method? 702 | self.add_type(new.type_id, new, self.module) 703 | return new 704 | 705 | def add_type(self, type_name: str, type_ref: AkiType, module_ref): 706 | if type_name in self.custom_types: 707 | return self.custom_types[type_name] 708 | self.custom_types[type_name] = type_ref 709 | setattr(type_ref, "enum_id", self.enum_id_ctr) 710 | self.enum_ids[self.enum_id_ctr] = type_ref 711 | self.enum_id_ctr += 1 712 | return type_ref 713 | --------------------------------------------------------------------------------