├── .gitignore ├── .gitmodules ├── .use_zig13 ├── COPYING ├── README.md ├── TODO ├── build.zig ├── build.zig.zon ├── doc ├── dampe.md ├── language.md └── test-harness.md ├── projects ├── astar.finw └── base64.finw ├── src ├── analyser.zig ├── buffer.zig ├── codegen.zig ├── common.zig ├── errors.zig ├── eventually.zig ├── lexer.zig ├── list.zig ├── main.zig ├── optimizer.zig ├── parser.zig ├── utils.zig └── vm.zig ├── std ├── alloc.finw ├── core.finw ├── dampe.finw ├── math.finw ├── net.finw ├── std.finw ├── varvara.finw └── vec.finw └── test.finw /.gitignore: -------------------------------------------------------------------------------- 1 | .zig-cache/ 2 | zig-out/ 3 | core* 4 | !core.finw 5 | tmp* 6 | .snarf 7 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "third_party/zig-clap"] 2 | path = third_party/zig-clap 3 | url = https://github.com/Hejsil/zig-clap.git 4 | [submodule "third_party/uxn"] 5 | path = third_party/uxn 6 | url = https://github.com/kiedtl/bur_uxn 7 | branch = for_bur 8 | -------------------------------------------------------------------------------- /.use_zig13: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kiedtl/finwe/ae8e5574297869448a7d185529898d2e5544c05b/.use_zig13 -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | MIT License 2 | =========== 3 | 4 | - Copyright © 2021-2024 Kiëd Llaentenn 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights to 9 | (mis)use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is furnished 11 | to do so, subject to the following conditions: 12 | 13 | - The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Finwë 2 | 3 | A compiled language for the [Uxn VM](https://wiki.xxiivv.com/site/uxn.html). 4 | 5 | Formerly named "Bureaucrat". 6 | 7 | ## What? 8 | 9 | This language is an experimental project that I began to determine how 10 | cutting-edge, high-level language features (e.g. signed integers, structs, 11 | and memory allocators) could be implemented in a constrained environment. 12 | 13 | This project is possibly the first stack-based language to implement 14 | compiler-enforced stack safety, i.e. using static analysis to catch underflows, 15 | overflows, type errors, and so on. 16 | 17 | (*Update 2025-05-29*: Apparently not the first; see 18 | [Tails](https://github.com/snej/tails) by Jens Alfke) 19 | 20 | Other features include a test harness, memory protection, and generic types + 21 | functions. 22 | 23 | **Examples**: 24 | 25 | * [base64 encoder](projects/base64.finw) 26 | * [A* implementation](projects/astar.finw) 27 | * [Gemini client](https://github.com/kiedtl/tuor/blob/trunk/src/main.finw) 28 | 29 | ## How? 30 | 31 | Consider the following example, which is obviously erroneous. It will not 32 | compile: 33 | 34 | ``` 35 | (use* core) 36 | 37 | (word main ( -- ) [ 38 | drop 39 | ]) 40 | ``` 41 | 42 | When the `drop` word is analysed, its signature is compared against the current 43 | stack state. Because the drop word requires one argument (`Any --`), and the 44 | stack has no items on it at that point, the following compile error is given: 45 | 46 | ``` 47 | 2 | 48 | 3 | (word main ( -- ) [ 49 | 4 | drop 50 | ^ 51 | StackUnderflow: Stack underflow (at index 1) 52 | at ot.finw:4:5 53 | ``` 54 | 55 | # Why? 56 | 57 | My first forays into Uxn were difficult. Like all concatentive languages, one 58 | needs to be able to keep the stack context in their heads while writing code; 59 | Uxn makes this more difficult by introducing 16-bit words, which act on a pair 60 | of byte, treating it as a single item. Thus, a single mistake can lead to some 61 | difficult-to-debug issues. 62 | 63 | Finwë was originally designed to abstract away the concept of 16-bit/8-bit 64 | words. In Finwë, all the core words can work with both (are "generic"), taking 65 | `Any` args (instead of `U16`/`U8`). At runtime, they are monomorphized into a 66 | specific flavor -- either 16-bit or 8-bit -- and compiled to Uxn bytecode. 67 | 68 | Naturally, allowing generic words necessitates that the compiler know the stack 69 | state at any given point. Tracking this across the program allows for other 70 | improvements, such as catching underflows/overflows, warning about type 71 | mismatches, and so on. Implementing a feature like this had been an idea of mine 72 | for a long time, so this was a welcome opportunity to put it to the test. 73 | 74 | 81 | 82 | ## Status 83 | 84 | Finwë is currently experimental. Much work remains regarding language features, 85 | safety, and especially error reporting. 86 | 87 | ## Installation 88 | 89 | No binary is currently provided, please compile on your own: 90 | 91 | ``` 92 | $ zig build 93 | $ zig-out/bin/finwe myfile.finw # Compile and run with builtin VM. 94 | $ zig-out/bin/finwe myfile.finw -x out.rom # Compile to ROM. 95 | $ zig-out/bin/finwe myfile.finw -a func # Dump assembly for function. 96 | $ zig-out/bin/finwe myfile.finw -d # Output debug info to .syms file. Requires -x. 97 | ``` 98 | 99 | Note that the standard library must be in the same directory as the file you are 100 | compiling. 101 | 102 | ## Docs 103 | 104 | - [Language overview](doc/language.md) 105 | - [Test harness](doc/test-harness.md) 106 | - [DAMPE Varvara extension](doc/dampe.md) 107 | 108 | External: 109 | - [Uxn homepage](https://wiki.xxiivv.com/site/uxn.html) 110 | - [Uxn opcode reference](https://wiki.xxiivv.com/site/uxntal_opcodes.html) 111 | - [Uxntal reference](https://wiki.xxiivv.com/site/uxntal.html) 112 | - [Varvara reference](https://wiki.xxiivv.com/site/varvara.html) 113 | 114 | ## License 115 | 116 | MIT 117 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | Roadmap 2 | ======= 3 | 4 | x Basic analyzer (keep track of stack count) 5 | x Basic analyzer (keep track of types) 6 | x Memory opcodes 7 | x Devices (same as enums?) 8 | x String literals 9 | x Variables 10 | x If statement 11 | x Structs 12 | x HERE 13 | x Malloc 14 | x Basic + free + realloc 15 | x Defrag 16 | x Opaque ("void") pointers 17 | x Maybe0 18 | x Unit tests 19 | x Remove macros 20 | x Test suite 21 | x Imports 22 | x Arrays (sized, unknown-size) 23 | x Vec 24 | x Metadata 25 | x (r) keyword (use rt-stack) 26 | x Signed numbers 27 | x Comparison 28 | x Printing 29 | x Lexer/Parser 30 | x Abs/Neg?/Division 31 | x A* pathfinding 32 | x GetIndex fixes 33 | x Enums 34 | x Cond (for Direction/move) 35 | x Refactoring 36 | x Scopes 37 | x Explicit main function 38 | x Decls within decls 39 | x Metadata 40 | x Error messages and context 41 | x Existing 42 | x Lexer 43 | x Parser 44 | x Analyser 45 | x Remove panics (except typeref stuff) 46 | x Lexer 47 | x Parser 48 | x Analyser 49 | x Analyser: proper checks 50 | x End of func 51 | x Loop body 52 | x When branches 53 | x Cond branches 54 | x Fix: inlining func w/ early return 55 | x Fix: inlining func w/ whens/conds/loops 56 | x Dampe 57 | x docs 58 | x mailing list discussion + proposal 59 | x change to .System/expansion 60 | 61 | *** v0.1 release *** 62 | 63 | - Fix: s/ASTNodeList/Block/g 64 | - Proper scoping per block, not per decl 65 | - Each block carries srcloc info (allows for cleanup of Cond struct) 66 | - New: Of() w/ method calls 67 | - New: hints (grep HINT src/errors.zig) 68 | - Enh: additional lines for long messages/context (e.g. error.StackBranching*) 69 | - Fix: proper error reporting on type expr evaluation error 70 | - Fix: memory leaks 71 | - Enh: #[] syntax 72 | - New: Allow wild w/o block 73 | - New: Loops: break/continue 74 | - Std: Fixed-point (8:8) 75 | x Basic arithmetic 76 | x Sin, cos (CORDIC + taylor series) 77 | - Natural log 78 | - Scr1 demo 79 | - Fix: Solve Effects issue 80 | - i.e. how to represent effect of quote in arity def? 81 | - would require being able to "expand" typeref... 82 | - need major refactoring? 83 | - Opt: Make std/alloc faster 84 | - Auto-merge when freeing (only immediate next block) 85 | - Remove Header/prev 86 | - Remove Header/guard* when dampe is used 87 | - New: Constant pointers, (const) builtin 88 | - Constant version of devs (for readonly ports)? 89 | - Fix: proper precedence rules for multimethods 90 | - New: dip builtin 91 | - Cleanup pervasive "move (r move)" pattern in stdlib 92 | - Bi@, bi*, sip, etc 93 | 94 | - Static analysis pt 2 95 | - New analyser pass: track comptime-known vals 96 | - Devices 97 | - Chk: is port readable/writeable when calling deo/dei 98 | 99 | Optimizations 100 | ------------- 101 | 102 | - Inline locals 103 | x Eliminate duplicate decls (from generics) 104 | - E.g. 0 1 swap and 0 (as ptr) 1 (as ptr) swap -> two different functions, 105 | which is unnecessary (only one needs to be generated) 106 | - Solution: after spouting out decl at codegen time, check if the generated 107 | output is exactly the same as another variant, and delete if that's the 108 | case (and set romloc to that other function's romloc) 109 | - Change variants field of decl to ASTNodePtrList to make this easy 110 | x Auto-inlining 111 | - Metadata: always/never inline 112 | - Tail recursion 113 | - (recurse) builtin 114 | 115 | Warnings 116 | -------- 117 | 118 | - Sprite structs can have metadata to warn if 0-initialized (either not enough 119 | data or no data) 120 | - #(warn data-zero-init) #(warn data-incomplete-init) 121 | - Certain structs should not be initialized w/ make() 122 | - e.g. fxpt 123 | -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | const Build = @import("std").Build; 2 | 3 | pub fn build(b: *Build) void { 4 | const clap = b.dependency("clap", .{}); 5 | 6 | // Standard target options allows the person running `zig build` to choose 7 | // what target to build for. Here we do not override the defaults, which 8 | // means any target is allowed, and the default is native. Other options 9 | // for restricting supported target set are available. 10 | const target = b.standardTargetOptions(.{}); 11 | 12 | // Standard release options allow the person running `zig build` to select 13 | // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. 14 | const optimize = b.standardOptimizeOption(.{}); 15 | 16 | const exe = b.addExecutable(.{ 17 | .name = "finwe", 18 | .root_source_file = b.path("src/main.zig"), 19 | .target = target, 20 | .optimize = optimize, 21 | }); 22 | exe.root_module.addImport("clap", clap.module("clap")); 23 | 24 | exe.linkLibC(); 25 | exe.addIncludePath(b.path("third_party/uxn/src/")); 26 | exe.addCSourceFiles(.{ .files = &[_][]const u8{ 27 | "third_party/uxn/src/uxn.c", 28 | "third_party/uxn/src/uxnemu.c", 29 | "third_party/uxn/src/devices/system.c", 30 | "third_party/uxn/src/devices/console.c", 31 | "third_party/uxn/src/devices/screen.c", 32 | "third_party/uxn/src/devices/audio.c", 33 | "third_party/uxn/src/devices/file.c", 34 | "third_party/uxn/src/devices/controller.c", 35 | "third_party/uxn/src/devices/mouse.c", 36 | "third_party/uxn/src/devices/datetime.c", 37 | "third_party/uxn/src/devices/net.c", 38 | } }); 39 | exe.addIncludePath(.{ .cwd_relative = "/usr/include/SDL2/" }); 40 | exe.linkSystemLibrary("SDL2"); 41 | exe.linkSystemLibrary("tls"); 42 | 43 | b.installDirectory(.{ 44 | .source_dir = b.path("std/"), 45 | .install_dir = .bin, 46 | .install_subdir = "std", 47 | }); 48 | b.installArtifact(exe); 49 | 50 | const run_cmd = b.addRunArtifact(exe); 51 | run_cmd.step.dependOn(b.getInstallStep()); 52 | if (b.args) |args| { 53 | run_cmd.addArgs(args); 54 | } 55 | 56 | const run_step = b.step("run", "Run the app"); 57 | run_step.dependOn(&run_cmd.step); 58 | 59 | const unit_tests = b.addTest(.{ 60 | .root_source_file = b.path("src/main.zig"), 61 | .target = target, 62 | .optimize = optimize, 63 | }); 64 | const run_unit_tests = b.addRunArtifact(unit_tests); 65 | const test_step = b.step("test", "Run unit tests"); 66 | test_step.dependOn(&run_unit_tests.step); 67 | } 68 | -------------------------------------------------------------------------------- /build.zig.zon: -------------------------------------------------------------------------------- 1 | .{ 2 | // This is the default name used by packages depending on this one. For 3 | // example, when a user runs `zig fetch --save `, this field is used 4 | // as the key in the `dependencies` table. Although the user can choose a 5 | // different name, most users will stick with this provided value. 6 | // 7 | // It is redundant to include "zig" in this name because it is already 8 | // within the Zig package namespace. 9 | .name = "finw", 10 | 11 | // This is a [Semantic Version](https://semver.org/). 12 | // In a future version of Zig it will be used for package deduplication. 13 | .version = "0.0.0", 14 | 15 | // This field is optional. 16 | // This is currently advisory only; Zig does not yet do anything 17 | // with this value. 18 | //.minimum_zig_version = "0.11.0", 19 | 20 | // This field is optional. 21 | // Each dependency must either provide a `url` and `hash`, or a `path`. 22 | // `zig build --fetch` can be used to fetch all dependencies of a package, recursively. 23 | // Once all dependencies are fetched, `zig build` no longer requires 24 | // internet connectivity. 25 | .dependencies = .{ 26 | .clap = .{ 27 | .url = "git+https://github.com/Hejsil/zig-clap?ref=0.9.1#d71cc39a94f3e6ccbad00c25d350c9147de4df9f", 28 | .hash = "122062d301a203d003547b414237229b09a7980095061697349f8bef41be9c30266b", 29 | }, 30 | }, 31 | .paths = .{ 32 | "build.zig", 33 | "build.zig.zon", 34 | "src", 35 | // For example... 36 | //"LICENSE", 37 | //"README.md", 38 | }, 39 | } 40 | -------------------------------------------------------------------------------- /doc/dampe.md: -------------------------------------------------------------------------------- 1 | # DAMPE Extension 2 | 3 | Dampe (Debugging and Memory Protection Extension) is an extension to the System 4 | device of Varvara, to add various features to aid debugging (especially memory 5 | corruption, buffer overflows, and the like). 6 | 7 | It adds a few commands to the .System/extension port: 8 | 9 | | Command | `.System/extension` id | `.System/extension` args | 10 | |:-----------:|:----------------------:|:------------------------:| 11 | | backtrace | 0x40 | n/a | 12 | | protect | 0x41 | `ptr* len*` | 13 | | unprotect | 0x42 | `ptr*` | 14 | | priv-enter | 0x43 | n/a | 15 | | priv-exit | 0x44 | n/a | 16 | | assert-prot | 0x45 | `ptr*` | 17 | 18 | Description of commands: 19 | 20 | | Command | description | 21 | |:-----------:|:-----------:| 22 | | backtrace | Prints a backtrace to stdout, iterating over the return stack and converting addresses to their respective functions (and adding line/column/file info where possible). Does not empty rst. | 23 | | protect | Protect memory region. If region is already protected or is contained in an already-protected region, emulator prints a backtrace and exits. | 24 | | unprotect | Unprotect memory region. Pointer MUST be the beginning of a previously-protected region. If the region isn't protected or it's not the beginning of the region, the emulator prints a backtrace and exits. | 25 | | priv-enter | Enter privileged mode, where writing to protected memory is permitted. | 26 | | priv-exit | Exit privileged mode. | 27 | | assert-prot | Assert that a region of memory has been protected, and crash otherwise. | 28 | 29 | Violation of protection results in the execution being terminated and a 30 | backtrace printed. Additional info may be added, such as a backtrace of the 31 | initial protection. 32 | 33 | ## Status 34 | 35 | The extension details, like Finwë itself, is beta and subject to change at any time. No other emulators are known to implement it. 36 | 37 | ## Limitations 38 | 39 | In its current implementation, DAMPE will not help you if a device decides to 40 | overwrite your memory. 41 | -------------------------------------------------------------------------------- /doc/language.md: -------------------------------------------------------------------------------- 1 | # Language 2 | 3 | *Note: familiarity with the Uxn VM and Varvara is assumed. Please check their 4 | respective docs for an overview if necessary.* 5 | 6 | ## Literals 7 | 8 | ``` 9 | 0 // A byte literal 10 | 0xA 0b10 // More byte literals 11 | 12 | 0s // A short literal, with an s suffix 13 | 0xFFFFs // More short literals 14 | 15 | -18i // Signed byte (I8) literal. 16 | -482is // Signed short (I16) literal. 17 | 18 | "string" // A string, the pointer of which is pushed onto the stack. 19 | // The string itself will be embedded in the data section. 20 | 21 | .Op/Orot // An enum literal. 22 | 23 | [ 1 2 3 ] // A quote literal. 24 | 25 | nil // Boolean literals. 26 | t // Equal to 1. 27 | ``` 28 | 29 | ## Words 30 | 31 | Here's a word that does nothing. 32 | 33 | ``` 34 | (word foo ( -- ) [ ]) 35 | ``` 36 | 37 | Here's a word that take a byte and returns a short. 38 | 39 | ``` 40 | (word foo ( U8 -- U16 ) [ (as U16) ]) 41 | ``` 42 | 43 | The arity can be left off, but this is strongly discouraged, as it can lead to 44 | miscompilation if the word is accidentally generic (i.e. compiles down to 45 | different bytecode depending on the argument type). 46 | 47 | ``` 48 | (word foo [ (as U16) ]) 49 | ``` 50 | 51 | ## Arity 52 | 53 | Various elements can have a declared arity, such as words, loop conditionals, 54 | and wild statements. 55 | 56 | The basic syntax is as follows: `( ARGS -- STACK | RETURN_ARGS -- RETURN_STACK)` 57 | 58 | The return-stack portion can be left off: `( ARGS -- STACK )` 59 | 60 | The args or the stack can be left off as well: `( ARGS -- )`, `( ARGS )`, `( -- 61 | STACK )` 62 | 63 | ## Types 64 | 65 | This is easily the most complex bit. 66 | 67 | There are several categories of types: the two basic elementary types (byte and 68 | short), the basic types which add meaning to the elementary types (pointers, 69 | etc), user-defined basic types (enums and single-element structs), composite 70 | types (arrays and structs), `Opaque`, generic types, type expressions, and the 71 | `Type` type. 72 | 73 | ### Elementary types 74 | 75 | - `U8` 76 | - `U16` 77 | 78 | Enjoy their pureness. Devine approved. 79 | 80 | ### Layered types 81 | 82 | These types act like either a byte or short -- but they add meaning which aids 83 | readability. Some can be used interchangeably, some cannot. 84 | 85 | - `Bool` (byte). `0` is false, `1` is true, everything else is true. 86 | - `I8` (byte). A signed byte. 87 | - `I16` (short). A signed short. 88 | - `Char8` (byte). Interchangeable with `U8`. 89 | - `Char16` (short). Interchangeable with `U16`. Intended to stand in as a 90 | unicode codepoint, but may be removed later on due to lack of usefulness. 91 | - `Dev8` (byte). A byte-sized device port. 92 | - `Dev16` (byte). A short-sized device port. 93 | 94 | Finally, there are pointers, both byte (zero-page) and short-sized. 95 | - Zero-page pointer support is currently incomplete. 96 | - Short-size pointers are fully supported, and have a special syntax: `@type`. 97 | 98 | ### Devices 99 | 100 | TODO (see std/varvara.finw in the meantime) 101 | 102 | ### Enums 103 | 104 | Enums are defined like so: 105 | 106 | ``` 107 | (enum Foo U8 foo bar baz) 108 | ``` 109 | 110 | Note that the enum size (either `U8` or `U16`) is required. 111 | 112 | The value can be specified, instead of being assigned at random: 113 | 114 | ``` 115 | (enum Foo U8 116 | [foo 0xAB] 117 | bar 118 | [baz 0xEF] 119 | ) 120 | ``` 121 | 122 | Enum literals take the following syntax: `.TYPE/elem`. 123 | 124 | For example: `.Foo/foo`, `.Foo/bar`, `.Foo/baz`. 125 | 126 | In the future, when Finwë gets some basic type inference, the need to specify 127 | the type might go away: `.foo`. 128 | 129 | 130 | There are some builtin enums, such as `Op`. See the `asm()` docs for more info. 131 | 132 | ### Structs 133 | 134 | Structs are defined like so: 135 | 136 | ``` 137 | (struct Foo 138 | [field1 U8] 139 | [field2 U8] 140 | ) 141 | ``` 142 | 143 | Structs larger than two bytes cannot be placed onto the stack, and can only 144 | exist in memory. 145 | 146 | Structs smaller than or equal to two bytes can be created on the stack with the 147 | `make()` builtin: 148 | 149 | ``` 150 | (struct Foo [foo U8] [bar U8]) 151 | 152 | 3 // foo field 153 | 4 // bar field 154 | (make Foo) 155 | ``` 156 | 157 | Both structs on the stack and structs in memory can be accessed with `:field`. 158 | 159 | ### Arrays 160 | 161 | Arrays cannot be placed onto the stack at all. Their purpose is solely to give a 162 | type to data and pointers. 163 | 164 | ``` 165 | (let foo [U8 256]) // Foo is an array of U8, with a size of 256 166 | 167 | (struct Foo 168 | [field1 U8] 169 | [field2 [U16 12]] // Arrays can be part of structs 170 | ) 171 | 172 | @[U16 12] // Array pointer. NOT a slice. 173 | ``` 174 | 175 | ### `Opaque` 176 | 177 | Opaque is a type-erased object. By itself, it cannot be used in any context, 178 | because the size is unknown -- it could be a byte, a short, or an array of 500 179 | shorts. 180 | 181 | `@Opaque` is equivalent to `void *` in C. It is used, for example, in the memory 182 | allocator, where the pointer is passed around but not dereferenced. As an added 183 | bonus, since `@Opaque` cannot be dereferenced, one is forced to cast it to a 184 | concrete type before using. 185 | 186 | ### Generic types 187 | 188 | Generic types allow for declaring generic words, which are then monomorphized. 189 | 190 | ``` 191 | (word foo ( Any -- Any ) [ 192 | // do stuff that can be done to either shorts or bytes 193 | ]) 194 | ``` 195 | 196 | Nearly all the core words are generic, which allows for using them with both 197 | shorts and bytes (though not a mix). 198 | 199 | - `Any`: takes any type, short or byte sized. 200 | - `Any8`: takes any byte-sized type. 201 | - `Any16`: takes any short-sized type. 202 | - `AnyPtr`: takes any pointer, zero-page or otherwise. 203 | - `AnyPtr16`: takes any 16-bit pointer. 204 | - `AnyDev`: takes any device port. (Recall that device ports are always byte-sized.) 205 | 206 | Finally, there is the type reference: `$`. They allow referencing the 207 | concrete type a generic was "filled in" with: 208 | 209 | ``` 210 | // This makes it clear that the function can take any argument, and then 211 | // return an element of that type. 212 | (word foo (Any -- $0) [ 213 | // stuff 214 | ]) 215 | 216 | // This is the (simplified) definition of the `rot` word. 217 | // It takes three arguments of any type, and then returns three elements of 218 | // those types, in a shuffled order. 219 | // 220 | // (The real `rot` definition is a bit more complicated to ensure that 221 | // the elements are all of the same size -- either bytes or shorts, not a mix.) 222 | // 223 | (word rot (Any Any Any -- $1 $0 $2) [ 224 | // inline assembly 225 | ]) 226 | 227 | // Although this was used as an example previously, it is actually incorrect, 228 | // because putting a generic in the stack is invalid. It means the function 229 | // could return literally anything, which is obviously wrong. 230 | (word foo (Any -- Any) [ 231 | // stuff 232 | ]) 233 | ``` 234 | 235 | Note that number order is: `( ... $2 $1 $0 -- )`. 236 | 237 | More powerful generic types are available through type expressions. 238 | 239 | ### Type expressions 240 | 241 | - `(AnySz )`: Generic. Matches any type with the same size as its arg. 242 | - Example: `(AnySz U16)` matches any short-sized type. 243 | - Why not just use `Any8` or `Any16`? This is useful with type references, 244 | when you take multiple generic types but need them to be a uniform size. 245 | E.g. `(AnySz $0) (AnySz $0) Any --` instead of just `Any Any Any --`. 246 | - `(USz )`: Resolves to either U8 or U16 depending on the arg size. 247 | - `(ISz )`: Resolves to either I8 or I16 depending on the arg size. 248 | - `(AnyOf )`: Takes any type that's a derivative of a struct template. See 249 | the struct template section for more info. 250 | - `(AnySet )`: Generic. Matches any one of its args. 251 | - `(Child )`: Resolves to the child type of a pointer or array arg. 252 | - E.g. `(Child @U8)` -> `U8` 253 | - `(Of )`: Creates an instance of a struct template. See the 254 | struct template section for more info. 255 | - `(FieldType )`: Resolves to the field type of its struct 256 | argument. 257 | - `(Omit )`: Creates a new struct from its arg, that omits a field. 258 | - `@(Fn )`: Generic. Matches any function pointer with a specific arity. 259 | - Makes no sense to use on its own -- must be a pointer. 260 | 261 | ### `Type` 262 | 263 | Sometimes, you need the caller of a generic function to specify a type, without 264 | actually passing an arg of that type. Rather than re-inventing turbofish, Finwë 265 | uses `Type` arguments and the `of()` builtin. 266 | 267 | ``` 268 | (word foo ( U8 @U8 Type -- (Some_Expression $0) ) [ 269 | // stuff 270 | ]) 271 | 272 | (of foo @Char8) 273 | ``` 274 | 275 | These special `Type` arguments do not appear on the stack at all. They live 276 | entirely in Finwë's head. 277 | 278 | ### Struct templates 279 | 280 | Struct templates allow for defining "generics" as they are called in other 281 | languages. 282 | 283 | For example, we can define a generic vector type like so: 284 | 285 | ``` 286 | (struct Vector (Any) 287 | [capacity U16] 288 | [len U16] 289 | [items @[$0]] 290 | ) 291 | ``` 292 | 293 | Let's break this down. 294 | - The struct takes a generic argument, `Any`. Later, any type references will be 295 | filled in with the argument that is passed. 296 | - The `items` field, a pointer to an array, is defined as `@[$0]`, or a pointer 297 | to an array of whatever the argument was. 298 | 299 | Then, we can instantiate any amount of actual, concrete types based on the type 300 | template, with the `Of()` type expression: 301 | 302 | ``` 303 | (Of Vector U8) 304 | (Of Vector I16) 305 | (Of Vector @(Fn (U8 -- U8))) 306 | (Of Vector @Opaque) 307 | ``` 308 | 309 | And, we can have functions that operate on any derivative of the `Vector` 310 | template, with the `AnyOf()` type expression: 311 | 312 | ``` 313 | (word last-item ( @(AnyOf Vector) -- (Child (FieldType (Child $0) items)) ) [ 314 | // stuff 315 | ]) 316 | ``` 317 | 318 | For an example of how this is used, see `std/vec.finw`. 319 | 320 | ### Type aliases 321 | 322 | ``` 323 | (typealias AnySigned (AnySet I8 I16)) 324 | ``` 325 | 326 | Note that they are currently very limited, as they cannot contain type 327 | references. 328 | 329 | ## Quotes 330 | 331 | Quotes are anonymous functions, and quote literals compile down to a reference 332 | to that anonymous function. In that way, they are very much unlike Tal's 333 | lambdas. 334 | 335 | Please note: the brackets in `(when [ body ])` etc do not denote a quote, it is 336 | simply a reuse of syntax. 337 | 338 | ``` 339 | [ (-- U8 U8 U8) 0 1 2 ] 340 | 341 | // Equivalent to: 342 | (word _anonymous (-- U8 U8 U8) [ 0 1 2 ] 343 | &_anonymous 344 | 345 | // (There's no such syntax as &func, just for demonstration purposes) 346 | ``` 347 | 348 | - Non-empty quotes are required to specify an arity. 349 | - The type of a quote pointer on the stack is `@(Fn )`. 350 | - Quotes can be executed with the `do` core word. 351 | 352 | ## Wild blocks 353 | 354 | Sometimes you need to bypass the analyser to do magic, usually with inline 355 | assembly. In these cases, the `wild` block can be used: 356 | 357 | ``` 358 | (wild ( -- ) [ 359 | // crazy stuff 360 | ]) 361 | ``` 362 | 363 | This works by forcing the analyser to "forget" what happened in the block, 364 | erasing the end result with the result of the arity applied to the previous 365 | stack state. It does *not* work by bypassing the analyser entirely. Thus, 366 | several wild blocks may be necessary. 367 | 368 | For example, the `swap-sb` word, which swaps a short and a byte, works by 369 | "chopping" the short up and rotating, twice: 370 | 371 | ``` 372 | (word swap-sb (Any16 Any8 -- $0 $1) [ 373 | (wild (--) [ (asm "" .Op/Orot) ]) 374 | (wild (--) [ (asm "" .Op/Orot) ]) 375 | (wild ($1 $0 -- $0 $1) []) 376 | ]) 377 | ``` 378 | 379 | The first two wild blocks encase the assembly, and make the analyser pretend 380 | that nothing happened. 381 | 382 | The third wild block sets the analyser's stack state to the correct state. 383 | 384 | Currently anything can occur in a wild block. In the future, this *may* be 385 | changed to only allow assembly, as miscompilations can result from calling 386 | generic functions. 387 | 388 | ## When/else 389 | 390 | When blocks are Finwë's conditionals. Behold: 391 | 392 | ``` 393 | // some boolean value is on the stack 394 | 395 | (when [ 0 ] [ 1 ]) // 0 if bool was t, 1 if nil 396 | ``` 397 | 398 | When blocks must not introduce stack branching -- i.e. where the stack depth or 399 | types could change depending on runtime control flow. This means that 400 | 401 | - A when clause without an else block must not change the stack depth/types. 402 | - A when clause with an else block must make the same change, if any, as the else 403 | block. 404 | 405 | Examples of stack branching: 406 | 407 | ``` 408 | // Invalid: when-block is (--) but else-block is (-- U8) 409 | (when [] [ 0 ]) 410 | 411 | // Same issue 412 | (when [ 0 ] []) 413 | 414 | // Invalid: when block is (-- U16) but else-block is (-- U8) 415 | (when [ 0s ] [ 0 ]) 416 | 417 | // Same changes made to stack depth, ok 418 | (when [ 0s ] [ 0s ]) 419 | 420 | // Invalid: when block is (-- @U8) but else-block is (-- U16) 421 | // (Yes, this is rejected even though both @U8 and U16 are short-sized 422 | (when [ 0s (as @U8) ] [ 0s ]) 423 | 424 | // Same changes made to stack depth, ok 425 | (when [ 0 ] [ 0 ]) 426 | ``` 427 | 428 | ## Loops 429 | 430 | There are currently two loop kinds: `until` and `while`. They both work in the 431 | same general way, with a loop conditional and loop body. (The `until` loop is 432 | more efficient.) 433 | 434 | ``` 435 | 0 (until [ 9 = ] [ 436 | print nl 437 | 1+ 438 | ]) drop 439 | ``` 440 | 441 | Finwë will automatically duplicate whatever arguments the loop conditional needs, 442 | so that the stack is unchanged for the loop body. By default, with no arity 443 | specified, the loop conditional is assumed to only use the TOS. This can be 444 | changed by specifying an arity: 445 | 446 | - `(until [ (U8 -- Bool) ...`: loop conditional takes a byte. (default) 447 | - `(until [ (U8 U8 -- Bool) ...`: loop conditional takes two bytes. 448 | - `(until [ (U16 U8 -- Bool) ...`: loop conditional takes a short and a byte. 449 | - `(until [ (U16 U16 -- Bool) ...`: loop conditional takes two shorts. 450 | - `(until [ (-- Bool) ...`: loop conditional takes no args. 451 | 452 | Notes: 453 | 454 | - The loop conditional must always return a single bool. 455 | - The body may not introduce stack branching (like when clauses). 456 | - The `until` loop is more efficient than the `while`, so prefer `until` as long 457 | as it doesn't cause larger/inefficient code. 458 | 459 | ### Break/continue 460 | 461 | `break` and `continue` both exist, but are somewhat cumbersome and fragile in 462 | their current state. `break` in particular lacks testing. 463 | 464 | If something strange is happening and you suspect a miscompilation, try removing 465 | `break` and `continue`, and file a bug if that turns out to be the source of the 466 | issue. 467 | 468 | `continue` will jump straight to the condition, so ensure the stack is in an 469 | acceptable state by then. Otherwise, you'll get cryptic "Stack changes in loop 470 | body" errors with no further context. 471 | 472 | ## Cond blocks 473 | 474 | Conds are where-clauses on steroids. Behold: 475 | 476 | ``` 477 | (word print (Dir -- ) [ 478 | (cond 479 | [ .Dir/n = ] [ "N " ] 480 | [ .Dir/s = ] [ "S " ] 481 | [ .Dir/e = ] [ "E " ] 482 | [ .Dir/w = ] [ "W " ] 483 | [ .Dir/ne = ] [ "NE" ] 484 | [ .Dir/nw = ] [ "NW" ] 485 | [ .Dir/se = ] [ "SE" ] 486 | [ .Dir/sw = ] [ "SW" ] 487 | ) 488 | print-string 489 | drop 490 | ]) 491 | ``` 492 | 493 | Notes: 494 | - Like loop conditionals, Finwë will automatically insert code to duplicate the 495 | conditional block's arguments. 496 | - Rules against stack branching apply -- stack effects for branches must be the 497 | same. 498 | - Cond conditional blocks may require different args. 499 | 500 | ## `r` blocks 501 | 502 | Return-blocks cause the code to operate on the return stack. Example: 503 | 504 | ``` 505 | + // Adds on the working stack 506 | (r +) // Adds on the return stack 507 | (r [ + + + ]) // Adds several times on the return stack 508 | ``` 509 | 510 | Related are the `STH` core words: 511 | 512 | ``` 513 | move // Equivalent to STH/STH2 514 | copy // Equivalent to STHk/STH2k 515 | (r move) // Equivalent to STHr/STH2r 516 | (r copy) // Equivalent to STHkr/STH2kr 517 | ``` 518 | 519 | Notes: 520 | 521 | - It is strongly recommended to only use `r` blocks on core words and other 522 | small, well-understood definitions. This feature has not been well-tested and 523 | may lead to stack corruption and miscompilations. Additionally, some language 524 | builtins are not available in this block. 525 | - This works under the hood by inlining whatever is being called and then 526 | toggling the `r` bit on the generated bytecode. Thus calling a large function 527 | is discouraged. 528 | - Prefer using `(r move) move` where possible. 529 | 530 | ## Casting 531 | 532 | There are several kinds of casting: 533 | 534 | - `as()` builtin, which a short being turned into a byte or vice versa. 535 | 536 | ``` 537 | 0s (as U8) // U16 -> U8 (compiles down to a NIP) 538 | 0 (as U16) // U8 -> U16 (compiles down to a LIT 00 SWP) 539 | 0s (as @Opaque) // U16 -> @Opaque (compiles down to absolutely nothing) 540 | ``` 541 | 542 | - `as()` builtin, where no change is being made on the stack, only the types are 543 | modified. 544 | 545 | ``` 546 | // Cast multiple stuff at once... 547 | 0 1 2 3 (as Char8 Char8 Char8 Char8) 548 | 549 | // ...this can only be done where no actual change is made 550 | 0s 1s 2s 3 551 | (as Char8 Char8 Char8 Char8) // Invalid 552 | ``` 553 | 554 | - `split()` builtin, where a short-sized type is split into two byte-sized 555 | types. No actual change is made on the stack. 556 | 557 | ``` 558 | 0xFFFFs (split U8 U8) // Boom. Stack effect was: (U16 -> U8 U8) 559 | ``` 560 | 561 | Notes: 562 | - No bytecode is emitted for `as()` without actual changes and `split()`. 563 | 564 | ## Inline assembly 565 | 566 | Syntax: `(asm "" .Op/opcode)` 567 | 568 | Where `` are: 569 | 570 | - `s`: short mode 571 | - `k`: keep mode 572 | - `r`: return-stack mode 573 | - `g`: generic mode 574 | 575 | Generic mode causes the analyser to fill in whether it's short-mode or not, 576 | depending on the type of TOS. This, along with generic functions, is how most of 577 | the core words can work with both shorts and bytes without needing separate 578 | functions for each. 579 | 580 | Opcodes are what you'd expect: `Orot`, `Oswp`, `Opop`, etc. For a few opcodes 581 | (such as `Ojmp` and friends, or `Oraw`, `Olit`, etc), support is not added and 582 | will probably never be added (due to lack of a use-case). 583 | 584 | ## Indexing 585 | 586 | Indexes are used to index into pointers or arrays. The indice can be 587 | compile-time known, a TOS short, a TOS byte, or a SOS short/byte. 588 | 589 | ``` 590 | // Declare an array 591 | (let foo [U16 12]) 592 | 593 | @foo :0 // 0th element 594 | @foo 1 : // 1st element 595 | 2 @foo : // 2nd element 596 | ``` 597 | 598 | Notes: 599 | - Using a `U8` as an index causes casting to `U16` under the hood, so prefer 600 | `U16` directly where possible. (Naturally, ` ` will lead to the 601 | most inefficient code. 602 | - If indexing an array with a known length and with a known index, Finwë can 603 | perform bounds checking. Thus, prefer `:` where possible. 604 | 605 | ## Variables and static data 606 | 607 | Simply declare variables like so: 608 | 609 | ``` 610 | (let name ) 611 | ``` 612 | 613 | ... and use them: 614 | 615 | ``` 616 | @name // Get a pointer 617 | $name // Memory load (LDA/LDA2) 618 | ``` 619 | 620 | If using `$name` syntax on an array, only the first memory is loaded. 621 | 622 | Currently, variables are never inlined, though that will change in a future 623 | release. 624 | 625 | ## Imports 626 | 627 | ``` 628 | (use my_module) // my_module.finw 629 | 630 | (let bar my_module/Bar) 631 | 632 | my_module/foo 633 | ``` 634 | 635 | Use `use*` if you want to import into the current namespace: 636 | 637 | ``` 638 | (use* my_module) 639 | 640 | (let bar Bar) 641 | 642 | foo 643 | ``` 644 | 645 | Typically, you will want to import at least the following: 646 | 647 | ``` 648 | // For the core words: dup, swap, rot, <, >, etc 649 | (use* core) 650 | 651 | // For print, nl, dbg, etc 652 | (use* varvara) 653 | ``` 654 | 655 | Please browse the `std/` for stdlib docs until those are written. 656 | -------------------------------------------------------------------------------- /doc/test-harness.md: -------------------------------------------------------------------------------- 1 | # Test harness 2 | 3 | Finwë has an inbuilt test harness that can be used to write unit tests. With the 4 | `-t` flag, Finwë will compile all the tests it can find into a single ROM, and 5 | then executes each test as a function while ensuring any assertions yield the 6 | desired value. 7 | 8 | ## Defining test function 9 | 10 | Simply use `test` to define a test function, that contains assertions. 11 | 12 | Tests from other modules are included when they are `use`'d. Unfortunately that 13 | means that any module that import `std`/`varvara`/etc will have the tests from 14 | those modules run as well. 15 | 16 | In the future, tests from other modules will have to be explicitly imported. 17 | 18 | ``` 19 | (test "name of test" [ 20 | 1 (should eq 1) 21 | ]) 22 | ``` 23 | 24 | ``` 25 | // Tests from my_module.finw imported 26 | 27 | (use my_module) 28 | ``` 29 | 30 | ## Assertions 31 | 32 | Asserts take the form of `(should )`, where `` is 33 | optional. 34 | 35 | `` is: 36 | 37 | - `eq`: TOS must equal ``, or the next value on the stack. 38 | - `neq`: TOS must not equal ``, or the next value on the stack. 39 | - `stdout-eq`: When a test is run, `stdout`/`stderr` is captured and printed out 40 | at the end if no `(should stdout-eq "value")` is executed. If it is executed, 41 | the output is compared to the string given. 42 | 43 | ## Other automatic checks 44 | 45 | An assertion is automatically added to the end of each test function, asserting 46 | that the test is completed and doesn't erroneously return early. 47 | 48 | The test harness also implicitly asserts that assertions not contained in a 49 | conditional or loop execute, also to catch early returns. 50 | -------------------------------------------------------------------------------- /projects/astar.finw: -------------------------------------------------------------------------------- 1 | (use* core) 2 | (use* varvara) 3 | (use std) 4 | (use vec) 5 | (use dampe) 6 | (use alloc) 7 | 8 | (enum Dir U8 [n 0] [s 1] [e 2] [w 3] [nw 4] [ne 5] [sw 6] [se 7]) 9 | 10 | #(method Dir) (word diag? (Dir -- Bool) [ 11 | (as U8) 4 >= 12 | ]) 13 | 14 | (test Dir/diag? [ 15 | .Dir/s ;diag? (should eq nil) .Dir/n ;diag? (should eq nil) 16 | .Dir/e ;diag? (should eq nil) .Dir/w ;diag? (should eq nil) 17 | .Dir/se ;diag? (should eq t) .Dir/ne ;diag? (should eq t) 18 | .Dir/sw ;diag? (should eq t) .Dir/nw ;diag? (should eq t) 19 | ]) 20 | 21 | #(method Dir) (word print (Dir -- ) [ 22 | (cond 23 | [ .Dir/n = ] [ "N " ] 24 | [ .Dir/s = ] [ "S " ] 25 | [ .Dir/e = ] [ "E " ] 26 | [ .Dir/w = ] [ "W " ] 27 | [ .Dir/ne = ] [ "NE" ] 28 | [ .Dir/nw = ] [ "NW" ] 29 | [ .Dir/se = ] [ "SE" ] 30 | [ .Dir/sw = ] [ "SW" ] 31 | ) 32 | print-string 33 | drop 34 | ]) 35 | 36 | #(method Dir) (word move (Coord Dir -- Coord) [ 37 | move (split U8 U8) 38 | (cond 39 | [ (-- Bool) (r copy) .Dir/n = ] [ 1- ] 40 | [ (-- Bool) (r copy) .Dir/s = ] [ 1+ ] 41 | [ (-- Bool) (r copy) .Dir/e = ] [ swap 1+ swap ] 42 | [ (-- Bool) (r copy) .Dir/w = ] [ swap 1- swap ] 43 | [ (-- Bool) (r copy) .Dir/ne = ] [ 1- swap 1+ swap ] 44 | [ (-- Bool) (r copy) .Dir/nw = ] [ 1- swap 1- swap ] 45 | [ (-- Bool) (r copy) .Dir/se = ] [ 1+ swap 1+ swap ] 46 | [ (-- Bool) (r copy) .Dir/sw = ] [ 1+ swap 1- swap ] 47 | ) 48 | (r drop) 49 | (make Coord) 50 | ]) 51 | 52 | (test Dir/move [ 53 | 0 0 (make Coord) .Dir/s ;move (split U8 U8) (should eq 1) (should eq 0) 54 | 3 3 (make Coord) .Dir/n ;move (split U8 U8) (should eq 2) (should eq 3) 55 | 3 3 (make Coord) .Dir/e ;move (split U8 U8) (should eq 3) (should eq 4) 56 | 3 3 (make Coord) .Dir/w ;move (split U8 U8) (should eq 3) (should eq 2) 57 | ]) 58 | 59 | (struct Coord [x U8] [y U8]) 60 | 61 | // #0000 b1 b2 00 00 62 | // SWP2 00 00 b1 b2 63 | // ROT 00 b1 b2 00 64 | // SWP 00 b2 00 b1 65 | #(method Coord) (word into-shorts ( Coord -- U16 U16 ) [ 66 | (split U8 U8) 0 0 67 | (wild (--) [(asm "s" .Op/Oswp)]) 68 | (wild (--) [(asm "" .Op/Orot)]) 69 | (wild (--) [(asm "" .Op/Oswp)]) 70 | (wild (U8 U8 U8 U8 -- U16 U16) []) 71 | ]) 72 | 73 | #(method Coord) (word print ( Coord -- ) [ 74 | (split U8 U8) swap "(" print-string print-dec ", " print-string print-dec ")" print-string 75 | ]) 76 | 77 | #(method Coord) (word eq? (Coord Coord -- Bool) [ (as U16 U16) = ]) 78 | 79 | #(method Coord) (word at (@[[Any 50] 30] Coord -- @(Child (Child $1))) [ 80 | (split U8 U8) rot-sbb : : (as @(Child (Child $1))) 81 | ]) 82 | 83 | #(method Coord) (word distance (Coord Coord -- U16) [ 84 | (wild ( Coord Coord -- U8 U8 U8 U8 ) []) // x y x y 85 | rot // x x y y 86 | - (as I8) abs (as U16) move // x x | abs(y-y) 87 | - (as I8) abs (as U16) (r move) // abs(x-x) abs(y-y) 88 | + 89 | ]) 90 | 91 | (test Coord/distance [ 92 | 0 0 (make Coord) 0 1 (make Coord) ;distance (should eq 1s) 93 | 0 0 (make Coord) 1 1 (make Coord) ;distance (should eq 2s) 94 | 0 9 (make Coord) 0 0 (make Coord) ;distance (should eq 9s) 95 | ]) 96 | 97 | (struct Node 98 | [came-from Coord] 99 | [unvisited Bool] 100 | ) 101 | 102 | // ( accumulate-func start end -- path ) 103 | (word astar (@(Fn (Coord -- )) Coord Coord -- Bool) [ 104 | (let nodes [[Node 50] 30]) 105 | (let g-score [[U16 50] 30]) 106 | (let open-list (Of vec/Vector Coord)) 107 | (let f-score [[U16 50] 30]) 108 | (let goal Coord) 109 | 110 | (word get-current ( -- Coord ) [ 111 | (let best-fscore U16) 112 | (let best-index U16) 113 | 114 | 0xFFs @best-fscore <- 115 | @open-list :len -> @best-index <- 116 | 117 | @open-list :len -> 118 | (while [ 0<> ] [ 119 | 1- 120 | dup @open-list :items -> : -> 121 | @f-score swap ;at -> 122 | dup $best-fscore < (when [ 123 | @best-fscore <- 124 | dup @best-index <- 125 | ] [ drop ]) 126 | ]) drop 127 | 128 | $best-index @open-list ;swap-remove 129 | @nodes over ;at :unvisited nil swap-sb <- 130 | ]) 131 | 132 | (word in-openlist? ( Coord -- Bool ) [ 133 | @open-list :len -> 134 | (while [ 0<> ] [ 135 | 1- 136 | dup @open-list :items -> : -> 137 | rot tuck 138 | ;eq? (when [ 2drop t return ]) 139 | swap 140 | ]) 141 | 2drop nil 142 | ]) 143 | 144 | // ( current neighbor -- ) 145 | (word check-neighbor ( Coord Dir -- ) [ 146 | move dup (r copy) ;move 147 | dup (split U8 U8) 30 >= swap 50 >= or (when [2drop (r drop) return]) 148 | @nodes over ;at :unvisited -> not (when [2drop (r drop) return]) 149 | @MAP over ;at -> '. <> (when [2drop (r drop) return]) 150 | 151 | // tenative_gscore = gscore[current] + cost 152 | over @g-score swap ;at -> 153 | (r move) ;diag? (when [2s] [1s]) + // ( current neighbor newg ) 154 | over @g-score swap ;at -> tuck> < // ( current neighbor newg newg <- 158 | // ( current neighbor g ) 159 | // fscore = gscore + distance(goal, neighbor) 160 | over $goal ;distance + 161 | over @f-score swap ;at <- 162 | // ( current neighbor ) 163 | dup in-openlist? not (when [ dup @open-list ;append ]) 164 | @nodes swap ;at :came-from <- 165 | ] [ 3drop ]) 166 | ]) 167 | 168 | 0xFF (sizeof [[Node 50] 30]) @nodes std/memset8 169 | 0xFF (sizeof [[U16 50] 30]) @g-score std/memset8 170 | 0xFF (sizeof [[U16 50] 30]) @f-score std/memset8 171 | 172 | over @g-score swap ;at 0s -< 173 | 2dup ;distance over @f-score swap ;at <- 174 | 175 | @open-list vec/init 176 | swap @open-list ;append 177 | 178 | @goal <- 179 | 180 | (while [ ( -- Bool ) @open-list :len -> 0<> ] [ 181 | get-current 182 | dup $goal ;eq? (when [ 183 | @open-list ;deinit 184 | @nodes swap ;at :came-from -> 185 | (while [ 0xFF 0xFF (make Coord) ;eq? not ] [ 186 | 2dup swap do 187 | @nodes swap ;at :came-from -> 188 | ]) 189 | drop 190 | $goal swap do 191 | t return 192 | ]) 193 | // unrolled loop 194 | dup .Dir/n check-neighbor dup .Dir/s check-neighbor 195 | dup .Dir/e check-neighbor dup .Dir/w check-neighbor 196 | dup .Dir/ne check-neighbor dup .Dir/nw check-neighbor 197 | dup .Dir/se check-neighbor .Dir/sw check-neighbor 198 | ]) 199 | 200 | drop 201 | nil 202 | ]) 203 | 204 | (let buf (Of vec/Vector Coord)) 205 | 206 | (word get-path ( Coord -- ) [ 207 | (let cache Coord) 208 | 209 | dup $cache ;eq? (when [ drop return ]) 210 | @cache <- 211 | 212 | @buf ;deinit 213 | alloc/defrag 214 | @buf vec/init 215 | 216 | [ (Coord -- ) @buf ;append ] 217 | 1 1 (make Coord) $cache 218 | astar 219 | drop 220 | ]) 221 | 222 | (word highlight-coord (Coord -- ) [ 223 | ;into-shorts 2dup 224 | 8s * .Screen/y deo 225 | 8s * .Screen/x deo 226 | @MAP : : -> draw-char-inverse 227 | ]) 228 | 229 | (word draw ( -- ) [ 230 | draw-map 231 | draw-path 232 | draw-mouse 233 | ]) 234 | 235 | (word draw-map ( -- ) [ 236 | 0x1 .Screen/auto deo 237 | 238 | (let anchorx U16 [0]) 239 | (let anchory U16 [0]) 240 | 241 | 0s dup @anchorx <- .Screen/x deo 242 | 0s dup @anchory <- .Screen/y deo 243 | 244 | 0 (until [ 30 = ] [ 245 | 0s dup @anchorx <- .Screen/x deo 246 | 247 | 0 (until [ 50 = ] [ 248 | 2dup swap @MAP : : -> draw-char 249 | 250 | $anchorx 8s + dup @anchorx <- .Screen/x deo 251 | 1+ 252 | ]) 253 | drop 254 | 255 | $anchory 8s + dup @anchory <- .Screen/y deo 256 | 1+ 257 | ]) drop 258 | ]) 259 | 260 | (word draw-char (U8 -- ) [ 261 | set-glyph-addr 262 | 0x04 .Screen/sprite deo 263 | ]) 264 | 265 | (word draw-char-inverse (U8 -- ) [ 266 | set-glyph-addr 267 | 0x01 .Screen/sprite deo 268 | ]) 269 | 270 | (word draw-mouse ( -- ) [ 271 | (let oldx U16) 272 | (let oldy U16) 273 | 274 | 0x0 .Screen/auto deo 275 | 276 | $oldx .Screen/x deo 277 | $oldy .Screen/y deo 278 | @MOUSE .Screen/addr deo 279 | 0x40 .Screen/sprite deo 280 | 281 | .Mouse/x dei dup @oldx <- .Screen/x deo 282 | .Mouse/y dei dup @oldy <- .Screen/y deo 283 | 284 | 0x41 .Screen/sprite deo 285 | ]) 286 | 287 | (word set-glyph-addr (U8 -- ) [ 288 | 32 - @FONT : .Screen/addr deo 289 | ]) 290 | 291 | (struct Sprite8 [data [U8 8]]) 292 | 293 | (word draw-path ( -- ) [ 294 | 0s 295 | (while [ @buf :len -> < ] [ 296 | dup @buf :items -> : -> highlight-coord 297 | 1+ 298 | ]) drop 299 | ]) 300 | 301 | (word on-frame ( -- ) [ ]) 302 | 303 | (word on-mouse ( -- ) [ 304 | .Mouse/state dei 0= (when [ 305 | draw 306 | // Movement 307 | .Mouse/x dei 8s / (as U8) 308 | .Mouse/y dei 8s / (as U8) 309 | (make Coord) get-path 310 | ] [ 311 | // Button press 312 | ]) 313 | ]) 314 | 315 | (word main [ 316 | [ (--) halt ] .Screen/vector deo 317 | [ (--) on-mouse halt ] .Mouse/vector deo 318 | 319 | alloc/init 320 | @buf vec/init 321 | 322 | 0x100s (as @Opaque) here-statics 0x100s - dampe/protect 323 | 324 | 0xf07fs .System/r deo 325 | 0xf0e0s .System/g deo 326 | 0xf0c0s .System/b deo 327 | 328 | 50s 8s * .Screen/width deo 329 | 30s 8s * .Screen/height deo 330 | ]) 331 | 332 | // Taken from left 333 | (let MOUSE Sprite8 [ 334 | 0x80c0s 0xe0f0s 0xf8e0s 0x1000s 335 | ]) 336 | 337 | // atari8 338 | (let FONT [Sprite8] [ 339 | 0x0000s 0x0000s 0x0000s 0x0000s 0x6060s 0x6060s 0x6000s 0x6000s 340 | 0x6666s 0x6600s 0x0000s 0x0000s 0x006cs 0xfe6cs 0x6cfes 0x6c00s 341 | 0x183es 0x603cs 0x067cs 0x1800s 0x0066s 0x6c18s 0x3066s 0x4600s 342 | 0x386cs 0x3870s 0xdeccs 0x7600s 0x6060s 0x6000s 0x0000s 0x0000s 343 | 0x1c30s 0x3030s 0x3030s 0x1c00s 0x380cs 0x0c0cs 0x0c0cs 0x3800s 344 | 0x0066s 0x3cffs 0x3c66s 0x0000s 0x0018s 0x187es 0x1818s 0x0000s 345 | 0x0000s 0x0000s 0x0030s 0x3060s 0x0000s 0x007es 0x0000s 0x0000s 346 | 0x0000s 0x0000s 0x0018s 0x1800s 0x0306s 0x0c18s 0x3060s 0xc000s 347 | 0x3c66s 0x6e76s 0x6666s 0x3c00s 0x1838s 0x1818s 0x1818s 0x7e00s 348 | 0x3c66s 0x060cs 0x1830s 0x7e00s 0x7e0cs 0x180cs 0x0666s 0x3c00s 349 | 0x0c1cs 0x3c6cs 0x7e0cs 0x0c00s 0x7e60s 0x7c06s 0x0666s 0x3c00s 350 | 0x3c60s 0x607cs 0x6666s 0x3c00s 0x7e06s 0x0c18s 0x3030s 0x3000s 351 | 0x3c66s 0x663cs 0x6666s 0x3c00s 0x3c66s 0x663es 0x060cs 0x3800s 352 | 0x0018s 0x1800s 0x0018s 0x1800s 0x0018s 0x1800s 0x1818s 0x3000s 353 | 0x0c18s 0x3060s 0x3018s 0x0c00s 0x0000s 0x7e00s 0x007es 0x0000s 354 | 0x3018s 0x0c06s 0x0c18s 0x3000s 0x3c66s 0x060cs 0x1800s 0x1800s 355 | 0x3c66s 0x6e6as 0x6e60s 0x3e00s 0x183cs 0x6666s 0x7e66s 0x6600s 356 | 0x7c66s 0x667cs 0x6666s 0x7c00s 0x3c66s 0x6060s 0x6066s 0x3c00s 357 | 0x786cs 0x6666s 0x666cs 0x7800s 0x7e60s 0x607cs 0x6060s 0x7e00s 358 | 0x7e60s 0x607cs 0x6060s 0x6000s 0x3e60s 0x606es 0x6666s 0x3e00s 359 | 0x6666s 0x667es 0x6666s 0x6600s 0x3c18s 0x1818s 0x1818s 0x3c00s 360 | 0x3e06s 0x0606s 0x0666s 0x3c00s 0x666cs 0x7870s 0x786cs 0x6600s 361 | 0x6060s 0x6060s 0x6060s 0x7e00s 0xc6ees 0xfed6s 0xc6c6s 0xc600s 362 | 0x6676s 0x7e7es 0x6e66s 0x6600s 0x3c66s 0x6666s 0x6666s 0x3c00s 363 | 0x7c66s 0x667cs 0x6060s 0x6000s 0x3c66s 0x6666s 0x766cs 0x3600s 364 | 0x7c66s 0x667cs 0x6c66s 0x6600s 0x3c66s 0x603cs 0x0666s 0x3c00s 365 | 0x7e18s 0x1818s 0x1818s 0x1800s 0x6666s 0x6666s 0x6666s 0x3e00s 366 | 0x6666s 0x6666s 0x663cs 0x1800s 0xc6c6s 0xc6d6s 0xfeees 0xc600s 367 | 0x6666s 0x3c18s 0x3c66s 0x6600s 0x6666s 0x663cs 0x1818s 0x1800s 368 | 0x7e06s 0x0c18s 0x3060s 0x7e00s 0x3c30s 0x3030s 0x3030s 0x3c00s 369 | 0xc060s 0x3018s 0x0c06s 0x0300s 0x3c0cs 0x0c0cs 0x0c0cs 0x3c00s 370 | 0x1038s 0x6cc6s 0x0000s 0x0000s 0x0000s 0x0000s 0x0000s 0xfe00s 371 | 0x0060s 0x3018s 0x0000s 0x0000s 0x0000s 0x3c06s 0x3e66s 0x3e00s 372 | 0x6060s 0x7c66s 0x6666s 0x7c00s 0x0000s 0x3c60s 0x6060s 0x3c00s 373 | 0x0606s 0x3e66s 0x6666s 0x3e00s 0x0000s 0x3c66s 0x7e60s 0x3c00s 374 | 0x1c30s 0x7c30s 0x3030s 0x3000s 0x0000s 0x3e66s 0x663es 0x067cs 375 | 0x6060s 0x7c66s 0x6666s 0x6600s 0x1800s 0x3818s 0x1818s 0x3c00s 376 | 0x1800s 0x1818s 0x1818s 0x1870s 0x6060s 0x666cs 0x786cs 0x6600s 377 | 0x3818s 0x1818s 0x1818s 0x3c00s 0x0000s 0xecfes 0xd6c6s 0xc600s 378 | 0x0000s 0x7c66s 0x6666s 0x6600s 0x0000s 0x3c66s 0x6666s 0x3c00s 379 | 0x0000s 0x7c66s 0x6666s 0x7c60s 0x0000s 0x3e66s 0x6666s 0x3e06s 380 | 0x0000s 0x7c66s 0x6060s 0x6000s 0x0000s 0x3e60s 0x3c06s 0x7c00s 381 | 0x0018s 0x7e18s 0x1818s 0x0e00s 0x0000s 0x6666s 0x6666s 0x3e00s 382 | 0x0000s 0x6666s 0x663cs 0x1800s 0x0000s 0xc6c6s 0xd67cs 0x6c00s 383 | 0x0000s 0x663cs 0x183cs 0x6600s 0x0000s 0x6666s 0x663es 0x067cs 384 | 0x0000s 0x7e0cs 0x1830s 0x7e00s 0x1c30s 0x3060s 0x3030s 0x1c00s 385 | 0x1818s 0x1818s 0x1818s 0x1818s 0x380cs 0x0c06s 0x0c0cs 0x3800s 386 | 0x0000s 0x60f2s 0x9e0cs 0x0000s 0x3c42s 0x9985s 0x8599s 0x423cs 387 | ]) 388 | 389 | (let MAP [[U8 51] 30] [ 390 | "#...............................................##" 391 | "..######.........................................#" 392 | "..#....#........#####............................." 393 | "..#....#............##.#####################......" 394 | "..#....#.....#......#......................#......" 395 | "..######.....#......#......................#......" 396 | ".............#......#......................#......" 397 | ".............#......#......................#......" 398 | "......########......#......................#......" 399 | "....................#......................#......" 400 | "....................############...........#......" 401 | "....................#..........#...........#......" 402 | "....................#..........#...........#......" 403 | ".....#......####....#..........#...........#......" 404 | ".....#......#..#....#..........#....#......#......" 405 | ".....#......#..#....#..........#...###.....#......" 406 | ".....#......#..#....#..........#..##.##....#......" 407 | ".....#......####....#..........#.##...##...#......" 408 | "....................#..........#..##.##....#......" 409 | "....................#..........#...#.#.....#......" 410 | "................#####..........#...........#......" 411 | "...............................#...........#......" 412 | ".....#.........................#############......" 413 | ".....#...#######..............................#.#." 414 | "........###...###..........................#..#.#." 415 | "........#.......#................######....#..###." 416 | "........#.......#................#....#....#..#.#." 417 | "........##.....##................#....#....#..#.#." 418 | "#........###.###.................######..........." 419 | "##...............................................#" 420 | ]) 421 | -------------------------------------------------------------------------------- /projects/base64.finw: -------------------------------------------------------------------------------- 1 | // Ported from uxn-utils b64enc.rom 2 | // https://git.sr.ht/~rabbits/uxn-utils/blob/main/cli/base64/b64enc.tal 3 | // 4 | // (c) 100r 5 | 6 | (use* varvara) 7 | (use* core) 8 | 9 | (let queue U8) 10 | 11 | (let MIME [U8] [ 12 | "ABCDEFGHIJKLMNOP" 13 | "QRSTUVWXYZabcdef" 14 | "ghijklmnopqrstuv" 15 | "wxyz0123456789+/" 16 | ]) 17 | 18 | (word main [ 19 | [ (--) on-console halt ] .Console/vector deo 20 | ]) 21 | 22 | (word on-console (--) [ 23 | #noreturn 24 | (word terminate ( U8 -- ) [ 25 | $queue <> (when [ 0 print-b64enc ]) 26 | 0x80 .System/state deo 27 | halt 28 | ]) 29 | 30 | .Console/read dei 31 | dup 0= (when [terminate]) 32 | print-b64enc 33 | ]) 34 | 35 | (word print-b64enc (U8 -- ) [ 36 | [ (U8 -- ) emit ] b64enc 37 | ]) 38 | 39 | (word b64enc (U8 @(Fn (U8 -- )) -- ) [ 40 | (let fun $0) 41 | @fun <- 42 | 43 | (let inc U8) 44 | (let buf U8) 45 | @buf <- 46 | 47 | 0x08 0x00 48 | (until [ (U8 U8 -- Bool) > not ] [ 49 | $buf over 7 swap - bsft 1 band 50 | $inc dup 1+ 6 2dup / * - @inc <- 51 | // ( bit id -- ) 52 | swap $queue bor dup + @queue <- 53 | 5 = (when [ 54 | $queue 1 bshr (as U16) @MIME + -> 55 | $fun do 56 | 0 @queue <- 57 | ]) 58 | 1+ 59 | ]) 60 | 61 | 2drop 62 | ]) 63 | 64 | (test b64enc [ 65 | "Many hands make light work." 66 | dup -> (until [ 0= ] [ 67 | print-b64enc 68 | 1+ dup -> 69 | ]) 2drop 70 | 71 | (should stdout-eq "TWFueSBoYW5kcyBtYWtlIGxpZ2h0IHdvcmsu") 72 | ]) 73 | -------------------------------------------------------------------------------- /src/buffer.zig: -------------------------------------------------------------------------------- 1 | // Originally ripped out of:{{{ 2 | // https://github.com/fengb/zigbot9001, main.zig 3 | 4 | const std = @import("std"); 5 | const mem = std.mem; 6 | const testing = std.testing; 7 | const assert = std.debug.assert; 8 | 9 | const gpa = &@import("common.zig").gpa; 10 | 11 | pub const StackBufferError = error{ 12 | IndexOutOfRange, 13 | NoSpaceLeft, 14 | }; 15 | 16 | pub fn StackBuffer(comptime T: type, comptime capacity: usize) type { 17 | return struct { 18 | data: [capacity]T = undefined, 19 | len: usize = 0, 20 | capacity: usize = capacity, 21 | 22 | const Self = @This(); 23 | 24 | pub fn init(data: ?[]const T) Self { 25 | if (data) |d| { 26 | var b: Self = .{ .len = d.len }; 27 | @memcpy(b.data[0..d.len], d); 28 | return b; 29 | } else { 30 | return .{}; 31 | } 32 | } 33 | 34 | pub fn reinit(self: *Self, data: ?[]const T) void { 35 | self.clear(); 36 | self.* = Self.init(data); 37 | } 38 | 39 | pub fn new(alloc: mem.Allocator) *Self { 40 | const buf = alloc.create(Self) catch unreachable; 41 | buf.reinit(null); 42 | return buf; 43 | } 44 | 45 | pub fn clone(self: *Self, alloc: mem.Allocator) *Self { 46 | const buf = alloc.create(Self) catch unreachable; 47 | buf.reinit(self.constSlice()); 48 | return buf; 49 | } 50 | 51 | pub fn slice(self: *Self) []T { 52 | return self.data[0..self.len]; 53 | } 54 | 55 | pub fn constSlice(self: *const Self) []const T { 56 | return self.data[0..self.len]; 57 | } 58 | 59 | pub fn eq(self: *const Self, other: []const T, eqfn: *const fn (T, T) bool) bool { 60 | if (self.len != other.len) return false; 61 | return for (self.constSlice(), 0..) |item, i| { 62 | if (!eqfn(item, other[i])) break false; 63 | } else true; 64 | } 65 | 66 | pub fn orderedRemove(self: *Self, index: usize) StackBufferError!T { 67 | if (self.len == 0 or index >= self.len) { 68 | return error.IndexOutOfRange; 69 | } 70 | 71 | const newlen = self.len - 1; 72 | if (newlen == index) { 73 | return try self.pop(); 74 | } 75 | 76 | const item = self.data[index]; 77 | for (self.data[index..newlen], 0..) |*data, j| 78 | data.* = self.data[index + 1 + j]; 79 | self.len = newlen; 80 | return item; 81 | } 82 | 83 | pub fn pop(self: *Self) !T { 84 | if (self.len == 0) return error.IndexOutOfRange; 85 | self.len -= 1; 86 | return self.data[self.len]; 87 | } 88 | 89 | pub fn resizeTo(self: *Self, size: usize) void { 90 | assert(size < self.capacity); 91 | self.len = size; 92 | } 93 | 94 | pub fn clear(self: *Self) void { 95 | self.resizeTo(0); 96 | } 97 | 98 | pub fn insert(self: *Self, ind: usize, item: T) StackBufferError!void { 99 | if (self.len >= capacity) { 100 | return error.NoSpaceLeft; 101 | } 102 | 103 | //@setRuntimeSafety(false); 104 | 105 | var i = self.len - ind; 106 | while (i > 0) { 107 | i -= 1; 108 | self.data[ind + i + 1] = self.data[ind..self.len][i]; 109 | } 110 | 111 | self.data[ind] = item; 112 | self.len += 1; 113 | } 114 | 115 | // FIXME: this is stupidly inefficient... too lazy to correct it... 116 | pub fn insertSlice(self: *Self, ind: usize, items: []const T) StackBufferError!void { 117 | if (self.len + items.len >= capacity) { 118 | return error.NoSpaceLeft; 119 | } 120 | 121 | for (items, 0..) |item, i| 122 | try self.insert(ind + i, item); 123 | } 124 | 125 | pub fn append(self: *Self, item: T) StackBufferError!void { 126 | if (self.len >= capacity) { 127 | return error.NoSpaceLeft; 128 | } 129 | 130 | self.data[self.len] = item; 131 | self.len += 1; 132 | } 133 | 134 | pub fn appendSlice(self: *Self, items: []const T) StackBufferError!void { 135 | for (items) |item| try self.append(item); 136 | } 137 | 138 | pub inline fn isFull(self: *Self) bool { 139 | return self.len == self.capacity; 140 | } 141 | 142 | pub inline fn last(self: *const Self) ?T { 143 | return if (self.len > 0) self.data[self.len - 1] else null; 144 | } 145 | 146 | pub inline fn secondLast(self: *const Self) ?T { 147 | return if (self.len > 1) self.data[self.len - 2] else null; 148 | } 149 | }; 150 | } 151 | 152 | test "insert" { 153 | { 154 | var b = StackBuffer(u8, 128).init("fefifofum"); 155 | try b.insert(0, 'a'); 156 | try testing.expectEqualSlices(u8, "afefifofum", b.constSlice()); 157 | } 158 | { 159 | var b = StackBuffer(u8, 128).init("fefifofum"); 160 | try b.insert(3, 'a'); 161 | try testing.expectEqualSlices(u8, "fefaifofum", b.constSlice()); 162 | } 163 | } 164 | 165 | test "insertSlice" { 166 | { 167 | var b = StackBuffer(u8, 128).init("fefifofum"); 168 | try b.insertSlice(0, "GLORY_TO_ZIG"); 169 | try testing.expectEqualSlices(u8, "GLORY_TO_ZIGfefifofum", b.constSlice()); 170 | } 171 | { 172 | var b = StackBuffer(u8, 128).init("fefifofum"); 173 | try b.insertSlice(3, "ABCD"); 174 | try testing.expectEqualSlices(u8, "fefABCDifofum", b.constSlice()); 175 | } 176 | } // }}} 177 | -------------------------------------------------------------------------------- /src/errors.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const mem = std.mem; 3 | const meta = std.meta; 4 | const assert = std.debug.assert; 5 | 6 | const common = @import("common.zig"); 7 | const lexer = @import("lexer.zig"); 8 | const Program = common.Program; 9 | const Error = common.Error; 10 | const Srcloc = common.Srcloc; 11 | const TypeFmt = common.TypeFmt; 12 | const gpa = &common.gpa; 13 | const AnalysisFmt = @import("analyser.zig").AnalysisFmt; 14 | 15 | pub fn lexerNodeToString(self: lexer.Node.Tag) []const u8 { 16 | return switch (self) { 17 | .Char8 => "char8", 18 | .Char16 => "char16", 19 | .U8 => "u8", 20 | .U16 => "u16", 21 | .I8 => "i8", 22 | .I16 => "i16", 23 | .String => "string", 24 | .EnumLit => "enum literal", 25 | .Keyword => "identifier", 26 | .MethodCall => "method call", 27 | .Var => "variable", 28 | .VarNum => "numerical variable", 29 | .Child => "child-getter", 30 | .ChildNum => "indicer", 31 | .ChildAmbig => "child-getter", 32 | .List => "list", 33 | .Quote => "quote", 34 | .At => unreachable, 35 | .Metadata => "metadata", 36 | .T => "t", 37 | .Nil => "nil", 38 | }; 39 | } 40 | 41 | pub fn printError(program: *Program, e: Error, lines: []const []const u8) void { 42 | var stderr = std.io.getStdErr().writer(); 43 | 44 | for (e.l.line -| 3..e.l.line) |line| 45 | stderr.print("\x1b[38;5;15m{: >4} |\x1b[m {s}\n", .{ line + 1, lines[line] }) catch unreachable; 46 | for (0..e.l.column + 4 + 2) |_| 47 | stderr.print(" ", .{}) catch unreachable; 48 | stderr.print("\x1b[91;1m^\x1b[m\n", .{}) catch unreachable; 49 | stderr.print("\x1b[91m{s}:\x1b[37;1m ", .{@errorName(e.e)}) catch unreachable; 50 | switch (e.e) { 51 | // Lexer stuff 52 | error.InvalidEnumLiteral => stderr.print("Invalid enum literal", .{}) catch unreachable, 53 | // HINT: must be UTF-8 54 | error.InvalidCharLiteral => stderr.print("Invalid character literal", .{}) catch unreachable, 55 | error.InvalidUtf8 => stderr.print("Invalid UTF-8 sequence", .{}) catch unreachable, 56 | error.IncompleteEscapeSeq => stderr.print("Incomplete escape sequence", .{}) catch unreachable, 57 | error.InvalidEscapeSeq => stderr.print("Invalid escape sequence", .{}) catch unreachable, 58 | error.UnterminatedString => stderr.print("Missing end quote", .{}) catch unreachable, 59 | error.InvalidToken => stderr.print("Invalid token", .{}) catch unreachable, 60 | // HINT: arrays are not ringbuffers, idiot 61 | error.InvalidSignedIndex => stderr.print("Signed type cannot be index", .{}) catch unreachable, 62 | error.LoneSigil => stderr.print("Lone @ or # not allowed", .{}) catch unreachable, 63 | error.NestedMetadata => stderr.print("Multiple # tokens not allowed", .{}) catch unreachable, 64 | // HINT: GitHub issue #### 65 | error.QuotedMetadata => stderr.print("#[] syntax not implemented", .{}) catch unreachable, 66 | error.UnexpectedClosingParen => stderr.print("Expected {s}", .{ 67 | e.ctx.parentype1.?.toString(), 68 | }) catch unreachable, 69 | 70 | // Parser stuff 71 | error.ExpectedItems => stderr.print("Not enough arguments (min {}, got {})", .{ 72 | e.ctx.usize1.?, e.ctx.usize2.?, 73 | }) catch unreachable, 74 | error.UnexpectedItems => stderr.print("Too many arguments (max {}, got {})", .{ 75 | e.ctx.usize1.?, e.ctx.usize2.?, 76 | }) catch unreachable, 77 | error.ExpectedNode => stderr.print("Expected {s}, got {s}", .{ 78 | lexerNodeToString(e.ctx.lexnodetype1.?), lexerNodeToString(e.ctx.lexnodetype2.?), 79 | }) catch unreachable, 80 | error.ExpectedValue => stderr.print("Expected value, got {}", .{ 81 | e.ctx.lexnodetype1.?, 82 | }) catch unreachable, 83 | error.ExpectedString => stderr.print("Expected string, got {}", .{ 84 | e.ctx.lexnodetype1.?, 85 | }) catch unreachable, 86 | error.ExpectedNum => stderr.print("Expected number value, got {}", .{ 87 | e.ctx.lexnodetype1.?, 88 | }) catch unreachable, 89 | error.InvalidType => if (e.ctx.string1) |str| { 90 | stderr.print("\"{s}\" is not a valid type", .{str}) catch unreachable; 91 | } else { 92 | stderr.print("Expression cannot be parsed into type", .{}) catch unreachable; 93 | }, 94 | error.InvalidMetadata => if (e.ctx.string1) |str| { 95 | stderr.print("\"{s}\" is not a recognized metadata", .{str}) catch unreachable; 96 | } else { 97 | stderr.print("Expression is not valid metadata", .{}) catch unreachable; 98 | }, 99 | error.InvalidKeyword => stderr.print("Invalid keyword \"{s}\"", .{ 100 | e.ctx.string1.?, 101 | }) catch unreachable, 102 | error.InvalidEnumField => stderr.print("No such field \"{s}\" in {s}", .{ 103 | e.ctx.string1.?, e.ctx.string2.?, 104 | }) catch unreachable, 105 | error.InvalidImport => stderr.print("Couldn't find import \"{s}\"", .{ 106 | e.ctx.string1.?, 107 | }) catch unreachable, 108 | error.InvalidEmbed => stderr.print("Couldn't open \"{s}\" to embed: {}", .{ 109 | e.ctx.string1.?, e.ctx.err1 orelse error.FileDoesNotExist, 110 | }) catch unreachable, 111 | // HINT (if str == u8,u16,i8,i16,etc): Did you mean ? 112 | error.NoSuchType => stderr.print("No such type \"{s}\"", .{ 113 | e.ctx.string1.?, 114 | }) catch unreachable, 115 | // HINT: type does not have a defined size 116 | error.InvalidFieldType => stderr.print("Type {} cannot be in container field", .{ 117 | TypeFmt.from(e.ctx.finwetype1.?, program), 118 | }) catch unreachable, 119 | // HINT: non-generic types cannot be used as template args 120 | error.InvalidStructArg => stderr.print("{} is not generic", .{ 121 | TypeFmt.from(e.ctx.finwetype1.?, program), 122 | }) catch unreachable, 123 | error.StupidArraySyntax => stderr.print("Just use normal []/@[] array syntax you doofus", .{}) catch unreachable, 124 | error.MissingQuoteArity => stderr.print("Anonymous functions require explicit arity", .{}) catch unreachable, 125 | // HINT: enum type can only be either U16 or U8 126 | error.InvalidEnumType => stderr.print("Only U16/U8/I8/I16 may be used as enum type", .{}) catch unreachable, 127 | error.InvalidBreakpoint => stderr.print("Unknown breakpoint type \"{s}\"", .{ 128 | e.ctx.string1.?, 129 | }) catch unreachable, 130 | // HINT: just flatten it already 131 | error.InvalidNestedDefault => stderr.print("Nested default value lists don't make sense here.", .{}) catch unreachable, 132 | 133 | // Parser + analyser 134 | error.UnknownLocal => stderr.print("No such variable \"{s}\" in scope", .{ 135 | e.ctx.string1.?, 136 | }) catch unreachable, 137 | error.UnknownIdent => stderr.print("No such function \"{s}\" in scope", .{ 138 | e.ctx.string1.?, 139 | }) catch unreachable, 140 | 141 | // Analyser 142 | error.GenericNotMatching => stderr.print("Arg {} not included in parameter {}", .{ 143 | TypeFmt.from(e.ctx.finwetype1.?, program), TypeFmt.from(e.ctx.finwetype2.?, program), 144 | }) catch unreachable, 145 | error.TypeNotMatching => stderr.print("Arg {} doesn't match parameter {}", .{ 146 | TypeFmt.from(e.ctx.finwetype1.?, program), TypeFmt.from(e.ctx.finwetype2.?, program), 147 | }) catch unreachable, 148 | error.CannotCallMethod => stderr.print("Cannot call method on type {}", .{ 149 | TypeFmt.from(e.ctx.finwetype1.?, program), 150 | }) catch unreachable, 151 | error.StructNotForStack => stderr.print("Struct {} does not fit on stack", .{ 152 | TypeFmt.from(e.ctx.finwetype1.?, program), 153 | }) catch unreachable, 154 | error.CannotGetIndex => stderr.print("{} cannot be indexed", .{ 155 | TypeFmt.from(e.ctx.finwetype1.?, program), 156 | }) catch unreachable, 157 | error.InvalidIndexType => stderr.print("{} cannot be used as index, only u8/u16", .{ 158 | TypeFmt.from(e.ctx.finwetype1.?, program), 159 | }) catch unreachable, 160 | // HINT: amazing you managed to get this error 161 | error.IndexWouldOverflow => stderr.print("Index of {} would overflow (type size is {})", .{ 162 | e.ctx.ushort1.?, e.ctx.ushort2.?, 163 | }) catch unreachable, 164 | error.IndexTooLarge => stderr.print("Index {} larger than container length {}", .{ 165 | e.ctx.ushort1.?, e.ctx.ushort2.?, 166 | }) catch unreachable, 167 | error.CannotGetFieldMultiPtr => stderr.print("Cannot get field of {} (too many indirections)", .{ 168 | TypeFmt.from(e.ctx.finwetype1.?, program), 169 | }) catch unreachable, 170 | error.CannotGetField => stderr.print("Cannot get field of {}", .{ 171 | TypeFmt.from(e.ctx.finwetype1.?, program), 172 | }) catch unreachable, 173 | error.NoSuchField => stderr.print("No \"{s}\" in {}", .{ 174 | e.ctx.string1.?, TypeFmt.from(e.ctx.finwetype1.?, program), 175 | }) catch unreachable, 176 | error.CannotSplitIntoShort => stderr.print("Split() targets must be byte-sized", .{}) catch unreachable, 177 | error.CannotSplitByte => stderr.print("Split() argument must be short-sized", .{}) catch unreachable, 178 | error.StackMismatch => stderr.print("Stack doesn't match arity: ({s} vs {s})", .{ 179 | AnalysisFmt.from(&e.ctx.analysis1.?, program), 180 | AnalysisFmt.from(&e.ctx.analysis2.?, program), 181 | }) catch unreachable, 182 | error.StackNotEmpty => stderr.print("Main and tests must end with empty stack", .{}) catch unreachable, 183 | error.StackBranching1 => stderr.print("Stack different across branches ({s} vs {s})", .{ 184 | AnalysisFmt.from(&e.ctx.analysis2.?, program), 185 | AnalysisFmt.from(&e.ctx.analysis1.?, program), 186 | }) catch unreachable, 187 | error.StackImbalanceLoop => stderr.print("Stack changes in loop body (loop: {s}; previous: {s})", .{ 188 | AnalysisFmt.from(&e.ctx.analysis1.?, program), 189 | AnalysisFmt.from(&e.ctx.analysis2.?, program), 190 | }) catch unreachable, 191 | // HINT: add else branch 192 | error.StackBranching2 => stderr.print("Stack at end of when clause must not change (when: {s}; previous: {s})", .{ 193 | AnalysisFmt.from(&e.ctx.analysis1.?, program), 194 | AnalysisFmt.from(&e.ctx.analysis2.?, program), 195 | }) catch unreachable, 196 | // HINT (if non-arity func): stack contents must be comptime-known at this point 197 | error.StackUnderflow => if (e.ctx.usize1) |index| { 198 | stderr.print("Stack underflow (at index {})", .{index}) catch unreachable; 199 | } else { 200 | stderr.print("Stack underflow", .{}) catch unreachable; 201 | }, 202 | error.ExpectedStruct => stderr.print("Expected struct type, got {}", .{ 203 | TypeFmt.from(e.ctx.finwetype1.?, program), 204 | }) catch unreachable, 205 | error.NakedBreak => stderr.print("Break cannot occur outside of a loop.", .{}) catch unreachable, 206 | error.NakedContinue => stderr.print("Continue cannot occur outside of a loop.", .{}) catch unreachable, 207 | error.NoreturnCannotReturn => stderr.print(" words cannot return items.", .{}) catch unreachable, 208 | 209 | error.UnsizedArityItem => stderr.print("{} does not have a defined size.", .{ 210 | TypeFmt.from(e.ctx.finwetype1.?, program), 211 | }) catch unreachable, 212 | 213 | error.SizeofUnsized => stderr.print("Cannot measure the immeasurable.", .{ 214 | //TypeFmt.from(e.ctx.finwetype1.?, program), 215 | }) catch unreachable, 216 | 217 | // error.Template => stderr.print("ohno {} {}", .{ 218 | // e.ctx.usize1.?, e.ctx.usize2.?, 219 | // }) catch unreachable, 220 | else => stderr.print("TODO: error message", .{}) catch unreachable, 221 | } 222 | stderr.print("\x1b[m\n", .{}) catch unreachable; 223 | stderr.print(" \x1b[36mat \x1b[m{s}:\x1b[33m{}\x1b[m:\x1b[34m{}\x1b[m\n", .{ 224 | e.l.file, e.l.line, e.l.column, 225 | }) catch unreachable; 226 | } 227 | 228 | pub fn printErrors(program: *Program, filename: []const u8) void { 229 | const Buf = struct { 230 | path: []const u8, 231 | buf: []u8, 232 | lines: std.ArrayList([]const u8), 233 | 234 | const AList = std.ArrayList(@This()); 235 | 236 | pub fn addOrGet(self: *AList, path: []const u8) []const []const u8 { 237 | for (self.items) |buf| { 238 | if (mem.eql(u8, buf.path, path)) 239 | return buf.lines.items; 240 | } 241 | 242 | const file = std.fs.cwd().openFile(path, .{}) catch unreachable; 243 | const size = file.getEndPos() catch unreachable; 244 | defer file.close(); 245 | 246 | var new: @This() = undefined; 247 | new.path = path; 248 | new.buf = gpa.allocator().alloc(u8, size) catch unreachable; 249 | _ = file.readAll(new.buf) catch unreachable; 250 | 251 | new.lines = std.ArrayList([]const u8).init(gpa.allocator()); 252 | var iter = mem.splitScalar(u8, new.buf, '\n'); 253 | while (iter.next()) |line| 254 | new.lines.append(line) catch unreachable; 255 | self.append(new) catch unreachable(); 256 | 257 | return self.items[self.items.len - 1].lines.items; 258 | } 259 | }; 260 | 261 | var bufs = Buf.AList.init(gpa.allocator()); 262 | defer bufs.deinit(); 263 | defer for (bufs.items) |buf| { 264 | buf.lines.deinit(); 265 | gpa.allocator().free(buf.buf); 266 | }; 267 | 268 | _ = Buf.addOrGet(&bufs, filename); 269 | 270 | for (program.errors.items) |err| 271 | printError(program, err, Buf.addOrGet(&bufs, err.l.file)); 272 | } 273 | -------------------------------------------------------------------------------- /src/eventually.zig: -------------------------------------------------------------------------------- 1 | // Convenience type that will "eventually" be filled with data, and will panic 2 | // if data is accessed before then. Will also panic if data is mutated more than 3 | // once. 4 | 5 | const std = @import("std"); 6 | 7 | pub fn Eventually(comptime T: type) type { 8 | return struct { 9 | inner: ?T = null, 10 | 11 | pub fn assertNotYet(self: *const @This()) void { 12 | std.debug.assert(self.inner == null); 13 | } 14 | 15 | pub fn mut(self: *@This()) *?T { 16 | self.assertNotYet(); 17 | return &self.inner; 18 | } 19 | 20 | pub fn get(self: *const @This()) T { 21 | return self.inner.?; 22 | } 23 | 24 | pub fn ref(self: *const @This()) *const T { 25 | return &self.inner; 26 | } 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /src/lexer.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const mem = std.mem; 3 | const activeTag = std.meta.activeTag; 4 | const assert = std.debug.assert; 5 | 6 | const gpa = &@import("common.zig").gpa; 7 | const String = @import("common.zig").String; 8 | const Program = @import("common.zig").Program; 9 | const Srcloc = @import("common.zig").Srcloc; 10 | pub const NodeList = std.ArrayList(Node); 11 | 12 | pub const Node = struct { 13 | node: NodeType, 14 | location: Srcloc, 15 | 16 | pub const NodeType = union(enum) { 17 | Char8: u8, 18 | Char16: u16, 19 | U8: u8, 20 | U16: u16, 21 | I8: i8, 22 | I16: i16, 23 | String: String, 24 | EnumLit: EnumLit, 25 | Keyword: []const u8, 26 | MethodCall: []const u8, 27 | Var: []const u8, 28 | VarNum: u8, 29 | Child: []const u8, 30 | ChildNum: u16, 31 | ChildAmbig, 32 | List: List, 33 | Quote: NodeList, 34 | At: *Node, 35 | Metadata: *Node, 36 | T, 37 | Nil, 38 | }; 39 | 40 | pub const Tag = std.meta.Tag(NodeType); 41 | 42 | pub const List = struct { 43 | metadata: NodeList, 44 | body: NodeList, 45 | }; 46 | 47 | pub const EnumLit = struct { 48 | of: ?[]const u8, 49 | v: []const u8, 50 | }; 51 | 52 | pub fn deinitMain(list: NodeList, alloc: mem.Allocator) void { 53 | var n = Node{ 54 | .node = .{ .List = .{ .metadata = undefined, .body = list } }, 55 | .location = undefined, 56 | }; 57 | n.deinit(alloc); 58 | } 59 | 60 | pub fn deinit(self: *Node, alloc: mem.Allocator) void { 61 | switch (self.node) { 62 | .Metadata, .At => |node| { 63 | node.deinit(alloc); 64 | alloc.destroy(node); 65 | }, 66 | .String => |str| str.deinit(), 67 | .MethodCall, .Var, .Child, .Keyword => |data| alloc.free(data), 68 | .EnumLit => |data| { 69 | alloc.free(data.v); 70 | if (data.of) |d| 71 | alloc.free(d); 72 | }, 73 | .Quote => |list| { 74 | for (list.items) |*li| li.deinit(alloc); 75 | list.deinit(); 76 | }, 77 | .List => |list| { 78 | for (list.body.items) |*li| li.deinit(alloc); 79 | list.body.deinit(); 80 | for (list.metadata.items) |*li| li.deinit(alloc); 81 | list.metadata.deinit(); 82 | }, 83 | else => {}, 84 | } 85 | } 86 | }; 87 | 88 | pub const Lexer = struct { 89 | program: *Program, 90 | input: []const u8, 91 | alloc: mem.Allocator, 92 | index: usize = 0, 93 | stack: Stack.List, // Lexing stack, not program one 94 | line: usize = 1, 95 | column: usize = 0, 96 | file: []const u8, 97 | metadata: NodeList, 98 | 99 | pub const Stack = struct { 100 | type: Type, 101 | 102 | pub const Type = enum { 103 | Root, 104 | Paren, 105 | Bracket, 106 | 107 | pub fn toString(self: Type) []const u8 { 108 | return switch (self) { 109 | .Root => "EOF", 110 | .Paren => ")", 111 | .Bracket => "]", 112 | }; 113 | } 114 | }; 115 | pub const List = std.ArrayList(Stack); 116 | }; 117 | 118 | const Self = @This(); 119 | 120 | pub const LexerError = error{ 121 | UnexpectedClosingParen, 122 | InvalidEnumLiteral, 123 | InvalidCharLiteral, 124 | InvalidUtf8, 125 | IncompleteEscapeSeq, 126 | InvalidEscapeSeq, 127 | InvalidToken, 128 | InvalidSignedIndex, 129 | UnterminatedString, 130 | LoneSigil, 131 | NestedMetadata, 132 | QuotedMetadata, 133 | } || std.fmt.ParseIntError || std.mem.Allocator.Error; 134 | 135 | pub fn init(p: *Program, input: []const u8, filename: []const u8, alloc: mem.Allocator) Self { 136 | return .{ 137 | .file = filename, 138 | .input = input, 139 | .alloc = alloc, 140 | .stack = Stack.List.init(alloc), 141 | .metadata = NodeList.init(alloc), 142 | .program = p, 143 | }; 144 | } 145 | 146 | pub fn deinit(self: *Self) void { 147 | self.stack.deinit(); 148 | } 149 | 150 | fn lerr(self: *Lexer, e: LexerError, args: anytype) LexerError { 151 | const srcloc = Srcloc{ 152 | .file = self.file, 153 | .line = self.line, 154 | .column = self.column, 155 | }; 156 | if (!self.program.forget_errors) 157 | self.program.errors.append(.{ .e = e, .l = srcloc, .ctx = @import("common.zig").Error.Context.from(args) }) catch unreachable; 158 | return e; 159 | } 160 | 161 | pub fn lexWord(self: *Self, vtype: u21, word: []const u8) LexerError!Node.NodeType { 162 | return switch (vtype) { 163 | '$', 'k', ';', ':', '.' => blk: { 164 | if (self.lexWord('N', word)) |node| { 165 | break :blk switch (vtype) { 166 | '$' => Node.NodeType{ .VarNum = node.U8 }, 167 | 'k' => node, 168 | '.' => self.lerr(error.InvalidEnumLiteral, .{}), // Cannot be numeric 169 | ':' => Node.NodeType{ 170 | .ChildNum = switch (node) { 171 | .U8 => |u| u, 172 | .U16 => |u| u, 173 | .I16, .I8 => return self.lerr(error.InvalidSignedIndex, .{}), 174 | else => unreachable, 175 | }, 176 | }, 177 | else => unreachable, 178 | }; 179 | } else |_| { 180 | if (mem.eql(u8, word, "nil") or mem.eql(u8, word, "Nil") or 181 | mem.eql(u8, word, "nah") or mem.eql(u8, word, "Nah")) 182 | { 183 | break :blk Node.NodeType{ .Nil = {} }; 184 | } else if (mem.eql(u8, word, "t") or mem.eql(u8, word, "T")) { 185 | break :blk Node.NodeType{ .T = {} }; 186 | } else { 187 | const enum_clarifier = mem.indexOfScalar(u8, word, '/'); 188 | if (vtype == '.' and enum_clarifier != null) { 189 | if (enum_clarifier.? == 0) { 190 | return self.lerr(error.InvalidEnumLiteral, .{}); 191 | } 192 | const a = try self.alloc.alloc(u8, word.len - enum_clarifier.? - 1); 193 | const b = try self.alloc.alloc(u8, word.len - (word.len - enum_clarifier.?)); 194 | @memcpy(a, word[enum_clarifier.? + 1 ..]); 195 | @memcpy(b, word[0..enum_clarifier.?]); 196 | break :blk Node.NodeType{ .EnumLit = .{ .v = a, .of = b } }; 197 | } else { 198 | const s = try self.alloc.alloc(u8, word.len); 199 | @memcpy(s, word); 200 | break :blk switch (vtype) { 201 | 'k' => Node.NodeType{ .Keyword = s }, 202 | '.' => Node.NodeType{ .EnumLit = .{ .v = s, .of = null } }, 203 | ';' => Node.NodeType{ .MethodCall = s }, 204 | ':' => Node.NodeType{ .Child = s }, 205 | '$' => Node.NodeType{ .Var = s }, 206 | else => unreachable, 207 | }; 208 | } 209 | } 210 | } 211 | }, 212 | // Never called by lexValue, only by lexWord to check if something 213 | // can be parsed as a number. 214 | 'N' => blk: { 215 | var base: u8 = 10; 216 | var offset: usize = 0; 217 | 218 | if (mem.startsWith(u8, word, "0x")) { 219 | base = 16; 220 | offset = 2; 221 | } else if (mem.startsWith(u8, word, "0b")) { 222 | base = 2; 223 | offset = 2; 224 | } else if (mem.startsWith(u8, word, "0o")) { 225 | base = 8; 226 | offset = 2; 227 | } 228 | 229 | if (mem.endsWith(u8, word, "is")) { 230 | const num = try std.fmt.parseInt(i16, word[offset .. word.len - 2], base); 231 | break :blk Node.NodeType{ .I16 = num }; 232 | } else if (mem.endsWith(u8, word, "i")) { 233 | const num = try std.fmt.parseInt(i8, word[offset .. word.len - 1], base); 234 | break :blk Node.NodeType{ .I8 = num }; 235 | } else if (mem.endsWith(u8, word, "s")) { 236 | const num = try std.fmt.parseInt(u16, word[offset .. word.len - 1], base); 237 | break :blk Node.NodeType{ .U16 = num }; 238 | } else { 239 | const num = try std.fmt.parseInt(u8, word[offset..], base); 240 | break :blk Node.NodeType{ .U8 = num }; 241 | } 242 | }, 243 | '\'' => blk: { 244 | const short = mem.endsWith(u8, word, "s"); 245 | 246 | var utf8 = (std.unicode.Utf8View.init(if (short) word[0 .. word.len - 1] else word) catch return self.lerr(error.InvalidUtf8, .{})).iterator(); 247 | const encoded_codepoint = utf8.nextCodepointSlice() orelse return self.lerr(error.InvalidCharLiteral, .{}); 248 | if (utf8.nextCodepointSlice()) |_| return self.lerr(error.InvalidCharLiteral, .{}); 249 | const codepoint = std.unicode.utf8Decode(encoded_codepoint) catch return self.lerr(error.InvalidUtf8, .{}); 250 | if (short) { 251 | break :blk Node.NodeType{ .Char16 = @intCast(codepoint) }; 252 | } else { 253 | break :blk Node.NodeType{ .Char8 = @intCast(codepoint % 255) }; 254 | } 255 | }, 256 | else => @panic("/dev/sda is on fire"), 257 | }; 258 | } 259 | 260 | pub fn moar(self: *Self) void { 261 | self.index += 1; 262 | if (self.index < self.input.len and 263 | self.input[self.index] == '\n') 264 | { 265 | self.line += 1; 266 | self.column = 0; 267 | } else { 268 | self.column += 1; 269 | } 270 | } 271 | 272 | pub fn lexString(self: *Self) LexerError!Node.NodeType { 273 | assert(self.input[self.index] == '"'); 274 | 275 | var buf = String.init(gpa.allocator()); 276 | self.moar(); // skip beginning quote 277 | 278 | while (self.index < self.input.len) : (self.moar()) { 279 | switch (self.input[self.index]) { 280 | '"' => { 281 | //self.moar(); 282 | return Node.NodeType{ .String = buf }; 283 | }, 284 | '\\' => { 285 | self.moar(); 286 | if (self.index == self.input.len) { 287 | return self.lerr(error.IncompleteEscapeSeq, .{}); 288 | } 289 | 290 | // TODO: \xXX, \uXXXX, \UXXXXXXXX 291 | const esc: u8 = switch (self.input[self.index]) { 292 | '"' => '"', 293 | '\\' => '\\', 294 | 'n' => '\n', 295 | 'r' => '\r', 296 | 'a' => '\x07', 297 | '0' => '\x00', 298 | 't' => '\t', 299 | else => return self.lerr(error.InvalidEscapeSeq, .{}), 300 | }; 301 | 302 | buf.append(esc) catch unreachable; 303 | }, 304 | else => buf.append(self.input[self.index]) catch unreachable, 305 | } 306 | } 307 | 308 | return self.lerr(error.UnterminatedString, .{}); 309 | } 310 | 311 | pub fn lexValue(self: *Self, vtype: u21) LexerError!Node.NodeType { 312 | if (vtype != 'k' and vtype != 'N') 313 | self.moar(); 314 | const oldi = self.index; 315 | 316 | const word_end = for (self.index..self.input.len) |ind| { 317 | if (self.input[ind] == '/' and ind < self.input.len - 1 and 318 | self.input[ind + 1] == '/') 319 | { 320 | break ind; 321 | } else switch (self.input[ind]) { 322 | 0x09...0x0d, 0x20, '(', ')', '[', ']' => break ind, 323 | else => {}, 324 | } 325 | } else self.input.len; 326 | 327 | const word = self.input[oldi..word_end]; 328 | if (word.len == 0) switch (vtype) { 329 | ':' => return .ChildAmbig, 330 | else => return self.lerr(error.InvalidToken, .{}), 331 | }; 332 | 333 | for (oldi..word_end - 1) |_| 334 | self.moar(); 335 | 336 | return self.lexWord(vtype, word); 337 | } 338 | 339 | fn lex(self: *Self) LexerError!?Node.NodeType { 340 | const ch = self.input[self.index]; 341 | if (ch == '/' and self.index < self.input.len - 1 and 342 | self.input[self.index + 1] == '/') 343 | { 344 | while (self.index < self.input.len and self.input[self.index] != 0x0a) 345 | self.moar(); 346 | return null; 347 | } 348 | 349 | return switch (ch) { 350 | 0x09...0x0d, 0x20 => null, 351 | '"' => try self.lexString(), 352 | '$', '.', ';', ':', '\'' => try self.lexValue(ch), 353 | '#', '@' => b: { 354 | if (self.index == self.input.len - 1) { 355 | return self.lerr(error.LoneSigil, .{}); 356 | } 357 | 358 | self.moar(); 359 | const ptr = try self.alloc.create(Node); 360 | ptr.* = Node{ 361 | .node = (try self.lex()) orelse return self.lerr(error.LoneSigil, .{}), 362 | .location = .{ .file = self.file, .line = self.line, .column = self.column }, 363 | }; 364 | if (ch == '#' and ptr.node == .Metadata) 365 | return self.lerr(error.NestedMetadata, .{}); 366 | if (ch == '#' and ptr.node == .Quote) 367 | return self.lerr(error.QuotedMetadata, .{}); // TODO 368 | break :b if (ch == '@') .{ .At = ptr } else .{ .Metadata = ptr }; 369 | }, 370 | '[' => Node.NodeType{ .Quote = try self.lexList(.Bracket) }, 371 | '(' => b: { 372 | const metadata = self.metadata; 373 | self.metadata = NodeList.init(self.alloc); 374 | break :b Node.NodeType{ .List = .{ 375 | .metadata = metadata, 376 | .body = try self.lexList(.Paren), 377 | } }; 378 | }, 379 | else => try self.lexValue('k'), 380 | }; 381 | } 382 | 383 | pub fn lexList(self: *Self, mode: Stack.Type) LexerError!NodeList { 384 | try self.stack.append(.{ .type = mode }); 385 | var res = NodeList.init(self.alloc); 386 | 387 | // Move past the first (/[ if we're parsing a list 388 | if (self.stack.items.len > 1) { 389 | switch (mode) { 390 | .Root => unreachable, 391 | .Paren => assert(self.input[self.index] == '('), 392 | .Bracket => assert(self.input[self.index] == '['), 393 | } 394 | self.moar(); 395 | } 396 | 397 | while (self.index < self.input.len) : (self.moar()) { 398 | const ch = self.input[self.index]; 399 | switch (ch) { 400 | ']', ')' => { 401 | const found: Stack.Type = if (ch == ']') .Bracket else .Paren; 402 | if (self.stack.items.len <= 1) { 403 | return self.lerr(error.UnexpectedClosingParen, .{Stack.Type.Root}); 404 | } else if (self.stack.items[self.stack.items.len - 1].type != found) { 405 | return self.lerr(error.UnexpectedClosingParen, .{ 406 | self.stack.items[self.stack.items.len - 1].type, 407 | }); 408 | } 409 | 410 | _ = self.stack.pop(); 411 | return res; 412 | }, 413 | else => { 414 | const node = (try self.lex()) orelse continue; 415 | if (node == .Metadata) { 416 | try self.metadata.append(Node{ .node = node, .location = .{ 417 | .file = self.file, 418 | .line = self.line, 419 | .column = self.column, 420 | } }); 421 | } else { 422 | try res.append(Node{ .node = node, .location = .{ 423 | .file = self.file, 424 | .line = self.line, 425 | .column = self.column, 426 | } }); 427 | } 428 | }, 429 | } 430 | } 431 | 432 | return res; 433 | } 434 | }; 435 | 436 | const testing = std.testing; 437 | 438 | test "basic lexing" { 439 | var p = Program.init(testing.allocator, "/") catch unreachable; 440 | defer p.deinit(); 441 | 442 | const input = "0xfe 0xf1 0xf0s fum (test :foo bar 0xAB) (12 ['ë] :0) ;fab"; 443 | var lexer = Lexer.init(&p, input, "", std.testing.allocator); 444 | const result = try lexer.lexList(.Root); 445 | defer lexer.deinit(); 446 | defer Node.deinitMain(result, std.testing.allocator); 447 | 448 | try testing.expectEqual(@as(usize, 7), result.items.len); 449 | 450 | try testing.expectEqual(activeTag(result.items[0].node), .U8); 451 | try testing.expectEqual(@as(u8, 0xfe), result.items[0].node.U8); 452 | 453 | try testing.expectEqual(activeTag(result.items[1].node), .U8); 454 | try testing.expectEqual(@as(u8, 0xf1), result.items[1].node.U8); 455 | 456 | try testing.expectEqual(activeTag(result.items[2].node), .U16); 457 | try testing.expectEqual(@as(u16, 0xf0), result.items[2].node.U16); 458 | 459 | try testing.expectEqual(activeTag(result.items[3].node), .Keyword); 460 | try testing.expectEqualSlices(u8, "fum", result.items[3].node.Keyword); 461 | 462 | try testing.expectEqual(activeTag(result.items[4].node), .List); 463 | { 464 | const list = result.items[4].node.List.body.items; 465 | 466 | try testing.expectEqual(activeTag(list[0].node), .Keyword); 467 | try testing.expectEqualSlices(u8, "test", list[0].node.Keyword); 468 | 469 | try testing.expectEqual(activeTag(list[1].node), .Child); 470 | try testing.expectEqualSlices(u8, "foo", list[1].node.Child); 471 | 472 | try testing.expectEqual(activeTag(list[2].node), .Keyword); 473 | try testing.expectEqualSlices(u8, "bar", list[2].node.Keyword); 474 | 475 | try testing.expectEqual(activeTag(list[3].node), .U8); 476 | try testing.expectEqual(@as(u8, 0xAB), list[3].node.U8); 477 | } 478 | 479 | try testing.expectEqual(activeTag(result.items[5].node), .List); 480 | { 481 | const list = result.items[5].node.List.body.items; 482 | 483 | try testing.expectEqual(activeTag(list[0].node), .U8); 484 | try testing.expectEqual(@as(u8, 12), list[0].node.U8); 485 | 486 | try testing.expectEqual(activeTag(list[1].node), .Quote); 487 | { 488 | const list2 = list[1].node.Quote.items; 489 | 490 | try testing.expectEqual(activeTag(list2[0].node), .Char8); 491 | try testing.expectEqual(@as(u21, 'ë'), list2[0].node.Char8); 492 | } 493 | 494 | try testing.expectEqual(activeTag(list[2].node), .ChildNum); 495 | try testing.expectEqual(@as(u16, 0), list[2].node.ChildNum); 496 | } 497 | 498 | try testing.expectEqual(activeTag(result.items[6].node), .MethodCall); 499 | try testing.expectEqualSlices(u8, "fab", result.items[6].node.MethodCall); 500 | } 501 | 502 | test "enum literals" { 503 | var p = Program.init(testing.allocator, "/") catch unreachable; 504 | defer p.deinit(); 505 | 506 | const input = ".foo .bar/baz"; 507 | var lexer = Lexer.init(&p, input, "", std.testing.allocator); 508 | const result = try lexer.lexList(.Root); 509 | defer lexer.deinit(); 510 | defer Node.deinitMain(result, std.testing.allocator); 511 | 512 | try testing.expectEqual(@as(usize, 2), result.items.len); 513 | 514 | try testing.expectEqual(activeTag(result.items[0].node), .EnumLit); 515 | try testing.expect(mem.eql(u8, result.items[0].node.EnumLit.v, "foo")); 516 | try testing.expectEqual(result.items[0].node.EnumLit.of, null); 517 | 518 | try testing.expectEqual(activeTag(result.items[1].node), .EnumLit); 519 | try testing.expect(mem.eql(u8, result.items[1].node.EnumLit.v, "baz")); 520 | try testing.expect(mem.eql(u8, result.items[1].node.EnumLit.of.?, "bar")); 521 | } 522 | 523 | test "string literals" { 524 | var p = Program.init(testing.allocator, "/") catch unreachable; 525 | defer p.deinit(); 526 | 527 | const input = "\"foo\""; 528 | var lexer = Lexer.init(&p, input, "", std.testing.allocator); 529 | const result = try lexer.lexList(.Root); 530 | defer lexer.deinit(); 531 | defer Node.deinitMain(result, std.testing.allocator); 532 | 533 | try testing.expectEqual(@as(usize, 1), result.items.len); 534 | 535 | try testing.expectEqual(activeTag(result.items[0].node), .String); 536 | try testing.expect(mem.eql(u8, result.items[0].node.String.items, "foo")); 537 | } 538 | 539 | test "sigils lexing" { 540 | var p = Program.init(testing.allocator, "/") catch unreachable; 541 | defer p.deinit(); 542 | 543 | const input = "bar $baz @foo @(foo) (@foo) @[@(@foo)]"; 544 | var lexer = Lexer.init(&p, input, "", std.testing.allocator); 545 | const result = try lexer.lexList(.Root); 546 | defer lexer.deinit(); 547 | defer Node.deinitMain(result, std.testing.allocator); 548 | 549 | try testing.expectEqual(@as(usize, 6), result.items.len); 550 | 551 | try testing.expectEqual(activeTag(result.items[0].node), .Keyword); 552 | try testing.expectEqualSlices(u8, "bar", result.items[0].node.Keyword); 553 | 554 | try testing.expectEqual(activeTag(result.items[1].node), .Var); 555 | try testing.expectEqualSlices(u8, "baz", result.items[1].node.Var); 556 | 557 | try testing.expectEqual(activeTag(result.items[2].node), .At); 558 | try testing.expectEqual(activeTag(result.items[2].node.At.node), .Keyword); 559 | try testing.expectEqualSlices(u8, "foo", result.items[2].node.At.node.Keyword); 560 | 561 | try testing.expectEqual(activeTag(result.items[3].node), .At); 562 | try testing.expectEqual(activeTag(result.items[3].node.At.node), .List); 563 | { 564 | const list = result.items[3].node.At.node.List.body.items; 565 | 566 | try testing.expectEqual(activeTag(list[0].node), .Keyword); 567 | try testing.expectEqualSlices(u8, "foo", list[0].node.Keyword); 568 | } 569 | 570 | try testing.expectEqual(activeTag(result.items[4].node), .List); 571 | { 572 | const list = result.items[4].node.List.body.items; 573 | 574 | try testing.expectEqual(activeTag(list[0].node), .At); 575 | try testing.expectEqual(activeTag(list[0].node.At.node), .Keyword); 576 | try testing.expectEqualSlices(u8, "foo", list[0].node.At.node.Keyword); 577 | } 578 | 579 | try testing.expectEqual(activeTag(result.items[5].node), .At); 580 | try testing.expectEqual(activeTag(result.items[5].node.At.node), .Quote); 581 | { 582 | const list = result.items[5].node.At.node.Quote.items; 583 | try testing.expectEqual(activeTag(list[0].node), .At); 584 | try testing.expectEqual(activeTag(list[0].node.At.node), .List); 585 | { 586 | const list2 = list[0].node.At.node.List.body.items; 587 | try testing.expectEqual(activeTag(list2[0].node), .At); 588 | try testing.expectEqual(activeTag(list2[0].node.At.node), .Keyword); 589 | try testing.expectEqualSlices(u8, "foo", list2[0].node.At.node.Keyword); 590 | } 591 | } 592 | } 593 | 594 | // test "tmp" { 595 | // const input = ":"; 596 | // var lexer = Lexer.init(input, "", std.testing.allocator); 597 | // const result = try lexer.lexList(.Root); 598 | // defer lexer.deinit(); 599 | // defer Node.deinitMain(result, std.testing.allocator); 600 | 601 | // std.log.err("{}", .{result.items[0].node}); 602 | // } 603 | -------------------------------------------------------------------------------- /src/list.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const mem = std.mem; 3 | const assert = std.debug.assert; 4 | const testing = std.testing; 5 | 6 | // Basic node that can be used for scalar data. 7 | pub fn ScalarNode(comptime T: type) type { 8 | return struct { 9 | const Self = @This(); 10 | 11 | __prev: ?*Self = null, 12 | __next: ?*Self = null, 13 | data: T, 14 | }; 15 | } 16 | 17 | pub fn LinkedList(comptime T: type) type { 18 | // Stuff commented out because it causes dependency issue 19 | // (ASTNode contains fields of type LinkedList(ASTNode)) 20 | 21 | // switch (@typeInfo(T)) { 22 | // .Struct => {}, 23 | // else => @compileError("Expected struct, got '" ++ @typeName(T) ++ "'"), 24 | // } 25 | 26 | // if (!@hasField(T, "__next")) 27 | // @compileError("Struct '" ++ @typeName(T) ++ "' does not have a '__next' field"); 28 | // if (!@hasField(T, "__prev")) 29 | // @compileError("Struct '" ++ @typeName(T) ++ "' does not have a '__prev' field"); 30 | 31 | return struct { 32 | const Self = @This(); 33 | 34 | pub const Iterator = struct { 35 | current: ?*T, 36 | reverse: bool = false, 37 | 38 | pub fn next(iter: *Iterator) ?*T { 39 | const current = iter.current; 40 | 41 | if (current) |c| { 42 | if (iter.reverse) { 43 | iter.current = c.__prev; 44 | } else { 45 | iter.current = c.__next; 46 | } 47 | return c; 48 | } else { 49 | return null; 50 | } 51 | } 52 | }; 53 | 54 | head: ?*T, 55 | tail: ?*T, 56 | allocator: mem.Allocator, 57 | 58 | pub const ChildType = T; 59 | 60 | pub fn init(allocator: mem.Allocator) Self { 61 | return Self{ 62 | .head = null, 63 | .tail = null, 64 | .allocator = allocator, 65 | }; 66 | } 67 | 68 | pub fn deinit(self: *const Self) void { 69 | if (self.head == null) { 70 | return; 71 | } 72 | 73 | var current = self.head.?; 74 | while (current.__next) |next| { 75 | self.allocator.destroy(current); 76 | current = next; 77 | } 78 | 79 | self.allocator.destroy(current); 80 | } 81 | 82 | // Make a copy of data and allocate it on the heap, then append to the 83 | // list. 84 | pub fn append(self: *Self, data: T) !void { 85 | var node = try self.allocator.create(T); 86 | node.* = data; 87 | 88 | if (self.tail) |tail| { 89 | assert(tail.__next == null); 90 | 91 | node.__prev = tail; 92 | node.__next = null; 93 | tail.__next = node; 94 | self.tail = node; 95 | } else { 96 | node.__prev = null; 97 | node.__next = null; 98 | self.head = node; 99 | self.tail = node; 100 | } 101 | } 102 | 103 | pub fn appendAndReturn(self: *Self, data: T) !*T { 104 | try self.append(data); 105 | return self.last() orelse @panic("/dev/sda is on fire"); 106 | } 107 | 108 | pub fn insertAtInd(self: *Self, ind: usize, data: T) !void { 109 | var node = try self.allocator.create(T); 110 | node.* = data; 111 | 112 | var i: usize = 0; 113 | var iter = self.iterator(); 114 | while (iter.next()) |item| : (i += 1) 115 | if (i == ind) { 116 | if (item.__prev) |prev| 117 | prev.__next = node 118 | else 119 | self.head = node; 120 | node.__prev = item.__prev; 121 | node.__next = item; 122 | item.__prev = node; 123 | return; 124 | }; 125 | } 126 | 127 | pub fn remove(self: *Self, node: *T) void { 128 | if (node.__prev) |prevn| prevn.__next = node.__next; 129 | if (node.__next) |nextn| nextn.__prev = node.__prev; 130 | 131 | if (self.head == node) self.head = node.__next; 132 | if (self.tail == node) self.tail = node.__prev; 133 | 134 | self.allocator.destroy(node); 135 | } 136 | 137 | pub fn nth(self: *Self, n: usize) ?*T { 138 | var i: usize = 0; 139 | var iter = self.iterator(); 140 | while (iter.next()) |item| : (i += 1) { 141 | if (i == n) { 142 | return item; 143 | } 144 | } 145 | return null; 146 | } 147 | 148 | pub fn first(self: *Self) ?*T { 149 | return if (self.head) |head| head else null; 150 | } 151 | 152 | pub fn last(self: *Self) ?*T { 153 | return if (self.tail) |tail| tail else null; 154 | } 155 | 156 | // TODO: allow const iteration 157 | pub fn iterator(self: *const Self) Iterator { 158 | return Iterator{ .current = self.head }; 159 | } 160 | 161 | // TODO: allow const iteration 162 | pub fn iteratorReverse(self: *const Self) Iterator { 163 | return Iterator{ .current = self.tail, .reverse = true }; 164 | } 165 | 166 | pub fn len(self: *const Self) usize { 167 | var iter = self.iterator(); 168 | var i: usize = 0; 169 | while (iter.next()) |_| i += 1; 170 | return i; 171 | } 172 | }; 173 | } 174 | 175 | test "basic LinkedList test" { 176 | const Node = ScalarNode(usize); 177 | const List = LinkedList(Node); 178 | 179 | var list = List.init(testing.allocator); 180 | defer list.deinit(); 181 | 182 | const datas = [_]usize{ 5, 22, 623, 1, 36 }; 183 | for (datas) |data| { 184 | try list.append(Node{ .data = data }); 185 | try testing.expectEqual(data, list.last().?.data); 186 | } 187 | 188 | try testing.expectEqual(datas[0], list.first().?.data); 189 | try testing.expectEqual(datas[4], list.last().?.data); 190 | 191 | // TODO: separate iterator test into its own test 192 | var index: usize = 0; 193 | var iter = list.iterator(); 194 | while (iter.next()) |node| : (index += 1) { 195 | try testing.expectEqual(datas[index], node.data); 196 | } 197 | 198 | iter = list.iterator(); 199 | while (iter.next()) |node| { 200 | if (node.data % 2 == 0) 201 | list.remove(node); 202 | } 203 | 204 | try testing.expectEqual(list.nth(0).?.data, 5); 205 | try testing.expectEqual(list.nth(1).?.data, 623); 206 | try testing.expectEqual(list.nth(2).?.data, 1); 207 | } 208 | 209 | test "LinkedList insertAtInd" { 210 | const Node = ScalarNode(usize); 211 | const List = LinkedList(Node); 212 | 213 | var list = List.init(testing.allocator); 214 | defer list.deinit(); 215 | 216 | const datas = [_]usize{ 1, 2, 3, 4 }; 217 | for (datas) |data| 218 | try list.append(Node{ .data = data }); 219 | 220 | try list.insertAtInd(0, Node{ .data = 99 }); 221 | try list.insertAtInd(2, Node{ .data = 88 }); 222 | 223 | // var i = list.iterator(); 224 | // while (i.next()) |data| 225 | // std.log.err("node: {}", .{data.data}); 226 | 227 | try testing.expectEqual(list.nth(0).?.data, 99); 228 | try testing.expectEqual(list.nth(1).?.data, 1); 229 | try testing.expectEqual(list.nth(2).?.data, 88); 230 | try testing.expectEqual(list.nth(3).?.data, 2); 231 | try testing.expectEqual(list.nth(4).?.data, 3); 232 | try testing.expectEqual(list.nth(5).?.data, 4); 233 | 234 | try list.append(Node{ .data = 77 }); 235 | 236 | try testing.expectEqual(list.nth(6).?.data, 77); 237 | } 238 | 239 | test "LinkedList reverse iterator" { 240 | const Node = ScalarNode(usize); 241 | var list = LinkedList(Node).init(testing.allocator); 242 | defer list.deinit(); 243 | 244 | try list.append(Node{ .data = 0 }); 245 | try list.append(Node{ .data = 1 }); 246 | try list.append(Node{ .data = 2 }); 247 | try list.append(Node{ .data = 3 }); 248 | try list.append(Node{ .data = 4 }); 249 | 250 | const data = [_]usize{ 4, 3, 2, 1, 0 }; 251 | var i: usize = 0; 252 | var iter = list.iteratorReverse(); 253 | while (iter.next()) |number| : (i += 1) 254 | try testing.expectEqual(number.data, data[i]); 255 | 256 | try testing.expectEqual(i, data.len); 257 | } 258 | 259 | test "basic nth() usage" { 260 | const Node = ScalarNode(usize); 261 | const List = LinkedList(Node); 262 | 263 | var list = List.init(testing.allocator); 264 | defer list.deinit(); 265 | 266 | try list.append(Node{ .data = 23 }); 267 | try list.append(Node{ .data = 0 }); 268 | try list.append(Node{ .data = 98 }); 269 | try list.append(Node{ .data = 11 }); 270 | try list.append(Node{ .data = 12 }); 271 | try list.append(Node{ .data = 72 }); 272 | 273 | try testing.expectEqual(list.nth(0).?.data, 23); 274 | try testing.expectEqual(list.nth(1).?.data, 0); 275 | try testing.expectEqual(list.nth(2).?.data, 98); 276 | try testing.expectEqual(list.nth(3).?.data, 11); 277 | try testing.expectEqual(list.nth(4).?.data, 12); 278 | try testing.expectEqual(list.nth(5).?.data, 72); 279 | } 280 | -------------------------------------------------------------------------------- /src/main.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const clap = @import("clap"); 3 | const mem = std.mem; 4 | const assert = std.debug.assert; 5 | 6 | const errors = @import("errors.zig"); 7 | const analyser = @import("analyser.zig"); 8 | const codegen = @import("codegen.zig"); 9 | const common = @import("common.zig"); 10 | const lexerm = @import("lexer.zig"); 11 | const parserm = @import("parser.zig"); 12 | const optimizer = @import("optimizer.zig"); 13 | const vmm = @import("vm.zig"); 14 | 15 | const gpa = &@import("common.zig").gpa; 16 | 17 | pub fn main() anyerror!void { 18 | const alloc = gpa.allocator(); 19 | // defer { 20 | // _ = gpa.deinit(); 21 | // } 22 | 23 | const params = comptime clap.parseParamsComptime( 24 | \\-h, --help Print this help message. 25 | \\-t, --test Run test harness. 26 | \\-1, --debug-asm Output ASM to stderr. 27 | \\-2, --debug-inf Output word analysis to stderr. 28 | \\-x, --emit Output UXN rom to file. 29 | \\-a, --dump-asm ... Print UXN bytecode for function. 30 | \\-d, --emit-debug Output debug info to syms file. Requires -x. 31 | \\-g, --graphical Enable graphical mode. 32 | \\... 33 | ); 34 | 35 | // Initalize our diagnostics, which can be used for reporting useful errors. 36 | // This is optional. You can also pass `.{}` to `clap.parse` if you don't 37 | // care about the extra information `Diagnostics` provides. 38 | var diag = clap.Diagnostic{}; 39 | var args = clap.parse(clap.Help, ¶ms, clap.parsers.default, .{ 40 | .allocator = alloc, 41 | .diagnostic = &diag, 42 | }) catch |err| { 43 | // Report useful error and exit 44 | diag.report(std.io.getStdErr().writer(), err) catch {}; 45 | return; 46 | }; 47 | defer args.deinit(); 48 | 49 | if (args.args.help != 0) { 50 | const stderr = std.io.getStdErr().writer(); 51 | stderr.print("Usage: finwe [options] [args]\n", .{}) catch {}; 52 | clap.help(stderr, clap.Help, ¶ms, .{ 53 | .description_on_new_line = false, 54 | .description_indent = 0, 55 | .indent = 3, 56 | .spacing_between_parameters = 0, 57 | }) catch {}; 58 | return; 59 | } 60 | 61 | for (args.positionals) |filename| { 62 | const file_path = std.fs.cwd().realpathAlloc(alloc, filename) catch |e| { 63 | std.log.err("Couldn't get absolute file path: {}", .{e}); 64 | std.process.exit(1); 65 | }; 66 | defer alloc.free(file_path); 67 | 68 | const file_dir = std.fs.path.dirname(file_path) orelse "/"; 69 | 70 | var program = common.Program.init(alloc, file_dir) catch |e| { 71 | switch (e) { 72 | error.CannotOpenFileDir => { 73 | std.log.err("Can't open file directory.", .{}); 74 | }, 75 | error.CannotOpenSelfDir => { 76 | std.log.err("Can't open compiler directory.", .{}); 77 | }, 78 | } 79 | std.process.exit(1); 80 | }; 81 | defer program.deinit(); 82 | 83 | const file = try std.fs.cwd().openFile(filename, .{}); 84 | defer file.close(); 85 | 86 | const size = try file.getEndPos(); 87 | const buf = try alloc.alloc(u8, size); 88 | defer alloc.free(buf); 89 | _ = try file.readAll(buf); 90 | 91 | var lexer = lexerm.Lexer.init(&program, buf, filename, alloc); 92 | const lexed = lexer.lexList(.Root) catch { 93 | assert(program.errors.items.len > 0); 94 | errors.printErrors(&program, filename); 95 | std.process.exit(1); 96 | }; 97 | defer lexer.deinit(); 98 | defer lexerm.Node.deinitMain(lexed, alloc); 99 | 100 | program.flag_dampe = args.args.emit == null; 101 | program.flag_graphical = args.args.graphical != 0; 102 | 103 | var parser = parserm.Parser.init(&program, args.args.@"test" > 0, alloc); 104 | parser.parse(&lexed) catch |e| { 105 | if (program.errors.items.len > 0) { 106 | errors.printErrors(&program, filename); 107 | std.process.exit(1); 108 | } else { 109 | @panic(@errorName(e)); 110 | } 111 | }; 112 | 113 | analyser.analyse(&program, args.args.@"test" > 0) catch |e| { 114 | if (program.errors.items.len > 0) { 115 | errors.printErrors(&program, filename); 116 | std.process.exit(1); 117 | } else { 118 | @panic(@errorName(e)); 119 | } 120 | }; 121 | 122 | if (args.args.@"debug-inf" != 0) 123 | for (program.defs.items) |def| { 124 | const d = def.node.Decl; 125 | std.log.info("Word {s}: {}", .{ d.name, d.analysis }); 126 | }; 127 | 128 | var intermediate = common.Ins.List.init(gpa.allocator()); 129 | try codegen.generate(&program, &intermediate); 130 | try optimizer.optimize(&program, &intermediate, false); 131 | for (args.args.@"dump-asm") |funcname| 132 | try codegen.printAsmFor(&program, &intermediate, funcname); 133 | var assembled = common.Ins.List.init(gpa.allocator()); 134 | try codegen.resolveUAs(&program, &intermediate, &assembled); 135 | try optimizer.optimize(&program, &assembled, true); 136 | 137 | if (args.args.@"debug-asm" != 0) 138 | for (assembled.items, 0..) |asmstmt, i| { 139 | std.log.info("{} -\t{}", .{ i, asmstmt }); 140 | }; 141 | std.log.info("--------------------------------------------", .{}); 142 | 143 | if (args.args.emit) |fname| { 144 | if (mem.eql(u8, fname, "-")) { 145 | const stdout = std.io.getStdOut().writer(); 146 | try codegen.emitBytecode(stdout, assembled.items); 147 | } else { 148 | const out = try std.fs.cwd().createFile(fname, .{}); 149 | defer out.close(); 150 | try codegen.emitBytecode(out.writer(), assembled.items); 151 | } 152 | if (args.args.@"emit-debug" != 0) { 153 | const sfname = std.fmt.allocPrint(alloc, "{s}.syms", .{fname}) catch unreachable; 154 | defer alloc.free(sfname); 155 | const outsyms = try std.fs.cwd().createFile(sfname, .{}); 156 | defer outsyms.close(); 157 | try codegen.emitDebug(outsyms.writer(), &program); 158 | } 159 | } else if (args.args.@"test" != 0) { 160 | var vm = vmm.VM.init(&program, assembled.items); 161 | defer vm.deinit(); 162 | vm.executeTests(); 163 | } else { 164 | var vm = vmm.VM.init(&program, assembled.items); 165 | defer vm.deinit(); 166 | vm.execute(); 167 | } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/optimizer.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const meta = std.meta; 3 | const mem = std.mem; 4 | const assert = std.debug.assert; 5 | 6 | //const ASTNode = @import("common.zig").ASTNode; 7 | //const ASTNodeList = @import("common.zig").ASTNodeList; 8 | //const Program = @import("common.zig").Program; 9 | const Program = @import("common.zig").Program; 10 | const Ins = @import("common.zig").Ins; 11 | const Op = @import("common.zig").Op; 12 | const OpTag = @import("common.zig").OpTag; 13 | const StackBuffer = @import("buffer.zig").StackBuffer; 14 | 15 | const WK_STACK = @import("common.zig").WK_STACK; 16 | const RT_STACK = @import("common.zig").RT_STACK; 17 | 18 | const gpa = &@import("common.zig").gpa; 19 | 20 | pub const SETS = [_]PatRepSet{ 21 | // zig fmt: off 22 | 23 | // - #00 #00 => #0000 24 | s(.{ O(.Olit), R(), O(.Olit), R(), rOs(.Olit), rR(1), rR(3) }), 25 | 26 | // - #0001 SWP => #0100 27 | s(.{ Os(.Olit), R(), R(), O(.Oswp), rOs(.Olit), rR(2), rR(1) }), 28 | 29 | // - SWP* SWP* => 30 | // - #00 POP => 31 | // - #0000 POP2 => 32 | s(.{ O(.Oswp), O(.Oswp), }), 33 | s(.{ Os(.Oswp), Os(.Oswp), }), 34 | s(.{ O(.Olit), R(), O(.Opop), }), 35 | s(.{ Os(.Olit), R(), R(), Os(.Opop), }), 36 | 37 | // - #1234 POP => #12 38 | // - #1234 NIP => #34 39 | // - #1234 #56 NIP => #1256 40 | s(.{ Os(.Olit), R(), R(), O(.Opop), rO(.Olit), rR(1) }), 41 | s(.{ Os(.Olit), R(), R(), O(.Onip), rO(.Olit), rR(2) }), 42 | s(.{ Os(.Olit), R(), R(), O(.Olit), R(), O(.Onip), rOs(.Olit), rR(2), rR(4) }), 43 | 44 | // constant folding 45 | s(.{ Os(.Olit), R(), R(), O(.Oadd), rO(.Olit), rMathRR(1, 2, '+') }), 46 | s(.{ Os(.Olit), R(), R(), O(.Osub), rO(.Olit), rMathRR(1, 2, '-') }), 47 | s(.{ Os(.Olit), R(), R(), O(.Omul), rO(.Olit), rMathRR(1, 2, '*') }), 48 | s(.{ Os(.Olit), R(), R(), O(.Odiv), rO(.Olit), rMathRR(1, 2, '/') }), 49 | s(.{ Os(.Olit), R(), R(), O(.Oequ), rO(.Olit), rMathRR(1, 2, '=') }), 50 | s(.{ Os(.Olit), R(), R(), O(.Oneq), rO(.Olit), rMathRR(1, 2, '!') }), 51 | s(.{ Os(.Olit), R(), R(), O(.Ogth), rO(.Olit), rMathRR(1, 2, '>') }), 52 | s(.{ Os(.Olit), R(), R(), O(.Olth), rO(.Olit), rMathRR(1, 2, '<') }), 53 | 54 | // constant folding, shorts 55 | s(.{ Os(.Olit), R(), R(), Os(.Olit), R(), R(), Os(.Oadd), 56 | rOs(.Olit), rMathRR2A(1, 4, '+'), rMathRR2B(1, 4, '+') }), 57 | s(.{ Os(.Olit), R(), R(), Os(.Olit), R(), R(), Os(.Osub), 58 | rOs(.Olit), rMathRR2A(1, 4, '-'), rMathRR2B(1, 4, '-') }), 59 | s(.{ Os(.Olit), R(), R(), Os(.Olit), R(), R(), Os(.Omul), 60 | rOs(.Olit), rMathRR2A(1, 4, '*'), rMathRR2B(1, 4, '*') }), 61 | s(.{ Os(.Olit), R(), R(), Os(.Olit), R(), R(), Os(.Odiv), 62 | rOs(.Olit), rMathRR2A(1, 4, '/'), rMathRR2B(1, 4, '/') }), 63 | 64 | // - #00XX MUL2 -> #XX SFT 65 | // - #00XX DIV2 -> #XX SFT 66 | // (from uxnlin) 67 | // (non-short-mode rules omitted -- they don't save any space) 68 | // 69 | s(.{ Os(.Olit),B(0x00),B(0x02), Os(.Omul), rO(.Olit), rB(0x10), rOs(.Osft) }), 70 | s(.{ Os(.Olit),B(0x00),B(0x04), Os(.Omul), rO(.Olit), rB(0x20), rOs(.Osft) }), 71 | s(.{ Os(.Olit),B(0x00),B(0x08), Os(.Omul), rO(.Olit), rB(0x30), rOs(.Osft) }), 72 | s(.{ Os(.Olit),B(0x00),B(0x10), Os(.Omul), rO(.Olit), rB(0x40), rOs(.Osft) }), 73 | s(.{ Os(.Olit),B(0x00),B(0x20), Os(.Omul), rO(.Olit), rB(0x50), rOs(.Osft) }), 74 | s(.{ Os(.Olit),B(0x00),B(0x40), Os(.Omul), rO(.Olit), rB(0x60), rOs(.Osft) }), 75 | s(.{ Os(.Olit),B(0x00),B(0x80), Os(.Omul), rO(.Olit), rB(0x70), rOs(.Osft) }), 76 | s(.{ Os(.Olit),B(0x01),B(0x00), Os(.Omul), rO(.Olit), rB(0x80), rOs(.Osft) }), 77 | s(.{ Os(.Olit),B(0x02),B(0x00), Os(.Omul), rO(.Olit), rB(0x90), rOs(.Osft) }), 78 | s(.{ Os(.Olit),B(0x04),B(0x00), Os(.Omul), rO(.Olit), rB(0xa0), rOs(.Osft) }), 79 | s(.{ Os(.Olit),B(0x08),B(0x00), Os(.Omul), rO(.Olit), rB(0xb0), rOs(.Osft) }), 80 | s(.{ Os(.Olit),B(0x10),B(0x00), Os(.Omul), rO(.Olit), rB(0xc0), rOs(.Osft) }), 81 | s(.{ Os(.Olit),B(0x20),B(0x00), Os(.Omul), rO(.Olit), rB(0xd0), rOs(.Osft) }), 82 | s(.{ Os(.Olit),B(0x40),B(0x00), Os(.Omul), rO(.Olit), rB(0xe0), rOs(.Osft) }), 83 | s(.{ Os(.Olit),B(0x80),B(0x00), Os(.Omul), rO(.Olit), rB(0xf0), rOs(.Osft) }), 84 | s(.{ Os(.Olit),B(0x00),B(0x02), Os(.Odiv), rO(.Olit), rB(0x01), rOs(.Osft) }), 85 | s(.{ Os(.Olit),B(0x00),B(0x04), Os(.Odiv), rO(.Olit), rB(0x02), rOs(.Osft) }), 86 | s(.{ Os(.Olit),B(0x00),B(0x08), Os(.Odiv), rO(.Olit), rB(0x03), rOs(.Osft) }), 87 | s(.{ Os(.Olit),B(0x00),B(0x10), Os(.Odiv), rO(.Olit), rB(0x04), rOs(.Osft) }), 88 | s(.{ Os(.Olit),B(0x00),B(0x20), Os(.Odiv), rO(.Olit), rB(0x05), rOs(.Osft) }), 89 | s(.{ Os(.Olit),B(0x00),B(0x40), Os(.Odiv), rO(.Olit), rB(0x06), rOs(.Osft) }), 90 | s(.{ Os(.Olit),B(0x00),B(0x80), Os(.Odiv), rO(.Olit), rB(0x07), rOs(.Osft) }), 91 | s(.{ Os(.Olit),B(0x01),B(0x00), Os(.Odiv), rO(.Olit), rB(0x08), rOs(.Osft) }), 92 | s(.{ Os(.Olit),B(0x02),B(0x00), Os(.Odiv), rO(.Olit), rB(0x09), rOs(.Osft) }), 93 | s(.{ Os(.Olit),B(0x04),B(0x00), Os(.Odiv), rO(.Olit), rB(0x0a), rOs(.Osft) }), 94 | s(.{ Os(.Olit),B(0x08),B(0x00), Os(.Odiv), rO(.Olit), rB(0x0b), rOs(.Osft) }), 95 | s(.{ Os(.Olit),B(0x10),B(0x00), Os(.Odiv), rO(.Olit), rB(0x0c), rOs(.Osft) }), 96 | s(.{ Os(.Olit),B(0x20),B(0x00), Os(.Odiv), rO(.Olit), rB(0x0d), rOs(.Osft) }), 97 | s(.{ Os(.Olit),B(0x40),B(0x00), Os(.Odiv), rO(.Olit), rB(0x0e), rOs(.Osft) }), 98 | s(.{ Os(.Olit),B(0x80),B(0x00), Os(.Odiv), rO(.Olit), rB(0x0f), rOs(.Osft) }), 99 | 100 | // keepifying instructions 101 | // - DUP -> $1k 102 | // - OVR OVR -> $1k 103 | s(.{ O(.Odup), A1(), rK(1) }), 104 | s(.{ Os(.Odup), A2(), rK(1) }), 105 | s(.{ Os(.Odup), A1s(), rK(1) }), 106 | s(.{ Os(.Oovr), Os(.Oovr), A2s(), rK(2) }), 107 | 108 | // - NEQ 00 EQU -> EQU 109 | // - EQU 00 EQU -> NEQ 110 | // - NEQ 01 EQU -> NEQ 111 | // - EQU 01 EQU -> EQU 112 | s(.{ O(.Oneq), O(.Olit), B(0x00), O(.Oequ), rO(.Oequ) }), 113 | s(.{ Os(.Oneq), O(.Olit), B(0x00), O(.Oequ), rOs(.Oequ) }), 114 | s(.{ O(.Oequ), O(.Olit), B(0x00), O(.Oequ), rO(.Oneq) }), 115 | s(.{ Os(.Oequ), O(.Olit), B(0x00), O(.Oequ), rOs(.Oneq) }), 116 | s(.{ O(.Oneq), O(.Olit), B(0x01), O(.Oequ), rO(.Oneq) }), 117 | s(.{ Os(.Oneq), O(.Olit), B(0x01), O(.Oequ), rOs(.Oneq) }), 118 | s(.{ O(.Oequ), O(.Olit), B(0x01), O(.Oequ), rO(.Oequ) }), 119 | s(.{ Os(.Oequ), O(.Olit), B(0x01), O(.Oequ), rOs(.Oequ) }), 120 | 121 | // - #01 ADD -> INC 122 | // - #02 ADD -> INC INC 123 | s(.{ O(.Olit), B(0x01), O(.Oadd), rO(.Oinc) }), 124 | s(.{ Os(.Olit), B(0x00), B(0x01), Os(.Oadd), rOs(.Oinc) }), 125 | s(.{ O(.Olit), B(0x02), O(.Oadd), rO(.Oinc), rO(.Oinc) }), 126 | s(.{ Os(.Olit), B(0x00), B(0x02), Os(.Oadd), rOs(.Oinc), rOs(.Oinc) }), 127 | 128 | // - #0003 ADD2 -> INC2 INC2 INC2 129 | s(.{ Os(.Olit), B(0x00),B(0x03), Os(.Oadd),rOs(.Oinc),rOs(.Oinc),rOs(.Oinc) }), 130 | 131 | // - OVR OVR -> DUP2 132 | // - NIP POP -> POP2 133 | // - POP POP -> POP2 134 | // - SWP POP -> NIP 135 | s(.{ O(.Oovr), O(.Oovr), rOs(.Odup) }), 136 | s(.{ O(.Onip), O(.Opop), rOs(.Opop) }), 137 | s(.{ O(.Opop), O(.Opop), rOs(.Opop) }), 138 | s(.{ O(.Oswp), O(.Opop), rO(.Onip) }), 139 | s(.{ Os(.Oswp), Os(.Opop), rOs(.Onip) }), 140 | 141 | // zig fmt: on 142 | }; 143 | 144 | pub const PatRepSet = struct { 145 | p: StackBuffer(Pattern, 8), 146 | r: StackBuffer(Replace, 8), 147 | }; 148 | 149 | pub fn s(args: anytype) PatRepSet { 150 | @setEvalBranchQuota(2000); 151 | 152 | comptime var new = PatRepSet{ 153 | .p = StackBuffer(Pattern, 8).init(null), 154 | .r = StackBuffer(Replace, 8).init(null), 155 | }; 156 | comptime var began_repl = false; 157 | const listinfo = @typeInfo(@TypeOf(args)); 158 | comptime { 159 | for (listinfo.Struct.fields) |fieldinfo| { 160 | const field = @field(args, fieldinfo.name); 161 | if (fieldinfo.type == Pattern) { 162 | if (began_repl) 163 | @compileError("Mixing patterns and replacements"); 164 | new.p.append(field) catch unreachable; 165 | } else if (fieldinfo.type == Replace) { 166 | began_repl = true; 167 | new.r.append(field) catch unreachable; 168 | } else { 169 | @compileError("Invalid argument type " ++ @typeName(fieldinfo.type)); 170 | } 171 | } 172 | } 173 | return new; 174 | } 175 | 176 | pub const Pattern = struct { 177 | pat: Type, 178 | short: bool = false, 179 | stk: usize = WK_STACK, 180 | keep: bool = false, 181 | 182 | pub const Type = union(enum) { 183 | O: OpTag, // Specific opcode 184 | B: u8, // Specific raw byte 185 | R, // Any raw byte 186 | A1, // Any opcode taking one arg 187 | A2, // Any opcode taking two args, not incl Osft/Ojcn 188 | }; 189 | 190 | pub fn matches(p: @This(), ins: Ins) bool { 191 | return switch (p.pat) { 192 | .R => ins.op == .Oraw, 193 | .B => |b| ins.op == .Oraw and ins.op.Oraw == b, 194 | .O => |o| o == ins.op and 195 | ins.short == p.short and ins.stack == p.stk and ins.keep == p.keep, 196 | .A1 => ins.op.argCount() == 1 and 197 | ins.op != .Ojci and 198 | ins.short == p.short and ins.stack == p.stk and ins.keep == p.keep, 199 | .A2 => ins.op.argCount() == 2 and 200 | ins.op != .Osft and ins.op != .Ojcn and 201 | ins.short == p.short and ins.stack == p.stk and ins.keep == p.keep, 202 | }; 203 | } 204 | }; 205 | 206 | // zig fmt: off 207 | fn O(o: OpTag) Pattern { return .{ .pat = .{ .O = o } }; } 208 | fn Os(o: OpTag) Pattern { return .{ .pat = .{ .O = o }, .short = true }; } 209 | fn R() Pattern { return .{ .pat = .R }; } 210 | fn B(b: u8) Pattern { return .{ .pat = .{ .B = b }, }; } 211 | fn A1() Pattern { return .{ .pat = .A1 }; } 212 | fn A2() Pattern { return .{ .pat = .A2 }; } 213 | fn A1s() Pattern { return .{ .pat = .A1, .short = true }; } 214 | fn A2s() Pattern { return .{ .pat = .A2, .short = true }; } 215 | // zig fmt: on 216 | 217 | pub const Replace = struct { 218 | rpl: Type, 219 | 220 | pub const Type = union(enum) { 221 | I: struct { 222 | op: OpTag, 223 | k: bool = false, 224 | s: bool = false, 225 | r: bool = false, 226 | }, // Non-Oraw instruction 227 | R: usize, // Reference 228 | B: u8, // Raw byte 229 | Math: MathTransform, // Constant math 230 | Math2A: MathTransform, // Constant math (short-mode), first byte 231 | Math2B: MathTransform, // Constant math (short-mode), second byte 232 | K: usize, // Keepified reference 233 | 234 | pub const MathTransform = struct { 235 | a: RefOrConst, 236 | b: RefOrConst, 237 | op: u21, 238 | }; 239 | }; 240 | 241 | pub const RefOrConst = union(enum) { 242 | ref: usize, 243 | val: u8, 244 | val2: u16, 245 | 246 | pub fn get(self: @This(), ins: []const Ins) u8 { 247 | return switch (self) { 248 | .ref => |i| ins[i].op.Oraw, 249 | .val => |v| v, 250 | .val2 => unreachable, 251 | }; 252 | } 253 | 254 | pub fn get2(self: @This(), ins: []const Ins) u16 { 255 | return switch (self) { 256 | .ref => |i| @as(u16, @intCast(ins[i].op.Oraw)) << 8 | 257 | ins[i + 1].op.Oraw, 258 | .val2 => |v| v, 259 | .val => unreachable, 260 | }; 261 | } 262 | }; 263 | 264 | pub fn exec(self: @This(), ins: []const Ins) Ins { 265 | return switch (self.rpl) { 266 | .I => |i| Ins{ 267 | .op = Op.fromTag(i.op) catch unreachable, 268 | .short = i.s, 269 | .keep = i.k, 270 | .stack = if (i.r) RT_STACK else WK_STACK, 271 | }, 272 | .Math => |m| Ins{ .op = .{ .Oraw = switch (m.op) { 273 | '+' => m.a.get(ins) +% m.b.get(ins), 274 | '-' => m.a.get(ins) -% m.b.get(ins), 275 | '*' => m.a.get(ins) *% m.b.get(ins), 276 | '/' => m.a.get(ins) / m.b.get(ins), 277 | '=' => @intFromBool(m.a.get(ins) == m.b.get(ins)), 278 | '!' => @intFromBool(m.a.get(ins) != m.b.get(ins)), 279 | '>' => @intFromBool(m.a.get(ins) > m.b.get(ins)), 280 | '<' => @intFromBool(m.a.get(ins) < m.b.get(ins)), 281 | else => unreachable, 282 | } }, .stack = WK_STACK }, 283 | .Math2A, .Math2B => |m| b: { 284 | const rawres = switch (m.op) { 285 | '+' => m.a.get2(ins) +% m.b.get2(ins), 286 | '-' => m.a.get2(ins) -% m.b.get2(ins), 287 | '*' => m.a.get2(ins) *% m.b.get2(ins), 288 | '/' => m.a.get2(ins) / m.b.get2(ins), 289 | '=' => @intFromBool(m.a.get2(ins) == m.b.get2(ins)), 290 | '!' => @intFromBool(m.a.get2(ins) != m.b.get2(ins)), 291 | '>' => @intFromBool(m.a.get2(ins) > m.b.get2(ins)), 292 | '<' => @intFromBool(m.a.get2(ins) < m.b.get2(ins)), 293 | else => unreachable, 294 | }; 295 | const res = if (self.rpl == .Math2A) rawres >> 8 else rawres & 0xFF; 296 | break :b Ins{ .op = .{ .Oraw = @intCast(res) }, .stack = WK_STACK }; 297 | }, 298 | .B => |b| Ins{ .op = .{ .Oraw = b }, .stack = 0 }, 299 | .R => |r| ins[r], 300 | .K => |r| Ins{ 301 | .op = ins[r].op, 302 | .short = ins[r].short, 303 | .keep = true, 304 | .stack = ins[r].stack, 305 | }, 306 | }; 307 | } 308 | }; 309 | 310 | // zig fmt: off 311 | fn rO(o: OpTag) Replace { return .{.rpl = .{.I = .{ .op = o }}}; } 312 | fn rOs(o: OpTag) Replace { return .{.rpl = .{.I = .{ .op = o, .s = true }}}; } 313 | fn rMathRR(r1: usize, r2: usize, op: u21) Replace { return .{.rpl = .{.Math = .{ .a = .{ .ref = r1 }, .b = .{ .ref = r2 }, .op = op }}}; } 314 | fn rMathRR2A(r1: usize, r2: usize, op: u21) Replace { return .{.rpl = .{.Math2A = .{ .a = .{ .ref = r1 }, .b = .{ .ref = r2 }, .op = op }}}; } 315 | fn rMathRR2B(r1: usize, r2: usize, op: u21) Replace { return .{.rpl = .{.Math2B = .{ .a = .{ .ref = r1 }, .b = .{ .ref = r2 }, .op = op }}}; } 316 | fn rR(r: usize) Replace { return .{ .rpl = .{.R = r} }; } 317 | fn rB(b: usize) Replace { return .{ .rpl = .{.B = b} }; } 318 | fn rK(r: usize) Replace { return .{ .rpl = .{.K = r} }; } 319 | // zig fmt: on 320 | 321 | pub fn optimize(p: *Program, p_ins: *Ins.List, only_safe: bool) !void { 322 | var ins = p_ins.items; 323 | var new = Ins.List.init(gpa.allocator()); 324 | var did_something = false; 325 | 326 | while (ins.len > 0) { 327 | const maybe_matching_set = for (&SETS) |*set| { 328 | if (set.p.len > ins.len) 329 | continue; 330 | if (only_safe and set.p.len != set.r.len) 331 | continue; 332 | const p_match = for (set.p.constSlice(), 0..) |pat, i| { 333 | if (!pat.matches(ins[i])) break false; 334 | } else true; 335 | if (p_match) break set; 336 | } else null; 337 | 338 | if (maybe_matching_set) |matching| { 339 | did_something = true; 340 | for (matching.r.constSlice()) |rpl| { 341 | // for (matching.r.constSlice(), 0..) |rpl, i| { 342 | // std.log.info("{} -> {}", .{ ins[i], rpl.exec(ins[0..matching.p.len]) }); 343 | new.append(rpl.exec(ins[0..matching.p.len])) catch unreachable; 344 | } 345 | ins = ins[matching.p.len..]; 346 | } else { 347 | new.append(ins[0]) catch unreachable; 348 | ins = ins[1..]; 349 | } 350 | } 351 | 352 | p_ins.deinit(); 353 | p_ins.* = new; 354 | 355 | if (did_something) 356 | try optimize(p, p_ins, only_safe); 357 | } 358 | -------------------------------------------------------------------------------- /src/utils.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const meta = std.meta; 3 | 4 | pub fn unwrapAs(comptime U: type, instance: U, expect: meta.Tag(U)) ?U { 5 | return if (instance == expect) instance else null; 6 | } 7 | -------------------------------------------------------------------------------- /src/vm.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const mem = std.mem; 3 | const math = std.math; 4 | const fmt = std.fmt; 5 | const meta = std.meta; 6 | const assert = std.debug.assert; 7 | const linux = std.os.linux; 8 | 9 | const codegen = @import("codegen.zig"); 10 | const ASTNode = @import("common.zig").ASTNode; 11 | const ASTNodeList = @import("common.zig").ASTNodeList; 12 | const Value = @import("common.zig").Value; 13 | const Srcloc = @import("common.zig").Srcloc; 14 | const Ins = @import("common.zig").Ins; 15 | const Program = @import("common.zig").Program; 16 | const StackBuffer = @import("buffer.zig").StackBuffer; 17 | 18 | const WK_STACK = @import("common.zig").WK_STACK; 19 | const RT_STACK = @import("common.zig").RT_STACK; 20 | const STACK_SZ = @import("common.zig").STACK_SZ; 21 | 22 | const gpa = &@import("common.zig").gpa; 23 | 24 | const c = @cImport({ 25 | @cInclude("uxn.h"); 26 | @cInclude("devices/system.h"); 27 | @cInclude("devices/console.h"); 28 | @cInclude("devices/screen.h"); 29 | @cInclude("devices/audio.h"); 30 | @cInclude("devices/file.h"); 31 | @cInclude("devices/controller.h"); 32 | @cInclude("devices/mouse.h"); 33 | @cInclude("devices/datetime.h"); 34 | @cInclude("devices/net.h"); 35 | }); 36 | 37 | extern "c" fn set_zoom(z: u8, win: c_int) void; 38 | extern "c" fn emu_init_graphical() c_int; 39 | extern "c" fn emu_run_graphical(u: [*c]c.Uxn) void; 40 | extern "c" fn emu_end_graphical(uxn: [*c]c.Uxn) c_int; 41 | extern "c" fn emu_run_plain(u: [*c]c.Uxn) void; 42 | extern "c" fn emu_end_plain(uxn: [*c]c.Uxn) c_int; 43 | extern "c" fn base_emu_deo(uxn: [*c]c.Uxn, addr: c_char) void; 44 | 45 | const Item = struct { romloc: usize, node: *ASTNode }; 46 | 47 | const Range = struct { 48 | a: usize, 49 | b: usize, 50 | rst: [0x100]u8, 51 | rst_ptr: usize, 52 | }; 53 | 54 | pub const VM = struct { 55 | uxn: c.Uxn = undefined, 56 | ram: []u8, 57 | here: usize = 0, 58 | assembled: []const Ins, 59 | program: *Program, 60 | captured_stdout: std.ArrayList(u8), 61 | captured_stderr: std.ArrayList(u8), 62 | protected: std.ArrayList(Range), 63 | 64 | is_testing: bool = false, 65 | is_test_done: bool = false, 66 | is_breakpoint: bool = false, 67 | is_privileged: bool = false, 68 | 69 | pub fn init(program: *Program, assembled: []const Ins) VM { 70 | const ram = gpa.allocator().alloc(u8, 0x10000 * c.RAM_PAGES) catch 71 | @panic("please uninstall Chrome before proceeding (OOM)"); 72 | @memset(ram, 0); 73 | 74 | var self = VM{ 75 | .ram = ram, 76 | .here = assembled.len, 77 | .program = program, 78 | .assembled = assembled, 79 | .captured_stdout = std.ArrayList(u8).init(gpa.allocator()), 80 | .captured_stderr = std.ArrayList(u8).init(gpa.allocator()), 81 | .protected = std.ArrayList(Range).init(gpa.allocator()), 82 | }; 83 | self.load(); 84 | self.uxn.ram = ram.ptr; 85 | 86 | c.system_connect(0x0, c.SYSTEM_VERSION, c.SYSTEM_DEIMASK, c.SYSTEM_DEOMASK); 87 | c.system_connect(0x1, c.CONSOLE_VERSION, c.CONSOLE_DEIMASK, c.CONSOLE_DEOMASK); 88 | c.system_connect(0xa, c.FILE_VERSION, c.FILE_DEIMASK, c.FILE_DEOMASK); 89 | c.system_connect(0xb, c.FILE_VERSION, c.FILE_DEIMASK, c.FILE_DEOMASK); 90 | c.system_connect(0xc, c.DATETIME_VERSION, c.DATETIME_DEIMASK, c.DATETIME_DEOMASK); 91 | c.system_connect(0xd, c.NET_VERSION, c.NET_DEIMASK, c.NET_DEOMASK); 92 | 93 | if (self.program.flag_graphical) { 94 | c.system_connect(0x2, c.SCREEN_VERSION, c.SCREEN_DEIMASK, c.SCREEN_DEOMASK); 95 | c.system_connect(0x3, c.AUDIO_VERSION, c.AUDIO_DEIMASK, c.AUDIO_DEOMASK); 96 | c.system_connect(0x4, c.AUDIO_VERSION, c.AUDIO_DEIMASK, c.AUDIO_DEOMASK); 97 | c.system_connect(0x5, c.AUDIO_VERSION, c.AUDIO_DEIMASK, c.AUDIO_DEOMASK); 98 | c.system_connect(0x6, c.AUDIO_VERSION, c.AUDIO_DEIMASK, c.AUDIO_DEOMASK); 99 | c.system_connect(0x8, c.CONTROL_VERSION, c.CONTROL_DEIMASK, c.CONTROL_DEOMASK); 100 | c.system_connect(0x9, c.MOUSE_VERSION, c.MOUSE_DEIMASK, c.MOUSE_DEOMASK); 101 | } 102 | 103 | //set_zoom(2, 0); 104 | 105 | if (self.program.flag_graphical) 106 | if (emu_init_graphical() == 0) 107 | @panic("Emulator graphics failed to init."); 108 | 109 | return self; 110 | } 111 | 112 | pub fn deinit(self: *VM) void { 113 | gpa.allocator().free(self.ram); 114 | self.captured_stdout.deinit(); 115 | self.captured_stderr.deinit(); 116 | self.protected.deinit(); 117 | } 118 | 119 | pub fn load(self: *VM) void { 120 | var fbstream = std.io.fixedBufferStream(self.ram); 121 | var writer = fbstream.writer(); 122 | writer.writeByteNTimes(0, 0x0100) catch unreachable; 123 | codegen.emitBytecode(writer, self.assembled) catch unreachable; 124 | } 125 | 126 | fn _getFuncNameForAddr(self: *VM, items: []const Item, addr: u16) ?*ASTNode { 127 | if (addr > self.here) 128 | return null; 129 | 130 | var prev: ?Item = null; 131 | for (items) |item| { 132 | if (item.romloc > addr) { 133 | if (prev != null and prev.?.romloc <= addr) { 134 | return prev.?.node; 135 | } else { 136 | return null; 137 | } 138 | } 139 | prev = item; 140 | } 141 | return null; 142 | } 143 | 144 | pub fn printBacktrace(self: *VM, pc: c_ushort, rst: []const u8, rst_ptr: usize) void { 145 | const stderr = std.io.getStdErr().writer(); 146 | 147 | var items = std.ArrayList(Item).init(gpa.allocator()); 148 | defer items.deinit(); 149 | 150 | for (self.program.defs.items) |def| 151 | items.append(.{ .romloc = def.romloc + 0x100, .node = def }) catch unreachable; 152 | 153 | std.sort.insertion(Item, items.items, {}, struct { 154 | pub fn lessThan(_: void, lhs: Item, rhs: Item) bool { 155 | return lhs.romloc < rhs.romloc; 156 | } 157 | }.lessThan); 158 | 159 | const paranoid = rst_ptr % 2 != 0; 160 | 161 | const _p = struct { 162 | pub fn f(vm: *VM, addr: u16, serr: anytype, i: *const std.ArrayList(Item)) void { 163 | serr.print(" \x1b[36mat \x1b[m{x:0>4}", .{addr}) catch unreachable; 164 | if (vm._getFuncNameForAddr(i.items, addr)) |node| { 165 | const s = if (node.node.Decl.is_test) "" else node.node.Decl.name; 166 | serr.print(" \x1b[37;1m{s: <15}\x1b[m", .{s}) catch unreachable; 167 | serr.print(" ({s}:\x1b[33m{}\x1b[m:\x1b[34m{}\x1b[m)\n", .{ 168 | node.srcloc.file, node.srcloc.line, node.srcloc.column, 169 | }) catch unreachable; 170 | } else { 171 | serr.print(" \x1b[37;1m???\x1b[m\n", .{}) catch unreachable; 172 | } 173 | } 174 | }.f; 175 | 176 | if (pc > 0) 177 | _p(self, pc, &stderr, &items); 178 | 179 | var ptr = rst_ptr; 180 | while (ptr > 0) : (ptr -= if (paranoid) 1 else 2) { 181 | const addr = @as(u16, @intCast(rst[ptr -| 2])) << 8 | rst[ptr -| 1]; 182 | _p(self, addr, &stderr, &items); 183 | } 184 | 185 | if (paranoid) 186 | stderr.print("\x1b[33;1mNOTE:\x1b[m Trace printed in paranoid mode (RST size not even).\n", .{}) catch unreachable; 187 | } 188 | 189 | pub fn execute(self: *VM) void { 190 | self.is_testing = false; 191 | 192 | if (uxn_eval(&self.uxn, c.PAGE_PROGRAM) != 0) { 193 | if (self.program.flag_graphical) 194 | emu_run_graphical(&self.uxn) 195 | else 196 | emu_run_plain(&self.uxn); 197 | } 198 | 199 | if (self.program.flag_graphical) 200 | _ = emu_end_graphical(&self.uxn) 201 | else 202 | _ = emu_end_plain(&self.uxn); 203 | } 204 | 205 | fn _failTest(stderr: anytype, comptime format: []const u8, args: anytype, romloc: usize, srcloc: Srcloc) !void { 206 | const failstr = "\x1b[2D\x1b[31m!!\x1b[m\n"; 207 | stderr.print("{s}", .{failstr}) catch unreachable; 208 | stderr.print("\x1b[32m> \x1b[m" ++ format ++ "\n", args) catch unreachable; 209 | stderr.print(" \x1b[36mat \x1b[m{s}:\x1b[33m{}\x1b[m:\x1b[34m{}\x1b[m", .{ 210 | srcloc.file, srcloc.line, srcloc.column, 211 | }) catch unreachable; 212 | stderr.print(" (\x1b[36mpc \x1b[m{x:0>4})\n", .{ 213 | romloc + 0x100, 214 | }) catch unreachable; 215 | stderr.print("\n", .{}) catch unreachable; 216 | return error.Fail; 217 | } 218 | 219 | fn _handleBreak(self: *VM, pc: c_ushort, stderr: anytype, program: *Program) !void { 220 | // TODO: underflow checks 221 | const wst: [*c]u8 = @ptrCast(&self.uxn.wst.dat[self.uxn.wst.ptr -| 1]); 222 | 223 | const tosb = wst.*; 224 | const toss = @as(u16, @intCast((wst - @as(u8, 1)).*)) << 8 | tosb; 225 | const sosb = (wst - @as(u8, 2)).*; 226 | const soss = @as(u16, @intCast((wst - @as(u8, 3)).*)) << 8 | sosb; 227 | 228 | const breakpoint_node = for (program.breakpoints.items) |node| { 229 | if (node.romloc + 0x100 == pc) break node; 230 | } else unreachable; // TODO: warn about unknown breakpoints 231 | const romloc = breakpoint_node.romloc; 232 | const srcloc = breakpoint_node.srcloc; 233 | const breakpoint = &breakpoint_node.node.Breakpoint; 234 | const btyp = breakpoint.type; 235 | 236 | breakpoint.is_executed = true; 237 | 238 | switch (btyp) { 239 | .RCheckpoint => self.is_test_done = true, 240 | .TosShouldEq, .TosShouldEqSos => { 241 | const v: Value = switch (btyp) { 242 | .TosShouldEqSos => |t| if (t.bits(program) == 16) 243 | .{ .typ = .U16, .val = .{ .u16 = soss } } 244 | else 245 | .{ .typ = .U8, .val = .{ .u8 = sosb } }, 246 | .TosShouldEq => |v| v, 247 | else => unreachable, 248 | }; 249 | 250 | if (v.typ.bits(program).? == 16) { 251 | if (v.toU16(program) != toss) { 252 | try _failTest(stderr, "Expected 0x{x:0>4}, got 0x{x:0>4}", .{ 253 | v.toU16(program), toss, 254 | }, romloc, srcloc); 255 | } 256 | self.uxn.wst.ptr -= 2; 257 | } else if (v.typ.bits(program).? == 8) { 258 | if (v.toU8(program) != tosb) { 259 | try _failTest(stderr, "Expected 0x{x:0>2}, got 0x{x:0>2}", .{ 260 | v.toU8(program), tosb, 261 | }, romloc, srcloc); 262 | } 263 | self.uxn.wst.ptr -= 1; 264 | } else unreachable; 265 | }, 266 | .TosShouldNeq, .TosShouldNeqSos => { 267 | const v: Value = switch (btyp) { 268 | .TosShouldNeqSos => |t| if (t.bits(program) == 16) 269 | .{ .typ = .U16, .val = .{ .u16 = soss } } 270 | else 271 | .{ .typ = .U8, .val = .{ .u8 = sosb } }, 272 | .TosShouldNeq => |v| v, 273 | else => unreachable, 274 | }; 275 | 276 | if (v.typ.bits(program).? == 16) { 277 | if (v.toU16(program) == toss) { 278 | try _failTest(stderr, "Unexpected 0x{x:0>4} == 0x{x:0>4}", .{ 279 | toss, soss, 280 | }, romloc, srcloc); 281 | } 282 | self.uxn.wst.ptr -= 2; 283 | } else if (v.typ.bits(program).? == 8) { 284 | if (v.toU8(program) == tosb) { 285 | try _failTest(stderr, "Unexpected 0x{x:0>2} == 0x{x:0>2}", .{ 286 | tosb, sosb, 287 | }, romloc, srcloc); 288 | } 289 | self.uxn.wst.ptr -= 1; 290 | } else unreachable; 291 | }, 292 | .StdoutShouldEq => |v| { 293 | defer self.captured_stdout.shrinkAndFree(0); 294 | if (self.captured_stdout.items.len != v.items.len) 295 | try _failTest(stderr, "Unexpected stdout output (len: {} vs {})", .{ 296 | v.items.len, self.captured_stdout.items.len, 297 | }, romloc, srcloc); 298 | if (!mem.eql(u8, self.captured_stdout.items, v.items)) 299 | try _failTest(stderr, "Unexpected stdout output", .{}, romloc, srcloc); 300 | }, 301 | } 302 | } 303 | 304 | fn _finalCheck(self: *VM, cur_test: *const ASTNode, stderr: anytype, program: *Program) !void { 305 | if (!self.is_test_done and !cur_test.node.Decl.is_noreturn) 306 | try _failTest(stderr, "Test never returned!", .{}, cur_test.romloc, cur_test.srcloc); 307 | 308 | for (program.breakpoints.items) |node| { 309 | const brk = &node.node.Breakpoint; 310 | assert(node.romloc != 0xFFFF); 311 | if (brk.parent_test.? == &cur_test.node.Decl) 312 | if (brk.must_execute and !brk.is_executed) 313 | if (brk.type == .RCheckpoint) { 314 | assert(!cur_test.node.Decl.is_noreturn); 315 | try _failTest(stderr, "Test never returned (test harness bug, should've caught earlier)!", .{}, node.romloc, node.srcloc); 316 | } else { 317 | try _failTest(stderr, "Assertion never checked (early return?)", .{}, node.romloc, node.srcloc); 318 | }; 319 | } 320 | 321 | self.is_test_done = false; 322 | } 323 | 324 | pub fn executeTests(self: *VM) void { 325 | const stderr = std.io.getStdErr().writer(); 326 | 327 | self.is_testing = true; 328 | 329 | test_loop: for (self.program.defs.items) |decl_node| { 330 | const decl = decl_node.node.Decl; 331 | if (!decl.is_test) continue; 332 | assert(decl.is_analysed); 333 | 334 | stderr.print("{s}", .{decl.name}) catch unreachable; 335 | stderr.writeByteNTimes(' ', 50 - decl.name.len) catch unreachable; 336 | stderr.print("\x1b[34..\x1b[m", .{}) catch unreachable; 337 | 338 | // TODO: assert that stacks are empty after each test 339 | self.protected.shrinkAndFree(0); 340 | self.uxn.wst.ptr = 0; 341 | self.uxn.rst.ptr = 0; 342 | @memset(self.ram[0..], 0); 343 | self.load(); 344 | 345 | assert(decl_node.romloc != 0xFFFF); 346 | var pc: c_ushort = @as(c_ushort, @intCast(decl_node.romloc)) + 0x100; 347 | 348 | while (true) { 349 | self._checkInstruction(pc, stderr); 350 | pc = c.uxn_step(&self.uxn, pc); 351 | if (pc == 0) @panic("test halted"); 352 | if (pc == 1) break; 353 | 354 | if (self.is_breakpoint) { 355 | self.is_breakpoint = false; 356 | _handleBreak(self, pc, stderr, self.program) catch continue :test_loop; 357 | } 358 | 359 | if (self.is_test_done) { 360 | break; 361 | } 362 | } 363 | 364 | _finalCheck(self, decl_node, stderr, self.program) catch continue :test_loop; 365 | 366 | stderr.print("\x1b[2D\x1b[36mOK\x1b[m\n", .{}) catch unreachable; 367 | 368 | if (self.captured_stdout.items.len > 0) { 369 | stderr.print("\x1b[32m= \x1b[mTest stdout =\n", .{}) catch unreachable; 370 | stderr.print("{s}", .{self.captured_stdout.items}) catch unreachable; 371 | _printHappyPercent(stderr); 372 | stderr.print("\x1b[32m= \x1b[mEnd stdout =\n", .{}) catch unreachable; 373 | stderr.print("\n", .{}) catch unreachable; 374 | self.captured_stdout.shrinkAndFree(0); 375 | } 376 | 377 | if (self.captured_stderr.items.len > 0) { 378 | stderr.print("\x1b[32m= \x1b[mTest stderr =\n", .{}) catch unreachable; 379 | stderr.print("{s}", .{self.captured_stderr.items}) catch unreachable; 380 | _printHappyPercent(stderr); 381 | stderr.print("\x1b[32m= \x1b[mEnd stderr =\n", .{}) catch unreachable; 382 | stderr.print("\n", .{}) catch unreachable; 383 | self.captured_stderr.shrinkAndFree(0); 384 | } 385 | } 386 | 387 | _ = emu_end_plain(&self.uxn); 388 | } 389 | 390 | fn executeExpansionCmd(self: *VM, cmdaddr: u16) void { 391 | switch (self.uxn.ram[cmdaddr]) { 392 | 0x40 => { 393 | self.printBacktrace(0, &self.uxn.rst.dat, self.uxn.rst.ptr); 394 | }, 395 | 0x41 => { 396 | const begin = @as(u16, self.uxn.ram[cmdaddr + 1]) << 8 | 397 | self.uxn.ram[cmdaddr + 2]; 398 | const count = @as(u16, self.uxn.ram[cmdaddr + 3]) << 8 | 399 | self.uxn.ram[cmdaddr + 4]; 400 | 401 | if (self._findProtectedRangeContaining(begin)) |i| { 402 | const stderr = std.io.getStdErr().writer(); 403 | const existing = self.protected.items[i]; 404 | _printHappyPercent(stderr); 405 | stderr.print("Protection exception\n", .{}) catch unreachable; 406 | stderr.print("Range {x:0>4} already protected (in {x:0>4}..{x:0>4})\n", .{ 407 | begin, existing.a, existing.b, 408 | }) catch unreachable; 409 | self.printBacktrace(0, &self.uxn.rst.dat, self.uxn.rst.ptr); 410 | stderr.print("Initial protection:\n", .{}) catch unreachable; 411 | self.printBacktrace(0, &existing.rst, existing.rst_ptr); 412 | stderr.print("Aborting.\n", .{}) catch unreachable; 413 | std.process.exit(1); 414 | } else { 415 | self.protected.append(.{ 416 | .a = begin, 417 | .b = begin + count, 418 | .rst = self.uxn.rst.dat, 419 | .rst_ptr = self.uxn.rst.ptr, 420 | }) catch unreachable; 421 | } 422 | }, 423 | 0x42 => { 424 | const paddr = @as(u16, self.uxn.ram[cmdaddr + 1]) << 8 | 425 | self.uxn.ram[cmdaddr + 2]; 426 | if (self._findProtectedRangeStrict(paddr)) |i| { 427 | _ = self.protected.swapRemove(i); 428 | } else { 429 | const stderr = std.io.getStdErr().writer(); 430 | _printHappyPercent(stderr); 431 | stderr.print("Protection exception\n", .{}) catch unreachable; 432 | stderr.print("Bad range to unprotect {x:0>4}\n", .{paddr}) catch unreachable; 433 | self.printBacktrace(0, &self.uxn.rst.dat, self.uxn.rst.ptr); 434 | stderr.print("Aborting.\n", .{}) catch unreachable; 435 | std.process.exit(1); 436 | } 437 | }, 438 | 0x43 => { 439 | self.is_privileged = true; 440 | }, 441 | 0x44 => { 442 | self.is_privileged = false; 443 | }, 444 | 0x45 => { 445 | const paddr = @as(u16, self.uxn.ram[cmdaddr + 1]) << 8 | 446 | self.uxn.ram[cmdaddr + 2]; 447 | if (self._findProtectedRangeContaining(paddr) == null) { 448 | const stderr = std.io.getStdErr().writer(); 449 | _printHappyPercent(stderr); 450 | stderr.print("Protection exception\n", .{}) catch unreachable; 451 | stderr.print("Address {x:0>4} not protected\n", .{paddr}) catch unreachable; 452 | self.printBacktrace(0, &self.uxn.rst.dat, self.uxn.rst.ptr); 453 | stderr.print("Aborting.\n", .{}) catch unreachable; 454 | std.process.exit(1); 455 | } 456 | }, 457 | else => unreachable, 458 | } 459 | } 460 | 461 | fn _findProtectedRangeStrict(self: *VM, addr: u16) ?usize { 462 | return for (self.protected.items, 0..) |prange, i| { 463 | if (prange.a == addr) break i; 464 | } else null; 465 | } 466 | 467 | fn _findProtectedRangeContaining(self: *VM, addr: u16) ?usize { 468 | return for (self.protected.items, 0..) |prange, i| { 469 | if (addr >= prange.a and addr < prange.b) break i; 470 | } else null; 471 | } 472 | 473 | fn _checkInstruction(self: *VM, pc: c_ushort, stderr: anytype) void { 474 | const ins = self.uxn.ram[pc]; 475 | switch (ins & 0x3f) { 476 | 0x15, // STA 477 | 0x35, // STA2 478 | => if (self.protected.items.len > 0 and !self.is_privileged) { 479 | const stk = if (ins & 0x40 != 0) &self.uxn.rst else &self.uxn.wst; 480 | const wst: [*c]u8 = @ptrCast(&stk.dat[stk.ptr -| 1]); 481 | const addr = @as(u16, @intCast((wst - @as(u8, 1)).*)) << 8 | wst.*; 482 | 483 | for (self.protected.items) |protected_range| { 484 | if (addr >= protected_range.a and addr < protected_range.b) { 485 | _printHappyPercent(stderr); 486 | stderr.print("Protection exception\n", .{}) catch unreachable; 487 | stderr.print("Write to {x:0>4} (in {x:0>4}..{x:0>4})\n", .{ 488 | addr, protected_range.a, protected_range.b, 489 | }) catch unreachable; 490 | self.printBacktrace(pc, &self.uxn.rst.dat, self.uxn.rst.ptr); 491 | stderr.print("Initial protection:\n", .{}) catch unreachable; 492 | self.printBacktrace(0, &protected_range.rst, protected_range.rst_ptr); 493 | stderr.print("Aborting.\n", .{}) catch unreachable; 494 | std.process.exit(1); 495 | } 496 | } 497 | }, 498 | else => {}, 499 | } 500 | } 501 | }; 502 | 503 | pub export fn emu_deo(u: [*c]c.Uxn, addr: c_char) callconv(.C) void { 504 | const self: *VM = @ptrCast(@as(*allowzero VM, @fieldParentPtr("uxn", u))); 505 | switch (addr) { 506 | 0x03 => { 507 | const cmdaddr = @as(u16, u.*.dev[0x02]) << 8 | u.*.dev[0x03]; 508 | switch (self.uxn.ram[cmdaddr]) { 509 | 0x40...0x45 => self.executeExpansionCmd(cmdaddr), 510 | else => base_emu_deo(u, addr), 511 | } 512 | }, 513 | 0x0e => switch (u.*.dev[0x0e]) { 514 | 0x0b => if (self.is_testing) { 515 | self.is_breakpoint = true; 516 | }, 517 | else => base_emu_deo(u, addr), 518 | }, 519 | 0x18 => if (self.is_testing) 520 | self.captured_stdout.append(u.*.dev[0x18]) catch unreachable 521 | else 522 | base_emu_deo(u, addr), 523 | 0x19 => if (self.is_testing) 524 | self.captured_stderr.append(u.*.dev[0x19]) catch unreachable 525 | else 526 | base_emu_deo(u, addr), 527 | else => base_emu_deo(u, addr), 528 | } 529 | } 530 | 531 | pub export fn emu_trace(u: [*c]c.Uxn, pc: c_ushort) callconv(.C) void { 532 | const self: *VM = @ptrCast(@as(*allowzero VM, @fieldParentPtr("uxn", u))); 533 | self.printBacktrace(pc, &self.uxn.rst.dat, self.uxn.rst.ptr); 534 | } 535 | 536 | pub export fn uxn_eval(u: [*c]c.Uxn, p_pc: c_ushort) callconv(.C) c_int { 537 | const self: *VM = @ptrCast(@as(*allowzero VM, @fieldParentPtr("uxn", u))); 538 | 539 | if (p_pc == 0 or self.uxn.dev[0x0f] > 0) 540 | return 0; 541 | 542 | const stderr = std.io.getStdErr().writer(); 543 | 544 | var pc = p_pc; 545 | while (true) { 546 | self._checkInstruction(pc, stderr); 547 | pc = c.uxn_step(&self.uxn, pc); 548 | if (pc <= 1) 549 | return @intCast(pc); 550 | assert(!self.is_breakpoint); 551 | } 552 | } 553 | 554 | // Print '%' just in case program didn't print a newline, then print a bunch of 555 | // spaces if a newline was output; the spaces will not stay on a line and we 556 | // can output a carriage return to get back to the start of the line. 557 | // Otherwise, the spaces will wrap to the next line, where we can safely 558 | // carriage return to the start of the line. 559 | // 560 | // Stolen from a blogpost describing this same feature in the fish/zsh shells 561 | // 562 | fn _printHappyPercent(stderr: anytype) void { 563 | if (@import("builtin").os.tag == .linux) { 564 | var wsz: std.os.linux.winsize = undefined; 565 | const fd = @as(usize, @bitCast(@as(isize, 1))); 566 | const rc = linux.syscall3(.ioctl, fd, linux.T.IOCGWINSZ, @intFromPtr(&wsz)); 567 | const columns = switch (std.posix.errno(rc)) { 568 | .SUCCESS => @as(usize, @intCast(wsz.ws_col)), 569 | .INTR => return, // F you linux 570 | else => return, // Not a tty, stick head in sand immediately 571 | }; 572 | stderr.print("\x1b[7m%\x1b[m", .{}) catch unreachable; 573 | stderr.writeByteNTimes(' ', columns - 1) catch unreachable; 574 | stderr.print("\r", .{}) catch unreachable; 575 | } 576 | } 577 | -------------------------------------------------------------------------------- /std/alloc.finw: -------------------------------------------------------------------------------- 1 | (use* core) 2 | (use* varvara) 3 | (use std) 4 | (use dampe) 5 | 6 | // I wanted to have U16 guards, that way we could have 0x1cedc0fe 7 | // 8 | // Sadly that blows up the size to 9 bytes, so we have to settle with 9 | // just lukewarm 0xc0fe 10 | // 11 | #private 12 | (struct Header 13 | [guard1 U8] 14 | [prev @Header] 15 | [next @Header] 16 | [free Bool] 17 | [guard2 U8] 18 | ) 19 | 20 | (word first (-- @Header) [ here (as @Header) ]) 21 | 22 | (word init ( -- ) [ 23 | 0s (as @Header) 24 | first :prev <- 25 | 0xFFFFs (as @Header) 26 | first :next <- 27 | T first :free <- 28 | 0xc0 first :guard1 <- 29 | 0xfe first :guard2 <- 30 | first (sizeof Header) (as U16) dampe/protect 31 | ]) 32 | 33 | // ( block-count oldptr block-type -- (sizeof block-type)*block-count ) 34 | (word re ( U16 @$0 Type -- (Of std/Maybe0 @$0) ) [ 35 | swap (sizeof $0) (as U16) * swap reget 36 | :value (make (Of std/Maybe0 @$0)) 37 | ]) 38 | 39 | // ( block-count block-type -- (sizeof block-type)*block-count ) 40 | (word for ( U16 Type -- (Of std/Maybe0 @$0) ) [ 41 | (sizeof $0) (as U16) * get 42 | :value (make (Of std/Maybe0 @$0)) 43 | ]) 44 | 45 | // ( bytes -- *ptr ) 46 | (word get ( U16 -- (Of std/Maybe0 @Opaque) ) [ 47 | (let size U16) 48 | @size <- 49 | 50 | first 51 | (while [ 52 | dup :free -> not 53 | swap-sb chunk-size $size < 54 | or 55 | ] [ 56 | :next -> 57 | dup 0xFFFFs = (when [ 58 | drop 59 | 0s (as (Of std/Maybe0 @Opaque)) 60 | return 61 | ]) 62 | ]) 63 | 64 | (let chosen @Header) 65 | @chosen <- 66 | dampe/priv-enter 67 | nil $chosen :free <- 68 | dampe/priv-exit 69 | 70 | $chosen chunk-size $size - 71 | (sizeof Header) (as U16) 1+ 72 | < not (when [ 73 | dampe/priv-enter 74 | 75 | (let next @Header) 76 | (sizeof Header) (as U16) $size $chosen + + @next <- 77 | 78 | $chosen $next :prev <- 79 | $chosen :next -> $next :next <- 80 | T $next :free <- 81 | $next $chosen :next -> :prev <- 82 | $next $chosen :next <- 83 | 0xc0 $next :guard1 <- 84 | 0xfe $next :guard2 <- 85 | $chosen dampe/assert-prot 86 | $next (sizeof Header) (as U16) dampe/protect 87 | 88 | dampe/priv-exit 89 | ]) 90 | 91 | $chosen (sizeof Header) (as @Opaque) + 92 | (make (Of std/Maybe0 @Opaque)) 93 | ]) 94 | (test alloc/alloc_dealloc_basic [ 95 | init 96 | 97 | 100s get ;unwrap 98 | dup 0 100s rot-sbs std/memset8 99 | de 100 | 002s get ;unwrap 101 | dup (as @U16) 0xAAs -< 102 | de 103 | 104 | (let ptrs [@Opaque 100]) 105 | 100s (until [ 0= ] [ 106 | 100s get ;unwrap 107 | dup 0 100s rot-sbs std/memset8 108 | over @ptrs : <- 109 | 1- 110 | ]) drop 111 | 100s (until [ 0= ] [ 112 | dup @ptrs : -> de 113 | 1- 114 | ]) drop 115 | ]) 116 | 117 | // ( bytes oldptr -- Maybe ) 118 | (word reget (U16 @Opaque -- (Of std/Maybe0 @Opaque)) [ 119 | (as @U8) 120 | dup get-header chunk-size 121 | rot 122 | // ( old old-sz new-sz ) 123 | tuck min swap 124 | // ( old min(old-sz,new-sz) new-sz ) 125 | get :value dup 0= (when [ nip nip (make (Of std/Maybe0 @Opaque)) return ]) 126 | // ( old min-sz new-unwrapped ) 127 | rot copy swap 128 | // ( min-sz old new ) 129 | (as @U8) std/memcpy (make (Of std/Maybe0 @Opaque)) 130 | (r move) de 131 | ]) 132 | (test alloc/reget_copies_correctly [ 133 | init 134 | 135 | "Foo" 136 | dup std/strlen tuck get ;unwrap (as @U8) std/memcpy 137 | 100s swap reget ;unwrap (as @U8) 138 | dup "Foo" std/strequ (should eq t) 139 | 140 | 1s swap reget ;unwrap (as @U8) 141 | dup :0 -> (should eq 'F) 142 | dup :1 -> (should eq 'o) 143 | drop 144 | ]) 145 | 146 | (word de (AnyPtr16 -- ) [ 147 | dup get-header 148 | 149 | //dup :guard1 -> 0xc0 <> 150 | //over-sb :guard2 -> 0xfe <> or 151 | //(when [ "Invalid pointer" error-dealloc ]) 152 | dup :free -> 153 | (when [ "Double-free" error-dealloc ]) 154 | 155 | dampe/priv-enter 156 | T swap-sb :free <- 157 | dampe/priv-exit 158 | drop 159 | ]) 160 | 161 | // ( chunk chunk-header error-string ) 162 | #private #noreturn 163 | (word error-dealloc ( AnyPtr16 AnyPtr16 @[U8] -- ) [ 164 | print-string ": " print-string 165 | swap print 166 | " (Header: " print-string print ")\n" print-string 167 | print-chunks 168 | "Deallocation error" std/panic 169 | ]) 170 | 171 | (word get-header (@Opaque -- @Header) [ 172 | (sizeof Header) (as U16) - (as @Header) 173 | ]) 174 | 175 | (word chunk-size (@Header -- U16) [ 176 | dup :next -> swap - 177 | (sizeof Header) (as U16) - 178 | ]) 179 | 180 | (word defrag (--) [ 181 | first 182 | (until [ 0xFFFFs = ] [ 183 | dup :free -> 184 | over-sb :next -> :free -> and 185 | (when [ 186 | dampe/priv-enter 187 | dup :next -> 188 | dup dampe/unprotect 189 | 190 | // current->next = next->next 191 | // ( current next ) 192 | over :next 193 | // ( current next @current->next ) 194 | over :next -> swap <- 195 | 196 | // ( current next ) 197 | dup 0xFFFFs = (when [ "Assertion failed" std/panic ]) 198 | 199 | // ( current next ) 200 | :next -> 201 | // ( current $next->next ) 202 | :prev 203 | // ( current $next->next->prev ) 204 | over swap <- 205 | // ( current ) 206 | 207 | dampe/priv-exit 208 | ] [ :next -> ]) 209 | ]) drop 210 | ]) 211 | 212 | (word print-chunk (@Header -- ) [ 213 | dup print " \t" print-string 214 | dup :free -> print-bool "\t" print-string 215 | dup :prev -> print " \t" print-string 216 | dup :next -> print " \t" print-string 217 | dup chunk-size print-dec "\t" print-string 218 | //dup :guard1 -> print "\t" print-string 219 | //dup :guard2 -> print 220 | nl 221 | drop 222 | ]) 223 | 224 | (word print-chunks (--) [ 225 | "Chunk\tFree\tPrev\tNext\tSize\tG1\tG2\n" print-string 226 | 227 | first 228 | (until [ 0xFFFFs = ] [ 229 | dup print-chunk 230 | :next -> 231 | ]) drop 232 | 233 | nl 234 | ]) 235 | 236 | (word panic-oom (--) [ 237 | "Out of memory!\n" print-string 238 | print-chunks 239 | "Please close a few Chrome tabs. Oh wait" std/panic 240 | ]) 241 | 242 | (test alloc/defrag [ 243 | init 244 | 245 | (let ptrs [@Opaque 4]) 246 | 247 | 107s get ;unwrap drop 248 | 107s get ;unwrap @ptrs :0 (as @@Opaque) <- 249 | 10s get ;unwrap @ptrs :1 (as @@Opaque) <- 250 | 26s get ;unwrap @ptrs :2 (as @@Opaque) <- 251 | 2s get ;unwrap drop 252 | 30s get ;unwrap drop 253 | 30s get ;unwrap @ptrs :3 (as @@Opaque) <- 254 | 255 | @ptrs :0 -> (as @Opaque) de 256 | @ptrs :1 -> (as @Opaque) de 257 | @ptrs :2 -> (as @Opaque) de 258 | @ptrs :3 -> (as @Opaque) de 259 | 260 | // XXX: will need to update if (sizeof Header) changes 261 | 262 | first 263 | dup :free -> (should eq nil) dup chunk-size (should eq 107s) :next -> 264 | dup :free -> (should eq t) dup chunk-size (should eq 107s) :next -> 265 | dup :free -> (should eq t) dup chunk-size (should eq 10s) :next -> 266 | dup :free -> (should eq t) dup chunk-size (should eq 26s) :next -> 267 | dup :free -> (should eq nil) dup chunk-size (should eq 2s) :next -> 268 | dup :free -> (should eq nil) dup chunk-size (should eq 30s) :next -> 269 | dup :free -> (should eq t) dup chunk-size (should eq 30s) :next -> 270 | dup :free -> (should eq t) 271 | :next -> (as U16) (should eq 0xFFFFs) 272 | 273 | defrag 274 | 275 | first 276 | dup :free -> (should eq nil) dup chunk-size (should eq 107s) :next -> 277 | dup :free -> (should eq t) dup chunk-size (should eq 157s) :next -> 278 | dup :free -> (should eq nil) dup chunk-size (should eq 2s) :next -> 279 | dup :free -> (should eq nil) dup chunk-size (should eq 30s) :next -> 280 | dup :free -> (should eq t) 281 | :next -> (as U16) (should eq 0xFFFFs) 282 | ]) 283 | -------------------------------------------------------------------------------- /std/core.finw: -------------------------------------------------------------------------------- 1 | (typealias AnySigned (AnySet I8 I16)) 2 | 3 | #inline (word do [ (asm "s" .Op/Ojsr) ]) 4 | #inline (word dip [ swap move do (r move) ]) 5 | #inline (word sip [ swap-bs copy swap-sb do (r move) ]) 6 | //#inline (word bi [ [sip] dip do ]) 7 | //#inline (word bi* [ [dip] dip do ]) 8 | //#inline (word bi& [ dup bi* ]) 9 | 10 | // do ( AnyFn -- (Effects $0 [...])) 11 | // sip ( Any AnyFn -- (Effects $0 [ $1 ... ]) $1) 12 | 13 | (test do [ 14 | [ (-- U8 U8 U8) 1 2 3 ] do 15 | (should eq 3) (should eq 2) (should eq 1) 16 | ]) 17 | 18 | (test sip [ 19 | 12 [ (U8 -- U8) 2 * ] sip 20 | (should eq 12) (should eq 24) 21 | ]) 22 | 23 | //(test bi& [ 24 | // 2 8 [ dup * ] bi& 25 | // (should eq 64) (should eq 4) 26 | //]) 27 | 28 | #inline (word copy ( Any -- $0 | -- $0 ) [ (asm "kg" .Op/Osth) ]) 29 | #inline (word move ( Any -- | -- $0 ) [ (asm "g" .Op/Osth) ]) 30 | 31 | #noreturn 32 | #inline (word halt ( -- ) [ (asm "" .Op/Obrk) ]) 33 | 34 | (word lda ( AnyPtr16 -- (Child $0) ) [ (asm "g" .Op/Olda) (as (Child $0)) ]) 35 | (word ldak ( AnyPtr16 -- $0 (Child $0)) [ (asm "kg" .Op/Olda) ]) 36 | (word sta ( Any AnyPtr16 -- ) [ (asm "g" .Op/Osta) ]) 37 | (word stak ( Any AnyPtr16 -- $1 $0 ) [ (asm "kg" .Op/Osta) ]) 38 | (word <- ((Child $0) AnyPtr16 -- ) [ sta ]) 39 | (word -< (AnyPtr16 (Child $1) -- ) [ swap sta ]) 40 | (word -> (AnyPtr16 -- (Child $0) ) [ lda ]) 41 | 42 | (word nip ((AnySz $0) Any -- $0 ) [ (asm "g" .Op/Onip) ]) 43 | (word dup ( Any -- $0 $0 ) [ (asm "g" .Op/Odup) ]) 44 | (word swap ((AnySz $0) Any -- $0 $1 ) [ (asm "g" .Op/Oswp) ]) 45 | (word drop ( Any -- ) [ (asm "g" .Op/Opop) ]) 46 | (word 2drop ( Any Any -- ) [ drop drop ]) 47 | (word 3drop ( Any Any Any -- ) [ drop drop drop ]) 48 | (word over ((AnySz $0) Any -- $1 $0 $1 ) [ (asm "g" .Op/Oovr) ]) 49 | (word 2dup ((AnySz $0) Any -- $1 $0 $1 $0 ) [ over over ]) 50 | 51 | (word rot ((AnySz $0) (AnySz $0) Any -- $1 $0 $2) [ (asm "g" .Op/Orot) ]) 52 | (word rot> ((AnySz $0) (AnySz $0) Any -- $0 $2 $1) [ rot rot ]) 53 | (word tuck ((AnySz $0) Any -- $0 $1 $0 ) [ swap over ]) 54 | (word tuck> ((AnySz $0) Any -- $1 $1 $0 ) [ over swap ]) 55 | 56 | //(word merge (Any8 Any8 Type -- $0 ) [ (wild ($2 $1 -- $0) []) ]) 57 | 58 | (word sext (I8 -- I16) [ 59 | dup 0x7f > (as U8) 0xff * swap (wild (U8 I8 -- I16) []) 60 | ]) 61 | 62 | 63 | // TODO (waitfor dumper) [rot signb swap signb <> ...] might be smaller than 64 | // using ret stack 65 | 66 | (word < ( $0 AnySigned -- Bool ) [ 67 | 2dup (as (USz $0) (USz $0)) < move 68 | signb swap signb <> 69 | (r move) swap 70 | (when [ not ]) 71 | ]) 72 | 73 | (word > ( $0 AnySigned -- Bool ) [ 74 | 2dup (as (USz $0) (USz $0)) > move 75 | signb swap signb <> 76 | (r move) swap 77 | (when [ not ]) 78 | ]) 79 | 80 | // https://wiki.xxiivv.com/site/uxntal_signed.html 81 | (word abssign ( AnySigned -- (USz $0) (USz $0) ) [ 82 | dup neg? (as (USz $0)) 0xfffes (as (USz $0)) * 1+ 83 | copy * (r move) 84 | ]) 85 | 86 | 87 | // Ported from XXIIVV 88 | // https://wiki.xxiivv.com/site/uxntal_signed.html 89 | (word / ($0 AnySigned -- $0) [ 90 | // Ported from XXIIVV 91 | abssign move swap 92 | abssign move swap 93 | / (r [* move]) * (as $0) 94 | ]) 95 | 96 | (word band ((AnySz $0) Any -- $0 ) [ (asm "g" .Op/Oand) ]) 97 | (word bor ((AnySz $0) Any -- $0 ) [ (asm "g" .Op/Oora) ]) 98 | (word beor ((AnySz $0) Any -- $0 ) [ (asm "g" .Op/Oeor) ]) 99 | (word bsft ( Any U8 -- (USz $1) ) [ (asm "g" .Op/Osft) ]) 100 | // TODO: warn if bshr/bshl arg is higher than 7 101 | // #(warn arg-gt $0 7) 102 | (word bshr ( Any U8 -- $1 ) [ bsft ]) 103 | (word bnot ( Any -- $0 ) [ 0xFFFFs (as $0) beor ]) 104 | (word = ((AnySz $0) Any -- Bool ) [ (asm "g" .Op/Oequ) ]) 105 | (word <> ((AnySz $0) Any -- Bool ) [ (asm "g" .Op/Oneq) ]) 106 | (word nil? ( Bool -- Bool ) [ nil = ]) 107 | (word T? ( Bool -- Bool ) [ T = ]) 108 | (word < ((AnySz $0) Any -- Bool ) [ (asm "g" .Op/Olth) ]) 109 | (word > ((AnySz $0) Any -- Bool ) [ (asm "g" .Op/Ogth) ]) 110 | (word <= ((AnySz $0) Any -- Bool ) [ > not ]) 111 | (word >= ((AnySz $0) Any -- Bool ) [ < not ]) 112 | (word + ((AnySz $0) Any -- $0 ) [ (asm "g" .Op/Oadd) ]) 113 | (word 1+ ( Any -- $0 ) [ (asm "g" .Op/Oinc) ]) 114 | (word 1- ( Any -- $0 ) [ 1 (as $0) - ]) 115 | (word - ((AnySz $0) Any -- $0 ) [ (asm "g" .Op/Osub) ]) 116 | (word / ((AnySz $0) Any -- $0 ) [ (asm "g" .Op/Odiv) ]) 117 | (word mod ((AnySz $0) Any -- $0 ) [ 2dup / * - ]) 118 | (word /mod ((AnySz $0) Any -- $0 $0 ) [ 2dup / rot> mod ]) 119 | (word * ((AnySz $0) Any -- $0 ) [ (asm "g" .Op/Omul) ]) 120 | (word 0= ( Any -- Bool ) [ 0 (as $0) = ]) 121 | (word 0<> ( Any -- Bool ) [ 0 (as $0) <> ]) 122 | (word 0< ( Any -- Bool ) [ 0 (as $0) < ]) 123 | (word 0> ( Any -- Bool ) [ 0 (as $0) > ]) 124 | (word signb ( I8 -- U8 ) [ (as U8) 0xf0 band ]) 125 | (word signb ( I16 -- U16 ) [ (as U16) 0xf000s band ]) 126 | (word sign ( I8 -- U8 ) [ signb 7 bshr ]) 127 | (word sign ( I16 -- U8 ) [ (split I8 U8) drop sign ]) 128 | // TODO: (max ) builtin 129 | (word neg? ( I8 -- Bool ) [ 0x7f > ]) 130 | (word neg? ( I16 -- Bool ) [ 0x7fffs > ]) 131 | (word min ( $0 Any -- $0 ) [ 2dup >= (when [ swap ]) drop ]) 132 | (word max ( $0 Any -- $0 ) [ 2dup <= (when [ swap ]) drop ]) 133 | 134 | (test min [ 135 | 0 1 min (should eq 0) 136 | 0 0xFF min (should eq 0) 137 | 23 18 min (should eq 18) 138 | -1i 38i min (should eq -1i) 139 | -1i -38i min (should eq -38i) 140 | ]) 141 | 142 | (test max [ 143 | 0 1 max (should eq 1) 144 | 0 0xFF max (should eq 0xFF) 145 | 23 18 max (should eq 23) 146 | -1i -38i max (should eq -1i) 147 | ]) 148 | 149 | (test sext [ 150 | 0i sext (should eq 0is) 151 | 1i sext (should eq 1is) 152 | 114i sext (should eq 114is) 153 | -18i sext (should eq -18is) 154 | -8i sext (should eq -8is) 155 | -127i sext (should eq -127is) 156 | -1i sext (should eq -1is) 157 | ]) 158 | 159 | (test <_signed [ 160 | -1i 1i < (should eq t) 161 | 1i -1i < (should eq nil) 162 | 1i 1i < (should eq nil) 163 | -121i 121i < (should eq t) 164 | 121i -121i < (should eq nil) 165 | ]) 166 | 167 | (word abs ( I16 -- U16 ) [ dup dup 0x1f bsft * - ]) 168 | (word abs ( I8 -- U8 ) [ dup dup 0x17 bsft * - ]) 169 | 170 | // TODO: optimize (xor by sign bit? idk) 171 | //(word abs ( I8 -- (USz $0) ) [ 172 | // dup sign 0= (when [(as (USz $0)) return]) 173 | // (as (USz $0)) bnot 1+ 174 | //]) 175 | 176 | (test abs [ 177 | -1i abs (should eq 1) 178 | 1i abs (should eq 1) 179 | -10i abs (should eq 10) 180 | -20i abs (should eq 20) 181 | -125i abs (should eq 125) 182 | 125i abs (should eq 125) 183 | -22891is abs (should eq 22891s) 184 | -1is abs (should eq 1s) 185 | 22891is abs (should eq 22891s) 186 | 125is abs (should eq 125s) 187 | ]) 188 | 189 | // TODO: this is stupid... needs to be optimized 190 | // (Probably bake or/and into language to ensure short-circuiting) 191 | (word not ( Bool -- Bool ) [ (when [ nil ] [ t ]) ]) 192 | (word or ( Bool Bool -- Bool ) [ (when [ drop t ]) ]) 193 | (word and ( Bool Bool -- Bool) [ (when [ (when [t] [ nil ]) ] [ drop nil ]) ]) 194 | 195 | (test or_basic [ 196 | t nil or (should eq T) 197 | nil t or (should eq T) 198 | t t or (should eq T) 199 | nil nil or (should eq nil) 200 | ]) 201 | 202 | (test and_basic [ 203 | t nil and (should eq nil) 204 | nil t and (should eq nil) 205 | t t and (should eq T) 206 | nil nil and (should eq nil) 207 | ]) 208 | 209 | (word rot-sbb (Any16 Any8 Any8 -- $1 $0 $2) [ 210 | (wild ($2 $1 $0 -- $1 $0 $2) [ (asm "s" .Op/Oswp) ]) 211 | ]) 212 | 213 | // STH2 ROT ROT STHrs SWP2 214 | (word rot-sbs (Any16 Any8 Any16 -- $1 $0 $2) [ 215 | // sa0 sa1 bt sb0 sb1 216 | (wild (--) [ (asm "s" .Op/Osth) ]) // sa0 sa1 bt | sb0 sb1 217 | (wild (--) [ (asm "" .Op/Orot) ]) // sa1 bt sa0 | sb0 sb1 218 | (wild (--) [ (asm "" .Op/Orot) ]) // bt sa0 sa1 | sb0 sb1 219 | (wild (--) [ (asm "rs" .Op/Osth) ]) // bt sb0 sb1 sa0 sa1 220 | (wild (--) [ (asm "s" .Op/Oswp) ]) // bt sa0 sa1 sb0 sb1 221 | (wild ($2 $1 $0 -- $1 $0 $2) []) 222 | ]) 223 | 224 | (word rot-bss (Any8 Any16 Any16 -- $1 $0 $2) [ 225 | // bt sa0 sa1 sb0 sb1 226 | (wild (--) [ (asm "s" .Op/Osth) ]) // bt sa0 sa1 | sb0 sb1 227 | (wild (--) [ (asm "" .Op/Orot) ]) // sa0 sa1 bt | sb0 sb1 228 | (wild (--) [ (asm "rs" .Op/Osth) ]) // sa0 sa1 bt sb0 sb1 229 | (wild (--) [ (asm "" .Op/Orot) ]) // sa0 sa1 sb0 sb1 bt 230 | (wild ($2 $1 $0 -- $1 $0 $2) []) 231 | ]) 232 | 233 | (word rot-bss> (Any8 Any16 Any16 -- $0 $2 $1) [ 234 | // bt sa0 sa1 sb0 sb1 235 | (wild (--) [ (asm "s" .Op/Oswp) ]) // bt sb0 sb1 sa0 sa1 236 | (wild (--) [ (asm "s" .Op/Osth) ]) // bt sb0 sb1 | sa0 sa1 237 | (wild (--) [ (asm "" .Op/Orot) ]) // sb0 sb1 bt | sa0 sa1 238 | (wild (--) [ (asm "rs" .Op/Osth) ]) // sb0 sb1 bt sa0 sa1 239 | (wild ($2 $1 $0 -- $0 $2 $1) []) 240 | ]) 241 | 242 | (word over-sb (Any16 Any8 -- $1 $0 $1) [ 243 | (wild (--) [ 0 ]) 244 | (wild (--) [ (asm "s" .Op/Oovr) ]) 245 | (wild (--) [ (asm "" .Op/Orot) ]) 246 | (wild (--) [ (asm "" .Op/Opop) ]) 247 | (wild ($1 $0 -- $1 $0 $1) []) 248 | ]) 249 | 250 | (word over-bs (Any8 Any16 -- $1 $0 $1) [ 251 | // b s0 s1 252 | (wild (--) [ (asm "k" .Op/Orot) ]) // b s0 s1 s0 s1 b 253 | (wild (--) [ (asm "" .Op/Onip) ]) // b s0 s1 s0 b 254 | (wild (--) [ (asm "" .Op/Onip) ]) // b s0 s1 b 255 | (wild ($1 $0 -- $1 $0 $1) []) 256 | ]) 257 | 258 | (word swap-sb (Any16 Any8 -- $0 $1) [ 259 | (wild (--) [ (asm "" .Op/Orot) ]) 260 | (wild (--) [ (asm "" .Op/Orot) ]) 261 | (wild ($1 $0 -- $0 $1) []) 262 | ]) 263 | 264 | (word swap-bs (Any8 Any16 -- $0 $1) [ 265 | (wild ($1 $0 -- $0 $1) [ (asm "" .Op/Orot) ]) 266 | ]) 267 | 268 | (test rot-sbb [ 2s 1 0 rot-sbb (should eq 2s) (should eq 0) (should eq 1) ]) 269 | (test rot-sbs [ 2s 1 0s rot-sbs (should eq 2s) (should eq 0s) (should eq 1) ]) 270 | (test rot-bss [ 2 1s 0s rot-bss (should eq 2) (should eq 0s) (should eq 1s) ]) 271 | (test rot-bss> [ 2 1s 0s rot-bss> (should eq 1s) (should eq 2) (should eq 0s) ]) 272 | (test over-sb [ 999s 7 over-sb (should eq 999s) (should eq 7) (should eq 999s) ]) 273 | (test over-bs [ 7 999s over-bs (should eq 7) (should eq 999s) (should eq 7) ]) 274 | (test swap-sb [ 0xffabs 0xba swap-sb (should eq 0xffabs) (should eq 0xba) ]) 275 | (test swap-bs [ 0xba 0xffabs swap-bs (should eq 0xba) (should eq 0xffabs) ]) 276 | -------------------------------------------------------------------------------- /std/dampe.finw: -------------------------------------------------------------------------------- 1 | (use* core) 2 | (use* varvara) 3 | 4 | // 5 | // dampe 6 | // debugging and memory protection extension 7 | // 8 | 9 | // Dampe command (for .System/expansion) 10 | // Must be kept up-to-date with VM code 11 | (struct DampeCmd 12 | [id U8] // Always 0x4[0..5] 13 | [ptr @Opaque] // Pointer to region 14 | [len U16] // Size of region, only used for protect command 15 | [_reserved U16] // For future use 16 | ) 17 | 18 | (let c DampeCmd) 19 | 20 | #dampe (word backtrace ( --) [ 0x40 @c :id <- ->expan ]) 21 | #dampe (word protect (AnyPtr16 U16 --) [ 0x41 @c :id <- @c :len <- @c :ptr <- ->expan ]) 22 | #dampe (word unprotect ( AnyPtr16 --) [ 0x42 @c :id <- @c :ptr <- ->expan ]) 23 | #dampe (word priv-enter ( --) [ 0x43 @c :id <- ->expan ]) 24 | #dampe (word priv-exit ( --) [ 0x44 @c :id <- ->expan ]) 25 | #dampe (word assert-prot ( AnyPtr16 --) [ 0x45 @c :id <- @c :ptr <- ->expan ]) 26 | 27 | #private (word ->expan ( -- ) [ @c .System/expansion deo ]) 28 | 29 | (word backtrace ( --) [ ]) 30 | (word protect (AnyPtr16 U16 --) [ 2drop ]) 31 | (word unprotect ( AnyPtr16 --) [ drop ]) 32 | (word priv-enter ( --) [ ]) 33 | (word priv-exit ( --) [ ]) 34 | (word assert-prot ( AnyPtr16 --) [ drop ]) 35 | -------------------------------------------------------------------------------- /std/math.finw: -------------------------------------------------------------------------------- 1 | (use* core) 2 | (use std) 3 | 4 | (use* varvara) 5 | 6 | (word +safe ((AnySz $0) Any -- $0 Bool) [ tuck + tuck <= ]) 7 | 8 | (test +safe [ 9 | 1 1 +safe (should eq t) (should eq 2) 10 | 9 9 +safe (should eq t) (should eq 18) 11 | 12 | 0xFF 0xFF +safe (should eq nil) (should eq 0xFE) 13 | 0xFF 1 +safe (should eq nil) (should eq 0) 14 | 0xFF 0 +safe (should eq t) (should eq 0xFF) 15 | 1 0xFF +safe (should eq nil) (should eq 0) 16 | 0 0xFF +safe (should eq t) (should eq 0xFF) 17 | 18 | 0xFF (while [ 0<> ] [ 19 | dup 0xFF +safe (should eq nil) drop 20 | dup 0xFF swap +safe (should eq nil) drop 21 | 1- 22 | ]) 23 | ]) 24 | -------------------------------------------------------------------------------- /std/net.finw: -------------------------------------------------------------------------------- 1 | (use* core) 2 | 3 | // TODO: mark as non-exhaustive (need new language feature) 4 | // 5 | // (for documentation purposes?) 6 | // (any enum used in device should be marked as such -- nothing's stopping 7 | // the emulator from putting an invalid value in there!) 8 | // 9 | (enum Status I8 10 | [ok-retry 2i] 11 | [ok 1i] 12 | [ok-no-data 0i] 13 | [err-tls-config -1i] 14 | [err-tls-init -2i] 15 | [err-tls-configure -1i] 16 | [err-resolve -3i] 17 | [err-connect -4i] 18 | [err-tls-upgrade -5i] 19 | [err-tls-handshake -6i] 20 | [err-system -7i] 21 | [err-not-inited -8i] 22 | [err-unknown -9i] 23 | [err-no-tls -9i] 24 | ) 25 | 26 | #(method Status) 27 | (word to-string (Status -- @U8) [ 28 | (cond 29 | [ .Status/ok-retry = ] [ "success (retry)" (as @U8) ] 30 | [ .Status/ok = ] [ "success" (as @U8) ] 31 | [ .Status/ok-no-data = ] [ "success (no data)" (as @U8) ] 32 | [ .Status/err-tls-config = ] [ "tls config failed" (as @U8) ] 33 | [ .Status/err-tls-init = ] [ "tls init failed" (as @U8) ] 34 | [ .Status/err-tls-configure = ] [ "tls configure failed" (as @U8) ] 35 | [ .Status/err-resolve = ] [ "couldn't resolve host" (as @U8) ] 36 | [ .Status/err-connect = ] [ "couldn't connect" (as @U8) ] 37 | [ .Status/err-tls-upgrade = ] [ "tls socket upgrade failed" (as @U8) ] 38 | [ .Status/err-tls-handshake = ] [ "tls handshake failed" (as @U8) ] 39 | [ .Status/err-system = ] [ "system error" (as @U8) ] 40 | [ .Status/err-not-inited = ] [ "connection doesn't exist" (as @U8) ] 41 | [ .Status/err-unknown = ] [ "unknown error" (as @U8) ] 42 | [ .Status/err-no-tls = ] [ "tls is unsupported by emu" (as @U8) ] 43 | [ "invalid status" (as @U8) ] 44 | ) 45 | swap-bs drop 46 | ]) 47 | 48 | (struct ConnectArgs 49 | [tls? Bool] // todo 50 | [port U16] 51 | [host @U8] 52 | ) 53 | 54 | (device Net 0xd0 55 | [_reserved1 @Opaque] 56 | [current U8] 57 | [length U16] 58 | [connect @ConnectArgs] 59 | [send @U8] 60 | [recv @U8] 61 | [close U8] 62 | [_reserved2 U8] 63 | [_reserved3 @Opaque] 64 | [status I8] // Status 65 | ) 66 | -------------------------------------------------------------------------------- /std/std.finw: -------------------------------------------------------------------------------- 1 | (use* core) 2 | (use* varvara) 3 | 4 | (use dampe) 5 | 6 | (struct Maybe0 (Any) [value $0])//{{{ 7 | 8 | #(method Maybe0) (word maybe? ((AnyOf Maybe0) -- Bool) [ 9 | :value 0<> 10 | ]) 11 | 12 | #(method Maybe0) (word maybe-not? ((AnyOf Maybe0) -- Bool) [ 13 | :value 0= 14 | ]) 15 | 16 | #(method Maybe0) (word unwrap ((AnyOf Maybe0) -- (FieldType $0 value)) [ 17 | :value 18 | dup 0= (when [ "Unwrapped 0 value" panic ]) 19 | ]) 20 | 21 | #(method Maybe0) (word orelse ( 22 | (FieldType $0 value) (AnyOf Maybe0) -- (FieldType $0 value) 23 | ) [ 24 | :value dup 0= (when [ drop ] [ nip ]) 25 | ]) 26 | 27 | (test Maybe0/maybe? [ 0 (make (Of Maybe0 U8)) ;maybe? (should eq nil) ]) 28 | (test Maybe0/maybe-not? [ 0 (make (Of Maybe0 U8)) ;maybe-not? (should eq t) ]) 29 | (test Maybe0/orelse [ 0 (make (Of Maybe0 U8)) 12 swap ;orelse (should eq 12) ]) 30 | //}}} 31 | 32 | // MaybeFF {{{ 33 | // Magic value is 0xFFFF for 2-byte types, 0xFF for 1-byte types 34 | // 35 | (struct MaybeFF (Any) [value $0]) 36 | 37 | #(method MaybeFF) (word maybe? ((AnyOf MaybeFF) -- Bool) [ 38 | :value (as (USz (FieldType $0 value))) 39 | 0xFFFFs (as (USz (FieldType $0 value))) <> 40 | ]) 41 | 42 | #(method MaybeFF) (word maybe-not? ((AnyOf MaybeFF) -- Bool) [ 43 | :value (as (USz (FieldType $0 value))) 44 | 0xFFFFs (as (USz (FieldType $0 value))) = 45 | ]) 46 | 47 | #(method MaybeFF) (word unwrap ((AnyOf MaybeFF) -- (FieldType $0 value)) [ 48 | :value dup 49 | 0xFFFFs (as (FieldType $0 value)) = (when [ "Unwrapped FF value" panic ]) 50 | ]) 51 | 52 | #(method MaybeFF) (word orelse ( (FieldType $0 value) (AnyOf MaybeFF) -- (FieldType $0 value)) [ 53 | :value dup 0xFFFFs (as (FieldType $0 value)) = (when [ drop ] [ nip ]) 54 | ]) 55 | 56 | (test MaybeFF/maybe? [ 57 | 0 (make (Of MaybeFF U8)) ;maybe? (should eq t) 58 | 0xFF (make (Of MaybeFF U8)) ;maybe? (should eq nil) 59 | 0xFFs (make (Of MaybeFF U16)) ;maybe? (should eq t) 60 | ]) 61 | 62 | (test MaybeFF/maybe-not? [ 0xFF (make (Of MaybeFF U8)) ;maybe-not? (should eq t) ]) 63 | (test MaybeFF/orelse [ 0xFF (make (Of MaybeFF U8)) 12 swap ;orelse (should eq 12) ]) 64 | //}}} 65 | 66 | (word itoa10 (AnySigned @U8 --) [ 67 | move 68 | dup sign 0<> (when [ 69 | '- (r copy) <- (r 1+) 70 | abs 71 | ] [ (as (USz $1)) ]) 72 | (r move) 73 | itoa10 74 | ]) 75 | 76 | (word itoa10 (Any @U8 --) [ 77 | move 78 | dup 9 (as $1) > (when [ (r 1+) ]) 79 | dup 99 (as $1) > (when [ (r 1+) ]) 80 | (until [ 0= ] [ 81 | 10 (as $1) /mod 82 | (as U8) '0 + (r copy) <- 83 | // TODO: replace with (r 1-) when bug is fixed 84 | (r [ 1s - (as @U8) ]) 85 | ]) drop 86 | (r drop) 87 | ]) 88 | (test itoa10 [ 89 | (let buf [U8 6]) 90 | 91 | 0 6s @buf memset8 92 | 3 @buf itoa10 @buf print-string (should stdout-eq "3") 93 | 94 | 0 6s @buf memset8 95 | -79i @buf itoa10 @buf print-string (should stdout-eq "-79") 96 | 97 | 0 6s @buf memset8 98 | 918s @buf itoa10 @buf print-string (should stdout-eq "918") 99 | 100 | 0 6s @buf memset8 101 | 100s @buf itoa10 @buf print-string (should stdout-eq "100") 102 | ]) 103 | 104 | // ( src dst -- dst[src] ) 105 | (word strcat ( @[Char8] @[Char8] -- @[Char8] ) [ 106 | (while [ (-- Bool) ldak 0<> ] [ 1+ ]) 107 | move 108 | ldak (while [ 0<> ] [ (r copy) <- (r 1+) 1+ ldak ]) drop 109 | 0 (r copy) <- 110 | drop (r move) 111 | ]) 112 | (test strcat [ 113 | (let buf [U8 24]) 114 | 115 | 0 24s @buf memset8 116 | "foo" @buf strcat drop @buf print-string (should stdout-eq "foo") 117 | "bar" @buf strcat drop @buf print-string (should stdout-eq "foobar") 118 | 119 | 0 24s @buf memset8 120 | "" @buf strcat drop @buf print-string (should stdout-eq "") 121 | "hi" @buf strcat drop @buf print-string (should stdout-eq "hi") 122 | "" @buf strcat drop @buf print-string (should stdout-eq "hi") 123 | 124 | 0 24s @buf memset8 125 | "C" @buf strcat "++" swap strcat drop 126 | @buf print-string (should stdout-eq "C++") 127 | 128 | 0 24s @buf memset8 129 | // Test that it adds the null terminator 130 | "foo\0aaaa" @buf strcat drop @buf print-string (should stdout-eq "foo") 131 | ]) 132 | 133 | // FIXME: hundred ways to optimize this, see ;scmp from left project 134 | // Probably have to wait for (r)/move/copy and friends though. 135 | // 136 | (word strequ ( @[Char8] @[Char8] -- Bool ) [ 137 | 2dup -> swap-sb -> 138 | 2dup 0= swap 0= and (when [ 2drop 2drop t return ]) // precheck for empty strs 139 | (until [ drop 2dup 0= swap 0= and ] [ 140 | <> (when [ 2drop nil return ]) 141 | 1+ dup -> move // ( a* b*+ | b-> ) 142 | swap 1+ dup -> (r move) // ( b*+ a*+ a-> b-> ) 143 | ]) 144 | 2drop 2drop 145 | t 146 | ]) 147 | (test strequ [ 148 | "windows11" "good" strequ (should eq nil) 149 | "" "" strequ (should eq t) 150 | "foo" "foo" strequ (should eq t) 151 | "Hail Zig" "Hail Zig\0zags" strequ (should eq t) 152 | "Hail Zig2" "Hail Zig" strequ (should eq nil) 153 | "foo" "" strequ (should eq nil) 154 | ]) 155 | 156 | // ( haystack needle -- haystack-starts-with-needle? ) 157 | (word strstartequ ( @[Char8] @[Char8] -- Bool ) [ 158 | move 159 | 160 | (while [ 161 | (-- Bool) 162 | ldak (r [ ldak move ]) 163 | 2dup = rot 0<> rot 0<> and and 164 | ] [ 165 | 1+ (r 1+) 166 | ]) 167 | drop 168 | (r [ move ]) -> 0= 169 | ]) 170 | (test strstartequ [ 171 | "Hail Zig2" "Hail Zig" strstartequ (should eq t) 172 | "blah blah" "foo bar" strstartequ (should eq nil) 173 | "blah foo" "foo bar" strstartequ (should eq nil) 174 | "foo" "foo bar" strstartequ (should eq nil) 175 | "foo bar" "foo" strstartequ (should eq t) 176 | //"" "" strstartequ (should eq t) 177 | "c" "b" strstartequ (should eq nil) 178 | "" "b" strstartequ (should eq nil) 179 | "b" "b" strstartequ (should eq t) 180 | "b" "" strstartequ (should eq t) 181 | "foo\0abc" "foo\0xyz" strstartequ (should eq t) 182 | ]) 183 | 184 | (word strlen ( @[Char8] -- U16 ) [ // TODO: move to str module 185 | 0s swap 186 | (while [ -> 0<> ] [ swap 1+ swap 1+ ]) 187 | drop 188 | ]) 189 | (test strlen [ 190 | "" strlen (should eq 0s) 191 | "Glory to the Empire" strlen (should eq 19s) 192 | "Hello, world!" strlen (should eq 13s) 193 | ]) 194 | 195 | // ( val len ptr -- ) 196 | (word memset8 (Any8 U16 AnyPtr16 -- ) [ 197 | (as @U8) 198 | dup rot + (as @U8) 199 | 200 | (while [ drop 2dup <> ] [ 201 | // ( val ptr end ) 202 | rot-bss> // ( end val ptr ) 203 | stak // ( end val ptr ) 204 | 1+ rot-sbs // ( val ptr+ end ) 205 | ]) 206 | 207 | 3drop 208 | ]) 209 | 210 | (struct ExpansionFillCommand 211 | [type U8] 212 | [len U16] 213 | [bank U16] 214 | [addr @Opaque] 215 | [value U8] 216 | ) 217 | 218 | // ( len ptr -- ) 219 | (word memzero (U16 AnyPtr16 -- ) [ 220 | (let cmdbuf ExpansionFillCommand [0x00 0x0000s 0x0000s 0x0000s 0x00]) 221 | 222 | @cmdbuf :addr <- 223 | @cmdbuf :len <- 224 | 225 | //0s @cmdbuf :bank <- 226 | //0 @cmdbuf :value <- 227 | 228 | @cmdbuf .System/expansion deo 229 | ]) 230 | (test memset8_and_memzero [ 231 | (let buf [U8 4]) 232 | 233 | 9 4s @buf memset8 234 | @buf :0 -> (should eq 9) 235 | @buf :1 -> (should eq 9) 236 | @buf :2 -> (should eq 9) 237 | @buf :3 -> (should eq 9) 238 | 239 | 4s @buf memzero 240 | @buf :0 -> (should eq 0) 241 | @buf :1 -> (should eq 0) 242 | @buf :2 -> (should eq 0) 243 | @buf :3 -> (should eq 0) 244 | ]) 245 | 246 | // ( len src* dst* -- dst* ) 247 | (word memcpy ( U16 $0 AnyPtr -- $0 ) [ 248 | (as @U8 @U8) move (r dup) 249 | 250 | dup rot // ( src src len | dst* dst* ) 251 | (sizeof (Child $0)) (as U16) * // ( src src len*sz | dst* dst* ) 252 | swap + swap // ( src*+len*sz src* | dst* dst* ) 253 | 254 | (while [ drop 2dup <> ] [ 255 | dup -> (r copy) <- 256 | 1+ (r 1+) 257 | ]) 258 | 259 | (r drop) 260 | drop drop 261 | 262 | (r move) (as $0) 263 | ]) 264 | (test memcpy [ 265 | (let foo [Char8 11]) 266 | 'X 10s @foo + <- 267 | 268 | "Hail FORTH" dup strlen swap @foo memcpy drop 269 | 0s @foo + -> (should eq 'H) 270 | 1s @foo + -> (should eq 'a) 271 | 2s @foo + -> (should eq 'i) 272 | 3s @foo + -> (should eq 'l) 273 | 4s @foo + -> (should eq 0x20) 274 | 5s @foo + -> (should eq 'F) 275 | 6s @foo + -> (should eq 'O) 276 | 7s @foo + -> (should eq 'R) 277 | 8s @foo + -> (should eq 'T) 278 | 9s @foo + -> (should eq 'H) 279 | 280 | 10s @foo + -> (should eq 'X) 281 | ]) 282 | 283 | (struct ExpansionCopyCommand 284 | [type U8] 285 | [len U16] 286 | [srcbank U16] 287 | [src @Opaque] 288 | [dstbank U16] 289 | [dst @Opaque] 290 | ) 291 | 292 | // ( len src* dst* -- ) 293 | (word memcpyr ( U16 $0 AnyPtr -- ) [ 294 | (let cmdbuf ExpansionCopyCommand [0x02]) 295 | 296 | @cmdbuf :dst <- 297 | @cmdbuf :src <- 298 | @cmdbuf :len <- 299 | 300 | @cmdbuf .System/expansion deo 301 | ]) 302 | 303 | #noreturn #dampe 304 | (word panic (@U8 -- ) [ 305 | "Panic: " print-string print-string nl 306 | dampe/backtrace 307 | "Aborting.\n" print-string 308 | 0x01 .System/state deo 309 | (asm "" .Op/Obrk) 310 | ]) 311 | 312 | #noreturn 313 | (word panic (@U8 -- ) [ 314 | "Panic: " print-string print-string nl 315 | .System/rst (asm "" .Op/Odei) 2 / 316 | (while [ 0> ] [ 317 | " at " print-string 318 | (wild ( -- U16) [ (asm "sr" .Op/Osth) ]) 319 | print nl 320 | 1- 321 | ]) 322 | "Aborting.\n" print-string 323 | 0x01 .System/state deo 324 | drop 325 | (asm "" .Op/Obrk) 326 | ]) 327 | -------------------------------------------------------------------------------- /std/varvara.finw: -------------------------------------------------------------------------------- 1 | (use* core) 2 | 3 | (device System 0x00 4 | [vector @(Fn (--))] 5 | [expansion U16] // @ExpansionCmd 6 | [wst U8] 7 | [rst U8] 8 | [metadata U16] // @Metadata 9 | [r U16] 10 | [g U16] 11 | [b U16] 12 | [debug U8] 13 | [state U8] 14 | ) 15 | 16 | (enum ConsoleType U8 17 | [no-queue 0] 18 | [stdin 1] 19 | [arg 2] 20 | [arg-spacer 3] 21 | [arg-end 4] 22 | ) 23 | 24 | (device Console 0x10 25 | [vector @(Fn (U8 --))] 26 | [read U8] 27 | [exec U8] // ? (not documented) 28 | [mode U8] // ? (not documented) 29 | [dead U8] // ? (not documented) 30 | [exit U8] // ? (not documented) 31 | [type ConsoleType] 32 | [write U8] // stdout 33 | [error U8] // stderr 34 | ) 35 | 36 | (device Screen 0x20 37 | [vector @(Fn (--))] 38 | [width U16] 39 | [height U16] 40 | [auto Bool] 41 | [_unused U8] 42 | [x U16] 43 | [y U16] 44 | [addr @U8] 45 | [pixel U8] 46 | [sprite U8] 47 | ) 48 | 49 | (enum ControllerButton U8 50 | [ctrl 0x01] 51 | [alt 0x02] 52 | [shift 0x04] 53 | [home 0x08] 54 | [up 0x10] 55 | [down 0x20] 56 | [left 0x40] 57 | [right 0x80] 58 | ) 59 | 60 | (device Controller 0x80 61 | [vector @(Fn (--))] 62 | [button U8] // Bitflag of ControllerButton 63 | [key U8] 64 | [_unused1 @Opaque] 65 | [p2 U8] 66 | [p3 U8] 67 | [p4 U8] 68 | [_unused2 @Opaque] 69 | [_unused3 @Opaque] 70 | [_unused4 @Opaque] 71 | [_unused5 @Opaque] 72 | ) 73 | 74 | (device Mouse 0x90 75 | [vector @(Fn (--))] 76 | [x U16] 77 | [y U16] 78 | [state U8] 79 | 80 | [_unused1 U16] 81 | [_unused2 U8] 82 | 83 | [scrollx U16] 84 | [scrollx U16] 85 | 86 | [_unused3 U16] 87 | ) 88 | 89 | (word dbg ( -- ) [ 0x1 .System/debug deo ]) 90 | (word dei ( AnyDev -- (USz $0) ) [ (asm "g" .Op/Odei) ]) 91 | (word deo ((AnySz $0) AnyDev -- ) [ (asm "g" .Op/Odeo) ]) 92 | (word emit ( Any8 -- ) [ .Console/write deo ]) 93 | (word nl ( -- ) [ 0x0a emit ]) 94 | (word spc ( -- ) [ 0x20 emit ]) 95 | 96 | (word print-string (@U8 -- ) [ ldak (while [ 0<> ] [ emit 1+ ldak ]) 2drop ]) 97 | (word print-bool (Bool -- ) [ (when [ "T" (as @U8) ] [ "nil" (as @U8) ]) print-string ]) 98 | 99 | (word print (Any --) [ 16 print-base ]) 100 | (word print-dec (Any --) [ 10 print-base ]) 101 | 102 | (word print-base (AnySigned U8 --) [ 103 | move 104 | dup sign 0<> (when [ 105 | "-" print-string 106 | abs 107 | ] [ (as (USz $1)) ]) 108 | (r move) 109 | print-base 110 | ]) 111 | 112 | (word print-base (Any U8 --) [ 113 | (let base U8) 114 | @base <- 115 | 116 | dup 0= (when [ drop '0 emit return ]) 117 | 118 | 0x99 (as $1) swap // stop indicator 119 | 120 | (wild ($1 $1 -- $1) [ 121 | (until [ 0= ] [ 122 | $base (as $1) /mod 123 | dup 9 (as $1) > (when 124 | [ 10 (as $1) - 'a (as $1) + ] 125 | [ '0 (as $1) + ] 126 | ) 127 | swap 128 | ]) 129 | 130 | drop // drop null byte 131 | (until [ 0x99 (as $1) = ] [ (as U8) emit ]) 132 | ]) 133 | 134 | drop // drop stop indicator 135 | ]) 136 | 137 | (test print-dec [ 138 | 10 print-dec nl (should stdout-eq "10\n") 139 | 20 print-dec nl (should stdout-eq "20\n") 140 | 35 print-dec nl (should stdout-eq "35\n") 141 | 99 print-dec nl (should stdout-eq "99\n") 142 | 999s print-dec nl (should stdout-eq "999\n") 143 | 0 print-dec nl (should stdout-eq "0\n") 144 | ]) 145 | 146 | (test print [ 147 | 0xbeefs print nl (should stdout-eq "beef\n") 148 | 0x3b print nl (should stdout-eq "3b\n") 149 | 0x0d print 150 | 0xef print 151 | 0xaceds print 152 | 0xcafes print nl (should stdout-eq "defacedcafe\n") 153 | 0x0 print nl (should stdout-eq "0\n") 154 | ]) 155 | 156 | (test print_signed [ 157 | -1i print-dec (should stdout-eq "-1") 158 | 0i print-dec (should stdout-eq "0") 159 | 1i print-dec (should stdout-eq "1") 160 | -199is print-dec (should stdout-eq "-199") 161 | -1is print-dec (should stdout-eq "-1") 162 | 0is print-dec (should stdout-eq "0") 163 | 2819is print-dec (should stdout-eq "2819") 164 | -2819is print-dec (should stdout-eq "-2819") 165 | ]) 166 | 167 | (test print-string [ 168 | "\n\n\0" print-string (should stdout-eq "\n\n") 169 | "All hail the Emperor" print-string (should stdout-eq "All hail the Emperor") 170 | ]) 171 | -------------------------------------------------------------------------------- /std/vec.finw: -------------------------------------------------------------------------------- 1 | (use* core) 2 | (use std) 3 | (use alloc) 4 | (use* varvara) 5 | 6 | #private 7 | (word unwrap-or-oom! ( (Of std/Maybe0 @Opaque) -- @Opaque ) [ 8 | dup ;maybe-not? (when [ alloc/panic-oom ]) :value 9 | ]) 10 | 11 | (struct Vector (Any) 12 | [capacity U16] 13 | [len U16] 14 | [items @[$0]] 15 | ) 16 | 17 | (word init ( @(AnyOf Vector) -- ) [ 18 | 0s swap init-sized 19 | ]) 20 | 21 | (word init-sized ( U16 @(AnyOf Vector) -- ) [ 22 | 2dup :capacity <- 23 | 0s over :len <- 24 | swap 25 | // ( vec* capacity ) 26 | (sizeof (Child (FieldType (Child $0) items))) (as U16) * 27 | alloc/get unwrap-or-oom! (as @(Of Vector $0)) 28 | // ( vec* items* ) 29 | swap :items <- 30 | ]) 31 | 32 | #(method Vector) 33 | (word add-return ( @(AnyOf Vector) -- @(Child (FieldType (Child $0) items)) ) [ 34 | dup ;add-one ;last-ptr 35 | ]) 36 | 37 | #(method Vector) 38 | (word append ( (Child (FieldType (Child $0) items)) @(AnyOf Vector) -- ) [ 39 | dup :len -> 1+ over :len <- 40 | dup :len -> over ;ensure-capacity 41 | dup :len -> 1- 42 | (sizeof (Child (FieldType (Child $0) items))) (as U16) * 43 | swap :items -> + <- 44 | ]) 45 | 46 | #(method Vector) 47 | (word append-many ( U16 @(Child (FieldType (Child $0) items)) @(AnyOf Vector) -- ) [ 48 | // len slice self 49 | move 50 | // len slice -- self 51 | over (r copy) :len -> + (r copy) ;ensure-capacity 52 | // len slice -- self 53 | tuck> (r copy) :len -> (r copy) :items -> : 54 | // len len slice self[len] -- self 55 | std/memcpy drop 56 | // len -- self 57 | (r move) ;grow-by-assume-cap 58 | ]) 59 | 60 | #(method Vector) 61 | (word last-ptr ( @(AnyOf Vector) -- @(Child (FieldType (Child $0) items)) ) [ 62 | dup :items -> swap :len -> 1- : 63 | (as @(Child (FieldType (Child $0) items))) 64 | ]) 65 | 66 | #(method Vector) 67 | (word last ( @(AnyOf Vector) -- (Child (FieldType (Child $0) items)) ) [ 68 | ;last-ptr -> 69 | ]) 70 | 71 | #(method Vector) 72 | (word swap-remove ( U16 @(AnyOf Vector) -- (Child (FieldType (Child $0) items)) ) [ 73 | 2dup :len -> = (when [ 74 | nip ;pop 75 | ] [ 76 | tuck :items -> : 77 | dup -> move // ( vec &vec[ind] | vec[ind] ) 78 | swap ;pop swap <- 79 | (r move) 80 | ]) 81 | ]) 82 | 83 | #(method Vector) 84 | (word pop ( @(AnyOf Vector) -- (Child (FieldType (Child $0) items)) ) [ 85 | //dup ;last move 1s ;shrink-by (r move) 86 | 1s swap dup ;last move ;shrink-by (r move) 87 | ]) 88 | 89 | #(method Vector) 90 | (word add-one ( @(AnyOf Vector) -- ) [ 91 | dup :len -> 1+ over :len <- 92 | dup :len -> swap ;ensure-capacity 93 | ]) 94 | 95 | #(method Vector) 96 | (word shrink-to ( U16 @(AnyOf Vector) -- ) [ 97 | :len <- 98 | ]) 99 | 100 | #(method Vector) 101 | (word grow-by-assume-cap ( U16 @(AnyOf Vector) -- ) [ 102 | tuck 103 | :len -> swap + 104 | // ( vec* new-len ) 105 | swap :len <- 106 | ]) 107 | 108 | #(method Vector) 109 | (word shrink-by ( U16 @(AnyOf Vector) -- ) [ 110 | tuck 111 | :len -> swap - // TODO: check for overflow 112 | // ( vec* new-len ) 113 | swap :len <- 114 | ]) 115 | 116 | #(method Vector) 117 | (word overensure-capacity ( U16 @(AnyOf Vector) -- ) [ 118 | 2dup :capacity -> swap < (when [ 119 | tuck :capacity -> 2s * max 120 | swap ;ensure-capacity 121 | ] [ 2drop ]) 122 | ]) 123 | 124 | #(method Vector) 125 | (word ensure-capacity ( U16 @(AnyOf Vector) -- ) [ 126 | ;try-ensure-capacity not (when [ alloc/panic-oom ]) 127 | ]) 128 | 129 | #(method Vector) 130 | (word try-ensure-capacity ( U16 @(AnyOf Vector) -- Bool ) [ 131 | //swap "Requesting " print-string dup print-dec nl swap 132 | 2dup :capacity -> over < (when [ 133 | swap 134 | 2dup :capacity <- 135 | swap (sizeof (Child (FieldType (Child $0) items))) (as U16) * 136 | // ( new-len vec* computed-size ) 137 | over :items -> (as @Opaque) alloc/reget 138 | dup ;maybe-not? (when [ 139 | 3drop nil return 140 | ]) 141 | :value (as $0) 142 | // ( new-len vec* item* ) 143 | swap :items <- 144 | drop 145 | ] [ 3drop ]) 146 | t 147 | ]) 148 | 149 | #(method Vector) 150 | (word deinit ( @(AnyOf Vector) -- ) [ 151 | :items -> alloc/de 152 | ]) 153 | 154 | (test Vector/init-sized [ 155 | alloc/init 156 | 157 | (let v1 (Of Vector U8)) 158 | 20s @v1 init-sized 159 | @v1 :len -> (should eq 0s) 160 | @v1 :capacity -> (should eq 20s) 161 | @v1 ;deinit 162 | 163 | (let v2 (Of Vector U16)) 164 | 10s @v2 init-sized 165 | @v2 :capacity -> (should eq 10s) 166 | @v2 ;deinit 167 | ]) 168 | 169 | (test Vector/append [ 170 | alloc/init 171 | (let lst (Of Vector U16)) 172 | @lst init 173 | 174 | 0x8s @lst ;append 175 | 314s @lst ;append 176 | 0x7s @lst ;append 177 | 1002s @lst ;append 178 | 0x184s @lst ;append 179 | 180 | @lst :items -> :4 -> (should eq 0x0184s) 181 | @lst :items -> :3 -> (should eq 1002s) 182 | @lst :items -> :2 -> (should eq 0x7s) 183 | @lst :items -> :1 -> (should eq 314s) 184 | @lst :items -> :0 -> (should eq 0x8s) 185 | ]) 186 | 187 | // It should shrink the len while retaining capacity. 188 | (test Vector/shrink-by [ 189 | alloc/init 190 | (let lst (Of Vector U16)) 191 | @lst init 192 | 193 | 1s @lst ;append 194 | 2s @lst ;append 195 | 3s @lst ;append 196 | 4s @lst ;append 197 | 198 | 2s @lst ;shrink-by 199 | 200 | @lst :len -> (should eq 2s) 201 | @lst :capacity -> (should eq 4s) 202 | 0s @lst :items -> + -> (should eq 1s) 203 | 2s @lst :items -> + -> (should eq 2s) 204 | ]) 205 | 206 | (test Vector/last [ 207 | alloc/init 208 | (let lst (Of Vector U16)) 209 | @lst init 210 | 211 | 0x0008s @lst ;append 212 | @lst ;last (should eq 0x8s) 213 | 0x0314s @lst ;append 214 | @lst ;last (should eq 0x314s) 215 | 0x0007s @lst ;append 216 | @lst ;last (should eq 0x7s) 217 | 0x1002s @lst ;append 218 | @lst ;last (should eq 0x1002s) 219 | 0x0184s @lst ;append 220 | @lst ;last (should eq 0x184s) 221 | ]) 222 | 223 | (test Vector/pop [ 224 | alloc/init 225 | (let lst (Of Vector U16)) 226 | @lst init 227 | 228 | 0x0008s @lst ;append 229 | 0x0314s @lst ;append 230 | 0x0007s @lst ;append 231 | 0x1002s @lst ;append 232 | 0x0184s @lst ;append 233 | 234 | @lst ;pop (should eq 0x0184s) 235 | @lst ;pop (should eq 0x1002s) 236 | @lst ;pop (should eq 0x0007s) 237 | @lst ;pop (should eq 0x0314s) 238 | @lst ;pop (should eq 0x0008s) 239 | ]) 240 | 241 | (test Vector/swap-remove [ 242 | alloc/init 243 | (let lst (Of Vector U16)) 244 | @lst init 245 | 246 | 1s @lst ;append 247 | 2s @lst ;append 248 | 3s @lst ;append 249 | 4s @lst ;append 250 | 5s @lst ;append 251 | 252 | 2s @lst ;swap-remove (should eq 3s) 253 | @lst :len -> (should eq 4s) 254 | 255 | 3s @lst ;swap-remove (should eq 4s) 256 | @lst :len -> (should eq 3s) 257 | 258 | 0s @lst ;swap-remove (should eq 1s) 259 | @lst :len -> (should eq 2s) 260 | 0 @lst :items -> : -> (should eq 5s) 261 | ]) 262 | 263 | (test Vector/append-many [ 264 | alloc/init 265 | (let lst (Of Vector U8)) 266 | @lst init 267 | 268 | "Hi!" dup std/strlen swap @lst ;append-many 269 | 270 | @lst :len -> (should eq 3s) 271 | @lst :items -> :0 -> (should eq 'H) 272 | @lst :items -> :1 -> (should eq 'i) 273 | @lst :items -> :2 -> (should eq '!) 274 | 275 | " How are you!" dup std/strlen swap @lst ;append-many 276 | 277 | @lst :len -> (should eq 16s) 278 | @lst :items -> :0 -> (should eq 'H) 279 | @lst :items -> :1 -> (should eq 'i) 280 | @lst :items -> :2 -> (should eq '!) 281 | @lst :items -> :3 -> (should eq 0x20) 282 | @lst :items -> :4 -> (should eq 'H) 283 | @lst :items -> :5 -> (should eq 'o) 284 | @lst :items -> :6 -> (should eq 'w) 285 | @lst :items -> :7 -> (should eq 0x20) 286 | @lst :items -> :8 -> (should eq 'a) 287 | @lst :items -> :9 -> (should eq 'r) 288 | @lst :items -> :10 -> (should eq 'e) 289 | @lst :items -> :11 -> (should eq 0x20) 290 | @lst :items -> :12 -> (should eq 'y) 291 | @lst :items -> :13 -> (should eq 'o) 292 | @lst :items -> :14 -> (should eq 'u) 293 | @lst :items -> :15 -> (should eq '!) 294 | ]) 295 | -------------------------------------------------------------------------------- /test.finw: -------------------------------------------------------------------------------- 1 | (use* core) 2 | (use* varvara) 3 | 4 | (use std) 5 | (use alloc) 6 | (use vec) 7 | 8 | // When calling a noreturn function, a raw jump should be used rather than 9 | // a normal call (pushing onto the return stack) 10 | (test lang_goto_for_noreturn_calls [ 11 | .System/rst dei 2 / 12 | #noreturn (word _ (U8 -- ) [ 13 | .System/rst dei 2 / 14 | swap - (should eq 0) 15 | halt 16 | ]) 17 | _ 18 | ]) 19 | 20 | (test lang_continue_basic [ 21 | 10 (until [ 0= ] [ 1- dup 2 mod 0= (when [ continue ]) dup print-dec ]) drop 22 | (should stdout-eq "97531") 23 | 10 (while [ 0<> ] [ 1- dup 2 mod 0= (when [ continue ]) dup print-dec ]) drop 24 | (should stdout-eq "97531") 25 | ]) 26 | 27 | (test lang_break_basic [ 28 | 10 (until [ 0= ] [ 1- dup 5 = (when [ break ]) dup print-dec ]) drop 29 | (should stdout-eq "9876") 30 | 10 (while [ 0<> ] [ 1- dup 5 = (when [ break ]) dup print-dec ]) drop 31 | (should stdout-eq "9876") 32 | ]) 33 | 34 | (test lang_Omit [ 35 | (struct Fo [foo U8] [bar U16] [baz [U8]]) 36 | (sizeof (Omit Fo baz)) (should eq 3) 37 | ]) 38 | 39 | (test lang_array_sizeof [ 40 | (sizeof @[@Opaque]) (should eq 2) 41 | (sizeof [@Opaque 1]) (should eq 2) 42 | (sizeof [@Opaque 2]) (should eq 4) 43 | (sizeof [U8 2]) (should eq 2) 44 | (sizeof @[U8 2]) (should eq 2) 45 | (sizeof [@U8 2]) (should eq 4) 46 | (sizeof [U8 100]) (should eq 100) 47 | (sizeof [@U8 100]) (should eq 200) 48 | 49 | (struct Fee [fi U8] [fo [U16 2]] [fum @[Char8]]) 50 | (sizeof Fee) (should eq 7) 51 | ]) 52 | 53 | (test lang_decl_scoping [ 54 | (word foobar [ 1 ]) 55 | (word baz [ 56 | (word foobar [ 2 ]) 57 | foobar 58 | ]) 59 | foobar (should eq 1) 60 | baz (should eq 2) 61 | ]) 62 | 63 | (test lang_type_scoping [ 64 | (struct A [foo U8]) 65 | (word baz [ 66 | (struct A [foo U16] [bar U16] [baz U8]) 67 | (sizeof A) 68 | ]) 69 | (sizeof A) (should eq 1) 70 | baz (should eq 5) 71 | ]) 72 | 73 | (test lang_stack_structs [ 74 | (struct A [ foo U8 ]) 75 | (struct B [ foo U16 ]) 76 | (struct C [ foo U8 ] [ bar U8 ]) 77 | 78 | 0x24 (make A) :foo (should eq 0x24) 79 | 80 | 1 2 3 0x24s (make B) drop (should eq 3) 81 | drop drop 82 | 83 | 1 2 3 4 (make C) drop (should eq 2) (should eq 1) 84 | 3 4 (make C) :foo (should eq 4) 85 | 3 4 (make C) :bar (should eq 3) 86 | 87 | 1 2 (make C) 3 4 (make C) swap :foo (should eq 2) 88 | drop 89 | ]) 90 | 91 | (test lang_monomorphic_var_static_alloc [ 92 | (word variable (Type -- @$0) [ 93 | (let _S $0) 94 | @_S 95 | ]) 96 | 97 | (of variable U8) (as @U16) 98 | (of variable U16) (should neq) 99 | 100 | (of variable @U8) (as @U16) 101 | (of variable U16) (should neq) 102 | 103 | 8 (of variable U8) <- 104 | (of variable U8) -> (should eq 8) 105 | 106 | ]) 107 | 108 | (test lang_inline [ 109 | #inline (word a ( -- U8 ) [ 1 ]) 110 | a (should eq 1) 111 | 112 | //#inline (word b ( U16 -- U16 U16 ) [ 1s swap ]) 113 | //2s b (should eq 2s) (should eq 1s) 114 | //(r [ 2s b ]) (asm "sr" .Op/Osth) (asm "sr" .Op/Osth) 115 | //(should eq 2s) (should eq 1s) 116 | ]) 117 | 118 | (test lang_inline_w_early_ret [ 119 | #inline (word a ( -- ) [ return ]) 120 | 2 a 3 (should eq 3) (should eq 2) 121 | 4 a 5 (should eq 5) (should eq 4) 122 | ]) 123 | 124 | (test lang_inline_w_inlined_labels [ 125 | #inline (word b ( U8 -- U8 ) [ 1 = (when [9 return] [8 return]) ]) 126 | #inline (word a ( U8 -- U8 ) [ 0 = (when [3 return] [1 b return]) ]) 127 | 128 | 0 a (should eq 3) 129 | 1 a (should eq 9) 130 | ]) 131 | 132 | (test lang_r_blk [ 133 | (word a ( -- U8 U8 U8 ) [ 1 2 3 ]) 134 | (word b (U8 U8 U8 -- U8 | -- U8) [ move + (r copy) * ]) 135 | (word c (U8 -- U8 | U8 -- U8) [ (r copy) * ]) 136 | 137 | (r [a b c]) (should eq 3) 138 | (r move) (should eq 27) 139 | ]) 140 | 141 | (test lang_enums [ 142 | (enum Foo U8 a [b 99] c [d 88]) 143 | .Foo/a .Foo/c (should neq) 144 | .Foo/b (as U8) (should eq 99) 145 | .Foo/d (as U8) (should eq 88) 146 | ]) 147 | 148 | (test lang_getindex [ 149 | (let array [U8] "Day will come again!") 150 | 151 | @array :0 -> (should eq 'D) 152 | 0 @array : -> (should eq 'D) 153 | 0s @array : -> (should eq 'D) 154 | @array 0 : -> (should eq 'D) 155 | @array 0s : -> (should eq 'D) 156 | 157 | @array :18 -> (should eq 'n) 158 | 18 @array : -> (should eq 'n) 159 | 18s @array : -> (should eq 'n) 160 | @array 18 : -> (should eq 'n) 161 | @array 18s : -> (should eq 'n) 162 | ]) 163 | 164 | (test lang_cond_no_cond_arity [ 165 | (word dostuff (U8 -- ) [ 166 | (cond 167 | [ 0 = ] [ "0" print-string nl ] 168 | [ 1 = ] [ "1" print-string nl ] 169 | [ 2 = ] [ "2" print-string nl ] 170 | [ 3 = ] [ "3" print-string nl ] 171 | [ 4 = ] [ "4" print-string nl ] 172 | [ "???" print-string nl ] 173 | ) 174 | drop 175 | ]) 176 | 0 dostuff 1 dostuff 2 dostuff 3 dostuff 4 dostuff 5 dostuff 177 | (should stdout-eq "0\n1\n2\n3\n4\n???\n") 178 | ]) 179 | 180 | (test lang_proper_folding [ 181 | #no-inline (word foo (-- U8) [ 1 ]) 182 | #no-inline (word bar (-- U8) [ 2 ]) 183 | [ (-- U8) foo ] do (should eq 1) 184 | [ (-- U8) bar ] do (should eq 2) 185 | ]) 186 | --------------------------------------------------------------------------------