├── .github └── workflows │ └── main.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── bootstrap └── ilo0.asm ├── configure ├── docker-compose.yml ├── docs └── graphs │ ├── free.dot │ ├── malloc.dot │ └── traceback.dot ├── examples ├── brainfuck.ilo ├── image.ilo └── pathtracer.ilo ├── ilo.vim ├── lib ├── ilo │ ├── core.ilo │ ├── formats.ilo │ ├── functions.ilo │ ├── generator.ilo │ ├── helpers.ilo │ ├── lexer.ilo │ ├── opcodes.ilo │ ├── parser.ilo │ ├── tokens.ilo │ └── types.ilo ├── std.ilo └── std │ ├── argparse.ilo │ ├── dict.ilo │ ├── linux.ilo │ ├── list.ilo │ ├── math.ilo │ ├── profiler.ilo │ └── textbuffer.ilo ├── src └── ilo.ilo ├── test.sh └── tests ├── add.ilo ├── and.ilo ├── buffer.ilo ├── comparisons.ilo ├── const.ilo ├── divide.ilo ├── drop.ilo ├── dup.ilo ├── elif-A.ilo ├── elif-B.ilo ├── elif-C.ilo ├── elif-D.ilo ├── exit.ilo ├── false.ilo ├── fibonacci.ilo ├── first.ilo ├── fixtures ├── importee.ilo ├── large.txt └── small.txt ├── function.ilo ├── getsetbool.ilo ├── getsetchar.ilo ├── getsetint.ilo ├── getsetptr.ilo ├── hello_world.ilo ├── if-false.ilo ├── if-true.ilo ├── ifelse-false.ilo ├── ifelse-true.ilo ├── ilo_functions.ilo ├── ilo_lexer.ilo ├── ilo_parser.ilo ├── ilo_parser_debug.ilo ├── ilo_parser_stack_man.ilo ├── import.ilo ├── import_duplicate.ilo ├── line_join.ilo ├── linked_list.ilo ├── location.ilo.skip ├── mod.ilo ├── multiply.ilo ├── or.ilo ├── over.ilo ├── reff.ilo ├── rot.ilo ├── second.ilo ├── shl.ilo ├── shr.ilo ├── stdlib_argparse.ilo ├── stdlib_assert_false.ilo ├── stdlib_assert_true.ilo ├── stdlib_concat.ilo ├── stdlib_dict.ilo ├── stdlib_div.ilo ├── stdlib_error.ilo ├── stdlib_errori.ilo ├── stdlib_exit.ilo ├── stdlib_free_many.ilo ├── stdlib_free_merge_next.ilo ├── stdlib_free_merge_prev.ilo ├── stdlib_free_middle.ilo ├── stdlib_free_only.ilo ├── stdlib_free_refill.ilo ├── stdlib_get_argument.ilo ├── stdlib_itohex.ilo ├── stdlib_itos.ilo ├── stdlib_list_contains.ilo ├── stdlib_list_copy.ilo ├── stdlib_list_eq.ilo ├── stdlib_list_int.ilo ├── stdlib_list_ptr.ilo ├── stdlib_list_structs.ilo ├── stdlib_malloc_large.ilo ├── stdlib_malloc_many.ilo ├── stdlib_malloc_small.ilo ├── stdlib_memcpy.ilo ├── stdlib_puti.ilo ├── stdlib_puts.ilo ├── stdlib_raise.ilo ├── stdlib_read_file_large.ilo ├── stdlib_read_file_small.ilo ├── stdlib_replace.ilo ├── stdlib_sqrt.ilo ├── stdlib_startswith.ilo ├── stdlib_stoi.ilo ├── stdlib_strcpy.ilo ├── stdlib_streq.ilo ├── stdlib_strlen.ilo ├── stdlib_substring.ilo ├── stdlib_textbuffer.ilo ├── stdlib_vec.ilo ├── string_quotes.ilo ├── subtract.ilo ├── swap.ilo ├── traceback.ilo ├── true.ilo ├── void.ilo ├── while.ilo └── while_skip.ilo /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'main' 7 | pull_request: 8 | branches: 9 | - 'main' 10 | workflow_dispatch: 11 | 12 | jobs: 13 | functional-tests: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Install dependencies 18 | run: | 19 | sudo apt install -y nasm ninja-build 20 | - name: Build ilo 21 | run: | 22 | ./configure 23 | ninja 24 | - name: Memory test 25 | run: | 26 | ./build/ilo --verify-memory src/ilo.ilo > /dev/null 27 | - name: Functional tests 28 | run: | 29 | ./test.sh 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | build.ninja 3 | *.swp 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM --platform=linux/x86_64 ubuntu:latest AS builder 2 | 3 | WORKDIR /app 4 | 5 | RUN apt update \ 6 | && apt install -y binutils nasm ninja-build python3 7 | 8 | COPY . . 9 | 10 | RUN ./configure \ 11 | && ninja 12 | 13 | CMD ["bash"] 14 | 15 | 16 | # TODO: Use this multi-stage Dockerfile to generate an Ilo binary for MacOS M1 (Pro) 17 | 18 | FROM --platform=linux/x86_64 ubuntu:latest 19 | 20 | WORKDIR /app 21 | 22 | RUN apt update \ 23 | && apt install -y binutils nasm ninja-build python3 24 | 25 | COPY --from=builder /app/build/ilo /bin/ilo 26 | 27 | CMD ["bash"] 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Joris Hartog 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ilo 2 | 3 | [![CI/CD](https://github.com/nootr/ilo/actions/workflows/main.yml/badge.svg)](https://github.com/nootr/ilo/actions) 4 | 5 | A programming language. 6 | 7 | * Stack-oriented like Forth 8 | * Whitespace-specific like Python 9 | * Type checking like Tsoding's Porth 10 | 11 | This project has one clear goal: to write a self-hosted compiler! 12 | 13 | 14 | ## Building the Ilo compiler 15 | 16 | Building the Ilo compiler is simple: 17 | 18 | ```bash 19 | ./configure 20 | ninja 21 | ``` 22 | 23 | Ilo currently only compiles to x86_64 assembly, but will have different target 24 | architectures in the future. 25 | 26 | 27 | ## Example code 28 | 29 | ```ilo 30 | import std 31 | 32 | # foo bar 33 | def add: int x, int y -> int 34 | x y + 35 | 36 | def main: ptr argv, int argc -> int 37 | 2 1 add 4 < if 38 | "Yeah\n" puts 39 | else 40 | "Huh?!\n" puts 41 | 42 | 0 # Exit code 43 | ``` 44 | 45 | 46 | ## Compiling with Docker 47 | 48 | Currently, Ilo is only able to compile itself to Linux x86_64. Compiling in MacOS is possible using Docker: 49 | 50 | ```bash 51 | docker compose run --rm ilo 52 | ``` 53 | 54 | There are plans to extend the Ilo compiler to cross compile to MacOS. 55 | -------------------------------------------------------------------------------- /configure: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Script that generates a build.ninja file for Ilo.""" 4 | 5 | import platform 6 | 7 | from os import walk 8 | from os.path import join 9 | from shutil import which 10 | 11 | 12 | class NinjaFile: 13 | RULES = ( 14 | ("bootstrap", "cp $in $out"), 15 | ("ilo", "$in --format $iloformat > $out"), 16 | ("asm", "$asmtool $asmflags $in -o $out"), 17 | ("bin", "$bintool $in -o $out"), 18 | ) 19 | 20 | def __init__( 21 | self, 22 | filename="build.ninja", 23 | builddir="build", 24 | system=None, 25 | machine=None, 26 | ): 27 | self.filename = filename 28 | self.builddir = builddir 29 | self.system = system or platform.system() 30 | self.machine = machine or platform.machine() 31 | self.dependencies = [] 32 | 33 | def generate(self): 34 | with open(self.filename, "w") as file: 35 | file.write(self.content) 36 | 37 | for dependency in self.dependencies: 38 | if not which(dependency): 39 | raise ValueError(f"Please install {dependency}") 40 | 41 | @staticmethod 42 | def get_files(location): 43 | return [ 44 | join(dirpath, filename) 45 | for (dirpath, _, filenames) in walk(location) 46 | for filename in filenames 47 | ] 48 | 49 | @property 50 | def is_linux(self): 51 | return self.system == "Linux" 52 | 53 | @property 54 | def is_x86_64(self): 55 | return self.machine == "x86_64" 56 | 57 | @property 58 | def variables(self): 59 | if self.is_linux and self.is_x86_64: 60 | self.dependencies.append("nasm") 61 | self.dependencies.append("ld") 62 | return { 63 | "asmflags": "-felf64", 64 | "asmtool": "nasm", 65 | "bintool": "ld", 66 | "iloformat": "linux_x86_64", 67 | } 68 | else: 69 | raise NotImplementedError( 70 | f"{self.machine}-{self.system} is not supported yet" 71 | ) 72 | 73 | @property 74 | def builds(self): 75 | yield ("../bootstrap/ilo0.asm", "ilo0.asm", "bootstrap") 76 | 77 | for ilo_bin, ilo_name in [ 78 | (None, "ilo0"), ("ilo0", "ilo1"), ("ilo1", "ilo") 79 | ]: 80 | if ilo_bin: 81 | yield ( 82 | f"{ilo_bin} src/ilo.ilo | {' '.join(self.get_files('lib/'))}", 83 | f"{ilo_name}.asm", 84 | "ilo", 85 | ) 86 | yield (f"{ilo_name}.asm", f"{ilo_name}.o", "asm") 87 | yield (f"{ilo_name}.o", f"{ilo_name}", "bin") 88 | 89 | @property 90 | def content(self): 91 | def _format_variable(key, value): 92 | return f"{key} = {value}" 93 | 94 | def _format_rule(name, command): 95 | return f"rule {name}\n command = {command}" 96 | 97 | def _format_build(in_file, out_file, tool): 98 | return f"build $builddir/{out_file}: {tool} $builddir/{in_file}" 99 | 100 | return "\n".join( 101 | [_format_variable("builddir", self.builddir)] + 102 | [_format_variable(k, v) for k, v in self.variables.items()] + 103 | [_format_rule(*r) for r in self.RULES] + 104 | [_format_build(*b) for b in self.builds] 105 | ) + "\n" 106 | 107 | 108 | n = NinjaFile() 109 | n.generate() 110 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | services: 3 | ilo: 4 | build: . 5 | volumes: 6 | - ./:/app 7 | -------------------------------------------------------------------------------- /docs/graphs/free.dot: -------------------------------------------------------------------------------- 1 | digraph free { 2 | start -> block_offset; 3 | 4 | subgraph cluster_free_block { 5 | label = "free_block"; 6 | 7 | block_offset [ label = "block := block.ptr - offset"]; 8 | block_offset -> next_block_free; 9 | 10 | next_block_free [ label = "block.next != NULL and block.next.free?" ]; 11 | next_block_free -> prev_block_free [ label = "False" ]; 12 | next_block_free -> merge_next [ label = "True" ]; 13 | 14 | merge_next [ label = "block.size += block.next.size\nblock.next := block.next.next" ]; 15 | merge_next -> prev_block_free; 16 | 17 | prev_block_free [ label = "block.prev != NULL and block.prev.free?" ]; 18 | prev_block_free -> free_block [ label = "False" ]; 19 | prev_block_free -> merge_prev [ label = "True" ]; 20 | 21 | merge_prev [ label = "block.prev.size += block.size\nblock.prev.next := block.next" ]; 22 | merge_prev -> free_block; 23 | 24 | free_block [ label = "block.free := True" ]; 25 | } 26 | 27 | free_block -> page_offset; 28 | 29 | subgraph cluster_free_page { 30 | label = "free_page"; 31 | 32 | page_offset [ label = "page := block.page" ]; 33 | page_offset -> page_empty; 34 | page_empty [ label = "page.blocks.free and page.blocks.next == NULL?"]; 35 | page_empty -> merge_prev_page [ label = "True" ]; 36 | 37 | merge_prev_page [ label = "page.prev.next := page.next\n*`next` field must be first field" ]; 38 | merge_prev_page -> next_page_exists; 39 | 40 | next_page_exists [ label = "page.next != NULL" ]; 41 | next_page_exists -> merge_next_page [ label = "True" ]; 42 | next_page_exists -> munmap [ label = "False" ]; 43 | 44 | merge_next_page [ label = "page.next.prev = page.prev" ]; 45 | merge_next_page -> munmap; 46 | 47 | munmap [ label = "munmap(page)" ]; 48 | } 49 | 50 | munmap -> done; 51 | page_empty -> done [ label = "False" ]; 52 | } 53 | -------------------------------------------------------------------------------- /docs/graphs/malloc.dot: -------------------------------------------------------------------------------- 1 | digraph malloc { 2 | start -> next_page; 3 | 4 | subgraph cluster_get_next_page { 5 | label = "get_next_page"; 6 | 7 | # Check if page exists 8 | next_page [ label = "page.next != NULL?" ]; 9 | next_page -> mmap [ label = "False" ]; 10 | next_page -> progress_page [ label = "True" ]; 11 | 12 | # Create new page 13 | mmap [ label = "page.next := mmap()" ]; 14 | mmap -> init_page; 15 | init_page [ label = "Initialize page\n(metadata + empty block)" ]; 16 | init_page -> progress_page; 17 | } 18 | 19 | subgraph cluster_search_block { 20 | label = "search_block"; 21 | 22 | # Search for suitable block 23 | progress_page [ label = "page := page.next" ]; 24 | progress_page -> first_block; 25 | first_block [ label = "block := page.blocks" ]; 26 | first_block -> fits; 27 | 28 | # Walk blocks 29 | fits [ label = "block.free and block.size >= size_needed?" ]; 30 | fits -> next_block [ label = "False" ]; 31 | next_block [ label = "block.next != NULL?" ]; 32 | next_block -> next_page [ label = "False" ]; 33 | next_block -> progress_block [ label = "True" ]; 34 | progress_block [ label = "block := block.next" ]; 35 | progress_block -> fits; 36 | } 37 | 38 | subgraph cluster_take_block { 39 | label = "take_block"; 40 | 41 | # Occupy block 42 | fits -> perfect_fit [ label = "True" ]; 43 | perfect_fit [ label = "block.size == needed_size?"]; 44 | perfect_fit -> return_block [ label = "True" ]; 45 | perfect_fit -> init_block [ label = "False" ]; 46 | return_block [ label = "return block.data" ]; 47 | init_block [ label = "Initialize block\n(metadata + next block)" ]; 48 | init_block -> return_block; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /docs/graphs/traceback.dot: -------------------------------------------------------------------------------- 1 | digraph traceback { 2 | start -> pop; 3 | 4 | subgraph cluster_traceback { 5 | label = "Traceback\nNote that the function call string and main flag are pushed together with the arguments."; 6 | 7 | pop [ label = "Pop message ptr to register" ]; 8 | pop -> print_call; 9 | print_call [ label = "Print function call\nlib/ilo/parser.ilo, line 123, in parse" ]; 10 | print_call -> check_main; 11 | check_main [ label = "Check if main flag is set" ]; 12 | check_main -> print_message [ label = "True" ]; 13 | check_main -> prev_fp [ label = "False" ]; 14 | prev_fp [ label = "Jump to previous framepointer" ]; 15 | prev_fp -> print_call; 16 | print_message [ label = "Print error message (saved in register)" ]; 17 | print_message -> exit; 18 | exit [ label = "End with exit code 1" ]; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/brainfuck.ilo: -------------------------------------------------------------------------------- 1 | # Rule 110 simulator written in Brainfuck, interpreted in Ilo 2 | # Rule 110 source code: https://github.com/nootr/rule110 3 | 4 | 5 | import lib.std 6 | 7 | buffer mem 1024 # Array of chars 8 | buffer mp 8 # ptr 9 | buffer pp 8 # ptr 10 | 11 | 12 | def get_character: -> char 13 | 1 malloc 14 | 0 # characters read 15 | while dup 0 = 16 | drop 17 | dup STDIN swap 1 read 18 | drop 19 | dup derefc 20 | swap free 21 | 22 | 23 | def main: -> int 24 | mem mp setp 25 | 26 | " ->>>+[->>>,----------]>>>-<<<<<<[>>++++ " 27 | "++[-<++++++>]<+[-<->]<<<<]>>>+[[-[->+>+<<" concat 28 | "]>>[-<<+>>]>[-<+<+>>>+<]<[->+<]<<[->>+>>+" concat 29 | "<<<<]>>[-<<+>>]<<<<<[->>>>>+>>+<<<<<<<]>>" concat 30 | ">>>[-<<<<<+>>>>>]>>---[<<<[>+<[-]]>>>[+]]" concat 31 | "<<<[-]>>+]>>-<<<[-]<<+[[-]<<<+]>>->>>>>>+" concat 32 | "[-[->+<]>>++++[->>++++<<]>>[-<<++>>]<<<[>" concat 33 | " +++<-<+>]>.[-]>+],[-]-<+[-<+]->>>>>>+=] " concat 34 | 35 | while dup derefc 0 castc != 36 | dup derefc '+' = if 37 | mp derefp dup derefc casti 1 + castc 38 | swap setc 39 | elif dup derefc '-' = 40 | mp derefp dup derefc casti 1 - castc 41 | swap setc 42 | elif dup derefc '>' = 43 | mp derefp 1 + mp setp 44 | elif dup derefc '<' = 45 | mp derefp 1 - mp setp 46 | elif dup derefc ',' = 47 | get_character mp derefp setc 48 | elif dup derefc '.' = 49 | 2 malloc 50 | dup mp derefp derefc swap setc 51 | dup 1 + 0 castc swap setc 52 | dup puts 53 | free 54 | elif dup derefc '[' = 55 | mp derefp derefc 0 castc = if 56 | 1 # counter 57 | while dup 0 != 58 | swap 1 + 59 | dup derefc '[' = if 60 | swap 1 + swap 61 | dup derefc ']' = if 62 | swap 1 - swap 63 | swap 64 | drop 65 | elif dup derefc ']' = 66 | mp derefp derefc 0 castc != if 67 | 1 # counter 68 | while dup 0 != 69 | swap 1 - 70 | dup derefc '[' = if 71 | swap 1 - swap 72 | dup derefc ']' = if 73 | swap 1 + swap 74 | swap 75 | drop 76 | 1 + 77 | 0 78 | -------------------------------------------------------------------------------- /examples/image.ilo: -------------------------------------------------------------------------------- 1 | import lib.std 2 | 3 | 4 | const WIDTH 256 5 | const HEIGHT 256 6 | 7 | 8 | def draw_pixel: int x, int y -> void 9 | # Red 10 | x itos puts " " puts 11 | 12 | # Green 13 | y itos puts " " puts 14 | 15 | # Blue 16 | "64 " puts 17 | 18 | 19 | def main: ptr argv, int argc -> int 20 | # Print PPM metadata 21 | "P3 " puts 22 | WIDTH itos puts " " puts 23 | HEIGHT itos puts " " puts 24 | "256\n" puts 25 | 26 | # Render image 27 | 0 28 | while dup HEIGHT < 29 | 0 30 | while dup WIDTH < 31 | over over swap draw_pixel 32 | 1 + 33 | drop 34 | 1 + 35 | 36 | "\n" puts 37 | 0 38 | -------------------------------------------------------------------------------- /examples/pathtracer.ilo: -------------------------------------------------------------------------------- 1 | import lib.std 2 | import lib.std.math 3 | 4 | 5 | const WIDTH 160 6 | const HEIGHT 90 7 | 8 | const FOCAL_LENGTH 90 9 | 10 | buffer ray_origin 24 11 | buffer ray_direction 24 12 | buffer ray_color 24 13 | 14 | 15 | buffer _sphere_dist_buffer 24 16 | 17 | def sphere_dist: int center_x, int center_y, int center_z, int radius -> int 18 | # Returns the distance of the current ray to a given sphere 19 | center_x _sphere_dist_buffer vec.x + seti 20 | center_y _sphere_dist_buffer vec.y + seti 21 | center_z _sphere_dist_buffer vec.z + seti 22 | 23 | _sphere_dist_buffer ray_origin _sphere_dist_buffer vec_sub 24 | _sphere_dist_buffer ray_direction _sphere_dist_buffer vec_sub 25 | 26 | _sphere_dist_buffer vec_len radius - 27 | 28 | 29 | def generate_camera_ray: int x, int y -> void 30 | # Set ray origin to (0, 0, 0) 31 | 0 ray_origin vec.x + seti 32 | 0 ray_origin vec.y + seti 33 | 0 ray_origin vec.z + seti 34 | 35 | # Set the ray direction: 36 | x WIDTH 2 div - ray_direction vec.x + seti 37 | HEIGHT 2 div y - ray_direction vec.y + seti 38 | 0 FOCAL_LENGTH - ray_direction vec.z + seti 39 | 40 | # Normalize vector 41 | ray_direction dup vec_unit 42 | 43 | 44 | buffer closest_object_distance 8 # int 45 | buffer closest_object_material 8 # int 46 | buffer normal_vector 24 47 | 48 | const MATERIAL_SKY 0 49 | const MATERIAL_RED 1 50 | const MATERIAL_GREEN 2 51 | const MATERIAL_BLUE 3 52 | 53 | def check_sphere: int center_x, int center_y, int center_z, int radius, int material -> void 54 | # Check if a given sphere is closest and update globals accordingly 55 | center_x center_y center_z radius sphere_dist 56 | dup closest_object_distance derefi < if 57 | dup closest_object_distance seti 58 | material closest_object_material seti 59 | 60 | center_x 8 - center_y center_z radius sphere_dist 61 | center_x 8 + center_y center_z radius sphere_dist - normal_vector vec.x + seti 62 | center_x center_y 8 - center_z radius sphere_dist 63 | center_x center_y 8 + center_z radius sphere_dist - normal_vector vec.y + seti 64 | center_x center_y center_z 8 - radius sphere_dist 65 | center_x center_y center_z 8 + radius sphere_dist - normal_vector vec.z + seti 66 | normal_vector dup vec_unit 67 | 68 | 69 | def find_closest_object: -> void 70 | 10000 closest_object_distance seti 71 | MATERIAL_SKY closest_object_material seti 72 | 73 | # x y z radius material 74 | 0 0 50000 - 0 4200 - 50000 MATERIAL_GREEN check_sphere 75 | 0 1000 - 500 0 4200 - 500 MATERIAL_RED check_sphere 76 | 1000 500 0 4200 - 500 MATERIAL_BLUE check_sphere 77 | 78 | 79 | buffer hit 1 80 | buffer steps 8 81 | buffer _march_buffer 24 82 | 83 | def update_ray_color: -> void 84 | hit derefb False = closest_object_material derefi MATERIAL_SKY = or if 85 | ray_color vec.x + derefi 255 + 2 div ray_color vec.x + seti 86 | ray_color vec.y + derefi 255 + 2 div ray_color vec.y + seti 87 | ray_color vec.z + derefi 255 + 2 div ray_color vec.z + seti 88 | elif closest_object_material derefi MATERIAL_RED = 89 | ray_color vec.x + derefi 255 + 2 div ray_color vec.x + seti 90 | ray_color vec.y + derefi 2 div ray_color vec.y + seti 91 | ray_color vec.z + derefi 2 div ray_color vec.z + seti 92 | elif closest_object_material derefi MATERIAL_GREEN = 93 | ray_color vec.x + derefi 2 div ray_color vec.x + seti 94 | ray_color vec.y + derefi 255 + 2 div ray_color vec.y + seti 95 | ray_color vec.z + derefi 2 div ray_color vec.z + seti 96 | elif closest_object_material derefi MATERIAL_BLUE = 97 | ray_color vec.x + derefi 2 div ray_color vec.x + seti 98 | ray_color vec.y + derefi 2 div ray_color vec.y + seti 99 | ray_color vec.z + derefi 255 + 2 div ray_color vec.z + seti 100 | else 101 | "Unknown material!" raise 102 | 103 | 104 | buffer _reflect_ray_buffer 24 105 | 106 | def reflect_ray: -> void 107 | normal_vector 108 | dup ray_direction vec_dot 500 div 109 | _reflect_ray_buffer 110 | vec_mul 111 | 112 | ray_direction _reflect_ray_buffer ray_direction vec_sub 113 | ray_direction ray_direction vec_unit 114 | 115 | 116 | def march: int bounce -> void 117 | # Will raymarch until something is hit or z is less than -20000 118 | # Sets a new ray when something is hit. 119 | 120 | False hit setb 121 | 0 steps seti 122 | 123 | while hit derefb False = \ 124 | ray_origin vec.z + derefi 0 20000 - > and \ 125 | steps derefi 1000 < and 126 | # Increment steps 127 | steps derefi 1 + steps seti 128 | 129 | # Fetch closest object 130 | find_closest_object 131 | 132 | # Check for hit 133 | closest_object_distance derefi 10 < if 134 | True hit setb 135 | else 136 | # Update ray origin 137 | ray_direction closest_object_distance derefi _march_buffer vec_mul 138 | _march_buffer 1000 _march_buffer vec_div 139 | ray_origin _march_buffer ray_origin vec_add 140 | 141 | update_ray_color 142 | hit derefb if 143 | reflect_ray 144 | 145 | bounce 0 > if 146 | bounce 1 - march 147 | 148 | 149 | def scale_color: int c -> int 150 | c 0 < if 151 | 0 152 | elif c 255 > 153 | 255 154 | else 155 | c 156 | 157 | 158 | def print_ray_color: -> void 159 | ray_color vec.x + derefi scale_color itos puts " " puts 160 | ray_color vec.y + derefi scale_color itos puts " " puts 161 | ray_color vec.z + derefi scale_color itos puts " " puts 162 | 163 | 164 | def draw_pixel: int x, int y -> void 165 | x y generate_camera_ray 166 | 167 | # Set initial color 168 | 0 ray_color vec.x + seti 169 | 0 ray_color vec.y + seti 170 | 0 ray_color vec.z + seti 171 | 172 | 1 march 173 | print_ray_color 174 | 175 | 176 | def main: ptr argv, int argc -> int 177 | # Print PPM metadata 178 | "P3 " puts 179 | WIDTH itos puts " " puts 180 | HEIGHT itos puts " " puts 181 | "256\n" puts 182 | 183 | # Render image 184 | 0 185 | while dup HEIGHT < 186 | 0 187 | while dup WIDTH < 188 | over over swap draw_pixel 189 | 1 + 190 | drop 191 | 1 + 192 | 193 | "\n" puts 194 | 0 195 | -------------------------------------------------------------------------------- /ilo.vim: -------------------------------------------------------------------------------- 1 | " Vim syntax file 2 | " Language: Ilo 3 | " Maintainer: jhartog (@nootr) 4 | " Latest revision: 8 Feb 2022 5 | 6 | " Usage Instructions 7 | " 8 | " cp ilo.vim ~/.vim/syntax 9 | " 10 | " And add the following line to your .vimrc: 11 | " 12 | " autocmd BufRead,BufNewFile *.ilo set filetype=ilo 13 | 14 | if exists("b:current_syntax") 15 | finish 16 | endif 17 | 18 | " Keywords 19 | syntax keyword iloFunctions strlen itos traceback 20 | syntax keyword iloKeywords import var def if elif else dup swap over rot drop syscall derefc derefi derefb derefp buffer setc seti setb setp const and or while castc casti castb castp location debug shl shr reff inline 21 | syntax keyword iloTypes int ptr char bool void 22 | syntax keyword iloBoolean True False 23 | 24 | " Comments 25 | syntax region iloComment start="#" end="$" 26 | 27 | " Literals 28 | syntax region iloString start=/\v"/ end=/\v"/ contains=iloEscapes 29 | syntax region iloChar start=/\v'/ end=/\v'/ contains=iloEscapes 30 | syntax region iloNumber start=/\s\d/ end=/[ \t\n]/ 31 | syntax match iloEscapes display contained "\\[nr\"']" 32 | 33 | " Set highlights 34 | highlight default link iloFunctions Function 35 | highlight default link iloKeywords Keyword 36 | highlight default link iloTypes Type 37 | highlight default link iloBoolean Boolean 38 | highlight default link iloComment Comment 39 | highlight default link iloString String 40 | highlight default link iloChar Character 41 | highlight default link iloNumber Number 42 | highlight default link iloEscapes SpecialChar 43 | 44 | let b:current_syntax = "ilo" 45 | -------------------------------------------------------------------------------- /lib/ilo/core.ilo: -------------------------------------------------------------------------------- 1 | # Core functions 2 | # This file is automatically included to the compiled file. 3 | 4 | 5 | def strlen: ptr s -> int 6 | # Returns the length of a given zero-terminated string 7 | 0 # Counter 8 | while dup s + derefc 0 castc != 9 | 1 + 10 | 11 | 12 | buffer _itos 21 13 | 14 | def itos: int i -> ptr 15 | # Converts an integer to string 16 | # Please note that the _itos buffer is overwritten every time itos is executed, so 17 | # make sure you copy the string to a different memory buffer if you want it to be 18 | # persistent. 19 | # TODO: Support negative numbers 20 | i 0 = if 21 | "0" 22 | else 23 | i # remainder 24 | _itos 19 + # index 25 | while over 0 > over _itos >= and 26 | # Get right-most digit 27 | over 10 % 28 | 29 | # Convert to character 30 | '0' casti + castc 31 | 32 | # Place at index 33 | over setc 34 | 35 | # Move left one character and divide remainder by 10 36 | 1 - swap 10 / swap 37 | 38 | 1 + 39 | 40 | # Cleanup stack 41 | swap drop 42 | 43 | 44 | def traceback: ptr message -> void 45 | # Prints the call stack and exits with exit code 1 46 | 2 # STDERR 47 | message # Pointer to string 48 | dup strlen # String length 49 | 1 # SYS_WRITE 50 | syscall 3 51 | 52 | 2 # STDERR 53 | "\nTraceback:\n" # Pointer to string 54 | dup strlen # String length 55 | 1 # SYS_WRITE 56 | syscall 3 57 | 58 | while __get_arg 16 bool 59 | # Print function call 60 | 2 # STDERR 61 | __get_arg 40 ptr 62 | dup strlen # String length 63 | 1 # SYS_WRITE 64 | syscall 3 65 | drop 66 | 67 | 2 # STDERR 68 | ", line " # String 69 | 7 # String length 70 | 1 # SYS_WRITE 71 | syscall 3 72 | drop 73 | 74 | 2 # STDERR 75 | __get_arg 32 int itos 76 | dup strlen # String length 77 | 1 # SYS_WRITE 78 | syscall 3 79 | drop 80 | 81 | 2 # STDERR 82 | " in " # String 83 | 4 # String length 84 | 1 # SYS_WRITE 85 | syscall 3 86 | drop 87 | 88 | 2 # STDERR 89 | __get_arg 24 ptr 90 | dup strlen # String length 91 | 1 # SYS_WRITE 92 | syscall 3 93 | drop 94 | 95 | 2 # STDERR 96 | "\n" # String 97 | 1 # String length 98 | 1 # SYS_WRITE 99 | syscall 3 100 | drop 101 | 102 | # Move to previous frame 103 | __restore_frame 104 | 105 | 1 60 syscall 1 106 | -------------------------------------------------------------------------------- /lib/ilo/formats.ilo: -------------------------------------------------------------------------------- 1 | import lib.std 2 | 3 | 4 | ## Constants 5 | 6 | const FORMAT_LINUX_X86_64 0 7 | const FORMAT_MAC_AARCH64 1 8 | 9 | 10 | def format_to_str: int format -> ptr 11 | # Returns the string representation of a given format 12 | format 13 | dup FORMAT_LINUX_X86_64 = if 14 | "Linux x86-64" 15 | elif dup FORMAT_MAC_AARCH64 = 16 | "Mac AArch64" 17 | else 18 | "Unknown format" raise "UNKNOWN" 19 | 20 | 21 | def str_to_format: ptr s -> int 22 | # Converts a string to a format 23 | s 24 | dup "linux_x86_64" streq if 25 | FORMAT_LINUX_X86_64 26 | elif dup "mac_aarch64" streq 27 | FORMAT_MAC_AARCH64 28 | else 29 | "Unknown format: " s concat raise 0 30 | -------------------------------------------------------------------------------- /lib/ilo/functions.ilo: -------------------------------------------------------------------------------- 1 | import lib.std 2 | import lib.ilo.types 3 | 4 | 5 | ## Structs 6 | 7 | # FUNCTION 8 | # A function signature containing information about its arguments and return type. 9 | 10 | const function.name 0 # ptr 11 | const function.args 8 # ptr to list of arguments 12 | const function.return_type 16 # int 13 | const FUNCTION_SIZE 24 14 | 15 | 16 | # ARGUMENT 17 | # Contains information about an argument. 18 | 19 | const argument.name 0 # ptr 20 | const argument.type 8 # int 21 | const argument.index 16 # int 22 | const ARGUMENT_SIZE 24 23 | 24 | buffer _argument 24 25 | 26 | 27 | def create_function: ptr name -> ptr 28 | # Creates a function and returns its pointer 29 | FUNCTION_SIZE malloc 30 | name over function.name + setp 31 | ARGUMENT_SIZE new_list over function.args + setp 32 | TYPE_VOID over function.return_type + seti 33 | 34 | 35 | def function_add_argument: int type, ptr name, ptr func -> void 36 | # Adds an argument to a function 37 | name _argument argument.name + setp 38 | type _argument argument.type + seti 39 | func function.args + derefp list.len + derefi _argument argument.index + seti 40 | 41 | _argument 42 | func function.args + derefp list_append 43 | func function.args + setp 44 | 45 | 46 | def function_set_return_type: int type, ptr func -> void 47 | # Sets a function's return type 48 | type func function.return_type + seti 49 | 50 | 51 | def function_get_arg: ptr name, ptr func -> ptr 52 | # Returns a given argument, returns NULL if not found 53 | NULL # argument 54 | 0 # index 55 | while dup func function.args + derefp list.len + derefi < 56 | dup func function.args + derefp list_fetch 57 | dup argument.name + derefp name streq if 58 | rot drop swap over 59 | drop 60 | 1 + 61 | drop 62 | 63 | 64 | def function_get_arg_offset: ptr name, ptr func -> int 65 | # Returns the memory offset of a given argument, returns -1 if not found 66 | name func function_get_arg 67 | dup NULL = if 68 | 0 1 - 69 | else 70 | func function.args + derefp list.len + derefi 71 | over argument.index + derefi - 72 | 5 + 73 | 8 * 74 | -------------------------------------------------------------------------------- /lib/ilo/generator.ilo: -------------------------------------------------------------------------------- 1 | import lib.std 2 | import lib.std.dict 3 | import lib.std.list 4 | import lib.std.textbuffer 5 | import lib.ilo.opcodes 6 | import lib.ilo.parser 7 | 8 | 9 | buffer _text 8 # ptr 10 | buffer _data 8 # ptr 11 | buffer _bss 8 # ptr 12 | buffer _string_index 8 # int 13 | buffer _textbuffer 8 # ptr 14 | buffer _string_labels 8 # ptr 15 | buffer _format_buffer 1024 # array of characters 16 | 17 | 18 | def _format: ptr l, ptr r -> ptr 19 | # Formats a line of assembly code into two columns 20 | l _format_buffer strcpy drop 21 | l strlen 22 | ' ' over _format_buffer + setc 23 | 1 + 24 | while dup 10 < 25 | ' ' over _format_buffer + setc 26 | 1 + 27 | r over _format_buffer + strcpy drop 28 | r strlen + _format_buffer + 29 | 10 castc over setc 30 | 0 castc over 1 + setc 31 | _format_buffer 32 | 33 | 34 | def _append: ptr l, ptr r, ptr buf -> void 35 | # Append to a given buffer 36 | l r _format 37 | buf derefp textbuffer_append buf setp 38 | 39 | 40 | def _append_opcode_info: int line, int opcode -> void 41 | # Print comment with opcode info 42 | _textbuffer derefp textbuffer_clear 43 | "; " swap textbuffer_append 44 | line itos swap textbuffer_append 45 | " " swap textbuffer_append 46 | opcode opcode_to_str swap textbuffer_append 47 | "" over textbuffer.content + _text _append 48 | _textbuffer setp 49 | 50 | 51 | def generate_code_x86_64_linux: ptr opcodes -> void 52 | # Takes a list of opcodes and converts it into x86_64 NASM assembly 53 | 54 | # Initialize buffers 55 | 512000 textbuffer_create _text setp 56 | 512000 textbuffer_create _data setp 57 | new_textbuffer _bss setp 58 | new_textbuffer _textbuffer setp 59 | 0 _string_index seti 60 | new_dict _string_labels setp 61 | 62 | "" "global _start" _text _append 63 | "" "section .text" _text _append 64 | "" "section .data" _data _append 65 | "" "section .bss" _bss _append 66 | 67 | # Generate assembly 68 | 0 69 | while dup opcodes list.len + derefi < 70 | dup opcodes list_fetch 71 | 72 | # Add opcode info comment 73 | dup opcode.line + derefi 74 | over opcode.opcode + derefi 75 | _append_opcode_info 76 | 77 | dup opcode.opcode + derefi OPCODE_DUP = if 78 | "" "pop rax" _text _append 79 | "" "push rax" _text _append 80 | "" "push rax" _text _append 81 | elif dup opcode.opcode + derefi OPCODE_DROP = 82 | "" "pop rax" _text _append 83 | elif dup opcode.opcode + derefi OPCODE_SWAP = 84 | "" "pop rax" _text _append 85 | "" "pop rbx" _text _append 86 | "" "push rax" _text _append 87 | "" "push rbx" _text _append 88 | elif dup opcode.opcode + derefi OPCODE_OVER = 89 | "" "pop rax" _text _append 90 | "" "pop rbx" _text _append 91 | "" "push rbx" _text _append 92 | "" "push rax" _text _append 93 | "" "push rbx" _text _append 94 | elif dup opcode.opcode + derefi OPCODE_ROT = 95 | "" "pop rax" _text _append 96 | "" "pop rbx" _text _append 97 | "" "pop rcx" _text _append 98 | "" "push rbx" _text _append 99 | "" "push rax" _text _append 100 | "" "push rcx" _text _append 101 | elif dup opcode.opcode + derefi OPCODE_PUSH_INT = 102 | _textbuffer derefp textbuffer_clear 103 | "mov rax, " swap textbuffer_append 104 | over opcode.operand + derefp swap textbuffer_append 105 | "" over textbuffer.content + _text _append 106 | "" "push rax" _text _append 107 | _textbuffer setp 108 | elif dup opcode.opcode + derefi OPCODE_PUSH_BOOL = 109 | _textbuffer derefp textbuffer_clear 110 | "mov rax, " swap textbuffer_append 111 | over opcode.operand + derefp swap textbuffer_append 112 | "" over textbuffer.content + _text _append 113 | "" "push rax" _text _append 114 | _textbuffer setp 115 | elif dup opcode.opcode + derefi OPCODE_PUSH_CHAR = 116 | _textbuffer derefp textbuffer_clear 117 | "mov rax, \`" swap textbuffer_append 118 | 119 | over opcode.operand + derefp derefc '\'' = if 120 | "\\\'" swap textbuffer_append 121 | elif over opcode.operand + derefp derefc '`' = 122 | "\\\`" swap textbuffer_append 123 | elif over opcode.operand + derefp derefc '\\' = 124 | "\\\\" swap textbuffer_append 125 | else 126 | over opcode.operand + derefp swap textbuffer_append 127 | 128 | "\`" swap textbuffer_append 129 | 130 | "" over textbuffer.content + _text _append 131 | "" "push rax" _text _append 132 | _textbuffer setp 133 | elif dup opcode.opcode + derefi OPCODE_PUSH_STRING = 134 | # Lookup string label, or create if new 135 | dup opcode.operand + derefp _string_labels derefp dict_fetch 136 | NULL = if 137 | # Increment string index 138 | _string_index derefi 139 | 1 + dup _string_index seti 140 | 141 | # Create label 142 | itos 143 | dup strlen 3 + malloc 144 | "s_" over strcpy drop 145 | over over 2 + strcpy drop 146 | swap drop 147 | 148 | over opcode.operand + derefp swap 149 | _string_labels derefp dict_insert _string_labels setp 150 | 151 | # Define string 152 | _textbuffer derefp textbuffer_clear 153 | over opcode.operand + derefp 154 | _string_labels derefp dict_fetch swap textbuffer_append 155 | ": db \`" swap textbuffer_append 156 | over opcode.operand + derefp swap textbuffer_append 157 | "\`, 0" swap textbuffer_append 158 | dup textbuffer.content + "" _data _append 159 | _textbuffer setp 160 | 161 | dup opcode.operand + derefp _string_labels derefp dict_fetch NULL != \ 162 | "String should have label" assert 163 | 164 | _textbuffer derefp textbuffer_clear 165 | "mov rax, " swap textbuffer_append 166 | over opcode.operand + derefp 167 | _string_labels derefp dict_fetch swap textbuffer_append 168 | "" over textbuffer.content + _text _append 169 | "" "push rax" _text _append 170 | _textbuffer setp 171 | elif dup opcode.opcode + derefi OPCODE_FUNCTION = 172 | _textbuffer derefp textbuffer_clear 173 | over opcode.operand + derefp swap textbuffer_append 174 | ":" swap textbuffer_append 175 | dup textbuffer.content + "" _text _append 176 | "" "push rbp" _text _append 177 | "" "mov rbp, rsp" _text _append 178 | _textbuffer setp 179 | elif dup opcode.opcode + derefi OPCODE_CALL = 180 | _textbuffer derefp textbuffer_clear 181 | "call " swap textbuffer_append 182 | over opcode.operand + derefp swap textbuffer_append 183 | "" over textbuffer.content + _text _append 184 | _textbuffer setp 185 | 186 | dup opcode.operand + derefp functions derefp dict_fetch 187 | 0 188 | while over function.args + derefp list.len + derefi 4 + over > 189 | "" "pop rbx" _text _append 190 | 1 + 191 | drop 192 | function.return_type + derefi TYPE_VOID != if 193 | "" "push rax" _text _append 194 | elif dup opcode.opcode + derefi OPCODE_GET_ARG = 195 | "" "mov rax, rbp" _text _append 196 | _textbuffer derefp textbuffer_clear 197 | "add rax, " swap textbuffer_append 198 | over opcode.operand + derefp swap textbuffer_append 199 | "" over textbuffer.content + _text _append 200 | _textbuffer setp 201 | "" "mov rbx, [rax]" _text _append 202 | "" "push rbx" _text _append 203 | elif dup opcode.opcode + derefi OPCODE_ADD = 204 | "" "pop rax" _text _append 205 | "" "pop rbx" _text _append 206 | "" "add rbx, rax" _text _append 207 | "" "push rbx" _text _append 208 | elif dup opcode.opcode + derefi OPCODE_SUBTRACT = 209 | "" "pop rax" _text _append 210 | "" "pop rbx" _text _append 211 | "" "sub rbx, rax" _text _append 212 | "" "push rbx" _text _append 213 | elif dup opcode.opcode + derefi OPCODE_MULTIPLY = 214 | "" "pop rax" _text _append 215 | "" "pop rbx" _text _append 216 | "" "imul rbx, rax" _text _append 217 | "" "push rbx" _text _append 218 | elif dup opcode.opcode + derefi OPCODE_DIVIDE = 219 | "" "xor rdx, rdx" _text _append 220 | "" "pop rbx" _text _append 221 | "" "pop rax" _text _append 222 | "" "div rbx" _text _append 223 | "" "push rax" _text _append 224 | elif dup opcode.opcode + derefi OPCODE_MOD = 225 | "" "xor rdx, rdx" _text _append 226 | "" "pop rbx" _text _append 227 | "" "pop rax" _text _append 228 | "" "div rbx" _text _append 229 | "" "push rdx" _text _append 230 | elif dup opcode.opcode + derefi OPCODE_RESTORE_FRAME = 231 | "" "pop rax" _text _append 232 | "" "mov rsp, rbp" _text _append 233 | "" "pop rbp" _text _append 234 | elif dup opcode.opcode + derefi OPCODE_RETURN = 235 | "" "ret" _text _append 236 | elif dup opcode.opcode + derefi OPCODE_BITWISE_AND = 237 | "" "pop rax" _text _append 238 | "" "pop rbx" _text _append 239 | "" "and rbx, rax" _text _append 240 | "" "push rbx" _text _append 241 | elif dup opcode.opcode + derefi OPCODE_BITWISE_OR = 242 | "" "pop rax" _text _append 243 | "" "pop rbx" _text _append 244 | "" "or rbx, rax" _text _append 245 | "" "push rbx" _text _append 246 | elif dup opcode.opcode + derefi OPCODE_GET_BUFFER = 247 | _textbuffer derefp textbuffer_clear 248 | "mov rax, " swap textbuffer_append 249 | over opcode.operand + derefp swap textbuffer_append 250 | "" over textbuffer.content + _text _append 251 | "" "push rax" _text _append 252 | _textbuffer setp 253 | elif dup opcode.opcode + derefi OPCODE_CREATE_BUFFER = 254 | _textbuffer derefp textbuffer_clear 255 | over opcode.operand + derefp buffer_operand_buffer_name + derefp \ 256 | swap textbuffer_append 257 | ": resb " swap textbuffer_append 258 | over opcode.operand + derefp buffer_operand_buffer_size + derefp \ 259 | swap textbuffer_append 260 | 261 | dup textbuffer.content + "" _bss _append 262 | _textbuffer setp 263 | elif dup opcode.opcode + derefi OPCODE_IS_EQUAL = 264 | "" "mov rax, 0" _text _append 265 | "" "mov rbx, 1" _text _append 266 | "" "pop rcx" _text _append 267 | "" "pop rdx" _text _append 268 | "" "cmp rdx, rcx" _text _append 269 | "" "cmove rax, rbx" _text _append 270 | "" "push rax" _text _append 271 | elif dup opcode.opcode + derefi OPCODE_IS_NOT_EQUAL = 272 | "" "mov rax, 0" _text _append 273 | "" "mov rbx, 1" _text _append 274 | "" "pop rcx" _text _append 275 | "" "pop rdx" _text _append 276 | "" "cmp rdx, rcx" _text _append 277 | "" "cmovne rax, rbx" _text _append 278 | "" "push rax" _text _append 279 | elif dup opcode.opcode + derefi OPCODE_IS_GREATER_OR_EQUAL = 280 | "" "mov rax, 0" _text _append 281 | "" "mov rbx, 1" _text _append 282 | "" "pop rcx" _text _append 283 | "" "pop rdx" _text _append 284 | "" "cmp rdx, rcx" _text _append 285 | "" "cmovge rax, rbx" _text _append 286 | "" "push rax" _text _append 287 | elif dup opcode.opcode + derefi OPCODE_IS_GREATER = 288 | "" "mov rax, 0" _text _append 289 | "" "mov rbx, 1" _text _append 290 | "" "pop rcx" _text _append 291 | "" "pop rdx" _text _append 292 | "" "cmp rdx, rcx" _text _append 293 | "" "cmovg rax, rbx" _text _append 294 | "" "push rax" _text _append 295 | elif dup opcode.opcode + derefi OPCODE_IS_LESS_OR_EQUAL = 296 | "" "mov rax, 0" _text _append 297 | "" "mov rbx, 1" _text _append 298 | "" "pop rcx" _text _append 299 | "" "pop rdx" _text _append 300 | "" "cmp rdx, rcx" _text _append 301 | "" "cmovle rax, rbx" _text _append 302 | "" "push rax" _text _append 303 | elif dup opcode.opcode + derefi OPCODE_IS_LESS = 304 | "" "mov rax, 0" _text _append 305 | "" "mov rbx, 1" _text _append 306 | "" "pop rcx" _text _append 307 | "" "pop rdx" _text _append 308 | "" "cmp rdx, rcx" _text _append 309 | "" "cmovl rax, rbx" _text _append 310 | "" "push rax" _text _append 311 | elif dup opcode.opcode + derefi OPCODE_IF = 312 | "" "pop rax" _text _append 313 | "" "test rax, rax" _text _append 314 | _textbuffer derefp textbuffer_clear 315 | "jz " swap textbuffer_append 316 | over opcode.operand + derefp swap textbuffer_append 317 | "" over textbuffer.content + _text _append 318 | _textbuffer setp 319 | elif dup opcode.opcode + derefi OPCODE_LABEL = 320 | _textbuffer derefp textbuffer_clear 321 | over opcode.operand + derefp swap textbuffer_append 322 | ":" swap textbuffer_append 323 | dup textbuffer.content + "" _text _append 324 | _textbuffer setp 325 | elif dup opcode.opcode + derefi OPCODE_JUMP = 326 | _textbuffer derefp textbuffer_clear 327 | "jmp " swap textbuffer_append 328 | over opcode.operand + derefp swap textbuffer_append 329 | "" over textbuffer.content + _text _append 330 | _textbuffer setp 331 | elif dup opcode.opcode + derefi OPCODE_SYSCALL = 332 | "" "pop rax" _text _append 333 | dup opcode.operand + derefp stoi 334 | dup 6 > if 335 | "Syscall should have 1-6 arguments" raise 336 | dup 5 > if 337 | "" "pop r9" _text _append 338 | dup 4 > if 339 | "" "pop r8" _text _append 340 | dup 3 > if 341 | "" "pop r10" _text _append 342 | dup 2 > if 343 | "" "pop rdx" _text _append 344 | dup 1 > if 345 | "" "pop rsi" _text _append 346 | dup 0 > if 347 | "" "pop rdi" _text _append 348 | drop 349 | "" "syscall" _text _append 350 | "" "push rax" _text _append 351 | elif dup opcode.opcode + derefi OPCODE_REF_F = 352 | _textbuffer derefp textbuffer_clear 353 | "mov rax, " swap textbuffer_append 354 | over opcode.operand + derefp swap textbuffer_append 355 | "" over textbuffer.content + _text _append 356 | "" "push rax" _text _append 357 | _textbuffer setp 358 | elif dup opcode.opcode + derefi OPCODE_DEREF_B = 359 | "" "pop rax" _text _append 360 | "" "xor rbx, rbx" _text _append 361 | "" "mov bl, [rax]" _text _append 362 | "" "push rbx" _text _append 363 | elif dup opcode.opcode + derefi OPCODE_DEREF_C = 364 | "" "pop rax" _text _append 365 | "" "xor rbx, rbx" _text _append 366 | "" "mov bl, [rax]" _text _append 367 | "" "push rbx" _text _append 368 | elif dup opcode.opcode + derefi OPCODE_DEREF_I = 369 | "" "pop rax" _text _append 370 | "" "mov rbx, [rax]" _text _append 371 | "" "push rbx" _text _append 372 | elif dup opcode.opcode + derefi OPCODE_DEREF_P = 373 | "" "pop rax" _text _append 374 | "" "mov rbx, [rax]" _text _append 375 | "" "push rbx" _text _append 376 | elif dup opcode.opcode + derefi OPCODE_SET_B = 377 | "" "pop rax" _text _append 378 | "" "pop rbx" _text _append 379 | "" "mov [rax], bl" _text _append 380 | elif dup opcode.opcode + derefi OPCODE_SET_C = 381 | "" "pop rax" _text _append 382 | "" "pop rbx" _text _append 383 | "" "mov [rax], bl" _text _append 384 | elif dup opcode.opcode + derefi OPCODE_SET_I = 385 | "" "pop rax" _text _append 386 | "" "pop rbx" _text _append 387 | "" "mov [rax], rbx" _text _append 388 | elif dup opcode.opcode + derefi OPCODE_SET_P = 389 | "" "pop rax" _text _append 390 | "" "pop rbx" _text _append 391 | "" "mov [rax], rbx" _text _append 392 | elif dup opcode.opcode + derefi OPCODE_SHIFT_LEFT = 393 | "" "pop rcx" _text _append 394 | "" "pop rax" _text _append 395 | "" "shl rax, cl" _text _append 396 | "" "push rax" _text _append 397 | elif dup opcode.opcode + derefi OPCODE_SHIFT_RIGHT = 398 | "" "pop rcx" _text _append 399 | "" "pop rax" _text _append 400 | "" "shr rax, cl" _text _append 401 | "" "push rax" _text _append 402 | elif dup opcode.opcode + derefi OPCODE_WHILE_START = 403 | "" "pop rax" _text _append 404 | "" "test rax, rax" _text _append 405 | _textbuffer derefp textbuffer_clear 406 | "jz " swap textbuffer_append 407 | over opcode.operand + derefp swap textbuffer_append 408 | "_end" swap textbuffer_append 409 | "" over textbuffer.content + _text _append 410 | _textbuffer setp 411 | elif dup opcode.opcode + derefi OPCODE_WHILE_END = 412 | _textbuffer derefp textbuffer_clear 413 | "jmp " swap textbuffer_append 414 | over opcode.operand + derefp swap textbuffer_append 415 | "" over textbuffer.content + _text _append 416 | textbuffer_clear 417 | 418 | over opcode.operand + derefp swap textbuffer_append 419 | "_end:" swap textbuffer_append 420 | dup textbuffer.content + "" _text _append 421 | _textbuffer setp 422 | else 423 | _textbuffer derefp textbuffer_clear 424 | "Unknown opcode: " swap textbuffer_append 425 | over opcode.opcode + derefi opcode_to_str swap textbuffer_append 426 | "\n" swap textbuffer_append 427 | dup textbuffer.content + raise 428 | _textbuffer setp 429 | 430 | drop 431 | 1 + 432 | 433 | # Add entrypoint 434 | "_start:" "" _text _append 435 | "" "xor rax, rax" _text _append 436 | "" "push rax" _text _append 437 | "" "push rax" _text _append 438 | "" "push rax" _text _append 439 | "" "push rax" _text _append 440 | "" "call main" _text _append 441 | "_end:" "" _text _append 442 | "" "mov rdi, rax" _text _append 443 | "" "mov rax, 60" _text _append 444 | "" "syscall" _text _append 445 | 446 | # Output buffers 447 | _text derefp 448 | dup textbuffer.content + puts 449 | 450 | _data derefp 451 | dup textbuffer.content + puts 452 | 453 | _bss derefp 454 | dup textbuffer.content + puts 455 | -------------------------------------------------------------------------------- /lib/ilo/helpers.ilo: -------------------------------------------------------------------------------- 1 | import lib.std 2 | 3 | 4 | def syntax_error: int line_no, ptr message -> void 5 | # Raises a syntax error and exits with 1 6 | "Syntax error at line " error 7 | line_no errori 8 | message NULL != if 9 | ": " error 10 | message error 11 | "\n" error 12 | 1 exit 13 | 14 | 15 | def is_int: char c -> bool 16 | # Returns True if given character is a number 17 | '0' c <= c '9' <= and 18 | 19 | 20 | def is_alphabet: char c -> bool 21 | # Returns True if given character is in the alphabet or `-`, `_` or `.` 22 | 'A' c <= c 'Z' <= and 23 | 'a' c <= c 'z' <= and or 24 | c '-' = or 25 | c '_' = or 26 | c '.' = or 27 | 28 | 29 | def is_alphanumeric: char c -> bool 30 | # Returns True if given character is alphanumeric 31 | c is_int c is_alphabet or 32 | 33 | 34 | def count_indent: ptr s -> int 35 | # Takes a string and returns the number of leading spaces and/or tabs 36 | 0 37 | s 38 | while dup derefc ' ' = over derefc 9 castc = or 39 | 1 + swap 40 | 1 + swap 41 | drop 42 | 43 | 44 | def is_word: ptr s, ptr q -> bool 45 | # Returns True if str s starts with substring q, followed by a non-alphabet char 46 | 0 47 | while dup s + derefc over q + derefc = over q + derefc 0 castc != and 48 | 1 + 49 | dup q + derefc 0 castc = 50 | swap s + derefc is_alphanumeric False = and 51 | -------------------------------------------------------------------------------- /lib/ilo/lexer.ilo: -------------------------------------------------------------------------------- 1 | import lib.std 2 | import lib.std.list 3 | import lib.ilo.tokens 4 | import lib.ilo.helpers 5 | 6 | 7 | buffer _indent_stack 8 # ptr 8 | buffer _at_start 1 # bool 9 | 10 | 11 | def _tokenize: ptr source, ptr tokens -> ptr 12 | # Tokenizes a given source code and returns a pointer to the list of tokens 13 | 14 | # Initialize variables 15 | TOKEN_SIZE new_list tokens setp 16 | 17 | _indent_stack derefp NULL != if 18 | _indent_stack derefp free 19 | 0 INT_SIZE new_list list_append_int _indent_stack setp 20 | 21 | True _at_start setb 22 | 23 | # Tokenize source code 24 | 1 # line 25 | source # current_char 26 | 27 | while dup derefc 0 castc != 28 | # Count indenting and create block begin/end tokens if necessary 29 | _indent_stack derefp list.len + derefi 0 > \ 30 | "Indent stack should have items\n" assert 31 | 32 | dup count_indent 33 | _at_start derefb if 34 | False _at_start setb 35 | 36 | dup _indent_stack derefp list_peek_int > if 37 | dup _indent_stack derefp list_append_int _indent_stack setp 38 | rot dup TOKEN_BLOCK_START NULL 39 | tokens derefp create_token tokens setp 40 | rot rot 41 | dup _indent_stack derefp list_peek_int < if 42 | while dup _indent_stack derefp list_pop_int != 43 | _indent_stack derefp list.len + derefi 0 = if 44 | rot dup "Unexpexted indenting" syntax_error rot rot 45 | rot dup TOKEN_BLOCK_END NULL 46 | tokens derefp create_token tokens setp 47 | rot rot 48 | dup _indent_stack derefp list_append_int _indent_stack setp 49 | + 50 | 51 | dup derefc '#' = if 52 | while dup derefc 10 castc != over derefc 0 castc != and 53 | 1 + 54 | elif dup derefc '\\' = 55 | while dup derefc 10 castc != over derefc 0 castc != and 56 | 1 + 57 | 1 + swap 1 + swap 58 | elif dup derefc is_int 59 | dup 60 | 61 | # Count length of integer 62 | 0 swap 63 | while dup derefc is_int 64 | 1 + swap 1 + swap 65 | drop 66 | 67 | # Get token value 68 | over swap substring 69 | 70 | # Create token 71 | rot dup rot dup strlen rot rot TOKEN_INT swap 72 | tokens derefp create_token tokens setp 73 | 74 | # Shift source 75 | rot + 76 | elif dup derefc '\'' = 77 | 1 + 78 | dup derefc '\\' = if 79 | 1 + 80 | dup 1 + derefc '\'' != if 81 | swap "Character should be a single character" syntax_error 0 swap 82 | dup 1 substring rot dup rot TOKEN_CHAR swap 83 | tokens derefp create_token tokens setp 84 | swap 2 + 85 | elif dup derefc '"' = 86 | 1 + 87 | 88 | # Find length of string 89 | 0 90 | over 91 | while dup derefc '"' != 92 | dup derefc '\\' = if 93 | 1 + swap 1 + swap 94 | 1 + swap 1 + swap 95 | 96 | # Separate string 97 | # TODO: Unescape the string here and escape the string again in the code 98 | # generator. 99 | rot rot substring 100 | 101 | # Create token 102 | rot dup rot TOKEN_STRING swap 103 | tokens derefp create_token tokens setp 104 | swap 105 | 1 + 106 | elif dup "->" startswith 107 | over TOKEN_ARROW NULL 108 | tokens derefp create_token tokens setp 109 | 2 + 110 | elif dup derefc '+' = 111 | over TOKEN_ARITHMETIC "+" 112 | tokens derefp create_token tokens setp 113 | 1 + 114 | elif dup derefc '-' = 115 | over TOKEN_ARITHMETIC "-" 116 | tokens derefp create_token tokens setp 117 | 1 + 118 | elif dup derefc '*' = 119 | over TOKEN_ARITHMETIC "*" 120 | tokens derefp create_token tokens setp 121 | 1 + 122 | elif dup derefc '/' = 123 | over TOKEN_ARITHMETIC "/" 124 | tokens derefp create_token tokens setp 125 | 1 + 126 | elif dup derefc '%' = 127 | over TOKEN_ARITHMETIC "%" 128 | tokens derefp create_token tokens setp 129 | 1 + 130 | elif dup "and" is_word 131 | over TOKEN_ARITHMETIC "and" 132 | tokens derefp create_token tokens setp 133 | 3 + 134 | elif dup "or" is_word 135 | over TOKEN_ARITHMETIC "or" 136 | tokens derefp create_token tokens setp 137 | 2 + 138 | elif dup "shl" is_word 139 | over TOKEN_ARITHMETIC "shl" 140 | tokens derefp create_token tokens setp 141 | 3 + 142 | elif dup "shr" is_word 143 | over TOKEN_ARITHMETIC "shr" 144 | tokens derefp create_token tokens setp 145 | 3 + 146 | elif dup "True" is_word 147 | over TOKEN_BOOL "1" 148 | tokens derefp create_token tokens setp 149 | 4 + 150 | elif dup "False" is_word 151 | over TOKEN_BOOL "0" 152 | tokens derefp create_token tokens setp 153 | 5 + 154 | elif dup derefc ':' = 155 | over TOKEN_COLON NULL 156 | tokens derefp create_token tokens setp 157 | 1 + 158 | elif dup derefc ',' = 159 | over TOKEN_COMMA NULL 160 | tokens derefp create_token tokens setp 161 | 1 + 162 | elif dup "!=" startswith 163 | over TOKEN_COMPARISON "!=" 164 | tokens derefp create_token tokens setp 165 | 2 + 166 | elif dup ">=" startswith 167 | over TOKEN_COMPARISON ">=" 168 | tokens derefp create_token tokens setp 169 | 2 + 170 | elif dup "<=" startswith 171 | over TOKEN_COMPARISON "<=" 172 | tokens derefp create_token tokens setp 173 | 2 + 174 | elif dup derefc '=' = 175 | over TOKEN_COMPARISON "=" 176 | tokens derefp create_token tokens setp 177 | 1 + 178 | elif dup derefc '<' = 179 | over TOKEN_COMPARISON "<" 180 | tokens derefp create_token tokens setp 181 | 1 + 182 | elif dup derefc '>' = 183 | over TOKEN_COMPARISON ">" 184 | tokens derefp create_token tokens setp 185 | 1 + 186 | elif dup "buffer" is_word 187 | over TOKEN_KEYWORD "buffer" 188 | tokens derefp create_token tokens setp 189 | 6 + 190 | elif dup "castb" is_word 191 | over TOKEN_KEYWORD "castb" 192 | tokens derefp create_token tokens setp 193 | 5 + 194 | elif dup "castc" is_word 195 | over TOKEN_KEYWORD "castc" 196 | tokens derefp create_token tokens setp 197 | 5 + 198 | elif dup "casti" is_word 199 | over TOKEN_KEYWORD "casti" 200 | tokens derefp create_token tokens setp 201 | 5 + 202 | elif dup "castp" is_word 203 | over TOKEN_KEYWORD "castp" 204 | tokens derefp create_token tokens setp 205 | 5 + 206 | elif dup "const" is_word 207 | over TOKEN_KEYWORD "const" 208 | tokens derefp create_token tokens setp 209 | 5 + 210 | elif dup "debug" is_word 211 | over TOKEN_KEYWORD "debug" 212 | tokens derefp create_token tokens setp 213 | 5 + 214 | elif dup "def" is_word 215 | over TOKEN_KEYWORD "def" 216 | tokens derefp create_token tokens setp 217 | 3 + 218 | elif dup "inline" is_word 219 | over TOKEN_KEYWORD "inline" 220 | tokens derefp create_token tokens setp 221 | 6 + 222 | elif dup "derefb" is_word 223 | over TOKEN_KEYWORD "derefb" 224 | tokens derefp create_token tokens setp 225 | 6 + 226 | elif dup "derefc" is_word 227 | over TOKEN_KEYWORD "derefc" 228 | tokens derefp create_token tokens setp 229 | 6 + 230 | elif dup "derefi" is_word 231 | over TOKEN_KEYWORD "derefi" 232 | tokens derefp create_token tokens setp 233 | 6 + 234 | elif dup "derefp" is_word 235 | over TOKEN_KEYWORD "derefp" 236 | tokens derefp create_token tokens setp 237 | 6 + 238 | elif dup "drop" is_word 239 | over TOKEN_KEYWORD "drop" 240 | tokens derefp create_token tokens setp 241 | 4 + 242 | elif dup "dup" is_word 243 | over TOKEN_KEYWORD "dup" 244 | tokens derefp create_token tokens setp 245 | 3 + 246 | elif dup "elif" is_word 247 | over TOKEN_KEYWORD "elif" 248 | tokens derefp create_token tokens setp 249 | 4 + 250 | elif dup "else" is_word 251 | over TOKEN_KEYWORD "else" 252 | tokens derefp create_token tokens setp 253 | 4 + 254 | elif dup "if" is_word 255 | over TOKEN_KEYWORD "if" 256 | tokens derefp create_token tokens setp 257 | 2 + 258 | elif dup "import" is_word 259 | over TOKEN_KEYWORD "import" 260 | tokens derefp create_token tokens setp 261 | 6 + 262 | elif dup "location" is_word 263 | over TOKEN_KEYWORD "location" 264 | tokens derefp create_token tokens setp 265 | 8 + 266 | elif dup "over" is_word 267 | over TOKEN_KEYWORD "over" 268 | tokens derefp create_token tokens setp 269 | 4 + 270 | elif dup "rot" is_word 271 | over TOKEN_KEYWORD "rot" 272 | tokens derefp create_token tokens setp 273 | 3 + 274 | elif dup "setb" is_word 275 | over TOKEN_KEYWORD "setb" 276 | tokens derefp create_token tokens setp 277 | 4 + 278 | elif dup "setc" is_word 279 | over TOKEN_KEYWORD "setc" 280 | tokens derefp create_token tokens setp 281 | 4 + 282 | elif dup "seti" is_word 283 | over TOKEN_KEYWORD "seti" 284 | tokens derefp create_token tokens setp 285 | 4 + 286 | elif dup "setp" is_word 287 | over TOKEN_KEYWORD "setp" 288 | tokens derefp create_token tokens setp 289 | 4 + 290 | elif dup "swap" is_word 291 | over TOKEN_KEYWORD "swap" 292 | tokens derefp create_token tokens setp 293 | 4 + 294 | elif dup "syscall" is_word 295 | over TOKEN_KEYWORD "syscall" 296 | tokens derefp create_token tokens setp 297 | 7 + 298 | elif dup "reff" is_word 299 | over TOKEN_KEYWORD "reff" 300 | tokens derefp create_token tokens setp 301 | 4 + 302 | elif dup "bool" is_word 303 | over TOKEN_TYPE "bool" 304 | tokens derefp create_token tokens setp 305 | 4 + 306 | elif dup "char" is_word 307 | over TOKEN_TYPE "char" 308 | tokens derefp create_token tokens setp 309 | 4 + 310 | elif dup "int" is_word 311 | over TOKEN_TYPE "int" 312 | tokens derefp create_token tokens setp 313 | 3 + 314 | elif dup "ptr" is_word 315 | over TOKEN_TYPE "ptr" 316 | tokens derefp create_token tokens setp 317 | 3 + 318 | elif dup "void" is_word 319 | over TOKEN_TYPE "void" 320 | tokens derefp create_token tokens setp 321 | 4 + 322 | elif dup "while" is_word 323 | over TOKEN_KEYWORD "while" 324 | tokens derefp create_token tokens setp 325 | 5 + 326 | elif dup "__get_arg" is_word 327 | over TOKEN_KEYWORD "__get_arg" 328 | tokens derefp create_token tokens setp 329 | 9 + 330 | elif dup "__restore_frame" is_word 331 | over TOKEN_KEYWORD "__restore_frame" 332 | tokens derefp create_token tokens setp 333 | 15 + 334 | elif dup derefc is_alphabet 335 | dup 336 | 337 | # Count length of identifier 338 | 0 swap 339 | while dup derefc is_alphabet over derefc is_int or 340 | 1 + swap 1 + swap 341 | drop 342 | 343 | # Get token value 344 | over swap substring 345 | 346 | # Create token 347 | rot dup rot dup strlen rot rot TOKEN_IDENTIFIER swap 348 | tokens derefp create_token tokens setp 349 | 350 | # Shift source 351 | rot + 352 | elif dup derefc 10 castc = 353 | while dup derefc 10 castc = 354 | swap 1 + swap 1 + 355 | True _at_start setb 356 | else 357 | over NULL syntax_error 358 | drop 359 | 360 | while _indent_stack derefp list_peek_int 0 != 361 | _indent_stack derefp list_pop_int drop 362 | dup TOKEN_BLOCK_END NULL 363 | tokens derefp create_token tokens setp 364 | 365 | dup TOKEN_END NULL tokens derefp create_token tokens setp 366 | 367 | # Return a pointer to the list of tokens 368 | tokens derefp 369 | tokens free 370 | 371 | 372 | def tokenize: ptr source -> ptr 373 | # Convenience wrapper for the _tokenize function 374 | source PTR_SIZE malloc _tokenize 375 | -------------------------------------------------------------------------------- /lib/ilo/opcodes.ilo: -------------------------------------------------------------------------------- 1 | import lib.std 2 | 3 | 4 | ## Constants 5 | 6 | const OPCODE_NOOP 0 7 | const OPCODE_ADD 1 8 | const OPCODE_BITWISE_AND 2 9 | const OPCODE_BITWISE_OR 3 10 | const OPCODE_CALL 4 11 | const OPCODE_CLEANUP 5 12 | const OPCODE_CREATE_BUFFER 6 13 | const OPCODE_DEREF_B 7 14 | const OPCODE_DEREF_C 8 15 | const OPCODE_DEREF_I 9 16 | const OPCODE_DEREF_P 10 17 | const OPCODE_DIVIDE 11 18 | const OPCODE_DROP 12 19 | const OPCODE_DUP 13 20 | const OPCODE_FUNCTION 14 21 | const OPCODE_GET_ARG 15 22 | const OPCODE_GET_BUFFER 16 23 | const OPCODE_IF 17 24 | const OPCODE_IS_EQUAL 18 25 | const OPCODE_IS_GREATER 19 26 | const OPCODE_IS_GREATER_OR_EQUAL 20 27 | const OPCODE_IS_LESS 21 28 | const OPCODE_IS_LESS_OR_EQUAL 22 29 | const OPCODE_IS_NOT_EQUAL 23 30 | const OPCODE_JUMP 24 31 | const OPCODE_LABEL 25 32 | const OPCODE_MULTIPLY 26 33 | const OPCODE_MOD 27 34 | const OPCODE_OVER 28 35 | const OPCODE_PUSH_BOOL 29 36 | const OPCODE_PUSH_CHAR 30 37 | const OPCODE_PUSH_INT 31 38 | const OPCODE_PUSH_STRING 32 39 | const OPCODE_REF_F 33 40 | const OPCODE_RESTORE_FRAME 34 41 | const OPCODE_RETURN 35 42 | const OPCODE_ROT 36 43 | const OPCODE_SET_B 37 44 | const OPCODE_SET_C 38 45 | const OPCODE_SET_I 39 46 | const OPCODE_SET_P 40 47 | const OPCODE_SHIFT_LEFT 41 48 | const OPCODE_SHIFT_RIGHT 42 49 | const OPCODE_SUBTRACT 43 50 | const OPCODE_SWAP 44 51 | const OPCODE_SYSCALL 45 52 | const OPCODE_WHILE_END 46 53 | const OPCODE_WHILE_START 47 54 | 55 | 56 | def opcode_to_str: int opcode -> ptr 57 | # Returns the string representation of a given opcode 58 | opcode 59 | dup OPCODE_NOOP = if 60 | "no operation" 61 | elif dup OPCODE_ADD = 62 | "add" 63 | elif dup OPCODE_BITWISE_AND = 64 | "bitwise and" 65 | elif dup OPCODE_BITWISE_OR = 66 | "bitwise or" 67 | elif dup OPCODE_CALL = 68 | "call" 69 | elif dup OPCODE_CLEANUP = 70 | "cleanup" 71 | elif dup OPCODE_CREATE_BUFFER = 72 | "create buffer" 73 | elif dup OPCODE_DEREF_B = 74 | "dereference boolean" 75 | elif dup OPCODE_DEREF_C = 76 | "dereference character" 77 | elif dup OPCODE_DEREF_I = 78 | "dereference integer" 79 | elif dup OPCODE_DEREF_P = 80 | "dereference pointer" 81 | elif dup OPCODE_DIVIDE = 82 | "divide" 83 | elif dup OPCODE_DROP = 84 | "drop" 85 | elif dup OPCODE_DUP = 86 | "duplicate" 87 | elif dup OPCODE_FUNCTION = 88 | "function definition" 89 | elif dup OPCODE_GET_ARG = 90 | "get argument" 91 | elif dup OPCODE_GET_BUFFER = 92 | "get pointer to buffer" 93 | elif dup OPCODE_IF = 94 | "start of if-block" 95 | elif dup OPCODE_IS_EQUAL = 96 | "is equal?" 97 | elif dup OPCODE_IS_GREATER = 98 | "is greater?" 99 | elif dup OPCODE_IS_GREATER_OR_EQUAL = 100 | "is greater or equal?" 101 | elif dup OPCODE_IS_LESS = 102 | "is less?" 103 | elif dup OPCODE_IS_LESS_OR_EQUAL = 104 | "is less or equal?" 105 | elif dup OPCODE_IS_NOT_EQUAL = 106 | "is not equal?" 107 | elif dup OPCODE_JUMP = 108 | "jump" 109 | elif dup OPCODE_LABEL = 110 | "label" 111 | elif dup OPCODE_MOD = 112 | "mod" 113 | elif dup OPCODE_MULTIPLY = 114 | "multiply" 115 | elif dup OPCODE_OVER = 116 | "over" 117 | elif dup OPCODE_PUSH_BOOL = 118 | "push boolean" 119 | elif dup OPCODE_PUSH_CHAR = 120 | "push character" 121 | elif dup OPCODE_PUSH_INT = 122 | "push integer" 123 | elif dup OPCODE_PUSH_STRING = 124 | "push string" 125 | elif dup OPCODE_REF_F = 126 | "reference function" 127 | elif dup OPCODE_RESTORE_FRAME = 128 | "restore frame" 129 | elif dup OPCODE_RETURN = 130 | "return" 131 | elif dup OPCODE_ROT = 132 | "rot" 133 | elif dup OPCODE_SET_B = 134 | "set boolean value" 135 | elif dup OPCODE_SET_C = 136 | "set character value" 137 | elif dup OPCODE_SET_I = 138 | "set integer value" 139 | elif dup OPCODE_SET_P = 140 | "set pointer value" 141 | elif dup OPCODE_SHIFT_LEFT = 142 | "shift left" 143 | elif dup OPCODE_SHIFT_RIGHT = 144 | "shift right" 145 | elif dup OPCODE_SUBTRACT = 146 | "subtract" 147 | elif dup OPCODE_SWAP = 148 | "swap" 149 | elif dup OPCODE_SYSCALL = 150 | "syscall" 151 | elif dup OPCODE_WHILE_END = 152 | "end of while-loop" 153 | elif dup OPCODE_WHILE_START = 154 | "start of while-loop" 155 | else 156 | "Unknown opcode" raise "UNKNOWN" 157 | 158 | 159 | ## Structs 160 | 161 | # OPCODE 162 | # An opcode is actually a combination of an opcode, its operand and the line number of 163 | # the token from which it originates. 164 | 165 | const opcode.opcode 0 # int 166 | const opcode.operand 8 # ptr 167 | const opcode.line 16 # int 168 | const OPCODE_SIZE 24 169 | 170 | buffer _opcode 24 171 | 172 | 173 | def create_opcode: int opcode, ptr operand, int line, ptr list -> ptr 174 | # Appends an opcode to a list and returns a pointer to the list 175 | opcode _opcode opcode.opcode + seti 176 | operand _opcode opcode.operand + setp 177 | line _opcode opcode.line + seti 178 | _opcode list list_append 179 | 180 | 181 | def dump_opcodes: ptr opcodes -> void 182 | # Prints the contents of a given opcodelist to STDOUT 183 | "Number of opcodes: " puts 184 | opcodes list.len + derefi puti 185 | "\n" puts 186 | 187 | "Opcodes:\n" puts 188 | 0 189 | while dup opcodes list.len + derefi < 190 | "* " puts 191 | dup OPCODE_SIZE * 192 | opcodes list.items + + 193 | opcode.opcode + derefi opcode_to_str puts " " puts 194 | dup OPCODE_SIZE * 195 | opcodes list.items + + 196 | opcode.operand + derefp 197 | dup NULL != if 198 | puts " " puts 199 | else 200 | drop 201 | dup OPCODE_SIZE * 202 | opcodes list.items + + 203 | opcode.line + derefi puti " " puts 204 | "\n" puts 205 | 1 + 206 | drop 207 | -------------------------------------------------------------------------------- /lib/ilo/tokens.ilo: -------------------------------------------------------------------------------- 1 | import lib.std 2 | 3 | 4 | ## Constants 5 | 6 | const TOKEN_NOOP 0 7 | const TOKEN_ARITHMETIC 1 8 | const TOKEN_ARROW 2 9 | const TOKEN_BLOCK_START 3 10 | const TOKEN_BLOCK_END 4 11 | const TOKEN_BOOL 5 12 | const TOKEN_CHAR 6 13 | const TOKEN_COLON 7 14 | const TOKEN_COMMA 8 15 | const TOKEN_COMPARISON 9 16 | const TOKEN_IDENTIFIER 10 17 | const TOKEN_INT 11 18 | const TOKEN_KEYWORD 12 19 | const TOKEN_STRING 13 20 | const TOKEN_TYPE 14 21 | 22 | const TOKEN_END 99 23 | 24 | 25 | def token_to_str: int token -> ptr 26 | # Returns the string representation of a given token 27 | token TOKEN_NOOP = if 28 | "NOOP" 29 | elif token TOKEN_ARITHMETIC = 30 | "ARITHMETIC" 31 | elif token TOKEN_ARROW = 32 | "ARROW" 33 | elif token TOKEN_BLOCK_START = 34 | "BLOCK_START" 35 | elif token TOKEN_BLOCK_END = 36 | "BLOCK_END" 37 | elif token TOKEN_BOOL = 38 | "BOOL" 39 | elif token TOKEN_CHAR = 40 | "CHAR" 41 | elif token TOKEN_COLON = 42 | "COLON" 43 | elif token TOKEN_COMMA = 44 | "COMMA" 45 | elif token TOKEN_COMPARISON = 46 | "COMPARISON" 47 | elif token TOKEN_IDENTIFIER = 48 | "IDENTIFIER" 49 | elif token TOKEN_INT = 50 | "INT" 51 | elif token TOKEN_KEYWORD = 52 | "KEYWORD" 53 | elif token TOKEN_STRING = 54 | "STRING" 55 | elif token TOKEN_TYPE = 56 | "TYPE" 57 | elif token TOKEN_END = 58 | "END" 59 | else 60 | "Unknown token type" raise NULL 61 | 62 | 63 | ## Structs 64 | 65 | # TOKEN 66 | # A token is a string with an identified type. It also carries its line number for 67 | # debugging purposes. 68 | const token.type 0 # int 69 | const token.value 8 # ptr 70 | const token.line 16 # int 71 | const TOKEN_SIZE 24 72 | 73 | buffer _token 24 74 | 75 | 76 | def create_token: int line, int type, ptr value, ptr list -> ptr 77 | # Appends a token to a list and returns a pointer to the list 78 | line _token token.line + seti 79 | type _token token.type + seti 80 | value _token token.value + setp 81 | _token list list_append 82 | 83 | def dump_tokens: ptr tokens -> void 84 | # Prints the contents of a given list to STDOUT 85 | "Number of tokens: " puts 86 | tokens list.len + derefi puti 87 | "\n" puts 88 | 89 | "Tokens:\n" puts 90 | 0 91 | while dup tokens list.len + derefi < 92 | "* " puts 93 | dup TOKEN_SIZE * 94 | tokens list.items + + 95 | token.type + derefi token_to_str puts " " puts 96 | dup TOKEN_SIZE * 97 | tokens list.items + + 98 | token.value + derefp 99 | dup NULL != if 100 | puts " " puts 101 | else 102 | drop 103 | dup TOKEN_SIZE * 104 | tokens list.items + + 105 | token.line + derefi puti " " puts 106 | "\n" puts 107 | 1 + 108 | drop 109 | -------------------------------------------------------------------------------- /lib/ilo/types.ilo: -------------------------------------------------------------------------------- 1 | import lib.std 2 | 3 | 4 | const TYPE_UNKNOWN 0 5 | const TYPE_BOOL 1 6 | const TYPE_CHAR 2 7 | const TYPE_INT 3 8 | const TYPE_PTR 4 9 | const TYPE_VOID 5 10 | 11 | 12 | def type_to_str: int type -> ptr 13 | # Returns the string representation of a given type 14 | type TYPE_BOOL = if 15 | "BOOL" 16 | elif type TYPE_CHAR = 17 | "CHAR" 18 | elif type TYPE_INT = 19 | "INT" 20 | elif type TYPE_PTR = 21 | "PTR" 22 | elif type TYPE_VOID = 23 | "VOID" 24 | else 25 | "Unknown type" raise "UNKNOWN" 26 | 27 | 28 | def str_to_type: ptr type -> int 29 | # Converts a string to a type constant 30 | type "bool" streq type "BOOL" streq or if 31 | TYPE_BOOL 32 | elif type "char" streq type "CHAR" streq or 33 | TYPE_CHAR 34 | elif type "int" streq type "INT" streq or 35 | TYPE_INT 36 | elif type "ptr" streq type "PTR" streq or 37 | TYPE_PTR 38 | elif type "void" streq type "VOID" streq or 39 | TYPE_VOID 40 | else 41 | "Unknown type: " type concat raise 42 | TYPE_UNKNOWN 43 | -------------------------------------------------------------------------------- /lib/std.ilo: -------------------------------------------------------------------------------- 1 | import lib.std.linux 2 | 3 | 4 | const BOOL_SIZE 1 5 | const CHAR_SIZE 1 6 | const INT_SIZE 8 7 | const PTR_SIZE 8 8 | 9 | 10 | def NULL: -> ptr 11 | # Returns a null-pointer 12 | 0 castp 13 | 14 | 15 | def itohex: int x, ptr buf -> void 16 | # Converts a given 64-bit integer to its hexadecimal representation 17 | buf 18 | dup '0' swap setc 1 + 19 | dup 'x' swap setc 1 + 20 | 21 | 60 # offset 22 | while dup 0 >= 23 | dup 24 | 15 swap shl 25 | x and 26 | swap dup rot swap shr 27 | 28 | dup 0 = if 29 | rot dup '0' swap setc rot rot 30 | elif dup 1 = 31 | rot dup '1' swap setc rot rot 32 | elif dup 2 = 33 | rot dup '2' swap setc rot rot 34 | elif dup 3 = 35 | rot dup '3' swap setc rot rot 36 | elif dup 4 = 37 | rot dup '4' swap setc rot rot 38 | elif dup 5 = 39 | rot dup '5' swap setc rot rot 40 | elif dup 6 = 41 | rot dup '6' swap setc rot rot 42 | elif dup 7 = 43 | rot dup '7' swap setc rot rot 44 | elif dup 8 = 45 | rot dup '8' swap setc rot rot 46 | elif dup 9 = 47 | rot dup '9' swap setc rot rot 48 | elif dup 10 = 49 | rot dup 'a' swap setc rot rot 50 | elif dup 11 = 51 | rot dup 'b' swap setc rot rot 52 | elif dup 12 = 53 | rot dup 'c' swap setc rot rot 54 | elif dup 13 = 55 | rot dup 'd' swap setc rot rot 56 | elif dup 14 = 57 | rot dup 'e' swap setc rot rot 58 | elif dup 15 = 59 | rot dup 'f' swap setc rot rot 60 | drop 4 - swap 1 + swap 61 | drop 0 castc swap setc 62 | 63 | 64 | def max: int x, int y -> int 65 | # Returns the largest number 66 | x y > if 67 | x 68 | else 69 | y 70 | 71 | 72 | def strcpy: ptr src, ptr dest -> ptr 73 | # Copies a string from src to dest and returns a pointer to dest 74 | 0 # index 75 | while dup src + derefc 0 castc != 76 | dup src + derefc over dest + setc 77 | 1 + 78 | dest + 0 castc swap setc 79 | dest 80 | 81 | 82 | def replace: ptr s, char q, char r -> ptr 83 | # Replaces each instance of char q in str s with char r 84 | s 85 | while dup derefc 0 castc != 86 | dup derefc q = if 87 | dup r swap setc 88 | 1 + 89 | s 90 | 91 | 92 | def startswith: ptr s, ptr q -> bool 93 | # Returns True if str s starts with substring q 94 | 0 95 | while dup s + derefc over q + derefc = over q + derefc 0 castc != and 96 | 1 + 97 | q + derefc 0 castc = 98 | 99 | 100 | def streq: ptr s, ptr q -> bool 101 | # Returns True if str s is equal to string q 102 | s NULL = q NULL = and if 103 | True 104 | elif s NULL = q NULL = or 105 | False 106 | elif q strlen s strlen = 107 | False # found_difference 108 | 0 # index 109 | while dup q + derefc 0 castc != 110 | dup q + derefc 111 | over s + derefc != 112 | rot or swap 113 | 1 + 114 | drop 115 | False = 116 | else 117 | False 118 | 119 | 120 | const page.next 0 # ptr 121 | const page.prev 8 # ptr 122 | const page.size 16 # int 123 | const page.blocks 24 # ptr 124 | const PAGE_METADATA_SIZE 24 125 | 126 | const PAGE_SIZE 4096 127 | 128 | const block.next 0 # ptr 129 | const block.prev 8 # ptr 130 | const block.size 16 # integer 131 | const block.free 24 # boolean 132 | const block.file 25 # ptr 133 | const block.line 33 # int 134 | const block.data 41 # data 135 | const BLOCK_METADATA_SIZE 41 136 | 137 | buffer current_page 8 138 | buffer root_page 8 139 | 140 | 141 | buffer VERIFY_MEMORY 1 # Set to True to verify memory 142 | buffer DUMP_MEMORY 1 # Set to True to dump dynamically allocated memory 143 | 144 | buffer _vm_buf 19 145 | 146 | def verify_memory: -> void 147 | # Checks all the dynamically allocated memory for invalid metadata 148 | VERIFY_MEMORY derefb if 149 | DUMP_MEMORY derefb if 150 | "Verifying memory..\n" puts 151 | 152 | root_page 153 | while dup page.next + derefp NULL != 154 | dup page.next + derefp 155 | 156 | DUMP_MEMORY derefb if 157 | "* Page: " puts 158 | dup casti _vm_buf itohex _vm_buf puts 159 | "\n" puts 160 | 161 | " * prev: " puts 162 | dup page.prev + derefi _vm_buf itohex _vm_buf puts 163 | "\n" puts 164 | 165 | " * next: " puts 166 | dup page.next + derefi _vm_buf itohex _vm_buf puts 167 | "\n" puts 168 | 169 | " * size: " puts 170 | dup page.size + derefi _vm_buf itohex _vm_buf puts 171 | "\n" puts 172 | 173 | swap over page.prev + derefp = \ 174 | "Pointer to previous page should be valid\n" assert 175 | dup page.size + derefi PAGE_SIZE >= \ 176 | "Page size should be at least the default value\n" assert 177 | 178 | dup page.size + derefi PAGE_METADATA_SIZE - # Save page size to stack 179 | over page.blocks + 180 | 181 | dup block.next + derefp NULL = 182 | over block.prev + derefp NULL = and 183 | over block.free + derefb True = and False = \ 184 | "Page without blocks should be unmapped" assert 185 | 186 | while dup NULL != 187 | DUMP_MEMORY derefb if 188 | " * block: " puts 189 | dup casti _vm_buf itohex _vm_buf puts 190 | "\n" puts 191 | 192 | " * file: " puts 193 | dup block.file + derefp puts 194 | "\n" puts 195 | 196 | " * line: " puts 197 | dup block.line + derefi itos puts 198 | "\n" puts 199 | 200 | " * size: " puts 201 | dup block.size + derefi _vm_buf itohex _vm_buf puts 202 | "\n" puts 203 | 204 | " * next: " puts 205 | dup block.next + derefi _vm_buf itohex _vm_buf puts 206 | "\n" puts 207 | 208 | " * prev: " puts 209 | dup block.prev + derefi _vm_buf itohex _vm_buf puts 210 | "\n" puts 211 | 212 | " * free: " puts 213 | dup block.free + derefb if 214 | "True\n" puts 215 | else 216 | "False\n" puts 217 | 218 | dup block.size + derefi BLOCK_METADATA_SIZE + 219 | rot swap - 220 | dup 0 >= "Blocks should not overflow page memory\n" assert 221 | swap 222 | 223 | dup block.next + derefp NULL = 224 | over dup block.next + derefp swap casti - casti PAGE_SIZE <= or 225 | "Next block should be in the same page\n" assert 226 | 227 | dup block.prev + derefp NULL = 228 | over dup block.prev + derefi - casti PAGE_SIZE <= or 229 | "Previous block should be in the same page\n" assert 230 | 231 | dup block.next + derefp NULL != if 232 | dup block.next + derefp 233 | over block.size + derefi - 234 | BLOCK_METADATA_SIZE - 235 | over = "Next block should be directly after this one\n" assert 236 | 237 | dup block.prev + derefp NULL != if 238 | dup block.prev + derefp 239 | dup block.size + derefi + 240 | BLOCK_METADATA_SIZE + 241 | over = "Previous block should be directly before this one\n" assert 242 | 243 | block.next + derefp 244 | drop 245 | 246 | dup 0 > if 247 | DUMP_MEMORY derefb if 248 | " * unused bytes: " puts 249 | dup _vm_buf itohex _vm_buf puts 250 | "\n" puts 251 | False "Block sizes should sum up to page size\n" assert 252 | drop 253 | 254 | drop 255 | DUMP_MEMORY derefb if 256 | "Memory OK\n" puts 257 | 258 | 259 | def _malloc_get_next_page: ptr page, int size -> ptr 260 | # Returns the next page, creates a new page if there are none 261 | size PAGE_SIZE >= "Page size should be at least the default size\n" assert 262 | 263 | page page.next + derefp NULL = if 264 | NULL # addr; kernel chooses position in memory 265 | size # length; (common page size is 4k) 266 | PROT_READ PROT_WRITE or # prot; allow read and write 267 | MAP_PRIVATE MAP_ANONYMOUS or # flags 268 | 0 1 - # fd; -1 is needed for MAP_ANONYMOUS 269 | 0 # offset; should be 0 for MAP_ANONYMOUS 270 | SYS_MMAP 271 | syscall 6 272 | 273 | dup 0 < if 274 | "Unable to allocate memory page\n" raise 275 | 276 | castp 277 | 278 | # Initialize page 279 | dup page.next + NULL swap setp 280 | dup page.prev + page swap setp 281 | dup page.size + size swap seti 282 | 283 | # Initialize block 284 | dup page.blocks + 285 | dup block.free + True swap setb 286 | dup block.next + NULL swap setp 287 | dup block.prev + NULL swap setp 288 | dup block.file + "" swap setp 289 | dup block.line + 0 swap seti 290 | dup block.size + size PAGE_METADATA_SIZE - \ 291 | BLOCK_METADATA_SIZE - swap seti 292 | drop 293 | 294 | page page.next + setp 295 | 296 | # Return next page 297 | page page.next + derefp 298 | 299 | dup NULL != "Next page should not be NULL\n" assert 300 | 301 | 302 | def _malloc_split_block: ptr block, int old_size, int size -> ptr 303 | # Splits a free block into a taken and a free block and returns a pointer to the 304 | # first, taken block. 305 | block block.free + derefb "Splitted block should be free\n" assert 306 | 307 | block block.next + derefp # Save pointer to next block on the stack 308 | 309 | False block block.free + setb 310 | 311 | # Calculate remaining size of free block 312 | old_size BLOCK_METADATA_SIZE - size - 313 | 314 | dup 0 > if 315 | # Only set block size if split actually occurs 316 | size block block.size + seti 317 | 318 | block BLOCK_METADATA_SIZE + size + block block.next + setp 319 | 320 | True block block.next + derefp block.free + setb 321 | block block block.next + derefp block.prev + setp 322 | dup block block.next + derefp block.size + seti 323 | over block block.next + derefp block.next + setp 324 | "" block block.next + derefp block.file + setp 325 | 0 block block.next + derefp block.line + seti 326 | 327 | block block.next + derefp block.next + derefp NULL != if 328 | block block.next + derefp dup block.next + derefp block.prev + setp 329 | 330 | block 331 | 332 | 333 | def _malloc_block_is_available_or_NULL: ptr block, int size -> bool 334 | # Returns True if given block is free and has enough space, or it's a NULL pointer 335 | block NULL = if 336 | True 337 | else 338 | block block.free + derefb 339 | block block.size + derefi size >= and 340 | 341 | 342 | def malloc: int size -> ptr 343 | # Allocates a memory block of given size and returns its address 344 | 345 | # Set current page to root if this is the initial malloc 346 | current_page derefp NULL = if 347 | root_page current_page setp 348 | 349 | size 0 = if 350 | "Size should be greater than zero\n" raise 351 | 352 | # Find the next available block 353 | NULL # available_block 354 | while dup NULL = 355 | drop 356 | 357 | # Fetch next page 358 | current_page derefp 359 | size PAGE_METADATA_SIZE + BLOCK_METADATA_SIZE + \ 360 | PAGE_SIZE max _malloc_get_next_page 361 | dup current_page setp 362 | 363 | root_page page.next + derefp NULL != "First page should be set\n" assert 364 | dup root_page != "Page should not be root page\n" assert 365 | dup NULL != "Next page should be set\n" assert 366 | 367 | # Find available block 368 | page.blocks + 369 | 370 | dup block.size + derefi 0 != "Block size should be greater than zero\n" assert 371 | 372 | # Find next available block or NULL (no available blocks in this page) 373 | while dup size _malloc_block_is_available_or_NULL False = 374 | block.next + derefp 375 | 376 | dup NULL != "Pointer to found block should be non-NULL\n" assert 377 | dup block.free + derefb "Found block should be free\n" assert 378 | 379 | DUMP_MEMORY derefb if 380 | __get_arg 16 bool if 381 | # Save filename 382 | __get_arg 40 ptr 383 | over block.file + setp 384 | 385 | # Save line number 386 | __get_arg 32 int 387 | over block.line + seti 388 | 389 | dup block.size + derefi size = if 390 | # The block is a perfect fit 391 | dup block.free + False swap setb 392 | else 393 | # The block not is a perfect fit, split into a taken and a free block 394 | dup block.size + derefi size _malloc_split_block 395 | 396 | # Set current page to root page if it's the last page 397 | current_page derefp page.next + derefp NULL = if 398 | root_page current_page setp 399 | else 400 | current_page derefp page.prev + derefp current_page setp 401 | 402 | block.data + 403 | verify_memory 404 | 405 | 406 | def zalloc: int size -> ptr 407 | # Allocates a zero-initialized memory block of given size and returns its address 408 | size malloc # buf 409 | 0 # index 410 | while dup size < 411 | over over + 0 castc swap setc 412 | 1 + 413 | drop 414 | 415 | 416 | def memcpy: ptr src, ptr dest, int num -> void 417 | # Copies a given amount of bytes from src to dest 418 | 0 419 | while dup num < 420 | dup src + derefc 421 | over dest + setc 422 | 1 + 423 | verify_memory 424 | 425 | 426 | def merge_blocks: ptr a, ptr b -> void 427 | # Merges two freed blocks 428 | 429 | a block.free + derefb b block.free + derefb and "Blocks should be free" assert 430 | 431 | # Add b.size and sizeof(metadata) to a.size 432 | a block.size + derefi 433 | b block.size + derefi + 434 | BLOCK_METADATA_SIZE + 435 | a block.size + seti 436 | 437 | # Set b.next.prev to a 438 | b block.next + derefp NULL != if 439 | a 440 | b block.next + derefp block.prev + setp 441 | 442 | # Set a.next to b.next 443 | b block.next + derefp 444 | a block.next + setp 445 | 446 | 447 | def free: ptr block_data -> void 448 | # Frees a given block 449 | 450 | block_data block.data - 451 | 452 | dup block.free + derefb if 453 | "Trying to free already freed data\n" raise 454 | 455 | dup block.free + True swap setb 456 | 457 | dup block.next + derefp NULL != if 458 | dup block.next + derefp block.free + derefb if 459 | # Next block is free; merge 460 | dup dup block.next + derefp merge_blocks 461 | 462 | dup block.prev + derefp NULL != if 463 | dup block.prev + derefp block.free + derefb if 464 | # Previous block is free; merge 465 | dup block.prev + derefp over merge_blocks 466 | 467 | # Find the first block 468 | while dup block.prev + derefp NULL != 469 | dup block.prev + derefp over < "Previous block should be earlier\n" assert 470 | dup dup block.prev + derefi - PAGE_SIZE castp < \ 471 | "Previous block should be within the same page\n" assert 472 | 473 | block.prev + derefp 474 | 475 | dup block.next + derefp NULL = if 476 | # This page only has one free block; free/munmap it 477 | PAGE_METADATA_SIZE - 478 | dup page.next + derefp 479 | swap dup rot swap page.prev + derefp page.next + setp 480 | 481 | dup page.next + derefp NULL != if 482 | dup page.prev + derefp 483 | swap dup rot swap page.next + derefp page.prev + setp 484 | 485 | dup # addr; kernel chooses position in memory 486 | dup page.size + derefi # length; (common page size is 4k) 487 | SYS_MUNMAP 488 | syscall 2 489 | 490 | 0 < if 491 | "Unable to free memory page\n" raise 492 | 493 | verify_memory 494 | 495 | 496 | def puti: int i -> void 497 | # Prints a number to STDOUT 498 | i itos puts 499 | 500 | 501 | def errori: int i -> void 502 | # Prints a number to STDERR 503 | i itos error 504 | 505 | 506 | def _concat: ptr a, int a_len, ptr b, int b_len -> ptr 507 | # Takes two string and returns them after being concatenated. 508 | # Note that this mallocs, so don't forget to free! 509 | a_len b_len + 1 + malloc 510 | a swap strcpy 511 | dup a_len + b swap strcpy drop 512 | 513 | 514 | def concat: ptr a, ptr b -> ptr 515 | # Wrapper around _concat 516 | a a strlen b b strlen _concat 517 | 518 | 519 | def concatfl: ptr a, ptr b -> ptr 520 | # Concatenates two strings and frees the first 521 | a b concat 522 | a free 523 | 524 | 525 | def concatfr: ptr a, ptr b -> ptr 526 | # Concatenates two strings and frees the second 527 | a b concat 528 | b free 529 | 530 | 531 | def concatf: ptr a, ptr b -> ptr 532 | # Concatenates two strings and frees them 533 | a b concat 534 | a free 535 | b free 536 | 537 | 538 | def stoi: ptr s -> int 539 | # Converts a string to an integer 540 | s 0 541 | while over derefc 0 castc != 542 | 10 * 543 | over derefc 544 | 545 | dup '0' < over '9' > or if 546 | "Unable to convert string to integer: " s concat "\n" concat raise 547 | 548 | casti '0' casti - + 549 | swap 1 + swap 550 | 551 | 552 | buffer _read_file_buffer 513 553 | 554 | def read_file: ptr filename -> ptr 555 | # Returns the contents of a given file 556 | 1 malloc 557 | dup 0 castc swap setc 558 | filename 'r' open 559 | 560 | 1 while dup 0 > 561 | drop 562 | dup _read_file_buffer 512 read 563 | dup _read_file_buffer + 0 castc swap setc 564 | dup 0 > if 565 | rot dup _read_file_buffer concat 566 | swap free 567 | rot rot 568 | drop drop 569 | 570 | 571 | def substring: ptr s, int n -> ptr 572 | # Creates a null-terminated substring of the first n bytes of s. 573 | # Note that this function mallocs, so don't forget to free the result. 574 | n 1 + malloc 575 | s over n memcpy 576 | dup n + 0 castc swap setc 577 | -------------------------------------------------------------------------------- /lib/std/argparse.ilo: -------------------------------------------------------------------------------- 1 | import lib.std 2 | import lib.std.list 3 | import lib.std.dict 4 | 5 | 6 | # ARGS 7 | # Contains a list with positional arguments and a dictionary of optional keyword 8 | # arguments/flags. 9 | 10 | 11 | const ARG_TYPE_POSITIONAL 0 12 | const ARG_TYPE_FLAG 1 13 | const ARG_TYPE_OPTIONAL 2 14 | 15 | const arg.type 0 # int; ARG_TYPE_* 16 | const arg.value 8 # ptr; string 17 | const arg.description 16 # ptr; string 18 | const ARG_SIZE 24 19 | 20 | const args.posargs 0 # ptr; list 21 | const args.kwargs 8 # ptr; dict 22 | const args.filename 16 # ptr; string 23 | const ARGS_SIZE 24 24 | 25 | 26 | buffer _args 8 # ptr; args 27 | buffer _posargs 8 # ptr; list 28 | buffer _kwargs 8 # ptr; list 29 | buffer _flags 8 # ptr; list 30 | 31 | 32 | def argparse_init: -> void 33 | # Initialize args object 34 | ARGS_SIZE malloc 35 | PTR_SIZE new_list over args.posargs + setp 36 | new_dict over args.kwargs + setp 37 | "UNKNOWN" over args.filename + setp 38 | _args setp 39 | 40 | PTR_SIZE new_list _posargs setp 41 | PTR_SIZE new_list _kwargs setp 42 | PTR_SIZE new_list _flags setp 43 | 44 | 45 | def get_argument: ptr argv, int n, int argc -> ptr 46 | # Takes an array of arguments argv and returns the n'th argument 47 | n argc >= if 48 | "Could not parse arguments due to index error\n" raise 49 | 50 | argv 0 while dup n < 51 | swap dup strlen + 1 + swap 52 | 1 + 53 | drop 54 | 55 | 56 | def argparse_add_argument: ptr name, int arg_type, ptr description -> void 57 | # Adds an argument and sets it value to NULL 58 | ARG_SIZE malloc 59 | arg_type over arg.type + seti 60 | NULL over arg.value + setp 61 | description over arg.description + setp 62 | 63 | name 64 | over 65 | _args derefp args.kwargs + derefp 66 | dict_insert 67 | _args derefp args.kwargs + setp 68 | 69 | arg_type ARG_TYPE_OPTIONAL = if 70 | name _kwargs derefp list_append_ptr _kwargs setp 71 | elif arg_type ARG_TYPE_FLAG = 72 | name _flags derefp list_append_ptr _flags setp 73 | elif arg_type ARG_TYPE_POSITIONAL = 74 | name _posargs derefp list_append_ptr _posargs setp 75 | else 76 | "Unknown argument type\n" raise 77 | 78 | 79 | def argparse_print_help: -> void 80 | # Prints the help/usage and exits with exit code 0 81 | # Print usage 82 | "Usage: " puts 83 | _args derefp args.filename + derefp puts 84 | " [OPTIONS]" puts 85 | 86 | 0 87 | while dup _posargs derefp list.len + derefi < 88 | " " puts dup _posargs derefp list_fetch_ptr puts 89 | 1 + 90 | drop 91 | "\n\n" puts 92 | 93 | # Print positional argument descriptions 94 | _posargs derefp list.len + derefi 0 > if 95 | "Positional arguments:\n" puts 96 | 97 | 0 98 | while dup _posargs derefp list.len + derefi < 99 | dup _posargs derefp list_fetch_ptr 100 | " " puts dup puts "\n" puts 101 | 102 | _args derefp args.kwargs + derefp dict_fetch 103 | " " puts arg.description + derefp puts "\n\n" puts 104 | 105 | 1 + 106 | drop 107 | 108 | # Print optional argument descriptions 109 | _kwargs derefp list.len + derefi 0 > if 110 | "Optional arguments:\n" puts 111 | 112 | 0 113 | while dup _kwargs derefp list.len + derefi < 114 | dup _kwargs derefp list_fetch_ptr 115 | " " puts dup puts "\n" puts 116 | 117 | _args derefp args.kwargs + derefp dict_fetch 118 | " " puts arg.description + derefp puts "\n\n" puts 119 | 120 | 1 + 121 | drop 122 | 123 | # Print flag descriptions 124 | _flags derefp list.len + derefi 0 > if 125 | "Flags:\n" puts 126 | 127 | 0 128 | while dup _flags derefp list.len + derefi < 129 | dup _flags derefp list_fetch_ptr 130 | " " puts dup puts "\n" puts 131 | 132 | _args derefp args.kwargs + derefp dict_fetch 133 | " " puts arg.description + derefp puts "\n\n" puts 134 | 135 | 1 + 136 | drop 137 | 138 | 0 exit 139 | 140 | 141 | def argparse_parse_args: ptr argv, int argc -> ptr 142 | # Parses arguments and returns an args object 143 | argv 0 argc get_argument _args derefp args.filename + setp 144 | 145 | # Parse arguments 146 | 1 147 | while dup argc < 148 | argv over argc get_argument 149 | 150 | dup "-" startswith if 151 | dup "--help" streq 152 | over "-h" streq or if 153 | argparse_print_help 154 | 155 | dup _args derefp args.kwargs + derefp dict_fetch 156 | dup NULL = if 157 | argparse_print_help 158 | 159 | swap drop 160 | 161 | dup arg.type + derefi ARG_TYPE_OPTIONAL = if 162 | swap 1 + 163 | argv over argc get_argument 164 | rot arg.value + setp 165 | elif dup arg.type + derefi ARG_TYPE_FLAG = 166 | "SET" swap arg.value + setp 167 | else 168 | drop 169 | else 170 | _args derefp args.posargs + derefp 171 | list_append_ptr 172 | _args derefp args.posargs + setp 173 | 1 + 174 | 175 | _args derefp 176 | 177 | dup args.posargs + derefp list.len + derefi 178 | _posargs derefp list.len + derefi != if 179 | argparse_print_help 180 | -------------------------------------------------------------------------------- /lib/std/dict.ilo: -------------------------------------------------------------------------------- 1 | import lib.std 2 | 3 | 4 | # DICT 5 | # A hashmap to achieve O(1) lookup and insert times. It will use the sdbm hashing 6 | # algorithm [1] and linear probing [2] as collision resolving. 7 | # 8 | # The dictionary capacity will double when the items-bucket ratio exceeds 33%. 9 | # 10 | # [1] www.cse.yorku.ca/~oz/hash.html 11 | # [2] Linear Probing - Wikipedia: https://en.wikipedia.org/wiki/Linear_probing 12 | 13 | 14 | const dict.inserts 0 # int 15 | const dict.buckets 8 # int 16 | const DICT_METADATA_SIZE 16 17 | 18 | const dict_bucket.key 0 # ptr 19 | const dict_bucket.value 8 # ptr 20 | const dict_bucket.taken 16 # bool 21 | const DICT_BUCKET_SIZE 17 22 | 23 | 24 | def hash: ptr s, int size -> int 25 | # An implementation of the sdbm hashing algorithm 26 | 0 # hash 27 | 0 # index 28 | while dup size < 29 | dup s + derefc casti 30 | # hash index char 31 | rot 32 | # index char hash 33 | dup 6 shl 34 | # index char hash hash<<6 35 | over 16 shl + 36 | # index char hash hash<<6+hash<<16 37 | swap - + 38 | # index char+hash<<6+hash<<16-hash 39 | swap 40 | 1 + 41 | drop 42 | 43 | 44 | def create_dict: int buckets -> ptr 45 | # Initializes a dictionary with a given number of buckets and returns its pointer 46 | buckets DICT_BUCKET_SIZE * DICT_METADATA_SIZE + zalloc 47 | 0 over dict.inserts + seti 48 | buckets over dict.buckets + seti 49 | 50 | 51 | def new_dict: -> ptr 52 | # Creates a new dictionary and returns its pointer 53 | 50 create_dict 54 | 55 | 56 | def dict_fetch: ptr key, ptr dict -> ptr 57 | # Looks up a key in a given dictionary and return a pointer to the value (or NULL) 58 | dict dict.buckets + derefi 0 > "Dict should have buckets\n" assert 59 | 60 | key dup strlen hash dict dict.buckets + derefi % 61 | dict DICT_METADATA_SIZE + over DICT_BUCKET_SIZE * + 62 | while dup dict_bucket.taken + derefb \ 63 | over dict_bucket.key + derefp key streq False = and 64 | drop 65 | 1 + dict dict.buckets + derefi % 66 | dict DICT_METADATA_SIZE + over DICT_BUCKET_SIZE * + 67 | 68 | dup dict_bucket.taken + derefb if 69 | dup dict_bucket.value + derefp 70 | else 71 | NULL 72 | 73 | 74 | def dict_insert: ptr key, ptr value, ptr dict -> ptr 75 | # Adds a key-value pair to the dictionary and returns a pointer to the dictionary 76 | dict dict.buckets + derefi 0 > "Dict should have buckets" assert 77 | 78 | # Check if the dictionary needs to be expanded 79 | dict dict.inserts + derefi 3 * dict dict.buckets + derefi > if 80 | # Create a dictionary with twice the capacity 81 | dict dict.buckets + derefi 2 * create_dict 82 | 83 | # Re-hash dictionary 84 | 0 # bucket index 85 | while dup dict dict.buckets + derefi < 86 | dup DICT_BUCKET_SIZE * DICT_METADATA_SIZE + dict + 87 | 88 | # Re-hash bucket if it's filled 89 | dup dict_bucket.taken + derefb if 90 | rot dup rot 91 | dup dict_bucket.key + derefp 92 | swap dict_bucket.value + derefp 93 | rot 94 | dict_insert drop 95 | swap 96 | else 97 | drop 98 | 99 | 1 + 100 | drop 101 | 102 | # Free old dictionary 103 | dict free 104 | else 105 | dict 106 | 107 | # Hash key 108 | key dup strlen hash 109 | over dict.buckets + derefi % 110 | 111 | # Find first empty bucket 112 | over DICT_METADATA_SIZE + over DICT_BUCKET_SIZE * + 113 | while dup dict_bucket.taken + derefb 114 | drop 115 | 1 + over dict.buckets + derefi % 116 | over DICT_METADATA_SIZE + over DICT_BUCKET_SIZE * + 117 | 118 | # Fill bucket 119 | key over dict_bucket.key + setp 120 | value over dict_bucket.value + setp 121 | True over dict_bucket.taken + setb 122 | 123 | # Increment dictionary inserts count 124 | drop drop 125 | dup dict.inserts + derefi 1 + over dict.inserts + seti 126 | -------------------------------------------------------------------------------- /lib/std/linux.ilo: -------------------------------------------------------------------------------- 1 | const STDIN 0 2 | const STDOUT 1 3 | const STDERR 2 4 | 5 | const SYS_READ 0 6 | const SYS_WRITE 1 7 | const SYS_OPEN 2 8 | const SYS_MMAP 9 9 | const SYS_MUNMAP 11 10 | const SYS_RT_SIGACTION 13 11 | const SYS_RT_SIGRETURN 15 12 | const SYS_SETITIMER 38 13 | const SYS_EXIT 60 14 | 15 | const O_RDONLY 0 16 | const O_WRONLY 1 17 | const PROT_READ 1 18 | const PROT_WRITE 2 19 | const MAP_PRIVATE 2 20 | const MAP_ANONYMOUS 32 21 | 22 | const ITIMER_REAL 0 23 | const SIGALRM 14 24 | const SA_SIGINFO 4 25 | const SA_RESTORER 67108864 # 0x04000000 26 | 27 | 28 | def puts: ptr s -> void 29 | # Prints a string and returns the write syscall exit-code 30 | STDOUT # File descriptor 31 | s # Pointer to string 32 | s strlen # String length 33 | SYS_WRITE 34 | syscall 3 35 | 36 | 37 | def error: ptr s -> void 38 | # Prints an error and returns the write syscall exit-code 39 | STDERR # File descriptor 40 | s # Pointer to string 41 | s strlen # String length 42 | SYS_WRITE 43 | syscall 3 44 | 45 | 46 | def exit: int code -> void 47 | # Exits using a given exit code 48 | code SYS_EXIT syscall 1 49 | 50 | 51 | def raise: ptr message -> void 52 | # Prints a given error message and exits with exitcode 1 53 | message traceback 54 | 55 | 56 | def assert: bool assertion, ptr message -> void 57 | # Raises when assertion is False 58 | assertion False = if 59 | message traceback 60 | 61 | 62 | def open: ptr filename, char mode -> int 63 | # Opens a given file in (r)ead or (w)rite mode and returns a file descriptor. 64 | filename 65 | 0 66 | 67 | mode 'r' = if 68 | O_RDONLY or 69 | mode 'w' = if 70 | O_WRONLY or 71 | 72 | 0 73 | SYS_OPEN 74 | syscall 3 75 | 76 | dup 0 < if 77 | "Could not open file\n" raise 78 | 79 | 80 | def read: int fd, ptr buf, int count -> int 81 | # Reads from a file descriptor and returns the number of bytes read or zero if EOF 82 | fd buf count SYS_READ syscall 3 83 | dup 0 < if 84 | "Could not read file\n" raise 85 | 86 | 87 | def rt_sigaction: int signum, ptr act, ptr oldact, int sigsetsize -> int 88 | # Changes the action taken on a certain signal 89 | signum act oldact sigsetsize SYS_RT_SIGACTION syscall 4 90 | dup 0 != if 91 | "Could not change signal action\n" raise 92 | 93 | 94 | inline rt_sigreturn: -> void 95 | # Return from signal handler and cleanup stack frame 96 | SYS_RT_SIGRETURN syscall 0 97 | "RT_SIGRETURN should not return\n" raise 98 | 99 | 100 | const sigaction.handler 0 # ptr 101 | const sigaction.flags 8 # int 102 | const sigaction.restorer 16 # ptr 103 | const sigaction.mask 24 # int 104 | const SIGACTION_SIZE 32 105 | 106 | 107 | def setitimer: int which, ptr new_val, ptr old_val -> int 108 | # Set the value of an interval timer 109 | which new_val old_val SYS_SETITIMER syscall 3 110 | dup 0 != if 111 | "Could not set the timer\n" raise 112 | 113 | 114 | const itimerval.interval 0 # timeval 115 | const itimerval.value 16 # timeval 116 | const ITIMERVAL_SIZE 32 117 | 118 | const timeval.sec 0 # int 119 | const timeval.usec 8 # int 120 | const TIMEVAL_SIZE 16 121 | 122 | 123 | buffer _sigaction 32 124 | 125 | def setup_signal_handler: int signal, ptr handler, ptr restorer -> int 126 | # Setup a signal handler for a certain signal 127 | handler _sigaction sigaction.handler + setp 128 | 0 _sigaction sigaction.mask + seti 129 | 130 | restorer 0 castp = if 131 | 0 _sigaction sigaction.flags + seti 132 | 0 castp _sigaction sigaction.restorer + setp 133 | else 134 | SA_SIGINFO SA_RESTORER or \ 135 | _sigaction sigaction.flags + seti 136 | restorer _sigaction sigaction.restorer + setp 137 | 138 | signal _sigaction 0 castp 8 rt_sigaction 139 | 140 | 141 | buffer _itimerval 32 142 | 143 | def set_timer: int inter_secs, int inter_usecs, int val_secs, int val_usecs -> int 144 | # Sets the values of the internal timer 145 | inter_secs _itimerval itimerval.interval + timeval.sec + seti 146 | inter_usecs _itimerval itimerval.interval + timeval.usec + seti 147 | inter_secs _itimerval itimerval.value + timeval.sec + seti 148 | inter_usecs _itimerval itimerval.value + timeval.usec + seti 149 | 150 | ITIMER_REAL _itimerval 0 castp setitimer 151 | -------------------------------------------------------------------------------- /lib/std/list.ilo: -------------------------------------------------------------------------------- 1 | import lib.std 2 | 3 | 4 | # LIST 5 | # A dynamic list with O(n) time complexity (appending an item)[1]. It has a capacity of 6 | # 2^N items (starting at 2^0 = 1) and when an item needs to be appended and the list is 7 | # full, it's capacity is doubled. 8 | # 9 | # Note that this means that expanding lists means their location in the memory is likely 10 | # to change! 11 | # 12 | # [1] The Simple and Elegant Idea behind Efficient Dynamic Arrays 13 | # https://youtu.be/Ij7NQ-0mIVA 14 | 15 | const list.capacity 0 # int 16 | const list.len 8 # int 17 | const list.item_size 16 # int 18 | const list.items 24 # ptr 19 | const LIST_METADATA_SIZE 24 20 | 21 | 22 | def list_create: int capacity, int item_size -> ptr 23 | # Initializes an empty list with a given capacity and returns its pointer 24 | capacity item_size * LIST_METADATA_SIZE + malloc 25 | dup list.capacity + capacity swap seti 26 | dup list.item_size + item_size swap seti 27 | dup list.len + 0 swap seti 28 | 29 | 30 | def new_list: int item_size -> ptr 31 | # Returns a pointer to an empty list 32 | 50 item_size list_create 33 | 34 | 35 | def list_copy: ptr src -> ptr 36 | # Creates a copy of a list and returns its pointer 37 | src list.capacity + derefi 38 | src list.item_size + derefi * 39 | LIST_METADATA_SIZE + 40 | dup malloc 41 | dup rot src rot rot memcpy 42 | 43 | 44 | def list_eq: ptr a, ptr b -> bool 45 | # Returns True if two lists have the same content 46 | a list.len + derefi b list.len + derefi = 47 | a list.item_size + derefi b list.item_size + derefi = and if 48 | True 49 | LIST_METADATA_SIZE 50 | while dup a list.len + derefi a list.item_size + derefi * LIST_METADATA_SIZE + < 51 | dup a + derefc over b + derefc != if 52 | swap drop False swap 53 | 1 + 54 | drop 55 | else 56 | False 57 | 58 | 59 | def list_fetch: int index, ptr list -> ptr 60 | # Returns a pointer to a certain index in a given list 61 | index list list.len + derefi >= if 62 | "List index out of bounds\n" raise 63 | list LIST_METADATA_SIZE + index list list.item_size + derefi * + 64 | 65 | 66 | def list_fetch_int: int index, ptr list -> int 67 | # Returns the value of a certain index in a given list 68 | list list.item_size + derefi INT_SIZE != if 69 | "Can't call integer-specific method for list with non-integer items\n" raise 70 | 71 | index list list.len + derefi >= if 72 | "List index out of bounds\n" raise 73 | 74 | list LIST_METADATA_SIZE + index list list.item_size + derefi * + derefi 75 | 76 | 77 | def list_fetch_ptr: int index, ptr list -> ptr 78 | # Returns the value of a certain index in a given list 79 | list list.item_size + derefi PTR_SIZE != if 80 | "Can't call pointer-specific method for list with non-pointer items\n" raise 81 | 82 | index list list.len + derefi >= if 83 | "List index out of bounds\n" raise 84 | 85 | list LIST_METADATA_SIZE + index list list.item_size + derefi * + derefp 86 | 87 | 88 | def list_contains_string: ptr s, ptr list -> bool 89 | # Returns True when the list contains a given string 90 | list list.item_size + derefi PTR_SIZE != if 91 | "Can't call pointer-specific method for list with non-pointer items\n" raise 92 | 93 | False # value 94 | 0 # index 95 | while dup list list.len + derefi < 96 | dup list list_fetch_ptr s streq if 97 | swap drop True swap 98 | 1 + 99 | drop 100 | 101 | 102 | def list_pop: ptr list -> ptr 103 | # Copies, removes and returns the last item of a given list 104 | # The returned data is malloc'ed, so don't forget to free! 105 | list list.len + derefi 0 = if 106 | "Can't pop from an empty list\n" raise 107 | 108 | # Get index of last item 109 | list list.len + derefi 1 - 110 | 111 | # Get pointer to item 112 | dup list list_fetch 113 | 114 | # Decrease list.len 115 | over list list.len + seti 116 | 117 | # Copy 118 | list list.item_size + derefi malloc 119 | dup rot swap 120 | list list.item_size + derefi memcpy 121 | 122 | 123 | def list_pop_int: ptr list -> int 124 | # Removes and returns the last value of a given list 125 | list list.item_size + derefi INT_SIZE != if 126 | "Can't call integer-specific method for list with non-integer items\n" raise 127 | 128 | list list.len + derefi 0 = if 129 | "Can't pop from an empty list\n" raise 130 | 131 | list list.len + derefi 1 - 132 | dup list list.len + seti 133 | list list.item_size + derefi * list + LIST_METADATA_SIZE + derefi 134 | 135 | 136 | def list_pop_ptr: ptr list -> ptr 137 | # Removes and returns the last value of a given list 138 | list list.item_size + derefi PTR_SIZE != if 139 | "Can't call pointer-specific method for list with non-pointer items\n" raise 140 | 141 | list list.len + derefi 0 = if 142 | "Can't pop from an empty list\n" raise 143 | 144 | list list.len + derefi 1 - 145 | dup list list.len + seti 146 | list list.item_size + derefi * list + LIST_METADATA_SIZE + derefp 147 | 148 | 149 | def list_peek: ptr list -> ptr 150 | # Returns a pointer to the last item of a given list 151 | list list.len + derefi 0 = if 152 | "Can't peek in an empty list\n" raise 153 | 154 | list dup list.len + derefi 1 - swap list_fetch 155 | 156 | 157 | def list_peek_int: ptr list -> int 158 | # Returns the last value of a given list 159 | list list.item_size + derefi INT_SIZE != if 160 | "Can't call integer-specific method for list with non-integer items\n" raise 161 | 162 | list list.len + derefi 0 = if 163 | "Can't peek in an empty list\n" raise 164 | 165 | list dup list.len + derefi 1 - swap list_fetch_int 166 | 167 | 168 | def list_peek_ptr: ptr list -> ptr 169 | # Returns the last value of a given list 170 | list list.item_size + derefi PTR_SIZE != if 171 | "Can't call pointer-specific method for list with non-pointer items\n" raise 172 | 173 | list list.len + derefi 0 = if 174 | "Can't peek in an empty list\n" raise 175 | 176 | list dup list.len + derefi 1 - swap list_fetch_ptr 177 | 178 | 179 | def list_append: ptr item, ptr list -> ptr 180 | # Appends an item to a given list and return a pointer to the list 181 | list list.len + derefi 182 | list list.capacity + derefi = if 183 | # Create a list with double the capacity 184 | list list.capacity + derefi 2 * list list.item_size + derefi list_create 185 | 186 | # Copy items 187 | list list.items + 188 | over list.items + 189 | list list.len + derefi list list.item_size + derefi * 190 | memcpy 191 | 192 | # Update metadata 193 | list list.len + derefi 194 | over list.len + seti 195 | 196 | # Free old list 197 | list free 198 | else 199 | list 200 | 201 | # Append item to list 202 | dup list.len + derefi over list.item_size + derefi * LIST_METADATA_SIZE + over + 203 | over list.item_size + derefi 204 | item rot rot memcpy 205 | 206 | dup list.len + derefi 1 + over list.len + seti 207 | 208 | 209 | def list_append_int: int item, ptr list -> ptr 210 | # Appends an item to a given list and return a pointer to the list 211 | list list.item_size + derefi INT_SIZE != if 212 | "Can't call integer-specific method for list with non-integer items\n" raise 213 | 214 | list list.len + derefi 215 | list list.capacity + derefi = if 216 | # Create a list with double the capacity 217 | list list.capacity + derefi 2 * list list.item_size + derefi list_create 218 | 219 | # Copy items 220 | list list.items + 221 | over list.items + 222 | list list.len + derefi list list.item_size + derefi * 223 | memcpy 224 | 225 | # Update metadata 226 | list list.len + derefi 227 | over list.len + seti 228 | 229 | # Free old list 230 | list free 231 | else 232 | list 233 | 234 | # Append item to list 235 | dup list.len + derefi 236 | dup 1 + rot dup rot swap list.len + seti swap 237 | over list.item_size + derefi * LIST_METADATA_SIZE + over + item swap seti 238 | 239 | 240 | def list_append_ptr: ptr item, ptr list -> ptr 241 | # Appends an item to a given list and return a pointer to the list 242 | list list.item_size + derefi PTR_SIZE != if 243 | "Can't call pointer-specific method for list with non-pointer items\n" raise 244 | 245 | list list.len + derefi 246 | list list.capacity + derefi = if 247 | # Create a list with double the capacity 248 | list list.capacity + derefi 2 * list list.item_size + derefi list_create 249 | 250 | # Copy items 251 | list list.items + 252 | over list.items + 253 | list list.len + derefi list list.item_size + derefi * 254 | memcpy 255 | 256 | # Update metadata 257 | list list.len + derefi 258 | over list.len + seti 259 | 260 | # Free old list 261 | list free 262 | else 263 | list 264 | 265 | # Append item to list 266 | dup list.len + derefi 267 | dup 1 + rot dup rot swap list.len + seti swap 268 | list list.item_size + derefi * LIST_METADATA_SIZE + over + item swap setp 269 | -------------------------------------------------------------------------------- /lib/std/math.ilo: -------------------------------------------------------------------------------- 1 | import lib.std 2 | 3 | 4 | def div: int a, int b -> int 5 | # Returns a / b, where a and b are signed integers 6 | # TODO: This should obviously be what `/` should be, but the previous 7 | # implementation contained weird bugs which I was unable to fix. For more 8 | # info, see commits `a70ed1a` and `9fcc313`. 9 | b 0 != "Can't divide by zero" assert 10 | 11 | a 0 < b 0 < and if 12 | 0 a - 0 b - / 13 | elif a 0 < b 0 >= and 14 | 0 0 a - b / - 15 | elif a 0 >= b 0 < and 16 | 0 a 0 b - / - 17 | else 18 | a b / 19 | 20 | 21 | def usqrt: int x -> int 22 | # Returns the square root of a given unsigned integer 23 | 0 x 24 | while over over - dup 1 > swap 0 swap - 1 > or 25 | swap drop dup 26 | x over div + 2 div 27 | 28 | 29 | def sqrt: int x -> int 30 | # Returns the square root of a given signed integer 31 | x 0 < if 32 | 0 0 x - usqrt - 33 | else 34 | x usqrt 35 | 36 | 37 | const vec.x 0 38 | const vec.y 8 39 | const vec.z 16 40 | const VEC_SIZE 24 41 | 42 | def vec_add: ptr a, ptr b, ptr c -> void 43 | # Adds vector a to vector b and saves the result to vector c 44 | a vec.x + derefi b vec.x + derefi + c vec.x + seti 45 | a vec.y + derefi b vec.y + derefi + c vec.y + seti 46 | a vec.z + derefi b vec.z + derefi + c vec.z + seti 47 | 48 | def vec_sub: ptr a, ptr b, ptr c -> void 49 | # Subtracts vector a from vector b and saves the result to vector c 50 | a vec.x + derefi b vec.x + derefi - c vec.x + seti 51 | a vec.y + derefi b vec.y + derefi - c vec.y + seti 52 | a vec.z + derefi b vec.z + derefi - c vec.z + seti 53 | 54 | def vec_negate: ptr src, ptr dest -> void 55 | # Inverts the vector at src and saves it to dest 56 | 0 src vec.x + derefi - dest vec.x + seti 57 | 0 src vec.y + derefi - dest vec.y + seti 58 | 0 src vec.z + derefi - dest vec.z + seti 59 | 60 | def vec_mul: ptr src, int m, ptr dest -> void 61 | # Scales vector src up by m and saves it to dest 62 | src vec.x + derefi m * dest vec.x + seti 63 | src vec.y + derefi m * dest vec.y + seti 64 | src vec.z + derefi m * dest vec.z + seti 65 | 66 | def vec_div: ptr src, int m, ptr dest -> void 67 | # Scales vector src down by m and saves it to dest 68 | src vec.x + derefi m div dest vec.x + seti 69 | src vec.y + derefi m div dest vec.y + seti 70 | src vec.z + derefi m div dest vec.z + seti 71 | 72 | def vec_len_squared: ptr a -> int 73 | # Calculates the square of the length of vector a 74 | a vec.x + derefi dup * 75 | a vec.y + derefi dup * + 76 | a vec.z + derefi dup * + 77 | 78 | def vec_len: ptr a -> int 79 | # Calculates the square of the length of vector a 80 | a vec_len_squared sqrt 81 | 82 | buffer _vec_unit_buffer 24 83 | 84 | def vec_unit: ptr src, ptr dest -> void 85 | # Takes a vector src and saves its "unit" vector with length 1000 into dest 86 | src 1000 _vec_unit_buffer vec_mul 87 | _vec_unit_buffer src vec_len dest vec_div 88 | 89 | def vec_dot: ptr a, ptr b -> int 90 | # Returns a dot b 91 | a vec.x + derefi b vec.x + derefi * 92 | a vec.y + derefi b vec.y + derefi * + 93 | a vec.z + derefi b vec.z + derefi * + 94 | -------------------------------------------------------------------------------- /lib/std/profiler.ilo: -------------------------------------------------------------------------------- 1 | import lib.std 2 | import lib.std.list 3 | 4 | 5 | # PROFILER 6 | # A profiler which samples the call stack and saves it to a list. The timer and signal 7 | # handling code is based on an example by g0kkk[1]. 8 | # 9 | # [1] https://github.com/g0kkk/SignalHandler 10 | 11 | 12 | # TODO: Extend profiler sample struct to contain the full call stack 13 | const profiler_sample.function 0 # ptr; string 14 | const PROFILER_SAMPLE_SIZE 8 15 | 16 | buffer _profiler_stack 8 # ptr; list 17 | buffer _profiler_lock 1 # bool 18 | 19 | 20 | inline _profiler_signal_handler: -> void 21 | # Samples the call stack on a SIGALRM signal 22 | _profiler_lock derefb False = if 23 | True _profiler_lock setb 24 | 25 | __get_arg 24 ptr 26 | _profiler_stack derefp list_append_ptr _profiler_stack setp 27 | 28 | False _profiler_lock setb 29 | else 30 | "Warning: profiler lock is set\n" error 31 | 32 | def profiler_dump: -> void 33 | # Outputs the list of samples 34 | 0 35 | while dup _profiler_stack derefp list.len + derefi < 36 | dup _profiler_stack derefp list_fetch_ptr 37 | dup NULL != if 38 | puts 39 | else 40 | drop "_start" puts 41 | "\n" puts 42 | 1 + 43 | 44 | 45 | def profiler_init: -> void 46 | # Initializes the profiler 47 | # Setup signal handler 48 | SIGALRM 49 | reff _profiler_signal_handler 50 | reff rt_sigreturn 51 | setup_signal_handler drop 52 | 53 | # Setup timer 54 | 0 1000 0 1000 set_timer drop 55 | 56 | # Setup profiler 57 | 10 # 10 minutes 58 | 60 * # in seconds 59 | 1000 * # in milliseconds/samples 60 | PROFILER_SAMPLE_SIZE list_create _profiler_stack setp 61 | 62 | False _profiler_lock setb 63 | -------------------------------------------------------------------------------- /lib/std/textbuffer.ilo: -------------------------------------------------------------------------------- 1 | import lib.std 2 | 3 | 4 | # TEXTBUFFER 5 | # An array of characters which is optimized to append strings to. 6 | 7 | const textbuffer.len 0 # int 8 | const textbuffer.capacity 8 # int 9 | const textbuffer.content 16 10 | const TEXTBUFFER_METADATA_SIZE 16 11 | 12 | 13 | def textbuffer_create: int capacity -> ptr 14 | # Initializes an empty textbuffer with a given capacity and returns its pointer 15 | capacity TEXTBUFFER_METADATA_SIZE + malloc 16 | capacity over textbuffer.capacity + seti 17 | 0 over textbuffer.len + seti 18 | 0 castc over textbuffer.content + setc 19 | 20 | 21 | def new_textbuffer: -> ptr 22 | # Returns a pointer to an empty textbuffer 23 | 90 textbuffer_create 24 | 25 | 26 | def textbuffer_clear: ptr buf -> ptr 27 | # Resets the textbuffer and returns its pointer 28 | 0 buf textbuffer.len + seti 29 | buf 30 | 31 | 32 | def textbuffer_append: ptr s, ptr buf -> ptr 33 | # Copies a string into the buffer 34 | 35 | # Check if string fits in buffer 36 | s strlen 37 | buf 38 | while over over textbuffer.len + derefi + over textbuffer.capacity + derefi >= 39 | # The buffer is too small; double its capacity 40 | dup textbuffer.capacity + derefi 2 * textbuffer_create 41 | 42 | # Copy content and metadata 43 | over textbuffer.content + over textbuffer.content + strcpy drop 44 | over textbuffer.len + derefi over textbuffer.len + seti 45 | 46 | # Free old buffer 47 | swap free 48 | 49 | # Copy string to buffer 50 | s 51 | over dup textbuffer.len + derefi + TEXTBUFFER_METADATA_SIZE + 52 | strcpy drop 53 | 54 | # Update textbuffer.len 55 | swap over textbuffer.len + derefi + over textbuffer.len + seti 56 | -------------------------------------------------------------------------------- /src/ilo.ilo: -------------------------------------------------------------------------------- 1 | import lib.std 2 | import lib.std.argparse 3 | import lib.std.profiler 4 | import lib.ilo.formats 5 | import lib.ilo.generator 6 | import lib.ilo.lexer 7 | import lib.ilo.parser 8 | 9 | 10 | buffer DUMP_TOKENS 1 # bool 11 | buffer DUMP_OPCODES 1 # bool 12 | buffer ENABLE_PROFILER 1 # bool 13 | buffer FORMAT 8 # int 14 | 15 | 16 | def parse_arguments: ptr argv, int argc -> ptr 17 | # Returns an args object 18 | argparse_init 19 | 20 | "--format" ARG_TYPE_OPTIONAL "Set the output format (default: linux_x86_64)" 21 | argparse_add_argument 22 | "--profiler" ARG_TYPE_FLAG "Enable the profiler" 23 | argparse_add_argument 24 | "--verify-memory" ARG_TYPE_FLAG "Verify the dynamically allocated memory" 25 | argparse_add_argument 26 | "--dump-memory" ARG_TYPE_FLAG "Print the dynamically allocated memory" 27 | argparse_add_argument 28 | "--dump-tokens" ARG_TYPE_FLAG "Print the tokens" 29 | argparse_add_argument 30 | "--dump-opcodes" ARG_TYPE_FLAG "Print the opcodes" 31 | argparse_add_argument 32 | "filename" ARG_TYPE_POSITIONAL "Source code filename" 33 | argparse_add_argument 34 | 35 | argv argc argparse_parse_args 36 | 37 | 38 | def main: ptr argv, int argc -> int 39 | # Parse arguments 40 | argv argc parse_arguments 41 | 42 | "--format" over args.kwargs + derefp dict_fetch arg.value + derefp 43 | dup NULL = if 44 | drop FORMAT_LINUX_X86_64 FORMAT seti 45 | else 46 | str_to_format FORMAT seti 47 | 48 | "--profiler" over args.kwargs + derefp dict_fetch arg.value + derefp NULL != \ 49 | ENABLE_PROFILER setb 50 | "--verify-memory" over args.kwargs + derefp dict_fetch arg.value + derefp NULL != \ 51 | VERIFY_MEMORY setb 52 | "--dump-memory" over args.kwargs + derefp dict_fetch arg.value + derefp NULL != \ 53 | DUMP_MEMORY setb 54 | "--dump-tokens" over args.kwargs + derefp dict_fetch arg.value + derefp NULL != \ 55 | DUMP_TOKENS setb 56 | "--dump-opcodes" over args.kwargs + derefp dict_fetch arg.value + derefp NULL != \ 57 | DUMP_OPCODES setb 58 | 59 | ENABLE_PROFILER derefb if 60 | # Initialize profiler 61 | profiler_init 62 | 63 | # Get filename 64 | 0 over args.posargs + derefp list_fetch_ptr 65 | 66 | # Free args object 67 | swap free 68 | 69 | # Tokenize file 70 | dup read_file 71 | tokenize 72 | 73 | DUMP_TOKENS derefb if 74 | dup dump_tokens 75 | 76 | # Parse tokens 77 | parse 78 | 79 | DUMP_OPCODES derefb if 80 | dup dump_opcodes 81 | 82 | ENABLE_PROFILER derefb if 83 | # Dump profiler samples 84 | drop 85 | profiler_dump 86 | elif FORMAT derefi FORMAT_LINUX_X86_64 = 87 | # Generate code 88 | generate_code_x86_64_linux 89 | elif FORMAT derefi FORMAT_MAC_AARCH64 = 90 | drop "AArch64 format is not implemented yet\n" raise 91 | else 92 | drop "Unknown format\n" raise 93 | 94 | 0 95 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ILO="./build/ilo" 4 | 5 | echo "[INFO] $(sha1sum $ILO)" 6 | 7 | for file in $(find tests/*.ilo -maxdepth 1 -not -type d); do 8 | # Compile 9 | echo -n "[TEST] $(sha1sum $file).. " 10 | $ILO --verify-memory $file > test.asm 11 | nasm -felf64 test.asm -o test.o 12 | ld -o test test.o 13 | 14 | # Get metadata 15 | DESCRIPTION="$(grep '^# Description:' $file | cut -d: -f2-)" 16 | ARGS="$(grep '^# Args:' $file | cut -d: -f2-)" 17 | EXPECTED_EXIT_CODE="$( 18 | printf "%b" "$(grep '^# Exit code:' $file | cut -d: -f2-)" 19 | )" 20 | EXPECTED_STDOUT="$( 21 | printf "%b" "$(grep '^# Stdout:' $file | cut -d: -f2-)" 22 | )" 23 | EXPECTED_STDERR="$( 24 | printf "%b" "$(grep '^# Stderr:' $file | cut -d: -f2-)" 25 | )" 26 | 27 | # Run test 28 | EXIT_CODE="$(./test $ARGS > /dev/null 2>&1; echo $?)" 29 | STDOUT="$(./test $ARGS 2>/dev/null)" 30 | STDERR="$(./test $ARGS 2>&1 1>/dev/null)" 31 | 32 | # Verify results 33 | SUCCESS=1 34 | if [ "$EXIT_CODE" != "$EXPECTED_EXIT_CODE" ]; then 35 | echo "[FAIL] Test: $DESCRIPTION. Exit code: $EXIT_CODE (not $EXPECTED_EXIT_CODE)" 36 | SUCCESS=0 37 | fi 38 | 39 | if [ "$STDOUT" != "$EXPECTED_STDOUT" ]; then 40 | echo "[FAIL] Test: $DESCRIPTION. Stdout: $STDOUT (not $EXPECTED_STDOUT)" 41 | SUCCESS=0 42 | fi 43 | 44 | if [ "$STDERR" != "$EXPECTED_STDERR" ]; then 45 | echo "[FAIL] Test: $DESCRIPTION. Stderr: $STDERR (not $EXPECTED_STDERR)" 46 | SUCCESS=0 47 | fi 48 | 49 | if [[ "$SUCCESS" -eq 1 ]]; then 50 | echo "OK" 51 | fi 52 | 53 | # Cleanup 54 | rm test test.o test.asm 55 | 56 | if [[ "$SUCCESS" -eq 0 ]]; then 57 | echo "Test failed!" 58 | exit 1 59 | fi 60 | done 61 | 62 | echo "All tests OK!" 63 | -------------------------------------------------------------------------------- /tests/add.ilo: -------------------------------------------------------------------------------- 1 | # Description:Add integers 2 | # Exit code:7 3 | # Stdout: 4 | # Stderr: 5 | 6 | def main: ptr argv, int argc -> int 7 | 2 3 + 8 | 2 + 9 | -------------------------------------------------------------------------------- /tests/and.ilo: -------------------------------------------------------------------------------- 1 | # Description:Bitwise and 2 | # Exit code:34 3 | # Stdout: 4 | # Stderr: 5 | 6 | def main: int argc, ptr argv -> int 7 | 42 51 and 8 | -------------------------------------------------------------------------------- /tests/buffer.ilo: -------------------------------------------------------------------------------- 1 | # Description:Buffer 2 | # Exit code:1 3 | # Stdout: 4 | # Stderr: 5 | 6 | buffer foo 8 7 | 8 | def main: int argc, ptr argv -> int 9 | foo casti 0 != if 10 | 1 11 | else 12 | 2 13 | -------------------------------------------------------------------------------- /tests/comparisons.ilo: -------------------------------------------------------------------------------- 1 | # Description:Comparisons 2 | # Exit code:6 3 | # Stdout: 4 | # Stderr: 5 | 6 | def main: ptr argv, int argc -> int 7 | 0 8 | 3 3 = casti + 9 | 3 4 < casti + 10 | 3 4 > casti + 11 | 4 3 > casti + 12 | 4 3 < casti + 13 | 4 4 >= casti + 14 | 5 5 <= casti + 15 | 97 castc 'a' = casti + 16 | -------------------------------------------------------------------------------- /tests/const.ilo: -------------------------------------------------------------------------------- 1 | # Description:Constant 2 | # Exit code:42 3 | # Stdout: 4 | # Stderr: 5 | 6 | const foo 42 7 | const bar 123 8 | const baz 1337 9 | 10 | def main: int argc, ptr argv -> int 11 | foo 12 | -------------------------------------------------------------------------------- /tests/divide.ilo: -------------------------------------------------------------------------------- 1 | # Description:Divide 2 | # Exit code:1 3 | # Stdout: 4 | # Stderr: 5 | 6 | def main: int argc, ptr argv -> int 7 | 5 3 / 8 | -------------------------------------------------------------------------------- /tests/drop.ilo: -------------------------------------------------------------------------------- 1 | # Description:Drop 2 | # Exit code:3 3 | # Stdout: 4 | # Stderr: 5 | 6 | def main: ptr argv, int argc -> int 7 | 3 4 drop 8 | -------------------------------------------------------------------------------- /tests/dup.ilo: -------------------------------------------------------------------------------- 1 | # Description:Duplicate 2 | # Exit code:8 3 | # Stdout: 4 | # Stderr: 5 | 6 | def main: ptr argv, int argc -> int 7 | 3 4 dup + 8 | -------------------------------------------------------------------------------- /tests/elif-A.ilo: -------------------------------------------------------------------------------- 1 | # Description:If/elif/else-statement A 2 | # Exit code:0 3 | # Stdout:A 4 | # Stderr: 5 | 6 | def main: -> int 7 | True if 8 | 1 "A" 1 1 syscall 3 9 | elif True 10 | 1 "B" 1 1 syscall 3 11 | elif False 12 | 1 "C" 1 1 syscall 3 13 | else 14 | 1 "D" 1 1 syscall 3 15 | 0 16 | -------------------------------------------------------------------------------- /tests/elif-B.ilo: -------------------------------------------------------------------------------- 1 | # Description:If/elif/else-statement B 2 | # Exit code:0 3 | # Stdout:B 4 | # Stderr: 5 | 6 | def main: -> int 7 | False if 8 | 1 "A" 1 1 syscall 3 9 | elif True 10 | 1 "B" 1 1 syscall 3 11 | elif False 12 | 1 "C" 1 1 syscall 3 13 | else 14 | 1 "D" 1 1 syscall 3 15 | 0 16 | -------------------------------------------------------------------------------- /tests/elif-C.ilo: -------------------------------------------------------------------------------- 1 | # Description:If/elif/else-statement C 2 | # Exit code:0 3 | # Stdout:C 4 | # Stderr: 5 | 6 | def main: -> int 7 | False if 8 | 1 "A" 1 1 syscall 3 9 | elif False 10 | 1 "B" 1 1 syscall 3 11 | elif True 12 | 1 "C" 1 1 syscall 3 13 | else 14 | 1 "D" 1 1 syscall 3 15 | 0 16 | -------------------------------------------------------------------------------- /tests/elif-D.ilo: -------------------------------------------------------------------------------- 1 | # Description:If/elif/else-statement D 2 | # Exit code:0 3 | # Stdout:D 4 | # Stderr: 5 | 6 | def main: -> int 7 | False if 8 | 1 "A" 1 1 syscall 3 9 | elif False 10 | 1 "B" 1 1 syscall 3 11 | elif False 12 | 1 "C" 1 1 syscall 3 13 | else 14 | 1 "D" 1 1 syscall 3 15 | 0 16 | -------------------------------------------------------------------------------- /tests/exit.ilo: -------------------------------------------------------------------------------- 1 | # Description:Exit early 2 | # Exit code:1 3 | # Stdout: 4 | # Stderr: 5 | 6 | def main: ptr argv, int argc -> int 7 | 1 # Exit code 8 | 60 # Syscall no. (exit) 9 | syscall 1 10 | 11 | 2 # Exit code 12 | -------------------------------------------------------------------------------- /tests/false.ilo: -------------------------------------------------------------------------------- 1 | # Description:Push False boolean 2 | # Exit code:0 3 | # Stdout: 4 | # Stderr: 5 | 6 | def main: ptr argv, int argc -> bool 7 | False 8 | -------------------------------------------------------------------------------- /tests/fibonacci.ilo: -------------------------------------------------------------------------------- 1 | # Description:Fibonacci 2 | # Exit code:34 3 | # Stdout: 4 | # Stderr: 5 | 6 | def fib: int n -> int 7 | n 2 < if 8 | 1 9 | else 10 | n 1 - fib 11 | n 2 - fib + 12 | 13 | def main: ptr argv, int argc -> int 14 | 8 fib 15 | -------------------------------------------------------------------------------- /tests/first.ilo: -------------------------------------------------------------------------------- 1 | # Description:Argument order 2 | # Exit code:1 3 | # Stdout: 4 | # Stderr: 5 | 6 | def first: int x, int y -> int 7 | x 8 | 9 | def second: int x, int y -> int 10 | y 11 | 12 | def main: int argc, ptr argv -> int 13 | 1 2 first 14 | -------------------------------------------------------------------------------- /tests/fixtures/importee.ilo: -------------------------------------------------------------------------------- 1 | def foo: -> int 2 | 3 3 | -------------------------------------------------------------------------------- /tests/fixtures/large.txt: -------------------------------------------------------------------------------- 1 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 2 | -------------------------------------------------------------------------------- /tests/fixtures/small.txt: -------------------------------------------------------------------------------- 1 | foobarbaz 2 | -------------------------------------------------------------------------------- /tests/function.ilo: -------------------------------------------------------------------------------- 1 | # Description:Function call 2 | # Exit code:42 3 | # Stdout: 4 | # Stderr: 5 | 6 | def foo: int x, int y -> int 7 | x y + 8 | 9 | def main: int argc, ptr argv -> int 10 | 11 31 foo 11 | -------------------------------------------------------------------------------- /tests/getsetbool.ilo: -------------------------------------------------------------------------------- 1 | # Description:Get/set boolean 2 | # Exit code:1 3 | # Stdout: 4 | # Stderr: 5 | 6 | buffer foo 1 7 | 8 | def main: int argc, ptr argv -> int 9 | True foo setb 10 | foo derefb if 11 | 1 12 | else 13 | 2 14 | -------------------------------------------------------------------------------- /tests/getsetchar.ilo: -------------------------------------------------------------------------------- 1 | # Description:Get/set character 2 | # Exit code:42 3 | # Stdout: 4 | # Stderr: 5 | 6 | buffer foo 1 7 | 8 | def main: int argc, ptr argv -> char 9 | 42 castc foo setc 10 | foo derefc 11 | -------------------------------------------------------------------------------- /tests/getsetint.ilo: -------------------------------------------------------------------------------- 1 | # Description:Get/set integer 2 | # Exit code:1 3 | # Stdout: 4 | # Stderr: 5 | 6 | buffer foo 8 7 | 8 | def main: int argc, ptr argv -> int 9 | 420 foo seti 10 | foo derefi 420 = if 11 | 1 12 | else 13 | 2 14 | -------------------------------------------------------------------------------- /tests/getsetptr.ilo: -------------------------------------------------------------------------------- 1 | # Description:Get/set pointer 2 | # Exit code:1 3 | # Stdout: 4 | # Stderr: 5 | 6 | buffer foo 8 7 | 8 | def main: int argc, ptr argv -> int 9 | foo foo setp 10 | foo derefp foo = if 11 | 1 12 | else 13 | 2 14 | -------------------------------------------------------------------------------- /tests/hello_world.ilo: -------------------------------------------------------------------------------- 1 | # Description:Print "Hello, world!\n" 2 | # Exit code:11 3 | # Stdout:Hello, world!\n 4 | # Stderr: 5 | 6 | def main: int argc, ptr argv -> int 7 | 1 # File descriptor 8 | "Hello, world!\n" # String 9 | 14 # String length 10 | 1 # Syscall no. (write) 11 | syscall 3 12 | 13 | 11 # Exit code 14 | 60 # Syscall no. (exit) 15 | syscall 1 16 | 17 | 42 # This exit code should be ignored 18 | -------------------------------------------------------------------------------- /tests/if-false.ilo: -------------------------------------------------------------------------------- 1 | # Description:If-statement false 2 | # Exit code:0 3 | # Stdout: Yay! 4 | # Stderr: 5 | 6 | def main: int argc, ptr argv -> int 7 | False if 8 | 1 # File descriptor 9 | "It works!" # String 10 | 9 # String length 11 | 1 # Syscall no. (write) 12 | syscall 3 13 | drop 14 | 15 | 1 # File descriptor 16 | " Yay!" # String 17 | 5 # String length 18 | 1 # Syscall no. (write) 19 | syscall 3 20 | 21 | 0 # Exit code 22 | -------------------------------------------------------------------------------- /tests/if-true.ilo: -------------------------------------------------------------------------------- 1 | # Description:If-statement true 2 | # Exit code:0 3 | # Stdout:It works! Yay! 4 | # Stderr: 5 | 6 | def main: int argc, ptr argv -> int 7 | True if 8 | 1 # File descriptor 9 | "It works!" # String 10 | 9 # String length 11 | 1 # Syscall no. (write) 12 | syscall 3 13 | drop 14 | 15 | 1 # File descriptor 16 | " Yay!" # String 17 | 5 # String length 18 | 1 # Syscall no. (write) 19 | syscall 3 20 | 21 | 0 # Exit code 22 | -------------------------------------------------------------------------------- /tests/ifelse-false.ilo: -------------------------------------------------------------------------------- 1 | # Description:If/else-statement false 2 | # Exit code:0 3 | # Stdout:Yay! 4 | # Stderr: 5 | 6 | def main: int argc, ptr argv -> int 7 | 1 2 + 2 = if 8 | 1 # File descriptor 9 | "Huh?" # String 10 | 4 # String length 11 | 1 # Syscall no. (write) 12 | syscall 3 13 | else 14 | 1 # File descriptor 15 | "Yay!" # String 16 | 4 # String length 17 | 1 # Syscall no. (write) 18 | syscall 3 19 | 20 | 0 # Exit code 21 | -------------------------------------------------------------------------------- /tests/ifelse-true.ilo: -------------------------------------------------------------------------------- 1 | # Description:If/else-statement true 2 | # Exit code:0 3 | # Stdout:It works! 4 | # Stderr: 5 | 6 | def main: int argc, ptr argv -> int 7 | True if 8 | 1 # File descriptor 9 | "It works!" # String 10 | 9 # String length 11 | 1 # Syscall no. (write) 12 | syscall 3 13 | else 14 | 1 # File descriptor 15 | "Yay!" # String 16 | 4 # String length 17 | 1 # Syscall no. (write) 18 | syscall 3 19 | 20 | 0 # Exit code 21 | -------------------------------------------------------------------------------- /tests/ilo_functions.ilo: -------------------------------------------------------------------------------- 1 | # Description:ilo.constants - Functions 2 | # Exit code:0 3 | # Stdout: 4 | # Stderr: 5 | 6 | import lib.std 7 | import lib.std.list 8 | import lib.ilo.functions 9 | import lib.ilo.types 10 | 11 | 12 | buffer _function 8 # ptr 13 | 14 | 15 | def main: int argc, ptr argv -> int 16 | "foo" create_function _function setp 17 | 18 | TYPE_INT "arg1" _function derefp function_add_argument 19 | TYPE_PTR "arg2" _function derefp function_add_argument 20 | 21 | TYPE_BOOL _function derefp function_set_return_type 22 | 23 | _function derefp function.name + derefp "foo" streq \ 24 | "Function should be named foo" assert 25 | _function derefp function.args + derefp list.len + derefi 2 = \ 26 | "Function should have 2 arguments" assert 27 | _function derefp function.return_type + derefi TYPE_BOOL = \ 28 | "Function should return bool" assert 29 | 30 | 0 _function derefp function.args + derefp list_fetch 31 | dup argument.name + derefp "arg1" streq "First argument should be arg1" assert 32 | dup argument.type + derefi TYPE_INT = "First argument should be int" assert 33 | drop 34 | 35 | 1 _function derefp function.args + derefp list_fetch 36 | dup argument.name + derefp "arg2" streq "Second argument should be arg2" assert 37 | dup argument.type + derefi TYPE_PTR = "Second argument should be ptr" assert 38 | drop 39 | 40 | "arg1" _function derefp function_get_arg_offset 56 = \ 41 | "First argument should have a memory offset of 56" assert 42 | "arg2" _function derefp function_get_arg_offset 48 = \ 43 | "Second argument should have a memory offset of 48" assert 44 | "arg3" _function derefp function_get_arg_offset 0 1 - = \ 45 | "Unknown argument should return offset -1" assert 46 | 0 47 | -------------------------------------------------------------------------------- /tests/ilo_lexer.ilo: -------------------------------------------------------------------------------- 1 | # Description:ilo.lexer - Tokenizing source code 2 | # Exit code:0 3 | # Stdout: 4 | # Stderr: 5 | 6 | import lib.std 7 | import lib.std.list 8 | import lib.ilo.lexer 9 | 10 | 11 | def main: int argc, ptr argv -> int 12 | "def foo: int -> bool_" 13 | " # This is a function_" concat 14 | " True if_" concat 15 | " \"Yeah baby\"_" concat 16 | "const constant 123" concat 17 | 18 | # TODO: Unescape characters 19 | dup 20 + 10 castc swap setc 20 | dup 45 + 10 castc swap setc 21 | dup 57 + 10 castc swap setc 22 | dup 77 + 10 castc swap setc 23 | 24 | tokenize 25 | list.items + 26 | 27 | dup token.type + derefi TOKEN_KEYWORD = "Token 1 should be keyword" assert 28 | dup token.line + derefi 1 = "Token 1 should be on line 1" assert 29 | TOKEN_SIZE + 30 | dup token.type + derefi TOKEN_IDENTIFIER = "Token 2 should be identifier" assert 31 | dup token.line + derefi 1 = "Token 2 should be on line 1" assert 32 | TOKEN_SIZE + 33 | dup token.type + derefi TOKEN_COLON = "Token 3 should be colon" assert 34 | dup token.line + derefi 1 = "Token 3 should be on line 1" assert 35 | TOKEN_SIZE + 36 | dup token.type + derefi TOKEN_TYPE = "Token 4 should be type" assert 37 | dup token.line + derefi 1 = "Token 4 should be on line 1" assert 38 | TOKEN_SIZE + 39 | dup token.type + derefi TOKEN_ARROW = "Token 5 should be arrow" assert 40 | dup token.line + derefi 1 = "Token 5 should be on line 1" assert 41 | TOKEN_SIZE + 42 | dup token.type + derefi TOKEN_TYPE = "Token 6 should be type" assert 43 | dup token.line + derefi 1 = "Token 6 should be on line 1" assert 44 | TOKEN_SIZE + 45 | dup token.type + derefi TOKEN_BLOCK_START = "Token 7 should be block start" assert 46 | dup token.line + derefi 2 = "Token 7 should be on line 2" assert 47 | TOKEN_SIZE + 48 | dup token.type + derefi TOKEN_BOOL = "Token 8 should be boolean" assert 49 | dup token.line + derefi 3 = "Token 8 should be on line 3" assert 50 | TOKEN_SIZE + 51 | dup token.type + derefi TOKEN_KEYWORD = "Token 9 should be keyword" assert 52 | dup token.line + derefi 3 = "Token 9 should be on line 3" assert 53 | TOKEN_SIZE + 54 | dup token.type + derefi TOKEN_BLOCK_START = "Token 10 should be block start" assert 55 | dup token.line + derefi 4 = "Token 10 should be on line 4" assert 56 | TOKEN_SIZE + 57 | dup token.type + derefi TOKEN_STRING = "Token 11 should be string" assert 58 | dup token.line + derefi 4 = "Token 11 should be on line 4" assert 59 | TOKEN_SIZE + 60 | dup token.type + derefi TOKEN_BLOCK_END = "Token 12 should be block end" assert 61 | dup token.line + derefi 5 = "Token 12 should be on line 5" assert 62 | TOKEN_SIZE + 63 | dup token.type + derefi TOKEN_BLOCK_END = "Token 13 should be block end" assert 64 | dup token.line + derefi 5 = "Token 13 should be on line 5" assert 65 | TOKEN_SIZE + 66 | dup token.type + derefi TOKEN_KEYWORD = "Token 14 should be keyword" assert 67 | dup token.line + derefi 5 = "Token 14 should be on line 5" assert 68 | TOKEN_SIZE + 69 | dup token.type + derefi TOKEN_IDENTIFIER = "Token 15 should be identifier" assert 70 | dup token.line + derefi 5 = "Token 15 should be on line 5" assert 71 | TOKEN_SIZE + 72 | dup token.type + derefi TOKEN_INT = "Token 16 should be integer" assert 73 | dup token.line + derefi 5 = "Token 16 should be on line 5" assert 74 | 0 75 | -------------------------------------------------------------------------------- /tests/ilo_parser.ilo: -------------------------------------------------------------------------------- 1 | # Description:ilo.parser - Parse tokens 2 | # Exit code:0 3 | # Stdout: 4 | # Stderr: 5 | 6 | import lib.std 7 | import lib.std.list 8 | import lib.ilo.lexer 9 | import lib.ilo.parser 10 | 11 | 12 | def main: int argc, ptr argv -> int 13 | True VERIFY_MEMORY setb 14 | False DUMP_MEMORY setb 15 | 16 | "foo.ilo" 17 | 18 | "6 3 +_" dup 5 + 10 castc swap setc 19 | "'q' True_" dup 8 + 10 castc swap setc concat 20 | "_foo__" dup 5 + 10 castc swap setc \ 21 | dup '"' swap setc \ 22 | dup 4 + '"' swap setc concat 23 | "1 2 - 2 *_" dup 9 + 10 castc swap setc concat 24 | "1 2 !=_" dup 6 + 10 castc swap setc concat 25 | "syscall 3_" dup 9 + 10 castc swap setc concat 26 | "1 2 and_" dup 7 + 10 castc swap setc concat 27 | "1 2 or_" dup 6 + 10 castc swap setc concat 28 | "1 2 shr_" dup 7 + 10 castc swap setc concat 29 | "1 2 shl_" dup 7 + 10 castc swap setc concat 30 | "_bar_ " dup '"' swap setc \ 31 | dup 4 + '"' swap setc concat 32 | "derefc_" dup 6 + 10 castc swap setc concat 33 | "'f' _baz_ " dup 4 + '"' swap setc \ 34 | dup 8 + '"' swap setc concat 35 | "setc_" dup 4 + 10 castc swap setc concat 36 | "const a 8_" dup 9 + 10 castc swap setc concat 37 | "a_" dup 1 + 10 castc swap setc concat 38 | "buffer b 8_" dup 10 + 10 castc swap setc concat 39 | "b_" dup 1 + 10 castc swap setc concat 40 | "while True_" dup 10 + 10 castc swap setc concat 41 | " 1_" dup 5 + 10 castc swap setc concat 42 | " drop_" dup 8 + 10 castc swap setc concat 43 | "True if_" dup 7 + 10 castc swap setc concat 44 | " 1_" dup 5 + 10 castc swap setc concat 45 | "elif True_" dup 9 + 10 castc swap setc concat 46 | " 2_" dup 5 + 10 castc swap setc concat 47 | "else_" dup 4 + 10 castc swap setc concat 48 | " 3_" dup 5 + 10 castc swap setc concat 49 | "def o: int x -> bool_" \ 50 | dup 20 + 10 castc swap setc concat 51 | " x 1 =_" dup 9 + 10 castc swap setc concat 52 | "def p: -> void_" dup 14 + 10 castc swap setc concat 53 | " 1 o_" dup 7 + 10 castc swap setc concat 54 | 55 | tokenize 56 | parse 57 | list.items + 58 | 59 | OPCODE_SIZE 145 * + # Skip the core functions 60 | dup opcode.opcode + derefi OPCODE_PUSH_INT = "Op 1 should be integer" assert 61 | dup opcode.operand + derefp "6" streq "Op 1 should be 6" assert 62 | OPCODE_SIZE + 63 | dup opcode.opcode + derefi OPCODE_PUSH_INT = "Op 2 should be integer" assert 64 | dup opcode.operand + derefp "3" streq "Op 2 should be 3" assert 65 | OPCODE_SIZE + 66 | dup opcode.opcode + derefi OPCODE_ADD = "Op 3 should be integer" assert 67 | OPCODE_SIZE + 68 | dup opcode.opcode + derefi OPCODE_PUSH_CHAR = "Op 4 should be character" assert 69 | dup opcode.operand + derefp "q" streq "Op 4 should be q" assert 70 | OPCODE_SIZE + 71 | dup opcode.opcode + derefi OPCODE_PUSH_BOOL = "Op 5 should be character" assert 72 | dup opcode.operand + derefp "1" streq "Op 5 should be True" assert 73 | OPCODE_SIZE + 74 | dup opcode.opcode + derefi OPCODE_PUSH_STRING = "Op 6 should be string" assert 75 | dup opcode.operand + derefp "foo" streq "Op 6 should be foo" assert 76 | OPCODE_SIZE 3 * + 77 | dup opcode.opcode + derefi OPCODE_SUBTRACT = "Op 7 should be subtract" assert 78 | OPCODE_SIZE 2 * + 79 | dup opcode.opcode + derefi OPCODE_MULTIPLY = "Op 8 should be multiply" assert 80 | OPCODE_SIZE 3 * + 81 | dup opcode.opcode + derefi OPCODE_IS_NOT_EQUAL = "Op 9 should be not equal" assert 82 | OPCODE_SIZE + 83 | dup opcode.opcode + derefi OPCODE_SYSCALL = "Op 10 should be syscall" assert 84 | OPCODE_SIZE 3 * + 85 | dup opcode.opcode + derefi OPCODE_BITWISE_AND = "Op 11 should be and" assert 86 | OPCODE_SIZE 3 * + 87 | dup opcode.opcode + derefi OPCODE_BITWISE_OR = "Op 12 should be or" assert 88 | OPCODE_SIZE 3 * + 89 | dup opcode.opcode + derefi OPCODE_SHIFT_RIGHT = "Op 13 should be shift right" assert 90 | OPCODE_SIZE 3 * + 91 | dup opcode.opcode + derefi OPCODE_SHIFT_LEFT = "Op 14 should be shift left" assert 92 | OPCODE_SIZE 2 * + 93 | dup opcode.opcode + derefi OPCODE_DEREF_C = "Op 14 should be derefc" assert 94 | OPCODE_SIZE 3 * + 95 | dup opcode.opcode + derefi OPCODE_SET_C = "Op 15 should be setc" assert 96 | OPCODE_SIZE + 97 | dup opcode.opcode + derefi OPCODE_PUSH_INT = "Op 17 should be push int" assert 98 | dup opcode.operand + derefp "8" streq "Op 17 should be 8" assert 99 | OPCODE_SIZE + 100 | dup opcode.opcode + derefi OPCODE_CREATE_BUFFER = "Op 18 should be get buffer" assert 101 | OPCODE_SIZE + 102 | dup opcode.opcode + derefi OPCODE_GET_BUFFER = "Op 19 should be get buffer" assert 103 | dup opcode.operand + derefp "b" streq "Op 19 should be b" assert 104 | OPCODE_SIZE + 105 | dup opcode.opcode + derefi OPCODE_LABEL = "Op 20 should be label" assert 106 | dup opcode.operand + derefp "while_" startswith "Op 20 should start with while_" assert 107 | OPCODE_SIZE + 108 | dup opcode.opcode + derefi OPCODE_PUSH_BOOL = "Op 21 should be boolean" assert 109 | dup opcode.operand + derefp "1" streq "Op 21 should be True" assert 110 | OPCODE_SIZE + 111 | dup opcode.opcode + derefi OPCODE_WHILE_START = "Op 22 should be while start" assert 112 | dup opcode.operand + derefp "while_" startswith "Op 22 should start with while_" assert 113 | OPCODE_SIZE + 114 | dup opcode.opcode + derefi OPCODE_PUSH_INT = "Op 23 should be push int" assert 115 | dup opcode.operand + derefp "1" streq "Op 23 should be 1" assert 116 | OPCODE_SIZE + 117 | dup opcode.opcode + derefi OPCODE_DROP = "Op 24 should be drop" assert 118 | OPCODE_SIZE + 119 | dup opcode.opcode + derefi OPCODE_WHILE_END = "Op 25 should be while end" assert 120 | dup opcode.operand + derefp "while_" startswith "Op 25 should start with while_" assert 121 | OPCODE_SIZE 2 * + 122 | dup opcode.opcode + derefi OPCODE_IF = "Op 26 should be if" assert 123 | dup opcode.operand + derefp "if_" startswith "Op 26 should start with if_" assert 124 | OPCODE_SIZE 2 * + 125 | dup opcode.opcode + derefi OPCODE_JUMP = "Op 27 should be jump" assert 126 | dup opcode.operand + derefp "end_" startswith "Op 27 should start with end_" assert 127 | OPCODE_SIZE + 128 | dup opcode.opcode + derefi OPCODE_LABEL = "Op 28 should be label" assert 129 | dup opcode.operand + derefp "if_" startswith "Op 28 should start with if_" assert 130 | OPCODE_SIZE 2 * + 131 | dup opcode.opcode + derefi OPCODE_IF = "Op 29 should be if" assert 132 | dup opcode.operand + derefp "elif_" startswith "Op 29 should start with elif_" assert 133 | OPCODE_SIZE 2 * + 134 | dup opcode.opcode + derefi OPCODE_JUMP = "Op 30 should be jump" assert 135 | dup opcode.operand + derefp "end_" startswith "Op 30 should start with end_" assert 136 | OPCODE_SIZE + 137 | dup opcode.opcode + derefi OPCODE_LABEL = "Op 31 should be label" assert 138 | dup opcode.operand + derefp "elif_" startswith "Op 31 should start with elif_" assert 139 | OPCODE_SIZE 2 * + 140 | dup opcode.opcode + derefi OPCODE_LABEL = "Op 32 should be label" assert 141 | dup opcode.operand + derefp "end_" startswith "Op 32 should start with end_" assert 142 | OPCODE_SIZE + 143 | dup opcode.opcode + derefi OPCODE_FUNCTION = "Op 33 should be function" assert 144 | dup opcode.operand + derefp "o" streq "Op 33 should be o" assert 145 | OPCODE_SIZE + 146 | dup opcode.opcode + derefi OPCODE_GET_ARG = "Op 33 should be get arg" assert 147 | dup opcode.operand + derefp "48" streq "Op 33 should be 48" assert 148 | OPCODE_SIZE 3 * + 149 | dup opcode.opcode + derefi OPCODE_RESTORE_FRAME = "Op 34 should be res.fr." assert 150 | OPCODE_SIZE + 151 | dup opcode.opcode + derefi OPCODE_RETURN = "Op 35 should be return" assert 152 | "o" functions derefp dict_fetch function.args + derefp \ 153 | list.len + derefi 1 = "Func should have 1 arg" assert 154 | "o" functions derefp dict_fetch function.return_type + derefi \ 155 | TYPE_BOOL = "Func should return bool" assert 156 | OPCODE_SIZE 3 * + 157 | dup opcode.opcode + derefi OPCODE_PUSH_STRING = "Op 36 should be push str" assert 158 | OPCODE_SIZE + 159 | dup opcode.opcode + derefi OPCODE_PUSH_INT = "Op 37 should be push int" assert 160 | OPCODE_SIZE + 161 | dup opcode.opcode + derefi OPCODE_PUSH_STRING = "Op 38 should be push str" assert 162 | OPCODE_SIZE + 163 | dup opcode.opcode + derefi OPCODE_PUSH_BOOL = "Op 39 should be push bool" assert 164 | OPCODE_SIZE + 165 | dup opcode.opcode + derefi OPCODE_CALL = "Op 40 should be call" assert 166 | dup opcode.operand + derefp "o" streq "Op 40 should be o" assert 167 | 0 168 | -------------------------------------------------------------------------------- /tests/ilo_parser_debug.ilo: -------------------------------------------------------------------------------- 1 | # Description:ilo.parser - Parse debug keyword 2 | # Exit code:0 3 | # Stdout:Stack: PTR INT BOOL CHAR 4 | # Stderr: 5 | 6 | import lib.std 7 | import lib.ilo.lexer 8 | import lib.ilo.parser 9 | 10 | 11 | def main: int argc, ptr argv -> int 12 | "foo.ilo" 13 | 14 | "_s_ 1 True 'c' debug" dup '"' swap setc \ 15 | dup 2 + '"' swap setc 16 | 17 | tokenize 18 | parse 19 | 0 20 | -------------------------------------------------------------------------------- /tests/ilo_parser_stack_man.ilo: -------------------------------------------------------------------------------- 1 | # Description:ilo.parser - Parse, stack manipulation 2 | # Exit code:0 3 | # Stdout:Stack: INT INT INT CHAR BOOL INT CHAR INT INT CHAR INT 4 | # Stderr: 5 | 6 | import lib.std 7 | import lib.ilo.lexer 8 | import lib.ilo.parser 9 | 10 | 11 | def main: int argc, ptr argv -> int 12 | "foo.ilo" 13 | 14 | "1 dup " # INT INT 15 | "1 'c' drop " concat # INT 16 | "1 'c' True rot " concat # CHAR BOOL INT 17 | "1 'c' swap " concat # CHAR INT 18 | "1 'c' over " concat # INT CHAR INT 19 | "debug" concat 20 | 21 | tokenize 22 | parse 23 | 24 | 0 25 | -------------------------------------------------------------------------------- /tests/import.ilo: -------------------------------------------------------------------------------- 1 | # Description:Import file 2 | # Exit code:7 3 | # Stdout: 4 | # Stderr: 5 | 6 | import tests.fixtures.importee # Contains `foo -> 3` 7 | 8 | def main: int argc, ptr argv -> int 9 | foo 4 + 10 | -------------------------------------------------------------------------------- /tests/import_duplicate.ilo: -------------------------------------------------------------------------------- 1 | # Description:Duplicate imports 2 | # Exit code:7 3 | # Stdout: 4 | # Stderr: 5 | 6 | import tests.fixtures.importee 7 | import tests.fixtures.importee 8 | 9 | def main: int argc, ptr argv -> int 10 | foo 4 + 11 | -------------------------------------------------------------------------------- /tests/line_join.ilo: -------------------------------------------------------------------------------- 1 | # Description:Explicit line joining 2 | # Exit code:12 3 | # Stdout: 4 | # Stderr: 5 | 6 | def main: int argc, ptr argv -> int 7 | 2 3 * \ 8 | 2 * 9 | -------------------------------------------------------------------------------- /tests/linked_list.ilo: -------------------------------------------------------------------------------- 1 | # Description:Data structure - Linked list 2 | # Exit code:4 3 | # Stdout: 4 | # Stderr: 5 | 6 | buffer list 96 # 4 items, 24 bytes per item 7 | 8 | const item.prev 0 # ptr 9 | const item.next 8 # ptr 10 | const item.value 16 # int 11 | 12 | const ITEM_SIZE 24 13 | 14 | 15 | def NULL: -> ptr 16 | 0 castp 17 | 18 | 19 | def create: ptr root, int value -> void 20 | NULL root item.prev + setp 21 | NULL root item.next + setp 22 | value root item.value + seti 23 | 24 | def append: ptr root, int value -> void 25 | root item.next + derefp NULL = if 26 | root root ITEM_SIZE + item.prev + setp 27 | NULL root ITEM_SIZE + item.next + setp 28 | value root ITEM_SIZE + item.value + seti 29 | root ITEM_SIZE + root item.next + setp 30 | else 31 | root item.next + value append 32 | 33 | def delete: ptr root, int index -> ptr 34 | index 0 = if 35 | root item.next + derefp root item.prev + derefp item.next + setp 36 | root item.prev + derefp root item.next + derefp item.prev + setp 37 | root 38 | else 39 | root item.next + derefp index 1 - delete 40 | 41 | def main: int argc, ptr argv -> int 42 | # Initialize list [1] 43 | list 1 create 44 | 45 | # Append items [1, 2, 3, 4] 46 | list 2 append 47 | list 3 append 48 | list 4 append 49 | 50 | # Delete item [1, 3, 4] 51 | list 1 delete 52 | 53 | # Walk through list 54 | list # [1*, 3 , 4 ] 55 | item.next + derefp # [1 , 3*, 4 ] 56 | item.next + derefp # [1 , 3 , 4*] 57 | item.prev + derefp # [1 , 3*, 4 ] 58 | item.next + derefp # [1 , 3 , 4*] 59 | item.value + derefi 60 | -------------------------------------------------------------------------------- /tests/location.ilo.skip: -------------------------------------------------------------------------------- 1 | # Description:Location 2 | # Exit code:0 3 | # Stdout:tests/location.ilo, line 9 in main 4 | # Stderr: 5 | 6 | import lib.std 7 | 8 | def main: int argc, ptr argv -> int 9 | location puts 0 10 | -------------------------------------------------------------------------------- /tests/mod.ilo: -------------------------------------------------------------------------------- 1 | # Description:Modulo 2 | # Exit code:2 3 | # Stdout: 4 | # Stderr: 5 | 6 | def main: int argc, ptr argv -> int 7 | 5 3 % 8 | -------------------------------------------------------------------------------- /tests/multiply.ilo: -------------------------------------------------------------------------------- 1 | # Description:Multiply integers 2 | # Exit code:12 3 | # Stdout: 4 | # Stderr: 5 | 6 | def main: int argc, ptr argv -> int 7 | 2 3 * 8 | 2 * 9 | -------------------------------------------------------------------------------- /tests/or.ilo: -------------------------------------------------------------------------------- 1 | # Description:Bitwise or 2 | # Exit code:44 3 | # Stdout: 4 | # Stderr: 5 | 6 | def main: int argc, ptr argv -> int 7 | 40 36 or 8 | -------------------------------------------------------------------------------- /tests/over.ilo: -------------------------------------------------------------------------------- 1 | # Description:Over 2 | # Exit code:232 3 | # Stdout: 4 | # Stderr: 5 | 6 | def main: int argc, ptr argv -> int 7 | 2 3 over 8 | 10 * + 9 | 10 * + 10 | -------------------------------------------------------------------------------- /tests/reff.ilo: -------------------------------------------------------------------------------- 1 | # Description:Reference function 2 | # Exit code:1 3 | # Stdout: 4 | # Stderr: 5 | 6 | def foo: -> void 7 | 0 8 | 9 | def main: int argc, ptr argv -> int 10 | reff foo 0 castp != casti 11 | -------------------------------------------------------------------------------- /tests/rot.ilo: -------------------------------------------------------------------------------- 1 | # Description:Rot 2 | # Exit code:4 3 | # Stdout: 4 | # Stderr: 5 | 6 | def main: int argc, ptr argv -> int 7 | 1 2 3 rot + 8 | -------------------------------------------------------------------------------- /tests/second.ilo: -------------------------------------------------------------------------------- 1 | # Description:Argument order 2 | # Exit code:2 3 | # Stdout: 4 | # Stderr: 5 | 6 | def first: int x, int y -> int 7 | x 8 | 9 | def second: int x, int y -> int 10 | y 11 | 12 | def main: int argc, ptr argv -> int 13 | 1 2 second 14 | -------------------------------------------------------------------------------- /tests/shl.ilo: -------------------------------------------------------------------------------- 1 | # Description:Shift left 2 | # Exit code:20 3 | # Stdout: 4 | # Stderr: 5 | 6 | def main: int argc, ptr argv -> int 7 | 5 2 shl 8 | -------------------------------------------------------------------------------- /tests/shr.ilo: -------------------------------------------------------------------------------- 1 | # Description:Shift right 2 | # Exit code:5 3 | # Stdout: 4 | # Stderr: 5 | 6 | def main: int argc, ptr argv -> int 7 | 20 2 shr 8 | -------------------------------------------------------------------------------- /tests/stdlib_argparse.ilo: -------------------------------------------------------------------------------- 1 | # Description:Standard library - Argument parser 2 | # Exit code:0 3 | # Args:foo bar --asdf --qwerty baz 4 | # Stdout: 5 | # Stderr: 6 | 7 | import lib.std.argparse 8 | 9 | def main: ptr argv, int argc -> int 10 | argparse_init 11 | 12 | "foo" ARG_TYPE_POSITIONAL "foo" argparse_add_argument 13 | "bar" ARG_TYPE_POSITIONAL "bar" argparse_add_argument 14 | "--asdf" ARG_TYPE_FLAG "Lorem" argparse_add_argument 15 | "--qwerty" ARG_TYPE_OPTIONAL "Ipsum" argparse_add_argument 16 | "--null" ARG_TYPE_OPTIONAL "NULL" argparse_add_argument 17 | 18 | argv argc argparse_parse_args 19 | 20 | dup args.posargs + derefp list.len + derefi 2 = "2 positional args" assert 21 | 0 over args.posargs + derefp list_fetch_ptr "foo" streq "foo arg" assert 22 | 1 over args.posargs + derefp list_fetch_ptr "bar" streq "bar arg" assert 23 | 24 | "--asdf" over args.kwargs + derefp dict_fetch arg.value + derefp NULL != \ 25 | "--asdf arg" assert 26 | 27 | "--qwerty" over args.kwargs + derefp dict_fetch arg.value + derefp "baz" streq \ 28 | "--qwerty arg" assert 29 | 30 | "--null" over args.kwargs + derefp dict_fetch arg.value + derefp NULL = \ 31 | "--null arg" assert 32 | 33 | free 34 | 35 | 0 36 | -------------------------------------------------------------------------------- /tests/stdlib_assert_false.ilo: -------------------------------------------------------------------------------- 1 | # Description:Standard library - Assert False 2 | # Exit code:1 3 | # Stdout: 4 | # Stderr:OK\nTraceback:\nlib/std/linux.ilo, line 59 in assert\ntests/stdlib_assert_false.ilo, line 9 in main 5 | 6 | import lib.std 7 | 8 | def main: ptr argv, int argc -> int 9 | False "OK" assert 10 | "This should not be printed." puts 11 | 0 12 | -------------------------------------------------------------------------------- /tests/stdlib_assert_true.ilo: -------------------------------------------------------------------------------- 1 | # Description:Standard library - Assert True 2 | # Exit code:0 3 | # Stdout:OK 4 | # Stderr: 5 | 6 | import lib.std 7 | 8 | def main: ptr argv, int argc -> int 9 | True "This should not be printed." assert 10 | "OK" puts 11 | 0 12 | -------------------------------------------------------------------------------- /tests/stdlib_concat.ilo: -------------------------------------------------------------------------------- 1 | # Description:Standard library - concat 2 | # Exit code:0 3 | # Stdout:Hi, Bob! 4 | # Stderr: 5 | 6 | import lib.std 7 | 8 | def main: ptr argv, int argc -> int 9 | "Hi, " "Bob!" concat puts 10 | 0 11 | -------------------------------------------------------------------------------- /tests/stdlib_dict.ilo: -------------------------------------------------------------------------------- 1 | # Description:Standard library - Dictionary 2 | # Exit code:0 3 | # Stdout: 4 | # Stderr: 5 | 6 | import lib.std.dict 7 | 8 | 9 | def main: -> int 10 | new_dict 11 | 12 | "foo" "bar" rot dict_insert 13 | "baz" "qwe" rot dict_insert 14 | "asd" "zxc" rot dict_insert 15 | "zxc" "zxc" rot dict_insert 16 | 17 | "foo" over dict_fetch "bar" streq "foo -> bar" assert 18 | "baz" over dict_fetch "qwe" streq "baz -> qwe" assert 19 | "asd" over dict_fetch "zxc" streq "asd -> zxc" assert 20 | "zxc" over dict_fetch "zxc" streq "zxc -> zxc" assert 21 | "huh" over dict_fetch NULL = "huh -> NULL" assert 22 | 23 | 0 24 | -------------------------------------------------------------------------------- /tests/stdlib_div.ilo: -------------------------------------------------------------------------------- 1 | # Description:Standard library - signed division 2 | # Exit code:0 3 | # Stdout: 4 | # Stderr: 5 | 6 | import lib.std.math 7 | 8 | def main: ptr argv, int argc -> int 9 | 0 30 - 0 6 - div 5 = "-30 / -6 = 5" assert 10 | 30 0 6 - div 0 5 - = "30 / -6 = -5" assert 11 | 0 30 - 6 div 0 5 - = "-30 / 6 = -5" assert 12 | 30 6 div 5 = "30 / 6 = 5" assert 13 | 0 14 | -------------------------------------------------------------------------------- /tests/stdlib_error.ilo: -------------------------------------------------------------------------------- 1 | # Description:Standard library - error 2 | # Exit code:0 3 | # Stdout: 4 | # Stderr:foo 5 | 6 | import lib.std 7 | 8 | def main: ptr argv, int argc -> int 9 | "foo" error 10 | 0 11 | -------------------------------------------------------------------------------- /tests/stdlib_errori.ilo: -------------------------------------------------------------------------------- 1 | # Description:Standard library - errori 2 | # Exit code:0 3 | # Stdout: 4 | # Stderr:123456 5 | 6 | import lib.std 7 | 8 | def main: ptr argv, int argc -> int 9 | 123456 errori 10 | 0 11 | -------------------------------------------------------------------------------- /tests/stdlib_exit.ilo: -------------------------------------------------------------------------------- 1 | # Description:Standard library - exit 2 | # Exit code:42 3 | # Stdout: 4 | # Stderr: 5 | 6 | import lib.std 7 | 8 | def main: ptr argv, int argc -> int 9 | 42 exit 10 | "This should not happen" puts 0 11 | -------------------------------------------------------------------------------- /tests/stdlib_free_many.ilo: -------------------------------------------------------------------------------- 1 | # Description:Standard library - free, many 2 | # Exit code:0 3 | # Stdout: 4 | # Stderr: 5 | 6 | import lib.std 7 | 8 | const N 10000 9 | 10 | def main: int argc, ptr argv -> int 11 | True DUMP_MEMORY setb 12 | 0 13 | while dup N < 14 | 1000 malloc 15 | 1000 malloc 16 | 1000 malloc 17 | 1000 malloc 18 | free swap free free free 19 | 1 + 20 | 0 21 | -------------------------------------------------------------------------------- /tests/stdlib_free_merge_next.ilo: -------------------------------------------------------------------------------- 1 | # Description:Standard library - free, merge next 2 | # Exit code:0 3 | # Stdout: 4 | # Stderr: 5 | 6 | import lib.std 7 | 8 | 9 | def main: int argc, ptr argv -> int 10 | 10 malloc # A | A 11 | 10 malloc # A B | A B 12 | 10 malloc # A B C | A B C 13 | 10 malloc # A B C D | A B C D 14 | swap # A B D C | A B C D 15 | free # A B D | A B _ D 16 | swap # A D B | A B _ D 17 | free # A D | A _ _ D 18 | 20 malloc # A D E | A E~~ D 19 | 20 | dup rot # A E E D 21 | < "E should be before D" assert 22 | < "A should be before E" assert 23 | 0 24 | -------------------------------------------------------------------------------- /tests/stdlib_free_merge_prev.ilo: -------------------------------------------------------------------------------- 1 | # Description:Standard library - free, merge prev 2 | # Exit code:0 3 | # Stdout: 4 | # Stderr: 5 | 6 | import lib.std 7 | 8 | 9 | def main: int argc, ptr argv -> int 10 | 10 malloc # A | A 11 | 10 malloc # A B | A B 12 | 10 malloc # A B C | A B C 13 | 10 malloc # A B C D | A B C D 14 | rot # A C D B | A B C D 15 | free # A C D | A _ C D 16 | swap # A D C | A _ C D 17 | free # A D | A _ _ D 18 | 20 malloc # A D E | A E~~ D 19 | 20 | dup rot # A E E D 21 | < "E should be before D" assert 22 | < "A should be before E" assert 23 | 0 24 | -------------------------------------------------------------------------------- /tests/stdlib_free_middle.ilo: -------------------------------------------------------------------------------- 1 | # Description:Standard library - free, middle 2 | # Exit code:0 3 | # Stdout: 4 | # Stderr: 5 | 6 | import lib.std 7 | 8 | 9 | const item.prev 0 # ptr 10 | const item.next 8 # ptr 11 | const ITEM_SIZE 16 12 | 13 | buffer root 16 14 | 15 | def create_node: ptr parent, ptr child -> void 16 | child parent item.next + setp 17 | parent child item.prev + setp 18 | NULL child item.next + setp 19 | 20 | def append: ptr root -> void 21 | root 22 | while dup item.next + derefp NULL != 23 | item.next + derefp 24 | ITEM_SIZE malloc 25 | create_node 26 | 27 | def delete: ptr root, int num -> void 28 | root # current_node 29 | 0 # index 30 | while dup num < 31 | swap item.next + derefp swap 32 | 1 + 33 | drop 34 | dup item.prev + derefp swap dup rot swap item.next + derefp item.prev + setp 35 | dup item.next + derefp swap dup rot swap item.prev + derefp item.next + setp 36 | free 37 | 38 | def fetch: ptr node, int n -> ptr 39 | node # current_node 40 | 0 # index 41 | while dup n < 42 | swap 43 | item.next + derefp 44 | swap 45 | 1 + 46 | drop 47 | 48 | def main: int argc, ptr argv -> int 49 | root append 50 | root append 51 | root append 52 | root append 53 | root 2 delete 54 | root append 55 | root append 56 | 57 | root 1 fetch root 3 fetch < "1 should be before 3 in memory" assert 58 | root 4 fetch root 3 fetch < "4 should be before 3 in memory" assert 59 | root 3 fetch root 5 fetch < "3 should be before 5 in memory" assert 60 | 0 61 | -------------------------------------------------------------------------------- /tests/stdlib_free_only.ilo: -------------------------------------------------------------------------------- 1 | # Description:Standard library - free, only 2 | # Exit code:0 3 | # Stdout: 4 | # Stderr: 5 | 6 | import lib.std 7 | 8 | def main: int argc, ptr argv -> int 9 | 1 malloc free 10 | 100000 malloc free 11 | 0 12 | -------------------------------------------------------------------------------- /tests/stdlib_free_refill.ilo: -------------------------------------------------------------------------------- 1 | # Description:Standard library - free, refill 2 | # Exit code:0 3 | # Stdout: 4 | # Stderr: 5 | 6 | import lib.std 7 | 8 | def main: int argc, ptr argv -> int 9 | 1 malloc 10 | 1 malloc dup free 11 | 1 malloc = "Allocated block should replace freed block" assert 12 | 0 13 | -------------------------------------------------------------------------------- /tests/stdlib_get_argument.ilo: -------------------------------------------------------------------------------- 1 | # Description:Standard library - Argument parser, get_argument 2 | # Exit code:4 3 | # Args:foo bar baz 4 | # Stdout:bar 5 | # Stderr: 6 | 7 | import lib.std.argparse 8 | 9 | def main: ptr argv, int argc -> int 10 | argv 2 argc get_argument puts 11 | argc 12 | -------------------------------------------------------------------------------- /tests/stdlib_itohex.ilo: -------------------------------------------------------------------------------- 1 | # Description:Standard library - itohex 2 | # Exit code:0 3 | # Stdout:0xf0f0f0f0f0f0f0f0 4 | # Stderr: 5 | 6 | import lib.std 7 | 8 | buffer string 19 9 | 10 | def main: ptr argv, int argc -> int 11 | 17361641481138401520 string itohex 12 | string puts 13 | 0 14 | -------------------------------------------------------------------------------- /tests/stdlib_itos.ilo: -------------------------------------------------------------------------------- 1 | # Description:Standard library - itos 2 | # Exit code:0 3 | # Stdout:12345 4 | # Stderr: 5 | 6 | import lib.std 7 | 8 | def main: ptr argv, int argc -> int 9 | 12345 itos puts 10 | 0 11 | -------------------------------------------------------------------------------- /tests/stdlib_list_contains.ilo: -------------------------------------------------------------------------------- 1 | # Description:Standard library - list contains string 2 | # Exit code:0 3 | # Stdout: 4 | # Stderr: 5 | 6 | import lib.std.list 7 | 8 | 9 | def main: ptr argv, int argc -> int 10 | PTR_SIZE new_list 11 | 12 | "foo" swap list_append_ptr 13 | "bar" swap list_append_ptr 14 | 15 | "foo" over list_contains_string "List should contain foo" assert 16 | "bar" over list_contains_string "List should contain bar" assert 17 | "baz" over list_contains_string False = "List should not contain baz" assert 18 | 19 | free 20 | 0 21 | -------------------------------------------------------------------------------- /tests/stdlib_list_copy.ilo: -------------------------------------------------------------------------------- 1 | # Description:Standard library - list, copy 2 | # Exit code:0 3 | # Stdout: 4 | # Stderr: 5 | 6 | import lib.std.list 7 | 8 | def main: ptr argv, int argc -> int 9 | INT_SIZE new_list 10 | 1 swap list_append_int 11 | 3 swap list_append_int 12 | 3 swap list_append_int 13 | 7 swap list_append_int 14 | 15 | dup list_copy 16 | over over != "Copy should differ from original" assert 17 | swap free 18 | 19 | dup 3 swap list_fetch_int 7 = "Fourth element should be 7" assert 20 | dup 2 swap list_fetch_int 3 = "Third element should be 3" assert 21 | dup 1 swap list_fetch_int 3 = "Second element should be 3" assert 22 | dup 0 swap list_fetch_int 1 = "First element should be 1" assert 23 | 24 | dup list_pop_int 7 = "Popping should give 7" assert 25 | dup list_pop_int 3 = "Popping should give 3" assert 26 | dup list_peek_int 3 = "Peeking should give 3" assert 27 | dup list_peek_int 3 = "Peeking should still give 3" assert 28 | dup list_pop_int drop 29 | dup list_pop_int drop 30 | free 31 | 0 32 | -------------------------------------------------------------------------------- /tests/stdlib_list_eq.ilo: -------------------------------------------------------------------------------- 1 | # Description:Standard library - list, equal 2 | # Exit code:0 3 | # Stdout: 4 | # Stderr: 5 | 6 | import lib.std.list 7 | 8 | def main: ptr argv, int argc -> int 9 | INT_SIZE new_list 10 | 1 swap list_append_int 11 | 3 swap list_append_int 12 | 3 swap list_append_int 13 | 7 swap list_append_int 14 | 15 | INT_SIZE new_list 16 | 1 swap list_append_int 17 | 3 swap list_append_int 18 | 3 swap list_append_int 19 | 7 swap list_append_int 20 | 21 | list_eq "Similar lists should be equal" assert 22 | 23 | INT_SIZE new_list 24 | 1 swap list_append_int 25 | 3 swap list_append_int 26 | 7 swap list_append_int 27 | 28 | INT_SIZE new_list 29 | 1 swap list_append_int 30 | 3 swap list_append_int 31 | 3 swap list_append_int 32 | 7 swap list_append_int 33 | 34 | list_eq False = "Lists of different length should be unequal" assert 35 | 36 | INT_SIZE new_list 37 | 1 swap list_append_int 38 | 3 swap list_append_int 39 | 7 swap list_append_int 40 | 41 | INT_SIZE new_list 42 | 1 swap list_append_int 43 | 3 swap list_append_int 44 | 3 swap list_append_int 45 | 46 | list_eq False = "Lists with different items should be unequal" assert 47 | 48 | INT_SIZE new_list 49 | INT_SIZE new_list 50 | list_eq "Empty lists should be equal" assert 51 | 52 | INT_SIZE new_list 53 | INT_SIZE 2 * new_list 54 | list_eq False = "Empty lists of different size should be unequal" assert 55 | 56 | 0 57 | -------------------------------------------------------------------------------- /tests/stdlib_list_int.ilo: -------------------------------------------------------------------------------- 1 | # Description:Standard library - list, int 2 | # Exit code:0 3 | # Stdout: 4 | # Stderr: 5 | 6 | import lib.std.list 7 | 8 | def main: ptr argv, int argc -> int 9 | INT_SIZE new_list 10 | 1 swap list_append_int 11 | 3 swap list_append_int 12 | 3 swap list_append_int 13 | 7 swap list_append_int 14 | 15 | dup 3 swap list_fetch_int 7 = "Fourth element should be 7" assert 16 | dup 2 swap list_fetch_int 3 = "Third element should be 3" assert 17 | dup 1 swap list_fetch_int 3 = "Second element should be 3" assert 18 | dup 0 swap list_fetch_int 1 = "First element should be 1" assert 19 | 20 | dup list_pop_int 7 = "Popping should give 7" assert 21 | dup list_pop_int 3 = "Popping should give 3" assert 22 | dup list_peek_int 3 = "Peeking should give 3" assert 23 | dup list_peek_int 3 = "Peeking should still give 3" assert 24 | dup list_pop_int drop 25 | dup list_pop_int drop 26 | free 27 | 0 28 | -------------------------------------------------------------------------------- /tests/stdlib_list_ptr.ilo: -------------------------------------------------------------------------------- 1 | # Description:Standard library - list, ptr 2 | # Exit code:0 3 | # Stdout: 4 | # Stderr: 5 | 6 | import lib.std.list 7 | 8 | def main: ptr argv, int argc -> int 9 | PTR_SIZE new_list 10 | "a" swap list_append_ptr 11 | "b" swap list_append_ptr 12 | "c" swap list_append_ptr 13 | "d" swap list_append_ptr 14 | 15 | dup 3 swap list_fetch_ptr "d" streq "Fourth element should be d" assert 16 | dup 2 swap list_fetch_ptr "c" streq "Third element should be c" assert 17 | dup 1 swap list_fetch_ptr "b" streq "Second element should be b" assert 18 | dup 0 swap list_fetch_ptr "a" streq "First element should be a" assert 19 | 20 | dup list_pop_ptr "d" streq "Popping should give 7" assert 21 | dup list_pop_ptr "c" streq "Popping should give 3" assert 22 | dup list_peek_ptr "b" streq "Peeking should give 3" assert 23 | dup list_peek_ptr "b" streq "Peeking should still give 3" assert 24 | dup list_pop_ptr drop 25 | dup list_pop_ptr drop 26 | free 27 | 0 28 | -------------------------------------------------------------------------------- /tests/stdlib_list_structs.ilo: -------------------------------------------------------------------------------- 1 | # Description:Standard library - list of structs 2 | # Exit code:0 3 | # Stdout: 4 | # Stderr: 5 | 6 | import lib.std.list 7 | 8 | const foo.a 0 # int 9 | const foo.b 8 # int 10 | const FOO_SIZE 16 11 | 12 | 13 | def main: ptr argv, int argc -> int 14 | FOO_SIZE new_list 15 | 16 | FOO_SIZE malloc 17 | dup foo.a + 13 swap seti 18 | dup foo.b + 37 swap seti 19 | swap # item list 20 | list_append # list 21 | 22 | FOO_SIZE malloc 23 | dup foo.a + 12 swap seti 24 | dup foo.b + 34 swap seti 25 | swap # item list 26 | list_append # list 27 | 28 | dup 1 swap list_fetch foo.a + derefi 12 = "Second element's a should be 12" assert 29 | dup 0 swap list_fetch foo.b + derefi 37 = "First element's b should be 37" assert 30 | 31 | dup list_pop \ 32 | dup foo.b + derefi 34 = "Popped element's b should be 34" assert \ 33 | free 34 | dup list_peek foo.b + derefi 37 = "Peeked element's b should be 37" assert 35 | dup list_pop \ 36 | dup foo.a + derefi 13 = "Popped element's a should be 13" assert \ 37 | free 38 | 39 | free 40 | 0 41 | -------------------------------------------------------------------------------- /tests/stdlib_malloc_large.ilo: -------------------------------------------------------------------------------- 1 | # Description:Standard library - malloc, single, large 2 | # Exit code:0 3 | # Stdout:Hi! 4 | # Stderr: 5 | 6 | import lib.std 7 | 8 | def main: ptr argv, int argc -> int 9 | 500000 malloc 10 | dup 'H' swap setc 11 | dup 1 + 'i' swap setc 12 | dup 2 + '!' swap setc 13 | dup 3 + 0 castc swap setc 14 | puts 15 | 0 16 | -------------------------------------------------------------------------------- /tests/stdlib_malloc_many.ilo: -------------------------------------------------------------------------------- 1 | # Description:Standard library - malloc, many 2 | # Exit code:164 3 | # Stdout: 4 | # Stderr: 5 | 6 | import lib.std 7 | 8 | 9 | buffer root 24 10 | 11 | const item.prev 0 # ptr 12 | const item.next 8 # ptr 13 | const item.value 16 # int 14 | 15 | 16 | def create: ptr root, int num -> void 17 | root # current_node 18 | 1 # value 19 | while dup num < 20 | 24 malloc 21 | rot dup rot dup rot swap item.prev + setp 22 | rot dup rot dup rot swap item.value + seti 23 | dup item.next + NULL swap setp 24 | rot dup rot dup rot item.next + setp 25 | swap drop swap 26 | 1 + 27 | 28 | def fetch: ptr node, int n -> int 29 | node # current_node 30 | 0 # index 31 | while dup n < 32 | swap item.next + derefp swap 33 | 1 + 34 | drop item.value + derefi 35 | 36 | def main: int argc, ptr argv -> int 37 | NULL root item.prev + setp 38 | NULL root item.next + setp 39 | 0 root item.value + seti 40 | root 1000 create 41 | root 420 fetch 42 | -------------------------------------------------------------------------------- /tests/stdlib_malloc_small.ilo: -------------------------------------------------------------------------------- 1 | # Description:Standard library - malloc, single, small 2 | # Exit code:0 3 | # Stdout:Hi!\n 4 | # Stderr: 5 | 6 | import lib.std 7 | 8 | def main: ptr argv, int argc -> int 9 | 5 malloc 10 | dup 'H' swap setc 11 | dup 1 + 'i' swap setc 12 | dup 2 + '!' swap setc 13 | dup 3 + 10 castc swap setc 14 | dup 4 + 0 castc swap setc 15 | puts 16 | 0 17 | -------------------------------------------------------------------------------- /tests/stdlib_memcpy.ilo: -------------------------------------------------------------------------------- 1 | # Description:Standard library - memcpy 2 | # Exit code:0 3 | # Stdout:walo 4 | # Stderr: 5 | 6 | import lib.std 7 | 8 | buffer foo 5 9 | 10 | def main: ptr argv, int argc -> int 11 | "walo lili" foo 4 memcpy 12 | foo puts 13 | 0 14 | -------------------------------------------------------------------------------- /tests/stdlib_puti.ilo: -------------------------------------------------------------------------------- 1 | # Description:Standard library - puti 2 | # Exit code:0 3 | # Stdout:12345 4 | # Stderr: 5 | 6 | import lib.std 7 | 8 | def main: ptr argv, int argc -> int 9 | 12345 puti 10 | 0 11 | -------------------------------------------------------------------------------- /tests/stdlib_puts.ilo: -------------------------------------------------------------------------------- 1 | # Description:Standard library - puts 2 | # Exit code:0 3 | # Stdout:Hello world!!!!\n 4 | # Stderr: 5 | 6 | import lib.std 7 | 8 | def main: ptr argv, int argc -> int 9 | "H" puts 10 | "el" puts 11 | "lo " puts 12 | "worl" puts 13 | "d!!!!" puts 14 | "\n" puts 15 | 0 16 | -------------------------------------------------------------------------------- /tests/stdlib_raise.ilo: -------------------------------------------------------------------------------- 1 | # Description:Standard library - raise 2 | # Exit code:1 3 | # Stdout: 4 | # Stderr:Foo bar baz\nTraceback:\nlib/std/linux.ilo, line 53 in raise\ntests/stdlib_raise.ilo, line 9 in main 5 | 6 | import lib.std 7 | 8 | def main: ptr argv, int argc -> int 9 | "Foo bar baz" raise 0 10 | -------------------------------------------------------------------------------- /tests/stdlib_read_file_large.ilo: -------------------------------------------------------------------------------- 1 | # Description:Standard library - read_file, large 2 | # Exit code:0 3 | # Stdout: 4 | # Stderr: 5 | 6 | import lib.std 7 | 8 | 9 | def main: ptr argv, int argc -> int 10 | "tests/fixtures/large.txt" read_file 11 | strlen 12345 = "File should be 12345 bytes" assert 12 | 0 13 | -------------------------------------------------------------------------------- /tests/stdlib_read_file_small.ilo: -------------------------------------------------------------------------------- 1 | # Description:Standard library - read_file, small 2 | # Exit code:0 3 | # Stdout:foobarbaz 4 | # Stderr: 5 | 6 | import lib.std 7 | 8 | 9 | def main: ptr argv, int argc -> int 10 | "tests/fixtures/small.txt" read_file puts 11 | 0 12 | -------------------------------------------------------------------------------- /tests/stdlib_replace.ilo: -------------------------------------------------------------------------------- 1 | # Description:Standard library - replace 2 | # Exit code:0 3 | # Stdout: 4 | # Stderr: 5 | 6 | import lib.std 7 | 8 | def main: ptr argv, int argc -> int 9 | "foo" 'a' 'b' replace "foo" streq "foo -> foo" assert 10 | "bar" 'a' 'b' replace "bbr" streq "bar -> bbr" assert 11 | "aaa" 'a' 'b' replace "bbb" streq "aaa -> bbb" assert 12 | 0 13 | -------------------------------------------------------------------------------- /tests/stdlib_sqrt.ilo: -------------------------------------------------------------------------------- 1 | # Description:Standard library - square root 2 | # Exit code:0 3 | # Stdout: 4 | # Stderr: 5 | 6 | import lib.std.math 7 | 8 | def main: ptr argv, int argc -> int 9 | 0 sqrt 0 = "sqrt(0) = 0" assert 10 | 9 sqrt 3 = "sqrt(9) = 3" assert 11 | 10 sqrt 3 = "sqrt(10) = 3" assert 12 | 0 4 - sqrt 0 2 - = "sqrt(-4) = -2" assert 13 | 0 14 | -------------------------------------------------------------------------------- /tests/stdlib_startswith.ilo: -------------------------------------------------------------------------------- 1 | # Description:Standard library - startswith 2 | # Exit code:0 3 | # Stdout: 4 | # Stderr: 5 | 6 | import lib.std 7 | 8 | def main: ptr argv, int argc -> int 9 | "foobar" "foobar" startswith "1" assert 10 | "foobar" "foo" startswith "2" assert 11 | "foo" "foobar" startswith False = "3" assert 12 | 0 13 | -------------------------------------------------------------------------------- /tests/stdlib_stoi.ilo: -------------------------------------------------------------------------------- 1 | # Description:Standard library - stoi 2 | # Exit code:0 3 | # Stdout: 4 | # Stderr: 5 | 6 | import lib.std 7 | 8 | def main: ptr argv, int argc -> int 9 | "0" stoi 0 = "0 should be 0" assert 10 | "12" stoi 12 = "12 should be 12" assert 11 | "9912391" stoi 9912391 = "9912391 should be 9912391" assert 12 | 0 13 | -------------------------------------------------------------------------------- /tests/stdlib_strcpy.ilo: -------------------------------------------------------------------------------- 1 | # Description:Standard library - strcpy 2 | # Exit code:0 3 | # Stdout:Hello! world!Hello, world! 4 | # Stderr: 5 | 6 | import lib.std 7 | 8 | buffer a 20 9 | buffer b 20 10 | 11 | def main: ptr argv, int argc -> int 12 | "Hello, world!" a strcpy 13 | a b strcpy 14 | '!' a 5 + setc 15 | a puts 16 | b puts 17 | 0 18 | -------------------------------------------------------------------------------- /tests/stdlib_streq.ilo: -------------------------------------------------------------------------------- 1 | # Description:Standard library - streq 2 | # Exit code:0 3 | # Stdout: 4 | # Stderr: 5 | 6 | import lib.std 7 | 8 | def main: ptr argv, int argc -> int 9 | "foobar" "foobar" streq "Expression 1 should be True" assert 10 | "foobar" "foo" streq False = "Expression 2 should be False" assert 11 | "foo" "foobar" streq False = "Expression 3 should be False" assert 12 | 0 13 | -------------------------------------------------------------------------------- /tests/stdlib_strlen.ilo: -------------------------------------------------------------------------------- 1 | # Description:Standard library - strlen 2 | # Exit code:0 3 | # Stdout: 4 | # Stderr: 5 | 6 | import lib.std 7 | 8 | def main: ptr argv, int argc -> int 9 | "FooBarBaz" strlen 9 = "1" assert 10 | "" strlen 0 = "2" assert 11 | 0 12 | -------------------------------------------------------------------------------- /tests/stdlib_substring.ilo: -------------------------------------------------------------------------------- 1 | # Description:Standard library - substring 2 | # Exit code:0 3 | # Stdout:Hello 4 | # Stderr: 5 | 6 | import lib.std 7 | 8 | def main: ptr argv, int argc -> int 9 | "Hello, world!" 5 substring 10 | dup puts 11 | free 12 | 0 13 | -------------------------------------------------------------------------------- /tests/stdlib_textbuffer.ilo: -------------------------------------------------------------------------------- 1 | # Description:Standard library - Textbuffer 2 | # Exit code:0 3 | # Stdout:Hello, world! 4 | # Stderr: 5 | 6 | import lib.std 7 | import lib.std.textbuffer 8 | 9 | def main: ptr argv, int argc -> int 10 | 1 textbuffer_create 11 | 12 | "Hello, " swap textbuffer_append 13 | dup textbuffer.capacity + derefi 8 = "Buffer should have cap. 8" assert 14 | dup textbuffer.len + derefi 7 = "Buffer should have len 7" assert 15 | 16 | "world!" swap textbuffer_append 17 | dup textbuffer.capacity + derefi 16 = "Buffer should have cap. 16" assert 18 | dup textbuffer.len + derefi 13 = "Buffer should have len 13" assert 19 | 20 | dup textbuffer.content + puts 21 | free 22 | 0 23 | -------------------------------------------------------------------------------- /tests/stdlib_vec.ilo: -------------------------------------------------------------------------------- 1 | # Description:Standard library - vectors 2 | # Exit code:0 3 | # Stdout: 4 | # Stderr: 5 | 6 | import lib.std.math 7 | 8 | buffer vec_a 24 9 | buffer vec_b 24 10 | buffer vec_c 24 11 | 12 | def main: ptr argv, int argc -> int 13 | # vec_add 14 | 3 vec_a vec.x + seti 15 | 6 vec_a vec.y + seti 16 | 9 vec_a vec.z + seti 17 | 18 | 2 vec_b vec.x + seti 19 | 4 vec_b vec.y + seti 20 | 6 vec_b vec.z + seti 21 | 22 | vec_a vec_b vec_c vec_add 23 | 24 | vec_c vec.x + derefi 5 = "c.x = 5" assert 25 | vec_c vec.y + derefi 10 = "c.y = 10" assert 26 | vec_c vec.z + derefi 15 = "c.z = 15" assert 27 | 28 | # vec_sub 29 | vec_a vec_b vec_c vec_sub 30 | 31 | vec_c vec.x + derefi 1 = "c.x = 1" assert 32 | vec_c vec.y + derefi 2 = "c.y = 2" assert 33 | vec_c vec.z + derefi 3 = "c.z = 3" assert 34 | 35 | # vec_negate 36 | vec_a vec_c vec_negate 37 | 38 | vec_c vec.x + derefi 0 3 - = "c.x = -3" assert 39 | vec_c vec.y + derefi 0 6 - = "c.y = -6" assert 40 | vec_c vec.z + derefi 0 9 - = "c.z = -9" assert 41 | 42 | # vec_mul 43 | vec_a 10 vec_c vec_mul 44 | 45 | vec_c vec.x + derefi 30 = "c.x = 30" assert 46 | vec_c vec.y + derefi 60 = "c.y = 60" assert 47 | vec_c vec.z + derefi 90 = "c.z = 90" assert 48 | 49 | # vec_div 50 | vec_a 3 vec_c vec_div 51 | 52 | vec_c vec.x + derefi 1 = "c.x = 1" assert 53 | vec_c vec.y + derefi 2 = "c.y = 2" assert 54 | vec_c vec.z + derefi 3 = "c.z = 3" assert 55 | 56 | # vec_len_squared 57 | vec_a vec_len_squared 126 = "Squared length = 126" assert 58 | 59 | # vec_len 60 | vec_a vec_len 11 = "Length = 11" assert 61 | 62 | # vec_unit 63 | vec_a vec_c vec_unit 64 | 65 | vec_c vec.x + derefi 272 = "u.x = 272" assert 66 | vec_c vec.y + derefi 545 = "u.y = 545" assert 67 | vec_c vec.z + derefi 818 = "u.z = 818" assert 68 | 69 | # vec_dot 70 | vec_a vec_b vec_dot 84 = "a dot b = 84" assert 71 | 72 | 0 73 | -------------------------------------------------------------------------------- /tests/string_quotes.ilo: -------------------------------------------------------------------------------- 1 | # Description:String literal with escaped quotes 2 | # Exit code:28 3 | # Stdout:Yeah.. "sure" 4 | # Stderr: 5 | 6 | def main: int argc, ptr argv -> int 7 | 1 # File descriptor 8 | "Yeah.. \"sure\"" # String 9 | 13 # String length 10 | 1 # Syscall no. (write) 11 | syscall 3 12 | 13 | 28 # Exit code 14 | -------------------------------------------------------------------------------- /tests/subtract.ilo: -------------------------------------------------------------------------------- 1 | # Description:Subtract integers 2 | # Exit code:4 3 | # Stdout: 4 | # Stderr: 5 | 6 | def main: int argc, ptr argv -> int 7 | 8 2 - 8 | 2 - 9 | -------------------------------------------------------------------------------- /tests/swap.ilo: -------------------------------------------------------------------------------- 1 | # Description:Swap 2 | # Exit code:3 3 | # Stdout: 4 | # Stderr: 5 | 6 | def main: int argc, ptr argv -> int 7 | 3 4 swap 8 | -------------------------------------------------------------------------------- /tests/traceback.ilo: -------------------------------------------------------------------------------- 1 | # Description:Print traceback 2 | # Exit code:1 3 | # Stdout: 4 | # Stderr:Something went wrong\nTraceback:\ntests/traceback.ilo, line 7 in baz\ntests/traceback.ilo, line 10 in bar\ntests/traceback.ilo, line 13 in foo\ntests/traceback.ilo, line 16 in main 5 | 6 | def baz: -> void 7 | "Something went wrong" traceback 8 | 9 | def bar: -> void 10 | baz 11 | 12 | def foo: -> void 13 | bar 14 | 15 | def main: int argc, ptr argv -> int 16 | foo 17 | 0 18 | -------------------------------------------------------------------------------- /tests/true.ilo: -------------------------------------------------------------------------------- 1 | # Description:Push True boolean 2 | # Exit code:1 3 | # Stdout: 4 | # Stderr: 5 | 6 | def main: int argc, ptr argv -> bool 7 | True 8 | -------------------------------------------------------------------------------- /tests/void.ilo: -------------------------------------------------------------------------------- 1 | # Description:Return void 2 | # Exit code:2 3 | # Stdout: 4 | # Stderr: 5 | 6 | def foo: int n -> void 7 | n 8 | 9 | def main: ptr argv, int argc -> int 10 | 1 2 3 foo 11 | -------------------------------------------------------------------------------- /tests/while.ilo: -------------------------------------------------------------------------------- 1 | # Description:While loop 2 | # Exit code:10 3 | # Stdout: 4 | # Stderr: 5 | 6 | 7 | def main: int argc, ptr argv -> int 8 | 0 # Accumulator 9 | while dup 10 < 10 | 1 + 11 | -------------------------------------------------------------------------------- /tests/while_skip.ilo: -------------------------------------------------------------------------------- 1 | # Description:While loop skip 2 | # Exit code:0 3 | # Stdout: 4 | # Stderr: 5 | 6 | 7 | def main: int argc, ptr argv -> int 8 | 0 9 | while False 10 | drop 1 11 | --------------------------------------------------------------------------------