├── .gitignore ├── Makefile ├── README.md ├── UNLICENSE ├── etc ├── fngi.vim ├── gen.lua └── test_gen.lua ├── fngi.md ├── gen ├── comp.h ├── name.c └── spor.h ├── learn_in_y.fn ├── notes ├── c_int_conversion.c ├── casting.md ├── code_dump.md ├── core_structs.md ├── defer.md ├── dot.md ├── fiber.md ├── fn.md ├── fngi_syntax.md ├── harness.md ├── lua.md ├── lua_docs.md ├── macro.md ├── mergesort.py ├── misc.md ├── os.md ├── path.md ├── peacechains.h ├── role.md ├── round_two.md ├── shrm.md ├── spor_future.md ├── str.md ├── struct.md ├── type_stack.md └── why-not-wasm.md ├── src ├── boot.fn ├── comp.fn ├── dat.fn ├── fngi.c ├── fngi.h └── spor.fn └── tests ├── basic.fn ├── example_data.txt ├── lua_test.lua ├── main.c └── test_dat.fn /.gitignore: -------------------------------------------------------------------------------- 1 | zoa/ 2 | venv/ 3 | bin/ 4 | out/ 5 | spore/*.gch 6 | spor 7 | a.out 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC=gcc 2 | FLAGS=-m32 -no-pie -g -rdynamic 3 | DISABLE_WARNINGS=-Wno-pointer-sign -Wno-format 4 | LIBS=-Isrc/ -Igen/ -I../civc/src ../civc/src/civ* 5 | FNGI_SRC=src/fngi.* gen/*.c gen/*.h 6 | TEST_SRC=tests/main.c 7 | OUT=bin/test 8 | ARGS= 9 | export LUA_PATH = ../civc/lua/?.lua 10 | 11 | all: test 12 | 13 | test: lua build 14 | ./$(OUT) $(ARGS) 15 | 16 | build: 17 | mkdir -p bin/ 18 | lua etc/gen.lua 19 | $(CC) $(FLAGS) -Wall $(DISABLE_WARNINGS) $(LIBS) $(FNGI_SRC) $(TEST_SRC) -o $(OUT) 20 | 21 | lua: 22 | lua etc/test_gen.lua 23 | 24 | dbg: build 25 | gdb -q ./$(OUT) $(ARGS) 26 | 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fngi: a readable language that grows from the silicon 2 | 3 | > **WARNING:** this language is in alpha stage. Things are expected to be 4 | > broken. 5 | > 6 | > However, the language is in a usable, if extremely unstable state. Please see 7 | > the issue tracker for how to help, or ask on [discord][discord] (click 8 | > [here][discord invite] to get an invite). 9 | 10 | **For a quick tutorial and overview see [learn_in_y.fn](./learn_in_y.fn)** 11 | 12 | If we want to build an entirely understandable tech stack (as part of 13 | [civboot.org](http://civboot.org) or [bootstrappable.org](http://bootstrappable.org)) 14 | we need a language which can be implemented easily, has excellent expressiveness 15 | and readability, can modify itself, and has a simple but effective security 16 | model built in. The language has to be able to unroll from simple beginnings 17 | (implemented in a hex editor or similar) to a full-featured language that we can 18 | use to build a Civboot -- and have fun in the process! 19 | 20 | That language is fngi, a language inspired primarily by FORTH and C. It is 21 | implemented in a miniscule amount of C (fewer than 2-3k lines) and contains all 22 | the bells and whistles you might want from C, as well as an absurdly powerful 23 | macro system that can mutate it's own syntax at runtime -- literally it can 24 | built itself into a new programming language while it runs. 25 | 26 | Fngi itself has the following space targets: 27 | 28 | - >=128KiB microcontroller/processor: can bootstrap a text-based OS providing 29 | human-editable source and assembly that can be recompiled on the 30 | microcontroller (aka self-bootstrapping). The processor does _not_ need to 31 | have memory-mapping, thread management, etc -- it can be a very bare-bones 32 | device. 33 | - >=4KiB microcontroller: can run pre-compiled spor assembly. Will not be 34 | self-bootstrapping. 35 | 36 | Read more: 37 | 38 | - [fngi.md](./fngi.md) 39 | - [harness and zoab](./harness.md) 40 | - [Unstructured Notes](./notes/) 41 | 42 | > Fngi and civboot should be considred an 43 | > [Obsolete Technology](http://xkcd.com/1891) 44 | 45 | ## Building 46 | You need the 32 bit toolchain to build fngi (to use `-m32` flag in gcc). 47 | On ubuntu this is installed with `sudo apt-get install gcc-multilib` 48 | 49 | You then need the source code for the below repos in a single directory. 50 | use either the ssh or http forms below 51 | ``` 52 | cd ~/my_personal_project_directory/ 53 | git clone git@github.com:civboot/fngi.git # https://github.com/civboot/fngi.git 54 | git clone git@github.com:civboot/civc.git # https://github.com/civboot/civc.git 55 | git clone git@github.com:civboot/cxt.git # https://github.com/civboot/cxt.git 56 | git clone git@github.com:civboot/zoa.git # https://github.com/civboot/zoa.git 57 | 58 | cd fngi 59 | make test 60 | ``` 61 | 62 | ## REPL 63 | You can open a very basic REPL with: 64 | 65 | ``` 66 | make test ARGS=--repl 67 | ``` 68 | 69 | Example repl sesion: 70 | ``` 71 | \ Note: this is a line comment 72 | \ put 0xF1 and 2 on the stack (stack is in hex) 73 | \ After the colon is the types 74 | 0xF1 2 75 | > { F1 2} : S S 76 | 77 | \ add the top of the stack to 9 78 | + 9 79 | > { F1 B} : S S 80 | 81 | \ drop the working stack 82 | drp drp 83 | > {} : 84 | 85 | \ define a function that multiples input by 2 86 | \ TODO: input types 87 | pre fn mulTwo[stk:S -> S] do ( * 2 ) 88 | mulTwo(4) 89 | > { 8} : S 90 | ``` 91 | 92 | ## Motivation 93 | Fngi is a "macro first" language. Why? So that it can evolve to be whatever is 94 | needed of it. To understand this better, go back to early programming 95 | languages: unix had sh and C. 96 | 97 | Unix sh is barely a language. It has only a single type, the string. It has an 98 | extremely obtuse syntax who's sole method of operation is to dynamically execute 99 | scripts and modify strings. In truth, the shell "syntax" depends on a host of of 100 | other complex syntaxes: awk, grep, regexes, individual command lines, formats of 101 | system files (`/proc/`), etc. 102 | 103 | By contrast, C has a rigid syntax, a complex macro system, a complex build and 104 | linking system and and no dynamic features. You could never use C as a 105 | scripting language, since it has no way to execute a function you just defined. 106 | 107 | What if there could be only one _base_ language, and you could use it as a seed 108 | for more application-specific instances? It would have to be dynamic, but also 109 | compile to a VM bytecode which can be executed immediately, but can also be 110 | built into a native executable. If we are following in the footsteps of sh, it 111 | should also be extremely small and simple in it's beginninings. 112 | 113 | The first thing to note about fngi is that it has a **concrete syntax**. This 114 | means there is no (inherent) abstract syntax tree -- every token is simply 115 | compiled or executed immediately. In fngi, any function can declare itself as 116 | `syn` (syntactical) and take control of its own destiny, reading tokens and 117 | doing with them whatever it wishes. With great power comes great 118 | responsibility. This leads to a radical "macro first" language, where language 119 | features can be added by modules and imported as-needed. 120 | 121 | ## Goals 122 | 123 | fngi's goal is to evolve into nothing less than the primary programming language 124 | and operating system for [Civboot](http://civboot.org). There are many steps 125 | along the way, some of them complete. 126 | 127 | - [X] Make a complete fngi language, then throw it away. See 128 | [round_two](./notes/round_two.md) 129 | - [x] Create [civc] library to build on top of, based on lessons learned 130 | - [ ] Create fngi 131 | - [X] spor VM for executing compiled instructions 132 | - [X] TyDb for setting up the type checker 133 | - [X] fn for writing type-checked functions 134 | - [X] if/elif/else for flow control 135 | - [x] blk to create loop/while/etc. 136 | - [x] var, struct 137 | - [x] compilePath to compile source 138 | - [ ] global 139 | - [ ] `mod` to set the current dictionary path (module support). 140 | - [ ] Create standard library 141 | - [ ] Allocator options and allocator stack 142 | - [ ] String formatting 143 | - [ ] String printing 144 | - [ ] Regex 145 | - [ ] File reading and modification 146 | * [ ] zoa integration 147 | - [ ] Create an ultra-basic text-editor (zat) 148 | - [ ] Extend zat to create an ultra-basic CLI 149 | - [ ] Implement a tyStk (type checking) and hook into `fn`, `if`, etc 150 | * [ ] Implement a better shell (shrm) 151 | - [ ] Design and build a cross-compiler to compile spor -> IR -> native assembly 152 | - [ ] Create an embedded operating system (RISC-V, ARM, etc) 153 | - [ ] Create custom (stack-based) hardware and port the operating system 154 | 155 | See [civboot.org](http://civboot.org) for future goals. 156 | 157 | [zoa]: http://github.com/civboot/zoa 158 | [civc]: http://github.com/civboot/civc 159 | [discord]: https://discord.com/channels/1083089060765118464/1083089061553639477 160 | [discord invite]: https://discord.com/invite/2DYwsbJ84H 161 | 162 | ## Contributing 163 | 164 | When opening a PR to submit code to this repository you must include the 165 | following disclaimer in your first commit message: 166 | 167 | ```text 168 | I assent to license this and all future contributions to this project 169 | under the dual licenses of the UNLICENSE or MIT license listed in the 170 | `UNLICENSE` and `README.md` files of this repository. 171 | ``` 172 | 173 | ## LICENSING 174 | 175 | This work is part of the Civboot project and therefore primarily exists for 176 | educational purposes. Attribution to the authors and project is appreciated but 177 | not necessary. 178 | 179 | Therefore this body of work is licensed using the [UNLICENSE](./UNLICENSE), 180 | unless otherwise specified at the beginning of the source file. 181 | 182 | If for any reason the UNLICENSE is not valid in your jurisdiction or project, 183 | this work can be singly or dual licensed at your discression with the MIT 184 | license below. 185 | 186 | ```text 187 | Copyright 2021 Garrett Berg 188 | 189 | Permission is hereby granted, free of charge, to any person obtaining a copy of 190 | this software and associated documentation files (the "Software"), to deal in 191 | the Software without restriction, including without limitation the rights to 192 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 193 | of the Software, and to permit persons to whom the Software is furnished to do 194 | so, subject to the following conditions: 195 | 196 | The above copyright notice and this permission notice shall be included in all 197 | copies or substantial portions of the Software. 198 | 199 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 200 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 201 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 202 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 203 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 204 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 205 | SOFTWARE. 206 | ``` 207 | -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /etc/fngi.vim: -------------------------------------------------------------------------------- 1 | " Vim syntax file 2 | " Language: Spore ASM 3 | " Maintainer: Rett Berg 4 | " Latest Revision: 12 Nov 2021 5 | 6 | if exists("b:current_syntax") 7 | finish 8 | endif 9 | 10 | syn match elAlpha '[_a-zA-Z0-9]\+' nextgroup=elKey 11 | syn keyword elKey 12 | \ ret retIfNot reteq retIf retLt retGe 13 | \ fn do stk unty NULL Any declared 14 | \ blk loop while break cont 15 | \ if elif else 16 | \ global const var 17 | \ root mod use struct role impl meth absmeth 18 | \ drp ovr swp inc 19 | \ ge_s gt_s le_s lt_s 20 | \ imm ch 21 | \ nextgroup=elSymbol 22 | 23 | syn match elSymbol '[^()%#'._a-zA-Z0-9]' nextgroup=elSpecial 24 | syn match elSpecial '[()%#'.]' nextgroup=elDecimal 25 | syn match elDecimal '_0-9' nextgroup=elHex 26 | syn match elHex '0x_0-9a-fA-F' nextgroup=elBin 27 | syn match elBin '0b01' nextgroup=elCommentLn 28 | syn match elCommentToken '\\\w\+' nextgroup=elCommentLine 29 | syn match elCommentLn '\\\(\ .*\)\?$' nextgroup=elCommentBlk 30 | syn match elCommentBlk '\\[(].\{-}[)]' nextgroup=elStrRaw 31 | syn match elStrRaw '|.*|' 32 | 33 | let b:current_syntax = "fngi" 34 | 35 | hi def link elKey PreProc 36 | hi def link elSymbol Type 37 | hi def link elSpecial Keyword 38 | hi def link elDecimal Constant 39 | hi def link elHex Constant 40 | hi def link elBin Constant 41 | hi def link elCommentLn Comment 42 | hi def link elCommentToken Comment 43 | hi def link elCommentBlk Comment 44 | hi def link elStrRaw String 45 | -------------------------------------------------------------------------------- /etc/gen.lua: -------------------------------------------------------------------------------- 1 | if not _G.CIV then require('civ'):grequire() end 2 | 3 | local CONST_PAT = ( 4 | 'const%s+([_%w]+)%s*:' 5 | .. '%s*([_%w]+)%s*=' 6 | .. '%s*([_%w]+)' 7 | ) 8 | local STRUCT_PAT = 'struct%s+(%w+)%s*(%b[])' 9 | local TY_PAT = '(&*)%s*([_%w]+)' 10 | local FIELD_PAT = '([_%w]+)%s*:%s*' .. TY_PAT 11 | 12 | local NATIVE_TYS = Set{"U1", "U2", "U4", "S", "I1", "I2", "I4"} 13 | 14 | local UNSIZED = Set{'MMV', 'LCL', 'XL', 'XW', 'XLL', 'XRL', 'SLIT'} 15 | 16 | local RENAME = { 17 | Arena = "SpArena", 18 | Reader = "SpReader", 19 | Writer = "SpWriter", 20 | Fmt = "SpFmt", 21 | Logger = "SpLogger", 22 | } 23 | 24 | local tyDictDefined = false 25 | 26 | local FConst = struct('FConst', {'name', 'ty', 'value'}) 27 | -- Const: {name, ty, value} 28 | local function parseConsts(s, to) 29 | if not to then to = List{} end 30 | for name, ty, value in string.gmatch(s, CONST_PAT) do 31 | to:add(FConst{name=name, ty=ty, value=value}) 32 | end 33 | return to 34 | end 35 | 36 | local FField = struct('', { 37 | {'name', String}, {'refs', Int}, {'ty', String} 38 | }) 39 | local FStruct = struct('FStruct', { 40 | {'name', String}, {'fields', List} 41 | }) 42 | 43 | local allStructs = Map{} 44 | local function parseStructs(s, to) 45 | if not to then to = List{} end 46 | for sname, body in string.gmatch(s, STRUCT_PAT) do 47 | local fields = List{} 48 | for name, refs, ty in string.gmatch(body, FIELD_PAT) do 49 | if name == 'parent' then fields:extend(allStructs[ty].fields) 50 | else 51 | fields:add(FField{ 52 | name=name, refs=string.len(refs), ty=ty, 53 | }) 54 | end 55 | end 56 | local st = FStruct{name=sname, fields=fields} 57 | allStructs[sname] = st; to:add(st) 58 | end 59 | return to 60 | end 61 | 62 | local function parseFngi(path, to) 63 | local text = readAll(path) 64 | if not to then 65 | to = Map{consts=List{}, structs=List{}} 66 | end 67 | parseConsts(text, to.consts) 68 | parseStructs(text, to.structs) 69 | return to 70 | end 71 | 72 | 73 | local spor = parseFngi('src/spor.fn') 74 | local dat = parseFngi('src/dat.fn') 75 | 76 | local datStructs = copy(allStructs) 77 | local comp = parseFngi('src/comp.fn') 78 | local compStructs = allStructs:diff(datStructs) 79 | 80 | local function writeConsts(f, path, consts) 81 | f:write'// DO NOT EDIT MANUALLY! GENERATED BY etc/gen.lua\n' 82 | f:write('// See docs at: ' .. path .. '\n') 83 | f:write'#include "civ.h"\n\n' 84 | for _, c in ipairs(consts) do 85 | f:write('#define ' .. c.name .. ' ' .. c.value .. '\n') 86 | end 87 | return b 88 | end 89 | 90 | RENAME = { 91 | Arena= "SpArena", 92 | Reader= "SpReader", 93 | Writer= "SpWriter", 94 | Fmt= "SpFmt", 95 | Logger= "SpLogger", 96 | } 97 | 98 | 99 | local function writeStruct(f, s, needDeclare) 100 | end 101 | 102 | local function writeStructs(f, structs, needDeclare) 103 | local needDeclare = needDeclare or {} 104 | for _, s in ipairs(structs) do 105 | local sname = RENAME[s.name] or s.name 106 | f:write(concat{'\ntypedef struct _', sname, ' {\n'}) 107 | for _, field in ipairs(s.fields) do 108 | local ty_ = RENAME[field.ty] or field.ty 109 | if ty_ == 'Self' then ty_ = 'struct _' .. sname end 110 | if needDeclare[ty_] then ty_ = 'struct _' .. ty_ end 111 | f:write(concat{' ', ty_}); for _=1, field.refs do f:write'*' end 112 | f:write(concat{' ', field.name, ';\n'}) 113 | end 114 | f:write(concat{'} ', sname, ';\n'}) 115 | needDeclare[sname] = nil 116 | end 117 | end 118 | 119 | local COMP_H = [=====[ 120 | 121 | // -- this part is actually hand written in gen.lua -- 122 | struct _TyDict; 123 | struct _TyFn; 124 | 125 | typedef struct { 126 | struct _TyFn* drop; // this:&This -> () 127 | struct _TyFn* alloc; // this:&This sz:S alignment:U2 -> Ref 128 | struct _TyFn* free; // this:&This dat:Ref sz:S alignment:U2 -> () 129 | struct _TyFn* maxAlloc; // this:&This -> S 130 | } MSpArena; 131 | typedef struct { void* d; MSpArena* m; } SpArena; 132 | 133 | typedef struct { 134 | struct _TyFn* read; // this:&This -> () 135 | struct _TyFn* asBase; // this:&This -> &BaseFile 136 | } MSpReader; 137 | typedef struct { void* d; MSpReader* m; } SpReader; 138 | 139 | typedef struct { 140 | struct _TyFn* asBase; // this:&This -> &BaseFile 141 | struct _TyFn* write; // this:&This -> () 142 | } MSpWriter; 143 | typedef struct { void* d; MSpWriter* m; } SpWriter; 144 | 145 | typedef struct { 146 | MSpWriter w; 147 | struct _TyFn* state; // this:&This -> &FmtState 148 | } MSpFmt; 149 | typedef struct { void* d; MSpFmt* m; } SpFmt; 150 | 151 | typedef struct { 152 | MSpFmt fmt; 153 | struct _TyFn* logConfig; // this:&This -> &LogConfig 154 | 155 | struct _TyFn* start; // this:&This U1 -> U1 156 | struct _TyFn* add; // this:&This Slc -> () 157 | struct _TyFn* end; // this:&This -> () 158 | } MSpLogger; 159 | typedef struct { void* d; MSpLogger* m; } SpLogger; 160 | 161 | // -- everything after this is truly generated -- 162 | ]=====] 163 | 164 | local NAME_C = [===[ 165 | /* Custom generated by etc/gen.lua */ 166 | 167 | #include "civ.h" 168 | #include "spor.h" 169 | 170 | /*extern*/ U1* unknownInstr = "UNKNOWN"; 171 | 172 | Slc instrName(U1 instr) { 173 | switch(instr) { 174 | ]===] 175 | 176 | 177 | local function writeCase(f, name, ret) 178 | ret = ret or name 179 | f:write(concat{' case ', name, ': return Slc_ntLit("', ret, '");\n'}) 180 | end 181 | 182 | local function withSize(f, name) 183 | for _, sz in ipairs({'1', '2', '4'}) do 184 | writeCase(f, string.format('%s + SZ%s', name, sz), name .. sz) 185 | end 186 | end 187 | 188 | local function slitCases(f) 189 | for v=0,0x29 do 190 | writeCase(f, string.format('SLIT + 0x%X', v), string.format('0x%X', v)) 191 | end 192 | end 193 | 194 | local sz, instrs = Map{}, List{} 195 | for _, c in pairs(spor.consts) do 196 | if string.match(c.name, '^SZ') then sz[c.name] = c.value 197 | else instrs:add({instr=tonumber(c.value), name=c.name}) end 198 | end 199 | 200 | local function writeCLang(f, path, mod) 201 | writeConsts(f, path, mod.consts) 202 | writeStructs(f, mod.structs) 203 | return f 204 | end 205 | 206 | local function main() 207 | print("## Generating C using lua") 208 | local f = io.open('gen/name.c', 'w') 209 | f:write(NAME_C) 210 | for _, i in ipairs(instrs) do 211 | if i.name == 'SLIT' then slitCases(f) 212 | elseif (i.instr < 0x40) or UNSIZED[i.name] then writeCase(f, i.name) 213 | else withSize(f, i.name, i.instr) 214 | end 215 | end 216 | f:write' }\n' 217 | f:write' return (Slc) {.dat = unknownInstr, .len = 7};\n' 218 | f:write'}\n' 219 | f:close() 220 | 221 | local sporPath, compPath = 'gen/spor.h', 'gen/comp.h' 222 | writeCLang(io.open(sporPath, 'w'), sporPath, spor):close() 223 | f = io.open(compPath, 'w') 224 | writeConsts(f, compPath, comp.consts) 225 | f:write(COMP_H) 226 | 227 | writeStructs(f, comp.structs, {TyDict=1, TyFn=1}) 228 | f:close() 229 | end 230 | 231 | 232 | if not TESTING then main() end 233 | return { 234 | parseConsts = parseConsts, 235 | parseStructs = parseStructs, 236 | STRUCT_PAT = STRUCT_PAT, 237 | FIELD_PAT = FIELD_PAT, 238 | FConst = FConst, FField = FField, FStruct = FStruct, 239 | spor = spor, dat = dat, comp=comp, 240 | datStructs = datStructs, compStructs = compStructs, 241 | 242 | genConsts = genConsts, 243 | } 244 | -------------------------------------------------------------------------------- /etc/test_gen.lua: -------------------------------------------------------------------------------- 1 | require('civ'):grequire() 2 | TESTING = true 3 | 4 | local gen; test('load', nil, function() 5 | gen = dofile('./etc/gen.lua') 6 | end) 7 | 8 | local CONST_EXAMPLE = [[ 9 | const FOO: U1 = 42 10 | const BAR: U1 = 33 11 | ]] 12 | 13 | local STRUCT_EXAMPLE = [=[ 14 | struct Foo [ 15 | a:U2 16 | b:&S 17 | ] 18 | ]=] 19 | 20 | test('const', nil, function() 21 | local expected = List{ 22 | gen.FConst{name="FOO", ty="U1", value="42"}, 23 | gen.FConst{name="BAR", ty="U1", value="33"}, } 24 | local result = gen.parseConsts(CONST_EXAMPLE) 25 | assertEq(expected, result) 26 | end) 27 | 28 | test('struct', nil, function() 29 | local _, _, stName, stBody = string.find( 30 | STRUCT_EXAMPLE, gen.STRUCT_PAT) 31 | assertEq("Foo", stName) 32 | assertEq("[\n a:U2\n b:&S\n]", stBody) 33 | 34 | assertEq({'a', '', 'U1'}, {string.match('a:U1', gen.FIELD_PAT)}) 35 | assertEq({'b', '&&', 'S'}, {string.match('b:&&S', gen.FIELD_PAT)}) 36 | 37 | local _, _, fName, fRefs, fTy = string.find(stBody, gen.FIELD_PAT) 38 | assertEq("a", fName); 39 | assertEq("", fRefs); 40 | assertEq("U2", fTy) 41 | 42 | local matches = string.gmatch(stBody, gen.FIELD_PAT) 43 | assertEq({'a', '', 'U2'}, {matches()}) 44 | assertEq({'b', '&', 'S'}, {matches()}) 45 | end) 46 | 47 | 48 | test('parse struct', nil, function() 49 | local expected = List{gen.FStruct{ 50 | name='Foo', 51 | fields=List{ 52 | gen.FField{name='a', refs=0, ty='U2'}, 53 | gen.FField{name='b', refs=1, ty='S'}, 54 | }, 55 | }} 56 | local result = gen.parseStructs(STRUCT_EXAMPLE) 57 | assertEq(expected, result) 58 | end) 59 | 60 | local function nameMap(l) 61 | return Map.from(l, function(_, v) return v.name, v end) 62 | end 63 | local sporConsts = nameMap(gen.spor.consts) 64 | local datConsts = nameMap(gen.dat.consts) 65 | local compConsts = nameMap(gen.comp.consts) 66 | 67 | local sporStructs = nameMap(gen.spor.structs) 68 | local datStructs = nameMap(gen.dat.structs) 69 | local compStructs = nameMap(gen.comp.structs) 70 | 71 | local function assertConst(name, ty, value, consts) 72 | assertEq( 73 | gen.FConst{name=name, ty=ty, value=value}, 74 | consts[name]) 75 | end 76 | 77 | local function assertStruct(structs, name, repr) 78 | assertEq(repr, tostring(structs[name])) 79 | end 80 | 81 | test('spor', nil, function() 82 | assertConst('INC', 'U1', '0x10', sporConsts) 83 | assertConst('NOT', 'U1', '0x16', sporConsts) 84 | assertConst('SLIC', 'U1', '0x86', sporConsts) 85 | end) 86 | 87 | test('dat', nil, function() 88 | assertConst('INFO', 'U1', '0x04', datConsts) 89 | assertConst('SIZE', 'U2', '0x1000', datConsts) 90 | assertStruct(gen.datStructs, 'Slc', 91 | 'FStruct{name=Slc fields=[{name=dat refs=1 ty=U1}' 92 | .. ' {name=len refs=0 ty=U2}]}') 93 | assert(datStructs.Block) 94 | assert(datStructs.Buf) 95 | assert(not datStructs.TyDb) 96 | assert(not datStructs.InOut) 97 | assert(not datStructs.Globals) 98 | end) 99 | 100 | test('comp', nil, function() 101 | assertConst('FN_STATE_NO', 'U4', '0x0000', compConsts) 102 | assertConst('TY_FN', 'U1', '0x80', compConsts) 103 | assertConst('TY_FN_INLINE', 'U1', '0x04', compConsts) 104 | assert(not compStructs.Block) 105 | assert(not compStructs.Buf) 106 | assert(compStructs.TyDb) 107 | assert(compStructs.InOut) 108 | assert(compStructs.Globals) 109 | end) 110 | 111 | 112 | -------------------------------------------------------------------------------- /fngi.md: -------------------------------------------------------------------------------- 1 | # Fngi details 2 | 3 | Fngi is a general purpose strongly-typed concrete syntax[1] language with 4 | similar constraints to C. Unlike C it has a rich macro system allowing for 5 | arbitrary syntax extensions. It targets a bare-bones virtual machine called 6 | [spor](./spor.md), which will eventually be compileable to native code. 7 | 8 | [1] a "concrete syntax" is (tounge-in-cheek) the opposite of an "abstract 9 | syntax". It is defined as a syntax where the compiler only reads a token stream 10 | and never creates an abstract syntax tree of any kind. 11 | 12 | ## Fngi basics 13 | 14 | Fngi is a stack based language. This means arguments for a function can be 15 | gotten either from the current stack or an argument passed to them. Here are 16 | two examples to add 1 and 2, both are valid fngi: 17 | 18 | - prefix: `add(1, 2)` 19 | - postfix: `1 2 add;` 20 | 21 | Fngi syntax is token based -- no need to create anything complicated if 22 | compiling a single token is all you need for structuring code. The important 23 | thing is that a "single token" can be a syn function, which gets executed 24 | immediately and creates its own syntax! 25 | 26 | In fngi, all function calls (except syn functions) compile the next token first. 27 | This means that `1 + 4` put's `1` on the stack, then puts `4` on the stack then 28 | adds them (calls `+`). One of the most important syn functions is `(`. The only 29 | thing it does is compile tokens until it hits `)`. 30 | 31 | Tricks like this simplify the entire language dramatically. The way that `fn` 32 | (how you define a function) is defined is (1) parse inputs/outputs by compiling using 33 | `inp` or `stk` depending on whether you've encountered `->` and then (2) 34 | _compile a single token_, which is your function body. 35 | 36 | > Fngi has a few tokens defined for documentation/syntactic surgar: 37 | > - `,` does nothing and is intended as syntactic surgar (documentation) to 38 | > separate arguments in functions. 39 | > - `;` does nothing and is intended to be consumed when no arguments are being 40 | > passed to a PRE function, i.e. `ret;` 41 | > - `\` is a swiss-army knife comment, described below. It is NOT consumed by 42 | > a pre function. 43 | 44 | For example: `myFn 1` 45 | 46 | Gets compiled as: 47 | 48 | * push literal `1` onto working stack 49 | * call `myFn` 50 | 51 | So how do we pass more than one argument? It's easy, we use parenthesis! 52 | `(` will continue to compile tokens until it hits `)` 53 | 54 | So we can do: `myFn2(1, 2 + 3)` 55 | 56 | Which is compiled as: 57 | 58 | * push literal `1` onto working stack 59 | * push literal `2` onto working stack 60 | * push literal `3` onto working stack 61 | * call `+` (add). This consumes two values from WS and pushes one. 62 | * call `myFn` 63 | 64 | ### Comments 65 | 66 | The ultimate "do nothing" token is the swiss-army knife comment: `\` 67 | 68 | Comments have three forms: 69 | 70 | - `\token` comments out a single token. 71 | - `\(block comment)` is a block comment 72 | - `\ line comment` is a line comment 73 | 74 | So you might write one of the below: 75 | 76 | - `myFn(_, 2 + 2) \ first argument from the stack` 77 | - `myFn(\a, 2 + 3)` 78 | - `myFn(\(&a), 2 + 4)` 79 | 80 | The fngi comment is something that would be fantastic for other languages to 81 | replicate. Being able to sprinkle little `\token` comments to declare meaning 82 | improves readability tremendously. 83 | 84 | ## Example 85 | 86 | Below is an example fngi function to calculate fibronacci recursively. 87 | 88 | ```fngi 89 | fn fib[n:U4 -> U4] do ( 90 | if(n <= 1) do ( ret 1 ) 91 | fib(dec n) + fib(n - 2) 92 | ) 93 | ``` 94 | 95 | Let's explain a few details on how the parser/compiler stays extremely minimal: 96 | 97 | - `fn` is an SYN function, meaning the compiler executes it instead of 98 | compiling it to the heap. It handles the input syntax until `do` then compiles 99 | a single token for it's body. 100 | - `(..)` is NOT an expression. `(` is simply an syn function that compiles 101 | until it encounters the token `)`. In this case, what is between them is the 102 | function body. 103 | - `if` is another syn "syntax function" which expects syntax of the form 104 | `if [check] do [code]`. It can also peek at whether the next token is `elif` 105 | or `else` then it can look like: 106 | `if [check] do [code] elif [check] do [code] ... else [code]` 107 | - local variables are handled by the token compiler. They can be fetched or if 108 | the next token is `=` they will be set. 109 | - If a token can be interpreted as a number, then it is a number. Otherwise it's 110 | treated as a constant, variable or function (which might be a syn function). 111 | 112 | ## Fngi syntax 113 | 114 | Fngi has the following rules for token groups. 115 | 116 | "Single tokens": these tokens are never grouped together 117 | 118 | - `( ... )`: groups an execution 119 | - `:`: used for type specifiers, i.e. `a:&U2` 120 | - `|`: is used for inline strings, i.e. `|hello world|` 121 | - `#`: used as sugar for syntax-altering functions like `imm#`, `compile#`, etc 122 | used directly for raw strings, i.e. `##|this has #||# in it|##` 123 | - `.`: used for name paths, i.e. `my.name.chain` 124 | 125 | Otherwise a token is a group of: 126 | 127 | - alphanumeric characters (including `_`), i.e. `foo_bar123` 128 | - symbol characters not in the above "single" tokens, i.e. `+-*&` is a single 129 | token 130 | 131 | Functions can be defined for any token that is not purely numeric 132 | 133 | - Numerics: `0x123 (hex) 12_345 (decimal) 0b11_0101 (binary) 0cA 0c\n 134 | (ascii character)` 135 | - Valid function names: `+++ 0b12 (not binary) 123xx (not decimal)` 136 | 137 | ## Fngi "macros": SYN and `imm#` 138 | 139 | Fngi allows any function to be run "immediately" at compile time. This is done 140 | using the syn function `imm#` ('#' can be used as syntactic sugar by other 141 | syntax-altering functions). 142 | 143 | In fngi, `imm#token(1, 2)` will run not only `token` immediately (at compile 144 | time), but also it's args and anything inside of it's args. This is because 145 | `imm` sets itself as the "compile fn" for a single token. `imm`'s compile function 146 | just passes `asImm=true` for every token that is compiled. 147 | 148 | Some functions are defined as IMM, which means they will panic if executed 149 | without `$` (or in a `$(...)` block). 150 | 151 | Other functions are defined as SYN, and these are the true workhorses of fngi 152 | syntax. A SYN function is (1) always run immediately and (2) get's passed 153 | whether it was called with asImm. 154 | 155 | All fngi syntax besides literals, variable handling, and function calling is 156 | implemented with syn functions. 157 | 158 | > Note: If you want to compile literal computed at compile-time use `$L(1 + 3)` 159 | 160 | ## Structs 161 | Structs are very similar to C with the addition of stack operations. 162 | 163 | ``` 164 | \ Define a struct 165 | struct A [ 166 | a1: U4 167 | ] 168 | 169 | struct B [ 170 | b1: A 171 | b2: U4 172 | ] 173 | 174 | \ Take as input and construct 175 | fn createB[a:A -> B]do ( 176 | // construct variable 177 | var b: B = { 178 | // a = a; // this is valid 179 | b1 = { a1 = a.a1 } // or construct manually 180 | b2 = 32 181 | } 182 | b.field1 = (b.field1 + 7) // assign fields 183 | ret b // put on stack 184 | ) 185 | 186 | \ Define methods 187 | define B { 188 | meth sum(this: &B) -> U4 do ( this.b1.a1 + this.b2 ) 189 | } 190 | 191 | \ Use methods 192 | var b : B = { b1 = { a1 = 42 }, b2 = 33 } 193 | var sum: U4 = ( b.sum() + b.sum() ) 194 | ``` 195 | 196 | ## Similarities to FORTH 197 | 198 | Like FORTH, fngi allows you to define functions that are either compiled or run 199 | immediately (affecting compilation, often called "macros" in other languages). 200 | Also like FORTH, fngi's functions operate by push/poping from a working stack 201 | and using a call stack to track calls/returns. 202 | 203 | Both FORTH and fngi have very low implementation requirements. Fngi is 204 | implemented in about 2-3k lines of C, which is within the same order of 205 | magnitude of most FORTH implementations. Like FORTH, fngi does not come with 206 | hardware memory management requirements or any other bells or whistles. They 207 | practically bootstraps themselves from nothing. 208 | 209 | Fngi diverges from FORTH in the following ways: 210 | 211 | - Does not follow FORTH naming scheme in any way. Typically closer to C 212 | naming. 213 | - Although it is stack-based, all functions (except syn functions) cause the 214 | next token to be compiled before them. This means that syntax is typically 215 | written in C like standard-polish notation instead of FORTH's (hard 216 | to read) reverse-polish notation. 217 | - Fngi has far more support for local variables. 218 | - Has less dependence on the working stack (called the data stack in FORTH). 219 | No support for complex stack manipulation like `ROLL, TUCK, ?DUP`, double 220 | stack manipulation like `2DROP, 2SWAP` or return stack manipulation like 221 | `R>` 222 | 223 | ## C, FORTH and fngi Comparison 224 | 225 | ```c 226 | // C Syntax 227 | uint32_t fib(uint32_t n) { 228 | if (n <= 1) return 1; 229 | return fib(n-1) + fib(n-2); 230 | } 231 | ``` 232 | 233 | ```forth 234 | ( FORTH Syntax) 235 | :FIB ( n -> fibN) 236 | DUP 1 <= IF DROP 1 EXIT THEN 237 | DUP -1 FIB ( n fib(n-1) } 238 | SWP 2 - FIB + ; 239 | ``` 240 | 241 | ```fngi 242 | \ Fngi Syntax 243 | fn fib[n:U4 -> U4] do ( 244 | if(n <= 1) do ( ret 1 ) 245 | fib(dec n) + fib(n - 2) 246 | ) 247 | ``` 248 | 249 | For those familiar with FORTH the above will seem like syntax that _must_ 250 | require a far-too complicated parser. The truth is the opposite: the parser for 251 | the above is basically no more complicated than FORTH's. 252 | 253 | There are definitely a few areas of complexity in fngi that are not in forth 254 | though: 255 | 256 | * The virtual machine model definitely adds a bit of complexity, and the range 257 | of operations supported by the virtual machine (locals, security, etc) adds 258 | some complexity. 259 | * Support of a range of dictionary types, like constant, var, dict, etc adds at 260 | most a byte (of metadata) plus a pointer for each function. 261 | * Type support adds some data overhead, but not really very much. 262 | 263 | -------------------------------------------------------------------------------- /gen/comp.h: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT MANUALLY! GENERATED BY etc/gen.lua 2 | // See docs at: gen/comp.h 3 | #include "civ.h" 4 | 5 | #define CSZ_CATCH 0xFF 6 | #define T_NUM 0x0 7 | #define T_HEX 0x1 8 | #define T_ALPHA 0x2 9 | #define T_SINGLE 0x3 10 | #define T_SYMBOL 0x4 11 | #define T_WHITE 0x5 12 | #define C_UNTY 0x0008 13 | #define C_FN_STATE 0x0007 14 | #define FN_STATE_NO 0x0000 15 | #define FN_STATE_BODY 0x0001 16 | #define FN_STATE_STK 0x0002 17 | #define FN_STATE_INP 0x0003 18 | #define FN_STATE_OUT 0x0004 19 | #define TY_UNSIZED 0xFFFF 20 | #define TY_MASK 0xC0 21 | #define TY_VAR 0x40 22 | #define TY_FN 0x80 23 | #define TY_DICT 0xC0 24 | #define TY_FN_NATIVE 0x20 25 | #define TY_FN_TY_MASK 0x0F 26 | #define TY_FN_NORMAL 0x00 27 | #define TY_FN_IMM 0x01 28 | #define TY_FN_SYN 0x02 29 | #define TY_FN_SYNTY 0x03 30 | #define TY_FN_INLINE 0x04 31 | #define TY_FN_COMMENT 0x05 32 | #define TY_FN_METH 0x06 33 | #define TY_FN_ABSMETH 0x07 34 | #define TY_FN_SIG 0x0F 35 | #define TY_VAR_MSK 0x30 36 | #define TY_VAR_LOCAL 0x00 37 | #define TY_VAR_GLOBAL 0x10 38 | #define TY_VAR_CONST 0x20 39 | #define TY_VAR_ALIAS 0x30 40 | #define TY_DICT_MSK 0x07 41 | #define TY_DICT_NATIVE 0x00 42 | #define TY_DICT_MOD 0x01 43 | #define TY_DICT_BITMAP 0x02 44 | #define TY_DICT_STRUCT 0x03 45 | #define TY_DICT_ENUM 0x04 46 | #define TY_DICT_ROLE 0x05 47 | #define TY_NATIVE_SIGNED 0x08 48 | #define TY_REFS 0x03 49 | 50 | // -- this part is actually hand written in gen.lua -- 51 | struct _TyDict; 52 | struct _TyFn; 53 | 54 | typedef struct { 55 | struct _TyFn* drop; // this:&This -> () 56 | struct _TyFn* alloc; // this:&This sz:S alignment:U2 -> Ref 57 | struct _TyFn* free; // this:&This dat:Ref sz:S alignment:U2 -> () 58 | struct _TyFn* maxAlloc; // this:&This -> S 59 | } MSpArena; 60 | typedef struct { void* d; MSpArena* m; } SpArena; 61 | 62 | typedef struct { 63 | struct _TyFn* read; // this:&This -> () 64 | struct _TyFn* asBase; // this:&This -> &BaseFile 65 | } MSpReader; 66 | typedef struct { void* d; MSpReader* m; } SpReader; 67 | 68 | typedef struct { 69 | struct _TyFn* asBase; // this:&This -> &BaseFile 70 | struct _TyFn* write; // this:&This -> () 71 | } MSpWriter; 72 | typedef struct { void* d; MSpWriter* m; } SpWriter; 73 | 74 | typedef struct { 75 | MSpWriter w; 76 | struct _TyFn* state; // this:&This -> &FmtState 77 | } MSpFmt; 78 | typedef struct { void* d; MSpFmt* m; } SpFmt; 79 | 80 | typedef struct { 81 | MSpFmt fmt; 82 | struct _TyFn* logConfig; // this:&This -> &LogConfig 83 | 84 | struct _TyFn* start; // this:&This U1 -> U1 85 | struct _TyFn* add; // this:&This Slc -> () 86 | struct _TyFn* end; // this:&This -> () 87 | } MSpLogger; 88 | typedef struct { void* d; MSpLogger* m; } SpLogger; 89 | 90 | // -- everything after this is truly generated -- 91 | 92 | typedef struct _FileInfo { 93 | CStr* path; 94 | U2 line; 95 | } FileInfo; 96 | 97 | typedef struct _TyBase { 98 | struct _TyBase* l; 99 | struct _TyBase* r; 100 | U2 meta; 101 | } TyBase; 102 | 103 | typedef struct _TyI { 104 | struct _TyI* next; 105 | U2 meta; 106 | U2 arrLen; 107 | CStr* name; 108 | TyBase* ty; 109 | } TyI; 110 | 111 | typedef struct _TyIBst { 112 | struct _TyIBst* l; 113 | struct _TyIBst* r; 114 | TyI tyI; 115 | } TyIBst; 116 | 117 | typedef struct _Key { 118 | Slc name; 119 | TyI* tyI; 120 | } Key; 121 | 122 | typedef struct _Ty { 123 | struct _Ty* l; 124 | struct _Ty* r; 125 | U2 meta; 126 | U2 line; 127 | CStr* name; 128 | TyI* tyKey; 129 | struct _TyDict* container; 130 | FileInfo* file; 131 | } Ty; 132 | 133 | typedef struct _TyDict { 134 | struct _TyDict* l; 135 | struct _TyDict* r; 136 | U2 meta; 137 | U2 line; 138 | CStr* name; 139 | TyI* tyKey; 140 | struct _TyDict* container; 141 | FileInfo* file; 142 | Ty* children; 143 | TyI* fields; 144 | U2 sz; 145 | } TyDict; 146 | 147 | typedef struct _DictStk { 148 | TyDict** dat; 149 | U2 sp; 150 | U2 cap; 151 | } DictStk; 152 | 153 | typedef struct _TyVar { 154 | struct _TyVar* l; 155 | struct _TyVar* r; 156 | U2 meta; 157 | U2 line; 158 | CStr* name; 159 | TyI* tyKey; 160 | TyDict* container; 161 | FileInfo* file; 162 | S v; 163 | TyI* tyI; 164 | } TyVar; 165 | 166 | typedef struct _InOut { 167 | TyI* inp; 168 | TyI* out; 169 | } InOut; 170 | 171 | typedef struct _FnSig { 172 | struct _FnSig* l; 173 | struct _FnSig* r; 174 | U2 meta; 175 | InOut io; 176 | } FnSig; 177 | 178 | typedef struct _TyFn { 179 | struct _TyFn* l; 180 | struct _TyFn* r; 181 | U2 meta; 182 | U2 line; 183 | CStr* name; 184 | TyI* tyKey; 185 | TyDict* container; 186 | FileInfo* file; 187 | Ty* locals; 188 | U1* code; 189 | TyI* inp; 190 | TyI* out; 191 | U2 len; 192 | U1 lSlots; 193 | } TyFn; 194 | 195 | typedef struct _TyDb { 196 | BBA* bba; 197 | Stk tyIs; 198 | Stk done; 199 | } TyDb; 200 | 201 | typedef struct _Blk { 202 | struct _Blk* next; 203 | S start; 204 | Sll* breaks; 205 | TyI* startTyI; 206 | TyI* endTyI; 207 | } Blk; 208 | 209 | typedef struct _GlobalsCode { 210 | SpReader src; 211 | FileInfo* srcInfo; 212 | Buf token; 213 | Buf code; 214 | U2 metaNext; 215 | U2 cstate; 216 | U2 fnLocals; 217 | U1 fnState; 218 | Blk* blk_; 219 | TyFn* compFn; 220 | } GlobalsCode; 221 | 222 | typedef struct _Globals { 223 | GlobalsCode c; 224 | SpLogger log; 225 | BBA* bbaDict; 226 | TyDict rootDict; 227 | DictStk dictStk; 228 | DictStk implStk; 229 | CBst* cBst; 230 | TyIBst* tyIBst; 231 | FnSig* fnSigBst; 232 | TyDb tyDb; 233 | TyDb tyDbImm; 234 | BBA bbaTyImm; 235 | } Globals; 236 | -------------------------------------------------------------------------------- /gen/name.c: -------------------------------------------------------------------------------- 1 | /* Custom generated by etc/gen.lua */ 2 | 3 | #include "civ.h" 4 | #include "spor.h" 5 | 6 | /*extern*/ U1* unknownInstr = "UNKNOWN"; 7 | 8 | Slc instrName(U1 instr) { 9 | switch(instr) { 10 | case NOP: return Slc_ntLit("NOP"); 11 | case RETZ: return Slc_ntLit("RETZ"); 12 | case RET: return Slc_ntLit("RET"); 13 | case YLD: return Slc_ntLit("YLD"); 14 | case SWP: return Slc_ntLit("SWP"); 15 | case DRP: return Slc_ntLit("DRP"); 16 | case OVR: return Slc_ntLit("OVR"); 17 | case DUP: return Slc_ntLit("DUP"); 18 | case DUPN: return Slc_ntLit("DUPN"); 19 | case RG: return Slc_ntLit("RG"); 20 | case OWR: return Slc_ntLit("OWR"); 21 | case LR: return Slc_ntLit("LR"); 22 | case GR: return Slc_ntLit("GR"); 23 | case XR: return Slc_ntLit("XR"); 24 | case INC: return Slc_ntLit("INC"); 25 | case INC2: return Slc_ntLit("INC2"); 26 | case INC4: return Slc_ntLit("INC4"); 27 | case DEC: return Slc_ntLit("DEC"); 28 | case INV: return Slc_ntLit("INV"); 29 | case NEG: return Slc_ntLit("NEG"); 30 | case NOT: return Slc_ntLit("NOT"); 31 | case CI1: return Slc_ntLit("CI1"); 32 | case CI2: return Slc_ntLit("CI2"); 33 | case ADD: return Slc_ntLit("ADD"); 34 | case SUB: return Slc_ntLit("SUB"); 35 | case MOD: return Slc_ntLit("MOD"); 36 | case SHL: return Slc_ntLit("SHL"); 37 | case SHR: return Slc_ntLit("SHR"); 38 | case MSK: return Slc_ntLit("MSK"); 39 | case JN: return Slc_ntLit("JN"); 40 | case XOR: return Slc_ntLit("XOR"); 41 | case AND: return Slc_ntLit("AND"); 42 | case OR: return Slc_ntLit("OR"); 43 | case EQ: return Slc_ntLit("EQ"); 44 | case NEQ: return Slc_ntLit("NEQ"); 45 | case GE_U: return Slc_ntLit("GE_U"); 46 | case LT_U: return Slc_ntLit("LT_U"); 47 | case GE_S: return Slc_ntLit("GE_S"); 48 | case LT_S: return Slc_ntLit("LT_S"); 49 | case MUL: return Slc_ntLit("MUL"); 50 | case DIV_U: return Slc_ntLit("DIV_U"); 51 | case DIV_S: return Slc_ntLit("DIV_S"); 52 | case MMV: return Slc_ntLit("MMV"); 53 | case FT + SZ1: return Slc_ntLit("FT1"); 54 | case FT + SZ2: return Slc_ntLit("FT2"); 55 | case FT + SZ4: return Slc_ntLit("FT4"); 56 | case FTBE + SZ1: return Slc_ntLit("FTBE1"); 57 | case FTBE + SZ2: return Slc_ntLit("FTBE2"); 58 | case FTBE + SZ4: return Slc_ntLit("FTBE4"); 59 | case FTO + SZ1: return Slc_ntLit("FTO1"); 60 | case FTO + SZ2: return Slc_ntLit("FTO2"); 61 | case FTO + SZ4: return Slc_ntLit("FTO4"); 62 | case FTLL + SZ1: return Slc_ntLit("FTLL1"); 63 | case FTLL + SZ2: return Slc_ntLit("FTLL2"); 64 | case FTLL + SZ4: return Slc_ntLit("FTLL4"); 65 | case FTGL + SZ1: return Slc_ntLit("FTGL1"); 66 | case FTGL + SZ2: return Slc_ntLit("FTGL2"); 67 | case FTGL + SZ4: return Slc_ntLit("FTGL4"); 68 | case SR + SZ1: return Slc_ntLit("SR1"); 69 | case SR + SZ2: return Slc_ntLit("SR2"); 70 | case SR + SZ4: return Slc_ntLit("SR4"); 71 | case SRBE + SZ1: return Slc_ntLit("SRBE1"); 72 | case SRBE + SZ2: return Slc_ntLit("SRBE2"); 73 | case SRBE + SZ4: return Slc_ntLit("SRBE4"); 74 | case SRO + SZ1: return Slc_ntLit("SRO1"); 75 | case SRO + SZ2: return Slc_ntLit("SRO2"); 76 | case SRO + SZ4: return Slc_ntLit("SRO4"); 77 | case SRGL + SZ1: return Slc_ntLit("SRGL1"); 78 | case SRGL + SZ2: return Slc_ntLit("SRGL2"); 79 | case SRGL + SZ4: return Slc_ntLit("SRGL4"); 80 | case SRLL + SZ1: return Slc_ntLit("SRLL1"); 81 | case SRLL + SZ2: return Slc_ntLit("SRLL2"); 82 | case SRLL + SZ4: return Slc_ntLit("SRLL4"); 83 | case FTOK + SZ1: return Slc_ntLit("FTOK1"); 84 | case FTOK + SZ2: return Slc_ntLit("FTOK2"); 85 | case FTOK + SZ4: return Slc_ntLit("FTOK4"); 86 | case LIT + SZ1: return Slc_ntLit("LIT1"); 87 | case LIT + SZ2: return Slc_ntLit("LIT2"); 88 | case LIT + SZ4: return Slc_ntLit("LIT4"); 89 | case LCL: return Slc_ntLit("LCL"); 90 | case XL: return Slc_ntLit("XL"); 91 | case XW: return Slc_ntLit("XW"); 92 | case XLL: return Slc_ntLit("XLL"); 93 | case XRL: return Slc_ntLit("XRL"); 94 | case JL + SZ1: return Slc_ntLit("JL1"); 95 | case JL + SZ2: return Slc_ntLit("JL2"); 96 | case JL + SZ4: return Slc_ntLit("JL4"); 97 | case JLZ + SZ1: return Slc_ntLit("JLZ1"); 98 | case JLZ + SZ2: return Slc_ntLit("JLZ2"); 99 | case JLZ + SZ4: return Slc_ntLit("JLZ4"); 100 | case JLNZ + SZ1: return Slc_ntLit("JLNZ1"); 101 | case JLNZ + SZ2: return Slc_ntLit("JLNZ2"); 102 | case JLNZ + SZ4: return Slc_ntLit("JLNZ4"); 103 | case JTBL + SZ1: return Slc_ntLit("JTBL1"); 104 | case JTBL + SZ2: return Slc_ntLit("JTBL2"); 105 | case JTBL + SZ4: return Slc_ntLit("JTBL4"); 106 | case SLIC + SZ1: return Slc_ntLit("SLIC1"); 107 | case SLIC + SZ2: return Slc_ntLit("SLIC2"); 108 | case SLIC + SZ4: return Slc_ntLit("SLIC4"); 109 | case SLIT + 0x0: return Slc_ntLit("0x0"); 110 | case SLIT + 0x1: return Slc_ntLit("0x1"); 111 | case SLIT + 0x2: return Slc_ntLit("0x2"); 112 | case SLIT + 0x3: return Slc_ntLit("0x3"); 113 | case SLIT + 0x4: return Slc_ntLit("0x4"); 114 | case SLIT + 0x5: return Slc_ntLit("0x5"); 115 | case SLIT + 0x6: return Slc_ntLit("0x6"); 116 | case SLIT + 0x7: return Slc_ntLit("0x7"); 117 | case SLIT + 0x8: return Slc_ntLit("0x8"); 118 | case SLIT + 0x9: return Slc_ntLit("0x9"); 119 | case SLIT + 0xA: return Slc_ntLit("0xA"); 120 | case SLIT + 0xB: return Slc_ntLit("0xB"); 121 | case SLIT + 0xC: return Slc_ntLit("0xC"); 122 | case SLIT + 0xD: return Slc_ntLit("0xD"); 123 | case SLIT + 0xE: return Slc_ntLit("0xE"); 124 | case SLIT + 0xF: return Slc_ntLit("0xF"); 125 | case SLIT + 0x10: return Slc_ntLit("0x10"); 126 | case SLIT + 0x11: return Slc_ntLit("0x11"); 127 | case SLIT + 0x12: return Slc_ntLit("0x12"); 128 | case SLIT + 0x13: return Slc_ntLit("0x13"); 129 | case SLIT + 0x14: return Slc_ntLit("0x14"); 130 | case SLIT + 0x15: return Slc_ntLit("0x15"); 131 | case SLIT + 0x16: return Slc_ntLit("0x16"); 132 | case SLIT + 0x17: return Slc_ntLit("0x17"); 133 | case SLIT + 0x18: return Slc_ntLit("0x18"); 134 | case SLIT + 0x19: return Slc_ntLit("0x19"); 135 | case SLIT + 0x1A: return Slc_ntLit("0x1A"); 136 | case SLIT + 0x1B: return Slc_ntLit("0x1B"); 137 | case SLIT + 0x1C: return Slc_ntLit("0x1C"); 138 | case SLIT + 0x1D: return Slc_ntLit("0x1D"); 139 | case SLIT + 0x1E: return Slc_ntLit("0x1E"); 140 | case SLIT + 0x1F: return Slc_ntLit("0x1F"); 141 | case SLIT + 0x20: return Slc_ntLit("0x20"); 142 | case SLIT + 0x21: return Slc_ntLit("0x21"); 143 | case SLIT + 0x22: return Slc_ntLit("0x22"); 144 | case SLIT + 0x23: return Slc_ntLit("0x23"); 145 | case SLIT + 0x24: return Slc_ntLit("0x24"); 146 | case SLIT + 0x25: return Slc_ntLit("0x25"); 147 | case SLIT + 0x26: return Slc_ntLit("0x26"); 148 | case SLIT + 0x27: return Slc_ntLit("0x27"); 149 | case SLIT + 0x28: return Slc_ntLit("0x28"); 150 | case SLIT + 0x29: return Slc_ntLit("0x29"); 151 | } 152 | return (Slc) {.dat = unknownInstr, .len = 7}; 153 | } 154 | -------------------------------------------------------------------------------- /gen/spor.h: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT MANUALLY! GENERATED BY etc/gen.lua 2 | // See docs at: gen/spor.h 3 | #include "civ.h" 4 | 5 | #define NOP 0x00 6 | #define RETZ 0x01 7 | #define RET 0x02 8 | #define YLD 0x03 9 | #define SWP 0x04 10 | #define DRP 0x05 11 | #define OVR 0x06 12 | #define DUP 0x07 13 | #define DUPN 0x08 14 | #define RG 0x09 15 | #define OWR 0x0A 16 | #define LR 0x0B 17 | #define GR 0x0C 18 | #define XR 0x0F 19 | #define INC 0x10 20 | #define INC2 0x11 21 | #define INC4 0x12 22 | #define DEC 0x13 23 | #define INV 0x14 24 | #define NEG 0x15 25 | #define NOT 0x16 26 | #define CI1 0x17 27 | #define CI2 0x18 28 | #define ADD 0x20 29 | #define SUB 0x21 30 | #define MOD 0x22 31 | #define SHL 0x23 32 | #define SHR 0x24 33 | #define MSK 0x25 34 | #define JN 0x26 35 | #define XOR 0x27 36 | #define AND 0x28 37 | #define OR 0x29 38 | #define EQ 0x2A 39 | #define NEQ 0x2B 40 | #define GE_U 0x2C 41 | #define LT_U 0x2D 42 | #define GE_S 0x2E 43 | #define LT_S 0x2F 44 | #define MUL 0x30 45 | #define DIV_U 0x31 46 | #define DIV_S 0x32 47 | #define SZ_MASK 0x30 48 | #define SZ1 0x00 49 | #define SZ2 0x10 50 | #define SZ4 0x20 51 | #define MMV 0x40 52 | #define FT 0x41 53 | #define FTBE 0x42 54 | #define FTO 0x43 55 | #define FTLL 0x44 56 | #define FTGL 0x45 57 | #define SR 0x46 58 | #define SRBE 0x47 59 | #define SRO 0x48 60 | #define SRGL 0x49 61 | #define SRLL 0x4A 62 | #define FTOK 0x4B 63 | #define LIT 0x4C 64 | #define LCL 0x80 65 | #define XL 0x81 66 | #define XW 0x90 67 | #define XLL 0x91 68 | #define XRL 0xA0 69 | #define JL 0x82 70 | #define JLZ 0x83 71 | #define JLNZ 0x84 72 | #define JTBL 0x85 73 | #define SLIC 0x86 74 | #define SLIT 0xC0 75 | -------------------------------------------------------------------------------- /learn_in_y.fn: -------------------------------------------------------------------------------- 1 | \ Learn fngi in y minutes. 2 | \ 3 | \ Syntax highlighting: 4 | \ * vim: etc/fngi.vim 5 | 6 | \ Comments: this is a line comment 7 | \(this a block comment) 8 | \these \are \token \comments 9 | 10 | \ Use the logger directly to print "hello world". 11 | \ Note: strings are wrapped in '|' to prevent having so many 12 | \ escapes (when do you use '|' in natural language?) 13 | imm#if(lgr.start INFO) do (lgr.add|Hello Learn X in Y!| lgr.end;) 14 | 15 | \ Native data types. 16 | 17 | 18 | \ S (slot) is pointer sized 19 | global s:S = 9 20 | 21 | \ SI (integer slot) is pointer sized and signed 22 | global si:SI = --7 \ negative 7 23 | 24 | \ Or select the number of bytes 25 | global u1:U1 = 0xFF \ 1 byte unsigned 26 | global u2:U2 = 0xFFFF \ 2 byte unsigned 27 | global u4:U4 = 0xFFFF_FFFF \ 4 byte unsigned 28 | global i1:I1 = I1 --42 29 | global i2:I2 = I2 --1 30 | 31 | \ A reference (can be to any type) 32 | global refU1:&U1 = &u1 33 | 34 | \ Four U1's in an array 35 | global arrU1:Arr[4 U1]; 36 | 37 | \ Do assertions at immediate (aka compile) time 38 | imm#tAssertEq(2, s + S si) \ TODO: cast shouldn't be necessary 39 | imm#tAssertEq(0xFFFF_0000, u4 - u2) 40 | imm#tAssertEq(u1, @refU1) \ @ is the deref operator 41 | \ imm#tAssertEq(S --43, S i1 + S i2) \ TODO: negative arithmetic needs improvement 42 | 43 | \ Define a function with recurssion (fibonacci number) 44 | fn fib[n:S -> S] do ( 45 | if(n < 2) do ( ret 1 ) 46 | \ implicit return 47 | fib(dec n) + fib(n - 2) 48 | ) 49 | imm#tAssertEq(1, fib 0) 50 | imm#tAssertEq(1, fib 1) 51 | imm#tAssertEq(2, fib 2) 52 | imm#tAssertEq(3, fib 3) 53 | imm#tAssertEq(5, fib 4) 54 | imm#tAssertEq(8, fib 5) 55 | 56 | \ Compile const value 57 | \ Syntax: 58 | \ const $NAME:$TYPE = $VALUE 59 | \ 60 | \ Syntax code will always be like the above, each $NAME represents a single 61 | \ "token". Some tokens like '(' are syn functions (syntax functions) which 62 | \ compile multiple tokens. 63 | \ 64 | \ Note for experienced programmers: 65 | \ syn functions are similar to macros in other languages 66 | const dozen : S = 12 67 | const bakerDozen: S = (dozen + 1) 68 | \ bakerDozen = 7 \ ERROR cannot set const variable 69 | imm#tAssertEq(12, dozen) imm#tAssertEq(13, bakerDozen) 70 | 71 | 72 | \ Defining functions 73 | \ Syntax: 74 | \ fn $NAME[$INPUTS -> $OUTPUTS] do $CODE 75 | fn isDozen[v:S -> U1] do ( 76 | dozen == v \ value left on the stack is returned 77 | ) 78 | fn is2Dozen[v:S -> U1] do ( 79 | \ 'lit#' compiles the next token (computed immediately) as a literal 80 | lit#(dozen * 2) == v 81 | ) 82 | imm#tAssertEq(FALSE, isDozen(13)) 83 | imm#tAssert (isDozen(12)) 84 | imm#tAssertEq(FALSE, is2Dozen(12)) 85 | imm#tAssert (is2Dozen(24)) 86 | 87 | \ Write a function with a loop. 88 | \ Functions are type checked, the return value is whatever is 89 | \ left on the working stack. 90 | fn badMultiply[a:S b:S -> S] do ( 91 | var out:S = 0; 92 | \ blk let's us use break and cont (continue) 93 | blk ( if(not b) do break; 94 | out = (out + a) 95 | b = dec b \ dec reduces by 1 96 | cont) 97 | out 98 | ) 99 | imm#tAssertEq(2, badMultiply(1, 2)) 100 | imm#tAssertEq(4, badMultiply(2, 2)) 101 | imm#tAssertEq(12, badMultiply(4, 3)) 102 | 103 | \ We can also manipulate the working stack directly. 104 | \ The working stack is fundamentally how all operations are performed 105 | \ such as arithmetic and calling functions. 106 | \ 107 | \ Note: if you've never used FORTH or other stack based languages 108 | \ feel free to skip this. It's primarily a performance 109 | \ improvement (not using local variables uses less memory). 110 | \ In this tutorial we will typically NOT use stack manipulation. 111 | fn badMultiplyStk[stk\a:S stk\b:S -> S] do ( 112 | var out:S = 0 \ stk[a b] 113 | \ TODO: bug here if the drops are done before the break 114 | blk ( if(not dup\b;) do break; \ stk[a b] 115 | swp; \ stk[b a] 116 | out = (dup\a; + out) \ stk[b a] 117 | swp\(b a); dec \b; cont) \ stk[a b] 118 | drp\b; drp\a; out \ stk[out] 119 | ) 120 | imm#tAssertEq(0, badMultiplyStk(0, 5)) 121 | imm#tAssertEq(0, badMultiplyStk(5, 0)) 122 | imm#tAssertEq(25, badMultiplyStk(5, 5)) 123 | 124 | \ ############### 125 | \ # Struct 126 | \ Structs are a way to associate data. 127 | \ 128 | \ Note for C developers: 129 | \ They are very similar to C structs except they have a slightly more compact 130 | \ data layout: sub-structs do not "round up" their size to their alginment. 131 | \ See https://stackoverflow.com/questions/76451239 132 | \ 133 | \ Structs are JUST a collection of data and act as a container of names for 134 | \ global values, functions and methods. 135 | \ 136 | \ For method calls, the compiler automatically pushes &Self. 137 | \ 138 | \ Note for Object Oriented developers: 139 | \ Structs are not objects, there are no data structures which associate 140 | \ methods to a struct at runtime. 141 | 142 | \ Define a struct with two fields 143 | struct Point2D [ x:I2 y:I2 ] 144 | 145 | \ implement methods 146 | impl Point2D ( 147 | \ associate constants 148 | global 2D_ORIGIN:Point2D = Point2D(I2 0, I2 0) 149 | 150 | \ Define methods, which are associated functions that takes &Self as the 151 | \ first argument 152 | 153 | \ Method which adds a constant to both fields 154 | meth add2D[self:&Self, v:I2 ] do ( 155 | self.x = I2 (S self.x + S v); 156 | self.y = I2 (S self.y + S v); 157 | ) 158 | 159 | \ Method which tests equality 160 | meth eq2D[self:&Self, p:&Self -> S] do ( 161 | (S self.x == S p.x) and (S self.y == S p.y) 162 | ) 163 | 164 | \ return whether this is the origin 165 | meth isOrigin2D[self:&Self -> S] do self.eq2D(&Point2D.2D_ORIGIN) 166 | ) 167 | 168 | \ Copy the origin struct to a new global variable 169 | global 2d:Point2D = Point2D.2D_ORIGIN 170 | imm#( 171 | \ Note: ';' is required since isOrigin2D consumes one token 172 | \ and ';' is a "do nothing" token. 173 | \ Equivalently you could use '()' or ',' which also do nothing. 174 | tAssert(2d.isOrigin2D;) 175 | tAssert(2d.eq2D(&Point2D.2D_ORIGIN)) 176 | 2d.add2D(I2 4) tAssertEq(4, S 2d.x) 177 | tAssertEq(4, S 2d.y) 178 | tAssertEq(FALSE, 2d.isOrigin2D;) 179 | ) 180 | 181 | \ ############### 182 | \ # Struct Inheritance (single data inheritance) 183 | \ You can specify a parent struct as the first field, which will 184 | \ will cause the struct to inherit all parent's fields and methods. 185 | \ 186 | \ Single inheritance has the following features: 187 | \ * Allows eronomic access of parent items. 188 | \ * The type system reconizes that &Self == &Parent for function 189 | \ calls, etc. 190 | \ * Overriding parent items is not allowed. 191 | \ 192 | \ Note for Object Oriented developers: 193 | \ Again, this is not an Object. It is simply extending a collection 194 | \ of data and the type system recognizes their data prefixes are 195 | \ identical. 196 | struct Point3D [ 197 | parent:Point2D 198 | z:I2 \ additional field 199 | ] 200 | 201 | impl Point3D ( 202 | global 3D_ORIGIN:Point3D = Point3D(Point2D.2D_ORIGIN, I2 0) 203 | 204 | meth add3D[self:&Self, v:I2 ] do ( 205 | self.add2D(v) \ call parent method 206 | self.z = I2 (S self.z + S v); 207 | ) 208 | 209 | meth eq3D[self:&Self, p:&Self -> S] do ( 210 | self.eq2D(p) and (S self.z == S p.z) 211 | ) 212 | 213 | meth isOrigin3D[self:&Self -> S] do self.eq3D(&Point3D.3D_ORIGIN) 214 | ) 215 | 216 | global 3d:Point3D = Point3D.3D_ORIGIN 217 | imm#( 218 | \ Can access parent fields 219 | tAssertEq(0, S 3d.x) 220 | \ Can call methods and parent methods 221 | tAssert(3d.isOrigin3D;) tAssert(3d.isOrigin2D;) 222 | 3d.add2D(I2 5) 3d.add3D(I2 2) 223 | tAssertEq(7, S 3d.x) tAssertEq(7, S 3d.y) 224 | tAssertEq(2, S 3d.z) 225 | ) 226 | 227 | \ ############### 228 | \ # Roles 229 | \ Roles allow associating data and methods for the type system. This allows 230 | \ multiple implementations of common interfaces like memory allocators, files 231 | \ or loggers without generating new code for each of them. 232 | \ 233 | \ For Object Oriented Programmers: 234 | \ Roles can be thought of extremely simple and lightweight Interfaces. 235 | \ 236 | \ A role is just a struct of [ data:&Any, methods:&Methods ] where each method 237 | \ is a fn that takes the `data` as it's first argument. The fngi language 238 | \ helps enforce that data is only wrapped with it's associated methods. 239 | \ 240 | \ The role shown here is is for demonstration purposes. Roles are intended 241 | \ to be for things that back a resource which could have multiple possible 242 | \ implementations (each with their own tradeoffs) but which all have an 243 | \ identical interface. 244 | 245 | \ We use impl to put this inside the I2 struct, i.e. I2.Adder 246 | impl I2 role Adder [ 247 | absmeth add[&Self, I2] 248 | ] 249 | 250 | impl Point2D:I2.Adder { add = &Point2D.add2D } 251 | impl Point3D:I2.Adder { add = &Point3D.add3D } 252 | 253 | global adder2D:I2.Adder = I2.Adder &2d \ currently (4, 4) 254 | global adder3D:I2.Adder = I2.Adder &3d \ currently (7, 7, 2) 255 | imm#( 256 | adder2D.add(I2 1) 257 | tAssertEq(5, S 2d.x) tAssertEq(5, S 2d.y) 258 | 259 | adder3D.add(I2 2) 260 | tAssertEq(9, S 3d.x) tAssertEq(9, S 3d.y) 261 | tAssertEq(4, S 3d.z) 262 | ) 263 | 264 | \ ############### 265 | \ # Parsing Details 266 | \ Tokens are a group of similar characters, except for the following 267 | \ which always create a single character token: 268 | \ 269 | \ # | . : ( ) & ; 270 | \ 271 | \ The other groups are: 272 | \ * whitespace: any character code less than or equal to space (0x20) 273 | \ * alphanumeric: [a-ZA-Z0-9_] 274 | \ * symbols: anything not in the above 275 | 276 | \ */+ is a single token, abc is a different token 277 | const */+:U1 = 5 278 | const abc:U1 = 7 279 | 280 | \ Tokens from different groups don't combine 281 | \ Note we are using '+' as a function. Also 282 | \ note that ',' are not actually required. 283 | imm#tAssertEq(+(*/+abc), */+ + abc) 284 | 285 | 286 | -------------------------------------------------------------------------------- /notes/c_int_conversion.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | typedef uint8_t U8; 5 | typedef uint16_t U16; 6 | typedef uint32_t U32; 7 | typedef int8_t I8; 8 | typedef int16_t I16; 9 | typedef int32_t I32; 10 | 11 | 12 | U8 one = 1; 13 | U32 negOne = 0xFF; 14 | 15 | int main() { 16 | U32 a = (U32) negOne; 17 | I32 b = (I32) negOne; 18 | U32 c = (I32) ((I8) negOne); 19 | U32 d = (U32) ((U32) negOne); 20 | U32 e = (U8) -((I8) one); 21 | 22 | // a: 255 b:255 c:-1 23 | printf("a: %i b:%i c:%x d:%x e:%x\n", a, b, c, d, e); 24 | 25 | U32 t = 0xFFFF & (-one - 1); 26 | printf("t: %x\n", t); 27 | } 28 | -------------------------------------------------------------------------------- /notes/casting.md: -------------------------------------------------------------------------------- 1 | ## Type casting in fngi 2 | 3 | Fngi aims to "get out of your way" when writing types and functions. One of the 4 | primary ways it does this with minimal complexity is allowing simple type casting 5 | when the types are bit-compatible. 6 | 7 | If two structures are field-identical (in types, not necessarily in names) then 8 | they can be explicitly cast to eachother. For instance, `Foo [ a: U4, b: S]` and 9 | `Bar [x: U4, y: S]` can be cast into eachother. 10 | 11 | If a pointer type is required who's first fields are identical to a given type, 12 | then the pointers can be cast. For instance a reference to `B [ a: U4, b: U4]` can 13 | be cast into a reference to `A [ a: U4 ]`. 14 | 15 | This is allowed because the required type `A` has the same 16 | fields at it's start as `B`. Therefore a pointer to `B` can be auto-cast into an `A` 17 | pointer since they are bit-for-compatible. 18 | 19 | You could think of `B` as a "subclass" of `A` and you would be partially 20 | correct, since methods defined for `A` can be run using `B`. However, fngi 21 | doesn't keep track of concepts like "subclasses". 22 | 23 | ### How does the user do this? 24 | This is only done when the `given` TyI has the `auto` bit set on it's first 25 | type. This bit is set by the `auto` syn function, like so: 26 | 27 | ``` 28 | fn foo a:&A do ( ... ) 29 | 30 | \ in some function 31 | var b:B = ( ... ) 32 | foo(auto &b) 33 | ``` 34 | 35 | The above compiles. If `auto` was removed you would get a type error. 36 | 37 | ### More details 38 | auto-type casting applies not just to structs, but also to function pointers and 39 | Roles. For Role casts, the _names_ of the methods must match in addition to their 40 | type signatures. This means that a `File` Role, which starts with the `drop` and 41 | `close` methods, can be cast into a `Resource` Role which only has those 42 | methods. 43 | -------------------------------------------------------------------------------- /notes/code_dump.md: -------------------------------------------------------------------------------- 1 | # Code Dump: where code goes to die 2 | 3 | 4 | ## Walking some dots I guess 5 | 6 | ``` 7 | \ Walk a series of dots (a.foo.bar.baz), returning the last found node and 8 | \ whether there are unrecognized symbols after the dot (i.e. struct field) 9 | fn walkDots -> out(n: &DNode, isLast:U1) do ( 10 | NULL\curNode 11 | LOOP l0 12 | dictRefMaybe(dup\curNode) \ {prevNode curNode} 13 | if(not dup\curNode) do ( 14 | drp\curNode; tokenPlcSet(0); 15 | ret(\prevNode, FALSE\isLast); 16 | ) 17 | swp -> drp\prevNode; \ {curNode} 18 | if(scan; not tokenEq(SLC_DOT)) do ( \ check if next symbol is '.' 19 | tokenPlcSet(0); 20 | ret(\curNode, TRUE\isLast); 21 | ) 22 | assert(isTyDict(dup\curNode), E_dot); 23 | AGAIN l0 24 | ) 25 | 26 | fn bracketO do ret ( expectToken(SLC_BRACK_O, E_bracket) -> drp; ) 27 | fn bracketC do ret ( expectToken(SLC_BRACK_C, E_bracket) -> drp; ) 28 | 29 | \ \ offsetOf[MyStruct.myField] 30 | \ syn fn offsetOf do ret ( 31 | \ bracketO; walkDots -> assertNot(\isLast, E_type); \ {asNow &DNodeStruct} 32 | \ struct_fieldCtx(\dnode) -> drp\tyItem; bracketC; 33 | \ swp -> retIf(\asNow) -> jmp:L; 34 | \ ) 35 | \ $tAssertEq(0, offsetOf[DNode.l]) $tAssertEq(16, offsetOf[DNode.m]) 36 | ``` 37 | 38 | 39 | ## SlcNode 40 | 41 | ``` 42 | \ SlcNode struct 43 | const SN_left = 0; \ &SlcNode: .left 44 | const SN_right = RSIZE; \ &SlcNode: .right 45 | const SN_dat = (SN_right + RSIZE); \ Ref: .key.dat 46 | const SN_len = (SN_dat + RSIZE); \ U2: .key.len 47 | 48 | \ Find slice in BST, starting at node. Set result to node. 49 | \ returns 0 if node==NULL 50 | \ The return value is the result of `slcCmp(node.key, out.key)` 51 | pre FN SN_find 52 | $declVar(declL node, TY_VAR_INPUT, RSIZE) \ &&SlcNode 53 | $declVar(declL slcDat, TY_VAR_INPUT, RSIZE) \ slc.dat 54 | $declVar(declL slcLen, TY_VAR_INPUT, 2) \ slc.len 55 | $declVar(declL cmp, 0, RSIZE) 56 | $declEnd 57 | IF(not ftoR:0(GET node)) ret 0; END \ if (&SlcNode == NULL) ret 0 58 | LOOP l0 59 | slcCmp(ftoR:SN_dat(GET node), fto2:SN_len(GET node), 60 | GET slcDat, GET slcLen) -> SET cmp; 61 | IF(not GET cmp) ret 0; END \ found exact match 62 | IF(GET cmp < 0) 63 | ftoR:SN_left(ftR(GET node)) \ node.left 64 | IF(dup) srR(\(node.left), GET node) 65 | ELSE drp; ret GET cmp; END 66 | ELSE \ cmp > 0 67 | ftoR:SN_right(ftR(GET node)) \ node.right 68 | IF(dup) srR(\(node.right), GET node) 69 | ELSE drp; ret GET cmp; END 70 | END 71 | AGAIN l0 72 | ``` 73 | 74 | 75 | ## Next nonSyn 76 | 77 | ``` 78 | \ Return the next non-syn node. Any syn nodes are executed asNow=FALSE 79 | FN nextNonSyn \ [ -> &node(nullable)] 80 | LOOP l0 81 | dictRefMaybe(0) -> retIfNot(dup\node) \ {&node} 82 | IF(isTyFn(dup\node) and isFnSyn(ovr\node)) 83 | drp\node; single(FALSE\asNow); \ compile the token 84 | ELSE ret(\node) END 85 | AGAIN l0 86 | $tAssertEq(nextNonSyn (_) , ; -> answer, dictRef(0) answer) 87 | 88 | FN singleNonSyn \ compile single non-sync token 89 | nextNonSyn -> drp; single(FALSE); ret; 90 | 91 | ``` 92 | 93 | 94 | ## Use as test 95 | ``` 96 | \ Note: we need to add 3 here to reserve the cdata "_h" 97 | #3^ADD @TY_FN=_h \ { -> heap} get the code heap 98 | .R%FTGL@G_bbaPub.2, \ {&bba} 99 | .1%FTO @BBA_rooti, \ {rooti} 100 | @SLIT#0C^JN.1, %SHL \ {rooti<<12} convert to &block 101 | .R%FTGL@G_bbaPub.2, .1%FTO@BBA_len, %ADD \ + bba.len 102 | .R%FTGL@G_bbaPub.2, .R%FTO@BBA_ba.1, \ {&blockLoc &ba} 103 | .R%FTO@BA_blocks.1, %ADD %RET \ &blockLoc + &blocks 104 | ``` 105 | 106 | ## Fetch/Store Local Offset 107 | 108 | This was replaced by FTO and SRO 109 | 110 | 111 | In C: 112 | 113 | ```c 114 | FTLO: case SzI2 + FTLO: 115 | l = fetch(mem, LS_SP + popLit(SzI1), SzIA); // &local 116 | return WS_PUSH(fetch(mem, l + popLit(SzI1), szI)); // fetch offset 117 | GOTO_SZ(FTLO, SzI1) 118 | GOTO_SZ(FTLO, SzI4) 119 | SRLO: case SzI2 + SRLO: 120 | l = fetch(mem, LS_SP + popLit(SzI1), SzIA); // &local 121 | return store(mem, l + popLit(SzI1), WS_POP(), szI); 122 | GOTO_SZ(SRLO, SzI1) 123 | GOTO_SZ(SRLO, SzI4) 124 | ``` 125 | 126 | In spor test: 127 | 128 | ```spor 129 | \ Test FTLO 130 | 131 | #1234 @SZ4 $GLOBAL gStruct 132 | #67 @SZ1 $GLOBAL gStruct1 133 | $dictGetK gStruct @SZ4 $GLOBAL gStructR \ &gStruct 134 | 135 | $FN testFTSRLocalOffset \ () -> () 136 | @SZ4 $LOCAL _a \ just to test that local offset works 137 | @SZ4 $LOCAL r $END_LOCALS 138 | 139 | \ Test fetch local offset 140 | $REF gStruct $_SET r 141 | .4%FTLO $ldictGet r $h1 #0$h1 #1234$L2 $xsl tAssertEq 142 | .1%FTLO $ldictGet r $h1 #4$h1 #67$L2 $xsl tAssertEq 143 | 144 | \ Test store local offset 145 | #4242$L2 .4%SRLO $ldictGet r $h1 #0$h1 146 | .4%FTLO $ldictGet r $h1 #0$h1 #4242$L2 $xsl tAssertEq 147 | 148 | #15$L2 .1%SRLO $ldictGet r $h1 #4$h1 149 | .1%FTLO $ldictGet r $h1 #4$h1 #15$L2 $xsl tAssertEq 150 | %RET 151 | $testFTSRLocalOffset 152 | 153 | ``` 154 | 155 | ## Dot 156 | 157 | ```spor 158 | \ # Dot Compiler (.compiler) 159 | \ The below is the initial compiler for below cases: 160 | \ .var \ variable fetch 161 | \ .var = \ variable store 162 | \ .@var \ variable dereference 163 | \ .@var = \ variable dereference store 164 | \ .&var \ variable/function reference 165 | \ 166 | \ These are built to be extended by future dot compiler implementations for 167 | \ (i.e.) structs, modules, roles, etc. 168 | \ note: @4:1 [= ] syntax is a separate word (not .compiler) 169 | 170 | 171 | \ fn c_countChr [c -> count] : count and consume matching characters 172 | $SFN c_countChr $PRE \ { chr -> count } count and consume matching chrs 173 | :wa 174 | #0$L0 $LOOP l0 \ {chr count} 175 | %OVR $xsl c_peekChr \ {chr count chr c} 176 | %NEQ $IF \ {chr count} 177 | %SWP %DRP %RET $END 178 | %INC \ inc count. Next line inc tokenLen 179 | .1%FTGL@c_tokenLen$h2 %INC .1%SRGL@c_tokenLen$h2 180 | $AGAIN l0 181 | 182 | $SFN c_dotRefs \ { -> dotMeta } get dot meta for de/refs. 183 | \ Get the dotMeta for preceeding & or @ 184 | $xsl c_peekChr %DUP #26$L0 %EQ $IF \ Reference (&) case 185 | $xsl c_countChr %DUP #4$L0 %LT_U @E_cBadRefs$L2 $xsl assert 186 | @DOT_REF$L0 %ADD 187 | $ELSE %DUP #40$L1 %EQ $IF \ Dereference (@) case 188 | $xsl c_countChr %DUP #4$L0 %LT_U @E_cBadRefs$L2 $xsl assert 189 | @DOT_DEREF$L0 %ADD 190 | $ELSE %DRP #0$L0 \ no meta 191 | $END $END %RET \ {dotMeta} 192 | 193 | 194 | #26 $c_countChr &&& #3 $tAssertEq 195 | #26 $c_countChr #0 $tAssertEq 196 | $c_dotRefs && #2 @DOT_REF ^ADD $tAssertEq 197 | $c_dotRefs @@ #2 @DOT_DEREF ^ADD $tAssertEq 198 | $c_dotRefs #0 $tAssertEq 199 | ``` 200 | 201 | ## Zoa in spor 202 | 203 | ```spor 204 | \ ********** 205 | \ * [10] Zoa strings and logging zoab 206 | \ See ./harness.md for design reasoning. 207 | \ 208 | \ fn $loc |zoa string literal| : define a zoa string 209 | \ 210 | \ fn comzStart [] : zoab start 211 | \ fn comzArr [len join] : start an array of len and join 212 | \ fn comzLogStart [lvl extraLen] : start a log arr of data len exraLen 213 | \ fn print [len &raw] : print raw data to the user (LOG_USER) 214 | \ fn _printz [&z] : print zoab bytes (<=63) to the user 215 | \ fn TODO: not finished 216 | 217 | \ |zoa string literal| 218 | \ Creates a zoa string in the heap. 219 | $FN | 220 | $SYN $xsl assertNoNow 221 | $GET heap 222 | \ maxLen: (topHeap - heap) 223 | %DUP .A%FTGL@topHeap$h2 %SWP %SUB 224 | @D_zoa$L0 %DVFT .A%SRGL @heap$h2 %RET 225 | 226 | $FN c_logAll $PRE \ {&writeStruct len &buf } Write raw data to log output 227 | $LARGE $declEnd \ no locals, but used in XW. 228 | %DRP 229 | $LOOP l0 230 | %OVR %OVR 231 | @D_com$L0 %DVFT 232 | %NOT $IF %DRP %DRP %RET $END 233 | $AGAIN l0 234 | 235 | $FN ft4BE $PRE \ {&a -> U4} fetch4 unaligned big-endian 236 | %DUP%INC %DUP%INC %DUP%INC .1%FT \ {&a &a+1 &a+2 a@3 } 237 | %SWP .1%FT #08$L0%SHL %ADD \ {&a &a+1 (a@2<<8 + a@3)} 238 | %SWP .1%FT #10$L0%SHL %ADD \ {&a (a@1<<16 + a@2<<8 + a@3)} 239 | %SWP .1%FT #18$L0%SHL %ADD \ {a@0<<24 + a@1<<16 + a@2<<8 + a@3 } 240 | %RET 241 | 242 | $loc LOG_ZOAB_START #80$h1 #03$h1 243 | $FN comzStart #2$L0 @LOG_ZOAB_START$LA @D_com$L0 %DVSR %RET 244 | 245 | $loc TODO #0$h1 246 | $FN comzArr $PRE \ {len join} 247 | $declL b0 @SZ1 #1 $declVar $declEnd \ b0 is used as an array for com 248 | \ $IF @ZOAB_JOIN$L1 $ELSE #0$L0 $END %SWP \ join -> joinTy {joinTy len} 249 | \ %DUP #40$L1 %LT_U @E_cZoab$L2 $xsl assert \ assert len <= 0x3F {joinTy len} 250 | \ %JN $_SET b0 251 | \ #33$L0 $REF b0 $jmpl com \ send via com 252 | 253 | %DRP \ ignore join TODO 254 | @ZOAB_ARR$L1 %JN \ len->arrLen 255 | @TODO$L2 .1%SR \ store len @TODO 256 | #1$L0 @TODO$L2 @D_com$L0 %DVSR %RET \ {len &raw} communicate directly 257 | 258 | $FN comzLogStart $PRE \ {lvl extraLen} extraLen is in addition to sending the lvl 259 | $xsl comzStart \ TODO: check return code once it's added 260 | %INC @FALSE$L0 $xl comzArr 261 | @D_comZoab$L0 %DVFT %RET \ send lvl 262 | 263 | $FN print $PRE \ {len &raw}: print data to user 264 | @LOG_USER$L1 #1$L0 $xsl comzLogStart 265 | @FALSE$L0 @D_comZoab$L0 %DVSR %RET 266 | 267 | $FN _printz $PRE \ {&z}: print zoab bytes to user. (single segment) 268 | %DUP .1%FT %DUP #40$L1 %LT_U @E_cZoab$L2 $xsl assert \ {len} 269 | %SWP %INC $xsl print 270 | @D_comDone$L0 %DVFT %RET 271 | ``` 272 | 273 | ## Block Allocator 274 | 275 | ```spor 276 | \ ********** 277 | \ * Block Allocator (BA) 278 | \ The Block Allocator (BA) is used to allocate and free 4KiB blocks. It can 279 | \ store up to 255 of them (almost a full MiB). 280 | \ 281 | \ It's implementation architecture is built on the Byte Singly Linked List 282 | \ (BSLL). The structure of a BSLL is an array of bytes, up to length 255. 283 | \ Each byte can contain either 0xFF (BA_iNull) or the index of the next node. 284 | \ At initialization, the root points to the first index and each index 285 | \ points to the next index. Allocation is then simply popping indexes 286 | \ from the SLL. 287 | \ 288 | \ STRUCT BA [ \ size=10 289 | \ indexes: &U1; \ 0: pointer to indexes (up to 255 bytes) 290 | \ blocks: APtr; \ 4: pointer to data blocks, each of 4KiB 291 | \ len: U1; \ 5: number of indexes and blocks 292 | \ root: U1; \ 6: root free block, may be 0xFF if empty 293 | \ ] 294 | 295 | CONST NULL = 0; 296 | CONST BA_iNull = 0xFF; 297 | CONST BA_blockPo2 = 12; \ 4KiB == 2^12 298 | CONST BA_halfBlock = 2048; 299 | 300 | \ STRUCT BA 301 | \ BA_indexesOfs = 0; \ indexes: APtr 302 | CONST BA_blocksOfs = 4; \ blocks: APtr 303 | CONST BA_lenOfs = 8; \ len: U1 304 | CONST BA_rootOfs = 9; \ root: U1 305 | 306 | LFN BA_iToPtr PRE \ {bi &ba -> &block} 307 | $declVar(declL ba, TY_VAR_INPUT+SZA, ASIZE) declEnd 308 | IF(dup\bi == BA_iNull) drp; ret NULL; END \ {bi} 309 | assert(dup\bi < ft1(GET ba + BA_lenOfs), E_iBlock) \ {bi} 310 | ft4(GET ba + BA_blocksOfs); swp; \ {&firstBlk bi} 311 | ret(\firstBlk + (\bi << BA_blockPo2)) 312 | 313 | LFN BA_isPtrValid PRE \ {&block &ba -> bool} 314 | $declVar(declL blk, TY_VAR_INPUT+SZA, ASIZE) 315 | $declVar(declL ba, TY_VAR_INPUT+SZA, ASIZE) 316 | declEnd 317 | ret between( \ blk between [&firstBlk, &lastBlkEnd) 318 | GET blk, 319 | ft4(GET ba + BA_blocksOfs), \ {&firstBlk} 320 | dup\firstBlk + (ft1(GET ba + BA_lenOfs) << BA_blockPo2), 321 | ); 322 | 323 | LFN BA_ptrToI PRE \ {&blk &ba -> bi} 324 | $declVar(declL blk, TY_VAR_INPUT+SZA, ASIZE) 325 | $declVar(declL ba, TY_VAR_INPUT+SZA, ASIZE) 326 | \ $SZ4 INPUT blk $SZ4 INPUT ba 327 | declEnd 328 | IF(not GET blk) ret BA_iNull; END 329 | assert(BA_isPtrValid(GET blk, GET ba), E_ptrBlk) 330 | ret((GET blk - ft4(GET ba + BA_blocksOfs)) >> BA_blockPo2); 331 | ``` 332 | 333 | ```spor 334 | SFN BA_iGet PRE ret ft1(_ + ft4(_)) \ {bi &ba -> bi} 335 | SFN BA_iSet PRE \ {value bi &ba} set ba@bi = value 336 | _ + ft4(_) \ {value &index} 337 | ret sr1(_, _) 338 | 339 | LFN BA_init PRE \ {&ba} 340 | $declVar(declL ba, TY_VAR_INPUT+SZA, ASIZE) 341 | $declVar(declL iLast, SZ1, 1) 342 | declEnd 343 | SET iLast = dec(ft1(GET ba + BA_lenOfs)); 344 | sr1(0, GET ba + BA_rootOfs); \ initialze root as index=0 345 | 0 LOOP l0 \ {bi} 346 | IF(dup == GET iLast) 347 | BA_iNull; swp; ret BA_iSet(\BA_iNull, \bi, GET ba) 348 | END \ {bi} 349 | dup; inc(\bi); ovr; \(bi bi+1 bi) 350 | BA_iSet(\(bi+1), \bi, GET ba) 351 | inc(\bi) \ {bi+1} 352 | AGAIN l0 353 | 354 | \ $setSysLogLvl(LOG_EXECUTE) 355 | $BA_init(REF BA_kernel) 356 | 357 | \ { &ba -> bi} alocate a block index 358 | \ (returning a) root->a->b ===> root->b 359 | LFN BA_iAlloc PRE 360 | $declVar(declL ba, TY_VAR_INPUT+SZA, ASIZE) declEnd 361 | ft1(GET ba + BA_rootOfs) \ {biRoot} 362 | reteq(dup, BA_iNull) \ {biRoot} return BA_iNull if biRoot=BA_iNull 363 | sr1(BA_iGet(dup, GET ba), GET ba + BA_rootOfs); \ {biRoot} root=next node 364 | ret _; 365 | 366 | \ {bi &ba} free a block index 367 | \ (freeing bi=a) root->b ===> root->a->b 368 | LFN BA_iFree PRE 369 | $declVar(declL bi, TY_VAR_INPUT+SZ1, 1) 370 | $declVar(declL biRoot, SZ1, 1) 371 | $declVar(declL ba, TY_VAR_INPUT+SZA, ASIZE) 372 | declEnd 373 | assert(BA_iNull != GET bi, E_iBlock) 374 | SET biRoot = ft1(GET ba + BA_rootOfs) 375 | BA_iSet(GET biRoot, GET bi, GET ba) \ a -> b (note: may be BA_iNull) 376 | ret sr1(GET bi, GET ba + BA_rootOfs) \ root -> a 377 | 378 | SFN BA_alloc PRE \ {&ba -> &blockNullable}E 379 | BA_iAlloc(dup\ba); swp; 380 | ret BA_iToPtr(\bi, \ba); 381 | 382 | SFN BA_free PRE \ {&block &ba} 383 | swp; ovr; \ {&ba &block &ba} 384 | BA_ptrToI(\ba); swp; ret BA_iFree(\bi, \ba); 385 | ``` 386 | 387 | ``` 388 | \ ********** 389 | \ * Singly Linked List (SLL) 390 | \ Singly linked lists are used throughout fngi because of their simplicity 391 | \ and extensibility. A singly linked list is a data structure that looks like 392 | \ below, with how the operations affect it as well. 393 | \ 394 | \ start : root -> b -> NULL 395 | \ push a : root -> a -> b -> NULL 396 | \ now pop a: root -> b -> NULL (return a) 397 | \ 398 | \ STRUCT SLL [ next: &SLL ] 399 | \ Note: root is just a pointer to a SLL with no data (&SLL) 400 | 401 | LFN SLL_pop PRE \ {&root -> &a}: Pop a node from the SLL 402 | $declVar(declL root, TY_VAR_INPUT+SZA, ASIZE) declEnd 403 | ft4(GET root); \ {&a} 404 | retz(dup); \ {&a} 405 | ret sr4(ft4(dup), GET root); \ {&a} root->b 406 | 407 | SFN SLL_push PRE \ {&a &root}: Push a node onto the SLL 408 | ovr; ovr; \ {&a &root &a &root} 409 | ft4(\root); swp; \ {&a &root &b &a} 410 | sr4(\b, \a); \ a->b 411 | ret sr4(\a, \root); \ root->a 412 | ``` 413 | 414 | ```spor 415 | \ ********** 416 | \ * Arena Allocator (AA) 417 | \ The arena budy allocator is built on top of the BA. It allows allocations of 418 | \ power of 2 blocks of sizes 2^2 (4 bytes) to 2^12 (4KiB). 419 | \ 420 | \ The AA keeps track of blocks it owns by using the BA's indexes (the same way 421 | \ the BA keeps track of free blocks). When the arena is dropped, all (4KiB) 422 | \ blocks are returned to the BA. 423 | \ 424 | \ The AA does a best-effort job of trying to join freed blocks to prevent 425 | \ fragmentation. However, it prioritizes known speed over reduction of 426 | \ fragmentation. It's performance requires a maximum of 12 loops for any 427 | \ request, except for freeing blocks which can require a BA index scan (rare). 428 | \ 429 | \ If further fragmenetation reduction is required, a library can implement methods 430 | \ to sort the freed areas and join conjoining ones. However the proper method to 431 | \ eliminate fragmentation is to move any still-needed data to a new arena and 432 | \ drop the fragmented one (obviously requiring careful programming). 433 | \ 434 | \ ## Design: 435 | \ The allocator has an array of pointers, each pointing to a larger po2. 436 | \ Multiple free blocks in a single power of 2 are stored by creating a 437 | \ singly-linked-list in the first 4 bytes of the free memory. Any freed (4KiB) 438 | \ blocks are returned to the BA. 439 | \ 440 | \ STRUCT AA [ 441 | \ bi: U1; \ block index for allocated block. 442 | \ _reserved1: U1; 443 | \ _reserved2: U2; 444 | \ ba: &BA; 445 | \ \ Po2 2 - 11 Clever: &aa@2 is the 0th index (2nd power), &aa@11 is 11th 446 | \ roots: arr[10; APtr]; 447 | \ ] 448 | 449 | SFN sort2 PRE \ { a b -> _ _ } sort two values on the stack 450 | ovr; ovr; retlt(_, _) swp; ret; 451 | 452 | \ {&a &b po2 -> bool} return whether blocks can be joined. 453 | LFN canBlocksMerge PRE 454 | $declVar(declL po2, TY_VAR_INPUT+SZ1, 1) declEnd 455 | ovr\a != align(dup\a, 1 << inc(GET po2)) \ {&a, &b, &a is on po2 boundary} 456 | IF(_) drp\b; drp\a; ret FALSE; END 457 | ret(\a == (\b - (1 << GET po2))) \ return whether a is next to b 458 | 459 | CONST AA_size = 48; 460 | CONST AA_baOfs = 4; 461 | CONST AA_rootsOfs = 8; 462 | 463 | SFN AA_isValidPo2 PRE ret betweenIncl(_, 2, 12); \ {po2 -> bool} 464 | SFN AA_assertPo2 PRE (AA_isValidPo2(_), E_aaPo2)$jmpl assert 465 | 466 | LFN AA_init PRE \ {&ba &aa} 467 | $declVar(declL aa, TY_VAR_INPUT+SZA, ASIZE) declEnd \ {&ba} 468 | sr4(\ba, GET aa + AA_baOfs); \ init ba 469 | (GET aa + AA_rootsOfs, 10 << 2)$jmpl memClear \ all roots=NULL 470 | 471 | $declVar(declG AA_kernel, SZA, AA_size) 472 | $AA_init(REF BA_kernel, REF AA_kernel) 473 | 474 | LFN AA_findPo2 PRE \ {po2 &aa -> &free gotPo2}: find the closest available po2 475 | $declVar(declL aa, TY_VAR_INPUT+SZA, ASIZE) declEnd \ {&ba} 476 | AA_assertPo2(dup\po2); 477 | LOOP l0 \ Find a free block of the appropriate size 478 | reteq(dup\po2, 12) \ maximum size 479 | retif(ft4((dup\po2 << 2) + GET aa)) \ non-null size 480 | inc(\po2) 481 | AGAIN l0 482 | 483 | SFN AA_allocExactPo2 PRE \ {po2 &aa -> &free}: get an exact po2 484 | swp; \ {&aa po2} 485 | IF(dup\po2 == 12) drp\po2; BA_alloc(\aa + AA_baOfs); 486 | ELSE SLL_pop(\aa + (\po2 << 2)) END ret; 487 | 488 | LFN AA_allocPo2 PRE \ {po2 &aa -> &free} allocate memory of size po2 489 | $declVar(declL po2, TY_VAR_INPUT+SZ1, 1) 490 | $declVar(declL gotPo2, SZ1, 1) 491 | $declVar(declL aa, TY_VAR_INPUT+SZA, ASIZE) 492 | declEnd 493 | SET gotPo2 = AA_findPo2(GET po2, GET aa); 494 | LOOP l1 \ Break the block until it is the right size. 495 | AA_allocExactPo2(GET gotPo2, GET aa); \ {&free} 496 | retz(dup) \ return if NULL 497 | reteq(GET po2, GET gotPo2) \ {&free}: return if correct po2 498 | 499 | SET gotPo2 = dec(GET gotPo2); \ {&free}: reduce gotPo2 since we're spliting in half 500 | \ The next cycle will return or divide the lowest free block 501 | SLL_push(dup\free + (1 << GET gotPo2), GET aa + (GET gotPo2 << 2)) 502 | SLL_push(\free , GET aa + (GET gotPo2 << 2)) 503 | AGAIN l1 504 | 505 | \ LFN AA_freePo2 PRE \ {&free po2 &aa} 506 | \ $SZ1 INPUT po2 $SZ1 LOCAL gotPo2 $SZ4 INPUT aa declEnd \ {&free} 507 | \ AA_assertPo2(GET po2); \ {&free} 508 | \ LOOP l0 \ {&free} Merge free blocks while they are consecutive 509 | \ IF(GET po2 == 12) ret BA_free(GET aa + AA_baOfs); END \ if block, just free that. 510 | \ SLL_pop(dup, GET aa + (GET po2 << 2)) \ {&f, &fp} f=free fp=freePrev 511 | \ IF(not dup) \ check null case of existing free 512 | \ drp\fp; ret SLL_push(\f, GET aa + (GET po2 << 2)); 513 | \ END sort2(_, _); \ {&a &b} sort them for next checks. 514 | \ IF(canBlocksMerge(ovr\a, ovr\b, GET po2)) 515 | \ drp\b; SET po2 = inc(GET po2); AGAIN l0 \ drop &b and treat as one 516 | \ END \ cannot merge, just push both back and return. 517 | \ SLL_push(\b, GET aa + (GET po2 << 2)); 518 | \ ret SLL_push(\a, GET aa + (GET po2 << 2)); 519 | \ \ END LOOP l0 520 | 521 | $c_dictDump 522 | ``` 523 | 524 | ```spor 525 | \ ********** 526 | \ ** Test Block Allocator 527 | $tAssertEq(0xFF, BA_iNull) 528 | 529 | $declVar(declG BA_fake, SZ4, 12) 530 | $sr4(0, gRef BA_fake) 531 | $declVar(declG BA_fakeIndexes, SZ4, 4) 532 | $sr4(0, gRef BA_fakeIndexes) 533 | 534 | SFN BA_fakeInitForTest PRE \ {&indexes, &blocks, len) Initialize some fake data 535 | sr1(\len , REF BA_fake + BA_lenOfs) 536 | sr4(\blocks , REF BA_fake + BA_blocksOfs) 537 | sr4(\indexes, REF BA_fake) 538 | ret sr1(0, REF BA_fake + BA_rootOfs) 539 | 540 | $BA_fakeInitForTest(REF BA_fakeIndexes, 0x1000, 4) 541 | $tAssertNot(BA_isPtrValid(0, REF BA_fake)) \ null block 542 | $tAssertNot(BA_isPtrValid(0x500, REF BA_fake)) 543 | $tAssert (BA_isPtrValid(0x1000, REF BA_fake)) \ 1st block 544 | $tAssert (BA_isPtrValid(0x2000, REF BA_fake)) \ 2nd block 545 | $tAssert (BA_isPtrValid(0x3000, REF BA_fake)) \ 3rd block 546 | $tAssert (BA_isPtrValid(0x4000, REF BA_fake)) \ 4th block 547 | $tAssertNot(BA_isPtrValid(0x5000, REF BA_fake)) \ 5th block 548 | 549 | $tAssertEq(BA_iNull, BA_ptrToI(0, REF BA_fake)) \ hard-coded 550 | $tAssertEq(0, BA_ptrToI(0x1000, REF BA_fake)) 551 | $tAssertEq(1, BA_ptrToI(0x2000, REF BA_fake)) 552 | $tAssertEq(2, BA_ptrToI(0x3000, REF BA_fake)) 553 | 554 | $tAssertEq(0, BA_iToPtr(BA_iNull, REF BA_fake)) \ hard-coded 555 | $tAssertEq(0x1000, BA_iToPtr(0, REF BA_fake)) 556 | $tAssertEq(0x2000, BA_iToPtr(1, REF BA_fake)) 557 | $tAssertEq(0x3000, BA_iToPtr(2, REF BA_fake)) 558 | $tAssertEq(0x4000, BA_iToPtr(3, REF BA_fake)) 559 | \ $tAssertEq(0x5000, BA_iToPtr(4, REF BA_fake)) \ panics 560 | 561 | $BA_init(REF BA_kernel) \ TODO: make this the fake block 562 | LFN testBASimple 563 | $declVar(declL blk, SZ4, 4) 564 | $declVar(declL iBlk, SZ1, 1) declEnd 565 | tAssert(ft1(REF BA_kernel + BA_lenOfs)) 566 | tAssertEq(0, ft1(REF BA_kernel + BA_rootOfs)); 567 | tAssertEq(1, BA_iGet(0, REF BA_kernel)); 568 | tAssertEq(2, BA_iGet(1, REF BA_kernel)); 569 | 570 | SET iBlk = BA_iAlloc(REF BA_kernel); 571 | tAssertEq(0, GET iBlk) 572 | tAssertEq(1, ft1(REF BA_kernel + BA_rootOfs)); \ root changed 573 | SET blk = BA_iToPtr(GET iBlk, REF BA_kernel); 574 | tAssertEq(ft4(REF BA_kernel + BA_blocksOfs), GET blk) 575 | tAssertEq(GET blk, BA_iToPtr(GET iBlk, REF BA_kernel)); 576 | tAssertEq(ft4(REF BA_kernel + BA_blocksOfs), GET blk); 577 | tAssertEq(GET iBlk, BA_ptrToI(GET blk, REF BA_kernel)); 578 | BA_free(GET blk, REF BA_kernel) 579 | tAssertEq(0, ft1(REF BA_kernel + BA_rootOfs)); \ root changed 580 | tAssertEq(1, BA_iGet(0, REF BA_kernel)); 581 | tAssertEq(2, BA_iGet(1, REF BA_kernel)); 582 | ret; 583 | 584 | $testBASimple 585 | 586 | \ ********** 587 | \ ** Test SLL 588 | 589 | $declVar(declG sll_c, SZ4, 4) 590 | $declVar(declG sll_b, SZ4, 4) 591 | $declVar(declG sll_a, SZ4, 4) 592 | $declVar(declG sll_root, SZ4, 4) 593 | $sr4(gRef sll_a, gRef sll_root) \ root->a->b->c->NULL 594 | $sr4(gRef sll_b, gRef sll_a) 595 | $sr4(gRef sll_c, gRef sll_b) 596 | $sr4(NULL, gRef sll_c) 597 | 598 | $tAssertEq(REF sll_a, SLL_pop(REF sll_root)) \ root->b->c 599 | $tAssertEq(REF sll_b, SLL_pop(REF sll_root)) \ root->c 600 | $SLL_push(REF sll_a, REF sll_root) \ root->a->c 601 | $tAssertEq(REF sll_a, SLL_pop(REF sll_root)) \ root->c 602 | $tAssertEq(REF sll_c, SLL_pop(REF sll_root)) \ root->NULL 603 | $tAssertEq(NULL , SLL_pop(REF sll_root)) 604 | $tAssertEq(NULL , SLL_pop(REF sll_root)) 605 | 606 | \ ********** 607 | \ ** Test AA 608 | $sort2(5 10) $tAssertEq(_, 10) $tAssertEq(_, 5) 609 | $sort2(10 5) $tAssertEq(_, 10) $tAssertEq(_, 5) 610 | 611 | $tAssert (canBlocksMerge(16, 24, 3)) 612 | $tAssertNot(canBlocksMerge( 8, 16, 3)) 613 | $tAssertNot(canBlocksMerge( 8, 24, 3)) 614 | $tAssertNot(canBlocksMerge( 4, 12, 3)) 615 | $tAssertNot(canBlocksMerge(12, 20, 3)) 616 | $tAssertNot(canBlocksMerge(32, 64, 4)) 617 | $tAssert (canBlocksMerge(64, 96, 5)) 618 | $tAssertNot(canBlocksMerge(64, 96, 4)) 619 | 620 | $tAssert(AA_isValidPo2(2)) 621 | $tAssert(AA_isValidPo2(3)) 622 | $tAssert(AA_isValidPo2(11)) 623 | $tAssert(AA_isValidPo2(12)) 624 | $tAssertNot(AA_isValidPo2(0)) 625 | $tAssertNot(AA_isValidPo2(1)) 626 | $tAssertNot(AA_isValidPo2(13)) 627 | 628 | $declVar(declG AA_fake, SZ4, AA_size) 629 | $AA_init(REF BA_kernel, REF AA_fake) 630 | 631 | \ Populate some fake data in freePo2 array 632 | $sr4(2, REF AA_fake + (2<<2)) 633 | $sr4(5, REF AA_fake + (5<<2)) 634 | $sr4(10, REF AA_fake + (10<<2)) 635 | 636 | \ test AA_findPo2 637 | $tAssertEq( 2, AA_findPo2(2, REF AA_fake)) 638 | $tAssertEq( 5, AA_findPo2(3, REF AA_fake)) 639 | $tAssertEq( 5, AA_findPo2(5, REF AA_fake)) 640 | $tAssertEq(10, AA_findPo2(6, REF AA_fake)) 641 | $tAssertEq(10, AA_findPo2(9, REF AA_fake)) 642 | $tAssertEq(10, AA_findPo2(10, REF AA_fake)) 643 | $tAssertEq(12, AA_findPo2(11, REF AA_fake)) 644 | $tAssertEq(12, AA_findPo2(12, REF AA_fake)) 645 | 646 | \ test AA_allocExactPo2 (except for po2=12) 647 | 648 | $declVar(declG AA_blk0, SZA, ASIZE) 649 | $srA(BA_alloc(REF BA_kernel), gRef AA_blk0) 650 | 651 | \ Populate some "real fake" data. 652 | SFN AA_initForTest PRE \ {&ba} 653 | AA_init(_, REF AA_fake) 654 | 655 | GET AA_blk0 + (1<<2); \ po2=2 656 | sr4(NULL, ovr) sr4(_, REF AA_fake + (2<<2)) 657 | 658 | GET AA_blk0 + (1<<5); \ po2=5 659 | sr4(NULL, ovr) sr4(_, REF AA_fake + (5<<2)) 660 | 661 | GET AA_blk0 + (1<<10); \ po2=10 662 | sr4(NULL, ovr) sr4(_, REF AA_fake + (10<<2)) 663 | ret; 664 | 665 | $AA_initForTest(NULL); 666 | $tAssertEq(GET AA_blk0 + (1<<2) , AA_allocExactPo2(2, REF AA_fake)) 667 | $tAssertEq(NULL , AA_allocExactPo2(3, REF AA_fake)) 668 | $tAssertEq(NULL , AA_allocExactPo2(4, REF AA_fake)) 669 | $tAssertEq(GET AA_blk0 + (1<<5) , AA_allocExactPo2(5, REF AA_fake)) 670 | $tAssertEq(NULL , AA_allocExactPo2(6, REF AA_fake)) 671 | $tAssertEq(GET AA_blk0 + (1<<10), AA_allocExactPo2(10, REF AA_fake)) 672 | $tAssertEq(NULL , AA_allocExactPo2(11, REF AA_fake)) 673 | ``` 674 | 675 | ```spor 676 | \ All roots are now null 677 | $tAssertEq(NULL, ft4(REF AA_fake + (2<<2))) 678 | $tAssertEq(NULL, ft4(REF AA_fake + (3<<2))) 679 | $tAssertEq(NULL, ft4(REF AA_fake + (5<<2))) 680 | $tAssertEq(NULL, ft4(REF AA_fake + (10<<2))) 681 | 682 | $AA_initForTest(NULL); 683 | \ First let's do a freebie 684 | $tAssertEq(GET AA_blk0 + (1<<2) , AA_allocPo2(2, REF AA_fake)) 685 | 686 | \ Now, we want to allocate 2^9, but that will require spliting 2^10 687 | $tAssertEq(GET AA_blk0 + (1<<10), AA_allocPo2(9, REF AA_fake)) 688 | $tAssertEq(GET AA_blk0 + (1<<10) + (1<<9), AA_allocPo2(9, REF AA_fake)) 689 | \ They should now both be exhausted 690 | $tAssertEq(NULL, ft4(REF AA_fake + ( 9<<2))) 691 | $tAssertEq(NULL, ft4(REF AA_fake + (10<<2))) 692 | 693 | \ Let's do that again, but this time for 2^8 694 | $AA_initForTest(NULL); 695 | $tAssertEq(GET AA_blk0 + (1<<10), AA_allocPo2(8, REF AA_fake)) 696 | $tAssertEq(GET AA_blk0 + (1<<10) + (1<<8), AA_allocPo2(8, REF AA_fake)) 697 | $tAssertEq(GET AA_blk0 + (1<<10) + (2<<8), AA_allocPo2(8, REF AA_fake)) 698 | $tAssertEq(GET AA_blk0 + (1<<10) + (3<<8), AA_allocPo2(8, REF AA_fake)) 699 | \ They are now all exhausted 700 | $tAssertEq(NULL, ft4(REF AA_fake + ( 8<<2))) 701 | $tAssertEq(NULL, ft4(REF AA_fake + ( 9<<2))) 702 | $tAssertEq(NULL, ft4(REF AA_fake + (10<<2))) 703 | 704 | \ Now we need to test that we can reserve an actual block 705 | $BA_fakeInitForTest(REF BA_fakeIndexes, \blocks GET AA_blk0, 1) 706 | $AA_initForTest(REF BA_fake) 707 | 708 | \ TODO: write a test which reserves a ba block... it should just work :D 709 | 710 | \ TODO: write and test FREEDOM 711 | 712 | $BA_free(GET AA_blk0, REF BA_kernel) 713 | ``` 714 | -------------------------------------------------------------------------------- /notes/core_structs.md: -------------------------------------------------------------------------------- 1 | # Core Structs 2 | 3 | There are a few core structs. 4 | 5 | [WIP] 6 | -------------------------------------------------------------------------------- /notes/defer.md: -------------------------------------------------------------------------------- 1 | # Defer 2 | 3 | fngi will support defer. It will be similar to golang with a few caveats: 4 | 5 | - There is no "defer stack". The first defer simply puts the beginning of the 6 | block on the call stack. Following defers mutate this value (and the end of 7 | their blocks is a hard-jump to the previous defer block). 8 | - Because of the hard-jumping nature, you cannot (should not?) use defers 9 | inside of if-statements. This is especially true of the first defer. 10 | For continuing defers, if it is skipped it won't run... unless there is a 11 | defer _after_ it (since all the defer blocks are hard-wired together). This 12 | makes it extremely touchy when code changes. 13 | - Skipping the first defer will cause later defers to mutate the existing 14 | callstack: _very_ bad. 15 | - However, if there was only one defer or you could guarantee ALL defers 16 | were skipped if it was skipped then it could be skipped... at your own 17 | risk! 18 | - Defers do nothing special when a panic happens. In other words, panics 19 | ignore defer just like they ignore the call stack. 20 | - The defered token has no special handling of arguments before the deferred 21 | code executes... why does golang even do that??? 22 | 23 | When the `defer` is called it compiles a `DST` (defer start) instr and empty byte. This: 24 | 25 | - pushes ep+2 onto the call stack 26 | - jmps forward by the byte amount. 27 | - The empty byte should be updated with the block size by an END or equivalent. 28 | 29 | It then compiles the next token (which can be `( ... )`) and updates the jmp 30 | byte. 31 | 32 | When the next `defer` is called it compiles a `DCON` (defer continue) instr and 33 | empty byte. This: 34 | 35 | - **mutates** the call stack (doesn't push) to be ep+2 36 | - jmps foward by the byte amount (which again is determined by next token). 37 | 38 | > We could also use 6 bytes to read+update registers and do a JMPL. 39 | > However, we have a lot of room on the jmp command. I think it makes the most 40 | > sense to put it there, since defers will be very frequently used. 41 | 42 | When the function rets, it actually returns into the last encountered defer 43 | block. This will hard-jump to previous defer blocks until the first one, which 44 | will ret like a "normal" function. 45 | 46 | ## Use and Examples 47 | 48 | Conceptually, defer statements work by putting a defer block of code on a stack. 49 | The deferred blocks are then executed in FILO (first in last out) order. 50 | 51 | ```fngi 52 | fn foo[] do ( 53 | defer println(|printed last|); 54 | .v = .Foo.new() defer ( 55 | println(|freeing|); .v.free() ); 56 | print(|printed first: |); println(v.value); 57 | defer println(|printed before free|); 58 | println(|printed second|); 59 | ret; 60 | ) 61 | 62 | \ Outputs: 63 | \ printed first: 42 64 | \ printed second 65 | \ printed before free 66 | \ freeing 67 | \ printed last 68 | ``` 69 | 70 | If a `ret` was run before a defer, then those deferred items won't be run. 71 | 72 | Defers are mostly used for: 73 | 74 | - freeing memory before function return. 75 | - closing open resources. 76 | - performing logic that must be placed at the end. 77 | 78 | ## Type Stack 79 | 80 | Defers work with the type-stack system. The rules are: 81 | 82 | - Defer blocks are assumed to have nothing on the stack when they start and 83 | should "ret" nothing. 84 | - Otherwise, rets are checked as they normally are. 85 | 86 | If all returns are rets the right types, and all defers are returning nothing, 87 | then the function will return the values it says it will. 88 | 89 | > Possible feature: defer blocks that return something could "pop" that type off 90 | > the _bottom_ of the return type stack. Thus you could use defers for returning 91 | > values. Would probably want to specify this with, i.e. `defer [U4] ( ... )` 92 | -------------------------------------------------------------------------------- /notes/dot.md: -------------------------------------------------------------------------------- 1 | # Dot compiler 2 | 3 | The dot compiler is probably the most syntax-heavy component of fngi. 4 | The initial dot compiler supports all of the below syntaxes: 5 | 6 | ```fngi 7 | \ .var \ variable fetch 8 | \ .var = \ variable store 9 | \ .&var \ variable reference (also function) 10 | \ .@var \ variable dereference 11 | \ .@var = \ variable dereference store 12 | ``` 13 | 14 | In order to accomplish this (and cleanup the path for 16bit) several changes 15 | need(ed) to be made: 16 | 17 | - [done] Reference depth had to be added to both local and globals. We only support 18 | depth <= 3, so this easily fits in the current 8bit meta. 19 | - [done] Switch to &metaRef instead of metaRef for everything. This also clears the way 20 | for moving the meta byte to be separate from the value (not a good idea for 21 | 16bit systems, probably not a good idea regardless). 22 | - first use either meta or &meta in C. 23 | - then populate both for all types. 24 | - then remove &metaRef (converting to just &ref) 25 | - [done] Declaration for locals/globals needs to support reference depth 26 | -------------------------------------------------------------------------------- /notes/fiber.md: -------------------------------------------------------------------------------- 1 | # Fibers 2 | 3 | In designing fibers to use `async.h`, then main challenge is what to do for `D_catch`, 4 | which needs to be able to catch a panic. But then I realized: `D_catch` only 5 | actually needs the current stack depth of the catching function to be stored 6 | somewhere, which can be stored in a bitmap. The sp is already stored in the callstack. 7 | Simple and effective. 8 | 9 | I had _thought_ for the kernel to handle role methods that it needed to grow 10 | it's own stack, but that was incorrect. Role methods can be a normal `xlImpl` call, 11 | which only affects the fngi state. 12 | 13 | So, before we can design fibers, we must define the behavior of our panic 14 | handling. On returning from a `D_catch` nothing special will happen. In 15 | particular the following will NOT happen: 16 | 17 | - Not clear the err state. It is the caller's job to check the err state and 18 | act appropriately. 19 | - Not change the WS state, whether there was a panic or not. It is the caller's 20 | job to act appropriately. 21 | 22 | The _only_ thing `D_catch` will do is set a `catch` bit in csz when calling the 23 | function. 24 | 25 | When an error is caught (through longjmp) the following will happen: 26 | - Find the largest catch bit less than the current CS depth. 27 | - Clear the WS and push the previous CS depth (depth at the error). 28 | - Note: clearing the WS is essential to ensure there is enough for the CS depth and 29 | for standard error handling operations which may require pushing values. On some 30 | architectures, the WS may also be copied for inspection, or logged. 31 | - Set the CS and CSZ depths to the catch depth 32 | 33 | It does NOT do the following, which must be done by the caller: 34 | - Change the locals stack depth. Most callers will want to set this back to what 35 | it was when `D_catch` was called. This can be done by either traversing the 36 | call stack or by caching the initial value in a global variable / compiled 37 | location. 38 | - **Note:** you CANNOT cache it in a local variable, since the locals stack 39 | will be in the wrong location in an error condition! 40 | - If callers really wanted fast and re-entrant panic handlers, you could create a 41 | global stack which pushed and popped the local stack locations. 42 | - There will be a single global value reserved for this purpose, used soley 43 | for assertPanic. Users can also use it in their own applications if they 44 | know it will be used only once (the maximum number of panic handlers in 45 | most applications). 46 | 47 | ## Async Design 48 | 49 | With the above design, the maximum entire stack depth of fngi's VM is _very_ 50 | small, probably on the order of 16 calls and maybe a few KiB of memory. Also, 51 | most of the "state" for an async.h style design is already contained in the 52 | Fiber class. This includes the execution pointer, ws, call stack, etc. 53 | Actually, there possibly isn't any reason to use async.h at all. 54 | 55 | However, the existing design of executeLoop doesn't work, as it has no support 56 | for yield calls. The new design should: 57 | 58 | - Simply start executing from ep. Not take in any arguments. 59 | - executeInstr will _return_ a value to indicate one of: 60 | - `EXECUTE_NORMAL`: executeLoop will continue executing instructions. 61 | - `EXECUTE_RET`: executeLoop will handle a return. 62 | - If there is no CS depth, it will remove the fiber from the LL and return. 63 | The fiber will not be run again (unless re-added). 64 | - Otherwise, it will appropriately update the execution pointer/etc. 65 | - `EXECUTE_YLD`: will cause executeLoop to simply return immediately. Another 66 | fiber will be run. 67 | 68 | -------------------------------------------------------------------------------- /notes/fn.md: -------------------------------------------------------------------------------- 1 | # Creating fn and types 2 | 3 | First of all, types will go on the end of \ dictionaries based on a `C_TYPED` 4 | bit. Types will be stored in unaligned \ memmory next to pub/priv code. 5 | 6 | Now why did I need `SN_find` again lol... oh ya, because I 7 | NEED TO BUILD THE TYPE LOOKUP DICTIONARIES 8 | ... and the whole type system as well... 9 | ... no. This isn't that hard. It isn't as hard as what I wrote below. 10 | 11 | I need to implement JUST "builtin" types, and have a function to handle them 12 | and convert them to declVar. _Eventually_ that function will point to a better 13 | implementation, but for now it will just build the locals/etc and provide 14 | clean syntax. 15 | 16 | That's really _all that's needed_. Also, I don't have to implement this as 17 | `ufn`, this can be `fn` and call the relevant type registration stuff (if 18 | they are non-null). 19 | 20 | Stuff I do need: 21 | - stk: SYN function that checks it's part of a function types and calls ref to 22 | adds input types. 23 | - inp: SYN function that checks it's part of a function types and calls ref to 24 | add input types and compile to local variables. 25 | - out: SYN function that checks it's part of a function types and calls ref to 26 | add output types. 27 | - var: SYN function which can be used either on function input or body. 28 | Adds space to locals size but doesn't automatically do anything. Can be 29 | extended to use the dot compiler to auto-assign (i.e. `var x = 7;`) 30 | - Initial PRIV functions to handle the above for only hand-rolled builtin 31 | types. 32 | 33 | ## Type representation 34 | Types will be a bytestream (called a typestream) of unaligned big-endian values. 35 | 36 | The first byte of a type is a meta byte: `TTT- ----` 37 | - `TTT` is the same as the low-byte in DNode meta. Specifies CONST, FN, VAR, DICT 38 | which breaks down into GLOBAL, LOCAL, STRUCT, ENUM, MOD, SUBMOD 39 | - Some of the above are never used in the type stack (GLOBAL and LOCAL must 40 | be always 0, VAR shouldn't exist (use CONST), SUB/MOD can never exist) 41 | - If the type is STRUCT/ENUM, the next byte specifies the number of fields/variants 42 | - If the type is FN, the next two bytes specify the number of inputs + outputs 43 | - If the type is CONST, only one type item follows. 44 | 45 | Each type item starts with a byte: `C--- ----` 46 | 47 | - If C=0 this is a non-constructed (builtin) type, the meta is: `0EEE -DDD` 48 | - `E` is an enum of which one: `Any, Slot, U1, I1, U2, I2, U4, I4` 49 | - `D` contains the reference depth, meaning the physical size (in the struct, 50 | local, etc) is a Ref but the data (final FTO size) is the one selected by 51 | `E`. 52 | - `Any` types can never be materialized: you can only have a reference to them, 53 | you can never perform an operation (i.e. FT, ADD, etc) on them. They enable 54 | generic references (i.e. List[U4], BstU4[MyStruct]) 55 | - If C=1 this is a constructed type, the meta is: `1--- --SS` 56 | - It is followed by a big-endian pointer of size `SS` which is an `&DNode` 57 | 58 | The type stack will unpack these into the following structs: 59 | 60 | ``` 61 | struct TyItem { 62 | ty: Ref \ reference to data. Is null for builtin types. 63 | meta: U1 \ TYI meta 64 | } 65 | 66 | struct TyRepr { 67 | first: Slc[TyItem]; 68 | second: Slc[TyItem]; 69 | meta: \ TYR meta 70 | } 71 | ``` 72 | 73 | 74 | 75 | ## Stuff I don't need (but may use a bit of) 76 | 77 | Example syntax: 78 | 79 | ``` 80 | ufn myFn stk(a:U1 b:U2) inp c:U2; var i:U2; inp d:Ref -> out(x:Ref y:Ref) 81 | var i:U2 = 7; 82 | ... code 83 | var j:U2 = 10; 84 | ... code 85 | ) 86 | ``` 87 | 88 | I have now created `if` syntax (sans type checking). I now wish to create ufn syntax. 89 | For a _complete_ implementation, I need: 90 | - Define how types are represented and stored (probably BST using them as a key?) 91 | - Read types and sizes, for input compiling 92 | - Role method: push types 93 | - Role method: register current types (get hard pointer) 94 | 95 | Add later: 96 | 97 | This is... a lot. In particular, there is a chicken-and-egg problem with the 98 | definition of types and structs. Some of this can be avoided by substituting roles and 99 | doing the types later, but I don't like that. 100 | 101 | Instead, let's do a shortcut! The stk, inp, etc fns WILL use a role method... except for 102 | a select number of built-in types. For these, it will handle them directly. This solves 103 | another chicken-and-egg problem, which is that we need a way to represent these types 104 | in structs anyway. 105 | 106 | -------------------------------------------------------------------------------- /notes/fngi_syntax.md: -------------------------------------------------------------------------------- 1 | # fngi syntax 2 | 3 | ## Function syntax 4 | 5 | ```fngi 6 | fn foo [?a:U4 ?b:U2 c:u4] -> [U4] do ( 7 | // ... 8 | ) 9 | ``` 10 | 11 | - `?` declares an input that is _not stored_ as a local. Instead, it remains the 12 | stack. The name is used only for documentation (and for the type checker). 13 | These variables _must_ be the left-most variables. 14 | - The other inputs (without `?`) are popped in reverse-order from the stack at 15 | the beginning of the function and stored as locals. 16 | - `-> [...]` is optional and specifies the return type. 17 | - `do` is syntactic surgar to make function definitions (and if/while/etc) more 18 | readable, especially in cases where `()` are not used. 19 | -------------------------------------------------------------------------------- /notes/harness.md: -------------------------------------------------------------------------------- 1 | # Harness and Zoab 2 | 3 | > Fngi/spor uses a "harness" for passing arguments and interfacing with the 4 | > operating system. The harness is located in [./fngi](./fngi), which is an 5 | > executable python script. 6 | 7 | Fngi doesn't interface directly with the linux environment in the typical 8 | fashion. The primary differences are: 9 | 10 | 1. It uses zoab for it's configuration and logging, receiving/sending 11 | structured data. 12 | 2. The structured data it sends is structured in a specific way, allowing for 13 | the harness to implement core debugging and developer ergonomics tooling. 14 | 15 | Why is it implemented this way? There are several reasons: 16 | 17 | 1. [Zoab][zoa] is unbelievably simple and efficient to both implement and use. 18 | It has almost no cost over logging simple strings. 19 | 2. Logging is a core part of the developer code -> test cycle. This is 20 | _especially_ true on small or embedded systems. One of the first things a 21 | developer does when targetting an embeded system, after getting blinky 22 | light working, is to get serial IO of some kind. 23 | 24 | So logging will exist anyway, why not just use text logs? 25 | 26 | There are gobs of useful information to be had when an error occurs which help a 27 | developer triage the issue. Automating the finding of this information (with 28 | only a little bit of context) is next to trivial when using a full-featured 29 | langauge to code the automation. On the flip side, finding this information 30 | within fngi/spor, which are ultra-simple kernels that unroll into more 31 | full-fetured languages, would bloat them unnecessarily. 32 | 33 | For example: trying to format and log the entire stack trace (with function 34 | names, local variable breakdown, etc) would add an absurd amount of complexity 35 | (and memory requirements!) for fngi/spor's core implementation. Not only that, 36 | this complexity would have to be built in very early to have nearly any benefit. 37 | Conversly, simply logging the raw bytes of the stack is trivial. It is also 38 | almost trivial for the harness to take note of the memory addresses of functions 39 | (as they are registered) and local variable offsets and names. Inspecting a 40 | dumped stack is then a fairly simple excersize in software development when 41 | using a full-featured language with memory managed HashMaps. 42 | 43 | When developing fngi while using a fngi environment, the "more stable" existing 44 | environment can act as the harness and deal with the debug information, while 45 | the more dynamic system under development can ignore that complexity. You could 46 | even use a separate machine+display to handle the debug output, allowing 47 | minimalism of resource usage. The same is therefore allowed when developing fngi 48 | on a modern OS. 49 | 50 | [zoa]: http://github.com/vitiral/zoa 51 | -------------------------------------------------------------------------------- /notes/lua.md: -------------------------------------------------------------------------------- 1 | # Ideas for Lua 2 | 3 | Need to add a `help` function 4 | 5 | * displays globals information 6 | * can query it for nested search 7 | * data is output in a Schema 8 | 9 | Iterator: 10 | * function which when called repeatedly with no arguments returns (possibly 11 | different) values. 12 | * when it returns nil as the first value it is done. 13 | * it can also be a table with `__call` 14 | 15 | Schema type: 16 | 17 | * named columns, each of which is a list 18 | * prints into a nice table 19 | * all columns have implicit primary key of index 20 | * has keyed columns of data 21 | * each column can be in a different form: 22 | * List: normal way 23 | * function: call with Query object, get iterator 24 | 25 | Query 26 | 27 | * indexes: List or iterator of indexes 28 | * filter: function(index, value) returns true for keep 29 | * eq, lt, gt, lte, gte: value search 30 | * pattern: pattern search 31 | -------------------------------------------------------------------------------- /notes/lua_docs.md: -------------------------------------------------------------------------------- 1 | 2 | # Metamethods 3 | Copy/paste of http://lua-users.org/wiki/MetatableEvents 4 | 5 | ``` 6 | __index - Control 'prototype' inheritance. When accessing "myTable[key]" and the key does not appear in the table, but the metatable has an __index property: 7 | if the value is a function, the function is called, passing in the table and the key; the return value of that function is returned as the result. 8 | if the value is another table, the value of the key in that table is asked for and returned 9 | (and if it doesn't exist in that table, but that table's metatable has an __index property, then it continues on up) 10 | Use "rawget(myTable,key)" to skip this metamethod. 11 | __newindex - Control property assignment. When calling "myTable[key] = value", if the metatable has a __newindex key pointing to a function, call that function, passing it the table, key, and value. 12 | Use "rawset(myTable,key,value)" to skip this metamethod. 13 | (If the __newindex function does not set the key on the table (using rawset) then the key/value pair is not added to myTable.) 14 | __mode - Control weak references. A string value with one or both of the characters 'k' and 'v' which specifies that the the keys and/or values in the table are weak references. 15 | __call - Treat a table like a function. When a table is followed by parenthesis such as "myTable( 'foo' )" and the metatable has a __call key pointing to a function, that function is invoked (passing the table as the first argument, followed by any specified arguments) and the return value is returned. 16 | __metatable - Hide the metatable. When "getmetatable( myTable )" is called, if the metatable for myTable has a __metatable key, the value of that key is returned instead of the actual metatable. 17 | __tostring - Control string representation. When the builtin "tostring( myTable )" function is called, if the metatable for myTable has a __tostring property set to a function, that function is invoked (passing myTable to it) and the return value is used as the string representation. 18 | __len - (Lua 5.2+) Control table length that is reported. When the table length is requested using the length operator ( '#' ), if the metatable for myTable has a __len key pointing to a function, that function is invoked (passing myTable to it) and the return value used as the value of "#myTable". 19 | __pairs - (Lua 5.2+) Handle iteration through table pairs when for k,v in pairs(tbl) do ... end is called (See GeneralizedPairsAndIpairs). 20 | __ipairs - (Lua 5.2+) Handle iteration through table pairs when for k,v in ipairs(tbl) do ... end is called (See GeneralizedPairsAndIpairs). 21 | __gc - Userdata finalizer code. When userdata is set to be garbage collected, if the metatable has a __gc field pointing to a function, that function is first invoked, passing the userdata to it. The __gc metamethod is not called for tables. (See http://lua-users.org/lists/lua-l/2006-11/msg00508.html) 22 | __name - When it contains a string, may be used by tostring and in error messages. 23 | __close - (Lua 5.4+) Makes metatable to-be-closed variable, if not set to nil or false. 24 | Mathematic Operators 25 | __unm - Unary minus. When writing "-myTable", if the metatable has a __unm key pointing to a function, that function is invoked (passing the table), and the return value used as the value of "-myTable". 26 | __add - Addition. When writing "myTable + object" or "object + myTable", if myTable's metatable has an __add key pointing to a function, that function is invoked (passing the left and right operands in order) and the return value used. 27 | ''If both operands are tables, the left table is checked before the right table for the presence of an __add metaevent. 28 | __sub - Subtraction. Invoked similar to addition, using the '-' operator. 29 | __mul - Multiplication. Invoked similar to addition, using the '*' operator. 30 | __div - Division. Invoked similar to addition, using the '/' operator. 31 | __idiv - (Lua 5.3) Floor division (division with rounding down to nearest integer). '//' operator. 32 | __mod - Modulo. Invoked similar to addition, using the '%' operator. 33 | __pow - Involution. Invoked similar to addition, using the '^' operator. 34 | __concat - Concatenation. Invoked similar to addition, using the '..' operator. 35 | Bitwise Operators 36 | Lua 5.3 introduced the ability to use true integers, and with it bitwise operations. These operations are invoked similar to the addition operation, except that Lua will try a metamethod if any operand is neither an integer nor a value coercible to an integer. 37 | 38 | __band - (Lua 5.3) the bitwise AND (&) operation. 39 | __bor - (Lua 5.3) the bitwise OR (|) operation. 40 | __bxor - (Lua 5.3) the bitwise exclusive OR (binary ^) operation. 41 | __bnot - (Lua 5.3) the bitwise NOT (unary ~) operation. 42 | __shl - (Lua 5.3) the bitwise left shift (<<) operation. 43 | __shr - (Lua 5.3) the bitwise right shift (>>) operation. 44 | Equivalence Comparison Operators 45 | __eq - Check for equality. This method is invoked when "myTable1 == myTable2" is evaluated, but only if both tables have the exact same metamethod for __eq. 46 | For example, see the following code: 47 | 48 | __lt - Check for less-than. Similar to equality, using the '<' operator. 49 | Greater-than is evaluated by reversing the order of the operands passed to the __lt function. 50 | a > b == b < a 51 | __le - Check for less-than-or-equal. Similar to equality, using the '<=' operator. 52 | Greater-than-or-equal is evaluated by reversing the order of the operands passed to the __le function. 53 | a >= b == b <= a 54 | ``` 55 | -------------------------------------------------------------------------------- /notes/macro.md: -------------------------------------------------------------------------------- 1 | TCL is a cool language. I'm not sold on the "everything is a string" but it 2 | certainly does it far better than ba(sh). 3 | - [TCL The Misunderstood](http://antirez.com/articoli/tclmisunderstood.html) 4 | was a fun read. I think basing the macro system (partly) on how TCL does it 5 | makes some sense. This would map quite easily to fngi, which could expand 6 | a string template and set the compiler object until it's compiled quite 7 | seamlessly. 8 | - Pure [TCL editor](https://github.com/slebetman/tcled/blob/master/tcled) in 9 | ~3k lines. 10 | -------------------------------------------------------------------------------- /notes/mergesort.py: -------------------------------------------------------------------------------- 1 | """ 2 | I was confused how mergesort works on linked lists, and many of the answers did 3 | not make it clear. 4 | 5 | """ 6 | import dataclasses 7 | 8 | @dataclasses.dataclass 9 | class Sll: 10 | v: int 11 | next: 'Sll' = None 12 | 13 | def __repr__(self): 14 | out = ["["] 15 | node = self 16 | while node: 17 | out.append(str(node.v)) 18 | node = node.next 19 | out.append("]") 20 | return ' '.join(out) 21 | 22 | 23 | @dataclasses.dataclass 24 | class Root: 25 | root: Sll = None 26 | 27 | def pop(self) -> Sll: 28 | out = self.root 29 | self.root = out.next 30 | out.next = None 31 | return out 32 | 33 | def __len__(self): 34 | l = 0; node = self.root 35 | while node: 36 | l += 1; node = node.next 37 | return l 38 | 39 | def __repr__(self): 40 | if not self.root: return "[ ]" 41 | return repr(self.root) 42 | 43 | # For debug/tests 44 | 45 | @classmethod 46 | def frStr(cls, s): 47 | """Create from string.""" 48 | first = Sll(None); last = first 49 | for v in s.split(): 50 | last.next = Sll(int(v)) 51 | last = last.next 52 | return Root(first.next) 53 | 54 | 55 | def sortedMerge(a: Sll, b: Sll) -> Sll: 56 | """Merge two sorted Slls so the output is sorted.""" 57 | first = Sll(None) 58 | addTo = first 59 | while a and b: 60 | if a.v < b.v: 61 | addTo.next = a; a = a.next 62 | else: 63 | addTo.next = b; b = b.next 64 | addTo = addTo.next 65 | while a: addTo.next = a; a = a.next; addTo = addTo.next 66 | while b: addTo.next = b; b = b.next; addTo = addTo.next 67 | return first.next 68 | 69 | 70 | def popSort2(sll: Root) -> Sll: 71 | """Pop 2 items from Sll and return them sorted.""" 72 | if(not sll.root): return Root(None) 73 | a = sll.pop() 74 | if(not sll.root): return a 75 | b = sll.pop() 76 | if a.v < b.v: 77 | a.next = b; return a 78 | else: 79 | b.next = a; return b 80 | 81 | 82 | def _mergesort(fr: Root, count: int) -> Sll: 83 | if count <= 2: return popSort2(fr) 84 | aLen = count // 2 85 | a = _mergesort(fr, aLen) 86 | b = _mergesort(fr, count - aLen) 87 | return sortedMerge(a, b) 88 | 89 | 90 | def mergesort(fr: Root): 91 | out = _mergesort(fr, len(fr)) 92 | assert not fr.root 93 | return out 94 | 95 | 96 | print("basic root:", Root.frStr("1 2 3 4")) 97 | print("sort2:", popSort2(Root.frStr("2 1"))) 98 | print("sort2:", popSort2(Root.frStr("1 2"))) 99 | 100 | print("sorted merge: ", 101 | sortedMerge( 102 | Root.frStr("1 2 3 5").root, 103 | Root.frStr("1 3 7 8 10 10 13").root)) 104 | 105 | nodes = Root.frStr("9 7 9 3 4 6 1 2") 106 | print("Start with:", nodes) 107 | s = mergesort(nodes) 108 | print("Done:", s) 109 | 110 | 111 | -------------------------------------------------------------------------------- /notes/misc.md: -------------------------------------------------------------------------------- 1 | # Misc. notes 2 | 3 | ## Endianness 4 | 5 | Little/Big endian do NOT refer to which "end" is the "big" (aka most 6 | significant) byte. In fact, they have the reverse meaning as that. 7 | 8 | They are reference to the Liliputans from Gulliver's travels. 9 | 10 | The "Bigendians" broke the big side of the egg (number) over their head 11 | (array-start). Hence a bigendian system has the large part of the number at the 12 | head of the array. 13 | 14 | Conversely, "Little Endian" liliputans broke the little side of the egg 15 | (number) over their heads (array-start) and hence have the little part of the 16 | number at the head. 17 | 18 | ## Hexwords 19 | 20 | These are super critical 21 | 22 | - baseball: ba5eba11 23 | - foosball: f005ba11 24 | - bedablle 25 | - a caboose: aCab005e 26 | - deadbeat: DeadBea7 27 | - eat beef: 0Ea7Beef 28 | - defecate: 00Defec8 29 | - defect: 00Defec7 30 | - deadBeef 31 | - ca11ab1e 32 | - foldable: f01dab1e 33 | - scaleable: 5ca1ab1e 34 | - a oddball: a0ddba11 35 | - boldface: b01dface 36 | - cassette: ca55e77e 37 | - boatload: b0a710ad 38 | -------------------------------------------------------------------------------- /notes/os.md: -------------------------------------------------------------------------------- 1 | # Civboot OS 2 | 3 | The purpose of fngi (and spore) is to bootstrap into a Civboot OS. It also wants 4 | to be a general-purpose programming language outside of Civboot, but that is 5 | it's primary purpose. 6 | 7 | One of the things I've realized is that a virtual machine bytecode permits a lot 8 | of experimentation with how this might be implemented. In addition, if Civboot 9 | software remains lean and small, then there we can affort using a virtual 10 | environment for most code execution. This means that not only can the spore 11 | bytecode interpreter be the basis for executing code: it can be the _entire_ 12 | kernel as well. This was always in the back of my mind as it is analagous to 13 | CollapseOS's FORTH operating system, but I hadn't fully fleshed out the ideas 14 | behind a permissions model/etc. Interestingly, almost everything here makes it 15 | a better (more secure) general purpose language as well. 16 | 17 | The first point is that there needs to be some concept of the following: 18 | 19 | - ring: there is only ring0 and ring-other. ring0 can access and write to any 20 | memory and do anything. ring-other is a number between 1-127 which can only 21 | write to memory within it's ring. 22 | - proc: a process with a certain ring and permissions. It gets a "big block" 23 | of 16k for it's code to be compiled in, it's own locals-stack, working stack 24 | and call stack. 25 | - permissions: a U32 containing permissions bits regarding what the proc can do. 26 | 27 | The basic architecture: 28 | 29 | - The interpreter keeps a bytearray where each byte represents a 4k block in 30 | system memory. This is called the memring. Each byte is a ring number where 31 | the highest bit represents whether the 4k block is globally readable. 32 | - The interpreter knows the currently executing proc. Whenever a memory or 33 | device operation is performed, the permissions bits and memring is checked 34 | before they are performed. 35 | 36 | In the final Civboot, the above (or some version that is better which I haven't 37 | thought of yet) will be powered by the hardware. The advantages to the above 38 | are: 39 | 40 | - Low runtime cost w/out hardware, zero runtime cost with hardware. Register 41 | memory or a separate small (10-12 bit) bus could be used for the ring array 42 | to enable zero-cost checking of memory access. Possibly the memory itself 43 | could store the ring number for each block and the current ring be updated 44 | by the kernel. 45 | - Almost zero complexity. I believe the above is aproximately as simple as you 46 | could design a memory and device protection system. The allowed rings could 47 | be grown to 255 by using a bitarray for the global-read bit, and could be 48 | grown to more by using 16bits for the ring number. However, I don't believe 49 | Civboot will _ever_ require more than 255 process _groups_ (note _not_ 50 | processes). 51 | 52 | ## Proc Model 53 | 54 | The core data type throughout the OS is the 4k block and the Arena allocator. 55 | Processes communicate by passing bvalues and arenas between eachother, 56 | where bvalue is the primary "root" value being communicated. Both can be null. 57 | These are set as global variables and can be changed when 58 | cont(rc) or exit(rc) is called. 59 | 60 | The standard bvalue has two types: bdata which is essentially a len of bytes 61 | and blist which is a len of bvalues (pointers). All pointers should be to 62 | memory within the corresponding arena. Process are not _required_ to use 63 | bvalue for their root data type, but it is strongly recommended for most 64 | cases. This diverges from linux where the standard datatype is an unstructured 65 | stream of bytes. CivbootOS simplifies the UNIX design by allowing communication 66 | of entire blocks and arenas, reducing the glue that commonly exists between 67 | processes serializing and deserializing simple data. 68 | 69 | ## Device Registration 70 | 71 | Not all device operations need to be implemented by the native backend. Most 72 | will be "registered" and the native backend will simply execute them. Before 73 | executing them it will: 74 | 75 | - Cache the current ring and permissions bits. 76 | - Set the ring=0 and permissions=full 77 | - Basically do `D_xsCatch` on the registered function. 78 | - Before returning it will re-set the ring and permissions. If 79 | there was a panic it will be re-thrown. 80 | 81 | Therefore all device operations run as ring0 and can only be registered by a 82 | ring0 process. They can be thought of as kernel-level "drivers." It is their 83 | job to do appropriate permissions checking. 84 | 85 | DeviceId=0 is reserved for compiler-internal operations and will be protected 86 | by a permissions bit. DeviceId=1 will be used for "os" operations and uses 87 | various permissions bits. The operations include: 88 | 89 | - log for writing bvalue logs. Caller must drop bvalue. 90 | - env for fetching environment values. 91 | - block for de/allocing from the block allocator 92 | - arenaNew for creating and dropping "root registered" arenas. These have their 93 | own blocks and can be passed between processes. 94 | - proc for processes 95 | - LD creates a new process. Must pass in the process owned arena and 96 | config (both optional). 97 | - SR continues a process using ioArena and ioValue. 98 | - fs for communication with the file system "directory" database. LD reads 99 | fs node, SR updates them. 100 | - disk for communication with a "disk access" device. Requires a fs node to 101 | specify which one. 102 | 103 | > At the device level, eveything is NOT a file. There is very distincly a 104 | > "disk", "udp", etc. At the programmer level there will still be URIs to 105 | > access things ergonomically. 106 | 107 | ## Permissions Bits 108 | 109 | TODO: need to define. 110 | -------------------------------------------------------------------------------- /notes/path.md: -------------------------------------------------------------------------------- 1 | I've found the current ftOffset and srOffset, while quite clever, to be lacking. 2 | 3 | There needs to be a better API for "get next token chain." 4 | 5 | Let's do some example code: 6 | 7 | ``` 8 | struct A [ a0:U2 a1:U2 ] 9 | struct B [ b0:A b1:&A b2:U4] 10 | struct C [ c0:B c1:&B c2:A ] 11 | 12 | 13 | impl A [ 14 | meth aMeth[self:&Self -> U4 ] do (self.a0 + self.a1 ) 15 | ] 16 | ``` 17 | 18 | Okay, how do we represent these paths. Assume that all 19 | the first elements are variables. 20 | 21 | What do we actually _NEED_: 22 | 23 | * We need to know the _TyI of the result_ 24 | * We need to know the _offset necessary for fetching or storing_ 25 | 26 | ``` 27 | \ field compiles to how to _get_ the variable 28 | \ { op=FTLL, tyI=(U2, &0), offset=a.v + a0.v } 29 | a.a0 30 | 31 | \ { op=FTLL, tyI=(U2, &0), offset=a.v + a1.v } 32 | a.a1 33 | 34 | \ Method compiles to which method and the location 35 | \ of Self. 36 | \ Note: method always ends the path, so if 37 | \ the below was followed by `.+` then that 38 | \ would be compiled next. 39 | 40 | \ { op=FTLL, tyI=(A, &0), offset=a.v } 41 | \ { op=XL, tyI=(TyFn(aMeth), &0), offset=0 } 42 | a.aMeth 43 | 44 | \ { op=FTLL, tyI=(A, &0), offset=b.v + b0.v } 45 | b.b0 46 | 47 | \ { op=FTLL, tyI=(U2, &0), offset=b.v + b0.v + a1.v } 48 | b.b0.a1 49 | 50 | \ { op=FTLL, tyI=(A, &1), offset=b.v + b1.v } 51 | \ { op=FTO, tyI=(U2, &0), offset=a1.v} 52 | b.b1.a1 53 | 54 | \ { op=FTLL, tyI=(U4, &0), offset=b.v + b2.v } 55 | b.b2 56 | 57 | \ { op=FTLL, tyI=(A, &0), offset=c.v + c0.v + b0.v } 58 | c.c0.b0 59 | 60 | \ { op=FTLL, tyI=(U2, &0), offset=c.v + c0.v + b0.v + a1.v } 61 | c.c0.b0.a1 62 | 63 | \ { op=FTLL, tyI=(B, &1), offset=c.v + c1.v } 64 | \ { op=FTO, tyI=(U2, &0), offset=b0.v + a0.v } 65 | c.c1.b0.a0 66 | 67 | \ { op=FTLL, tyI=(B, &1), offset=c.v + c1.v } 68 | \ { op=FTO, tyI=(A, &1), offset=b1.v } 69 | \ { op=XL, tyI=(aMeth, &0), offset=0 } 70 | c.c1.b1.aMeth 71 | ``` 72 | 73 | Through references 74 | ``` 75 | var br: &B 76 | 77 | \ { op=FTLL, tyI=(B, &1), offset=b.v } 78 | \ { op=FTO, tyI=(A, &1), offset=b1.v } 79 | br.b1 80 | ``` 81 | 82 | Role Access 83 | ``` 84 | role Inner [ 85 | absmeth i[ -> S ] 86 | ] 87 | role R [ 88 | absmeth m[ -> S ] 89 | ] 90 | 91 | struct RS [ 92 | rs0: U4 93 | rs1: R 94 | ] 95 | 96 | var r: R 97 | 98 | \ { op=FTLL, tyI=(R, &0), offset=r.v } 99 | \ { op=XL, tyI=(m, &0), offset=0 } 100 | r.m 101 | 102 | var rs: RS 103 | 104 | \ { op=FTLL, tyI=(R, &0), offset=rs.v + rs1.v } 105 | \ { op=XL, tyI=(m, &0), offset=0 } 106 | rs.rs1.m 107 | ``` 108 | -------------------------------------------------------------------------------- /notes/peacechains.h: -------------------------------------------------------------------------------- 1 | // https://www.catch22.net/tuts/neatpad/piece-chains/ 2 | // 3 | // Piece Chains are the perfect fngi data structure for modifying text. 4 | // * Their parent type can be a SllSlc (nice!). This means that algorithms 5 | // (like find, regex, etc) target the SllSlc 6 | // * They are very useful for editing, since they can be split up easily 7 | 8 | #include 9 | #include 10 | #define U1 unsigned char 11 | #define U2 uint16_t 12 | #define S size_t 13 | 14 | typedef struct {void* data; void* methods;} Arena; // See civc for actual definition 15 | 16 | typedef struct { 17 | struct SllSlc* next; // Sll 18 | U1* dat; U2 len; // Slc 19 | } SllSlc; 20 | 21 | typedef struct { 22 | struct Piece* next; // Sll 23 | U1* dat; U2 len; // Slc 24 | struct Piece* prev; 25 | } Piece; 26 | 27 | // Casting is safe since they have identical memory layout in their prefix. This 28 | // means that methods implemented for SllSlc also work for Piece. 29 | static inline SllSlc* Piece_toSllSlc(Piece* p) { return (SllSlc*)p; } 30 | 31 | // When doing search the node and the offset of (i.e.) the start of a match are 32 | // returned. 33 | // This is done in-case modifications need to be made on the ref node after 34 | // searching. It's trivial to convert an SllSlcRef into an SllSlc if that's 35 | // what is wanted. 36 | typedef struct { SllSlc* ref; U2 offset; } SllSlcRef; 37 | SllSlc SllSlcRef_toSllSlc(SllSlcRef* r) { 38 | SllSlc* ref = r->ref; 39 | return (SllSlc) { 40 | .next = ref->next, 41 | .dat = ref->dat + r->offset, 42 | .len = ref->len - r->offset, 43 | }; 44 | } 45 | 46 | // Search in haystack for needle, returning it's location. 47 | // Note that this works with Piece as well, but the `ref` 48 | // will be a pointer to Piece instead of SllSlc (typecast needed in C, 49 | // overriden unty implementation needed in fngi). 50 | SllSlcRef SllSlc_find(SllSlc* haystack, SllSlc needle); 51 | 52 | //// Insertion 53 | // The below demonstrate the power of Piece: modifying text. 54 | // 55 | // The allocator is used for allocating new Piece nodes. Note that 56 | // **no string buffers are ever allocated**. Any Slcs simply point to 57 | // (pieces of) the original data or the "replacement" strings given. 58 | // 59 | // Note that supporting undo/redo would require something like the spanrange 60 | // and a slightly different implementation. This is fine -- the base string 61 | // manip will not support undo/redo and separate methods will be built for 62 | // supporting that. The Piece data structure does not change! 63 | 64 | // Insert ss into p at index. This mutates p 65 | Piece* Piece_insert(Piece* p, Arena a, S index, SllSlc ss); 66 | 67 | // Replace all instances of pattern with replacement. 68 | Piece* Piece_replace(Piece* p, Arena a, SllSlc pattern, SllSlc replacement); 69 | -------------------------------------------------------------------------------- /notes/role.md: -------------------------------------------------------------------------------- 1 | # Role: fngi's interface/trait/etc 2 | 3 | I love interfaces (java/js/golang/etc), similar to traits in rust. I think they 4 | are a fantastic way to specify a set of _actions_ that a particular piece of 5 | _data_ can perform. 6 | 7 | In some previous versions of fngi I claimed to want to do "rust style traits", 8 | and I recently came across 9 | [this tweet](https://merveilles.town/@neauoire/107038809041856242) expressing 10 | concern on this. I wanted to get a few of my thoughts down on what a fngi 11 | interface (called a "role") would look like. 12 | 13 | ## New thoughts 14 | 15 | 16 | The basic API is: 17 | ``` 18 | \ A resource role with a single method 19 | role Resource [ 20 | \ You can always drop a resource 21 | absmeth drop &Self; 22 | 23 | meth printDrop(this:&Resource) { 24 | print("Dropping myself") 25 | this.drop() 26 | } 27 | ] 28 | 29 | implement Resource for UnixFile { 30 | \ Explicitly specified for demonstration, not required 31 | drop=&UnixFile.drop 32 | } 33 | ``` 34 | 35 | 1. What type will a role be 36 | * `TyDict` with meta of `TY_DICT_ROLE` 37 | 38 | 2. How will a struct implement a role? 39 | * It could be a key on the struct. Problem with that is namespace overlapping. 40 | * It could be a key on the role. This has the same problem. 41 | * the key could be the full path -- this is wasteful. 42 | * The key could use _either_ a type or a name -- this is what we did. 43 | 44 | 45 | 46 | ## C layout 47 | 48 | > These thoughts were made before I had a clear idea of the interface. It has 49 | > changed since then, but I've left this here unedited. 50 | 51 | Before we talk about fngi, let's discuss C. Let's say you were designing a 52 | farming game. The farming game has animals, each with a favorite food and 53 | a different way of handling movement. 54 | 55 | > Clearly this is all really ugly in C. Neither the C compiler, nor the language 56 | > syntax help you. I believe these can be implemented more "natively" and in a 57 | > type-checked way without much complexity in fngi, see the botom of this 58 | > document. 59 | 60 | ```c 61 | typedef struct { 62 | size_t lastSaidIndex; 63 | Meat favoriteMeat; 64 | } Dog; 65 | 66 | typedef struct { 67 | size_t lastSaidIndex; 68 | Grass favoriteGrass; 69 | } Goat; 70 | ``` 71 | 72 | Let's also say there were a set of methods you wanted to pass around with these: 73 | 74 | ```c 75 | typedef struct { 76 | char* (*talk)(void* animal); // "talk" function pointer 77 | char* (*move)(void* animal, int x, int y); // "move to x,y" function pointer 78 | } Animal; 79 | ``` 80 | 81 | One way you could make Dog or Goat a virtual type is to essentially mimic 82 | Rust/C++ "fat pointers" and pass around a pointer to the (global) method 83 | implementation along with a pointer to the data. 84 | 85 | ```c 86 | char* goatTalk(Goat* goat) { 87 | print(goatWords[goat->lastSaidIndex]); 88 | goat->lastSaidIndex += 1; 89 | } 90 | 91 | // ... etc for goatMove 92 | 93 | Animal goatMethods = (Animal) { 94 | .talk = goatTalk; 95 | .move = goatMove; 96 | }; 97 | 98 | void talkAndMove(Animal* m, void* animal, int16_t x, int16_t y) { 99 | printf(m->talk(animal)); 100 | m->move(animal, x, y); 101 | } 102 | 103 | // and then in your code 104 | talkAndMove(&goatMethods, &myGoat, 5, 10); 105 | ``` 106 | 107 | The advantage here is radical simplicity over implementing a vtable, which would 108 | (at least) involve something like multiple layered "perfect hash" tables of 109 | organized type ids (or something), fundamentally pointing to the method. The 110 | compiler would then need to bundle some kind of syntax to generate all of this 111 | for the programmer. 112 | 113 | However, there are several real disadvantages over rust/C++ virtual traits: 114 | 115 | * There is no type unification. For instance you can't specify both the Animal 116 | and Equal interface simultaniously. 117 | * Instead, you would have to pass in two pointers. 118 | * Or you have to have a AnimalEqual struct and pass that in instead. 119 | * This doesn't implicitly support code generation: the compiler has no way to 120 | know that these functions are implemented for these types, and therefore can't 121 | generate code which uses this knowledge. All that is supported is minimalistic 122 | runtime lookup. 123 | 124 | Neither of these are overly concerning to me. Dynamic types are an extremely 125 | useful tool, but a tool that should be used sparingly. It's great to have things 126 | like "reader/writer objects" for bytestreams or "loggers" that can be swapped 127 | out at runtime, or can be changed when passed to certain functions. Testing 128 | is another area: a state-heavy piece of code can benefit immensely from being 129 | able to having a Fake used for testing clients of it. 130 | 131 | On the code generation front, fngi supports insanely powerful macros instead. If 132 | you want to generate a function for multiple types then it should be possible to 133 | create a set of NOW functions (macros) to help you with that; but as a 134 | library, not as part of the core language. IMO creating bloat with something 135 | like code generation should be explicit, not implicit in using a new concrete 136 | type with a function. 137 | 138 | ## How it would look in fngi 139 | 140 | In fngi, a `Struct` or `Enum` is it's own dictionary/namespace. Therefore you 141 | can define methods for it and the dot compiler (name compiler) will know to 142 | check if the top stack item is the struct itself and pass in a reference if so. 143 | This allows you to do `.animal.walk(5, 6)`, etc. 144 | 145 | For roles, you can simply do: 146 | 147 | ```fngi 148 | \ Define animal role 149 | role Animal [ 150 | fn talk [#Animal]; 151 | fn move [x:U2, y:U2, #Animal]; 152 | ] 153 | 154 | \ Stores an Animal implementation in Goat 155 | implement Goat do Animal [ 156 | talk = goatTalk; \ previously defined method 157 | move = goatMove; \ previously defined method 158 | ] 159 | 160 | fn talkAndMove [x: U2, y: U2, animal: #Animal] do ( 161 | logUser(.animal.talk()); 162 | .animal.move(.x, .y); 163 | ) 164 | 165 | \ and then in the code 166 | talkAndMove(.myGoat#Animal, 5, 10) 167 | ``` 168 | 169 | the `.` are the "dot compiler" (variable lookup) and `#` is an explicit "fat 170 | pointer" syntax, which is really two values on the stack. The dot compiler sees 171 | this syntax and hands off the `myGoat` struct pointer and the Animal token to 172 | the sub-compiler for `#`. This isn't _quite_ as "clean" as rust -- but 173 | explicitness is a goal of fngi whenever possible. 174 | 175 | I could also imagine being able to create "role bundles" (i.e. AnimalEqual) 176 | which are a struct of pointers to the role implementations. As for passing 177 | two role implementations for the same animal... that's probably not 178 | supported by the syntax. That really should be rarely necessary, and you could 179 | (of course) pass the same data twice for the rare cases it is needed. 180 | 181 | ## What about that favorite food? 182 | 183 | These have all been concrete types, aka non-generic. What about if you wanted to 184 | have a `fn feed [Food]` method, where `Food` is generic over `Grass` and `Meat`? 185 | 186 | Haha, gotcha! There is no way in Hel fngi will support generics. However, 187 | `Food` could be an Enum... or even an role type itself! 188 | -------------------------------------------------------------------------------- /notes/round_two.md: -------------------------------------------------------------------------------- 1 | # Round Two 2 | 3 | My first implementation of fngi (round 1) was a success! I managed to create an 4 | assembly language (spor) which bootstrapped into a syntactically readable 5 | language (fngi). If you want to see that version, check out commit 402df12. 6 | 7 | I learned a lot from it, but I've come to realize the approach is misguided. I 8 | don't want to recreate [bootstrappable.org] -- I should make a _simple 9 | language_ that is _inspectible_. 10 | 11 | My new language will stick to many of the design principles, but will be 12 | different in the following ways: 13 | 14 | 1. It will have tight C integration. This primarily means that it can call C 15 | functions "natively" and there will no longer be a DV opcode. 16 | 2. It will depend on a few small libraries which follow the fngi design 17 | philosophy and can be independently tested. These are [zoa][zoa] and 18 | [civc][civc]. This will let me abstract the "simple but complete bedrock" 19 | from the interesting task of writing a simple compiler. 20 | 3. It will be inspecible from the start. This means: 21 | * All function calls will be "double pointers" where the first pointer is to 22 | the dictionary node containing all the metadata. This will be tracked in an 23 | "info stack" which means that the callstack can easily be printed. 24 | * I will prioritize the REPL, with a simple line-oriented one from the 25 | beginning. When there is an error you will be able to run fngi code in-line 26 | to inspect the function and walk the stack. This will be extendable from 27 | fngi code. 28 | 4. The spor AST will contain hints of line number and block ending. This is 29 | mostly for the debugger and future IR parser to be able to injest, but it 30 | will also be useful for... 31 | 5. ... the typechecker (and structs) will be available from the start. I've 32 | decided that while it would be really cool to implement the core language in 33 | the non-type checked language, it doesn't really make sense. I'd rather spend 34 | a thousand lines of C code (really, I think it will be that small) and have 35 | an ergonomic language from the start than muscle my way to creating a type 36 | checker in a non-typed language. 37 | 38 | [zoa]: http://github.com/civboot/zoa 39 | [civc]: http://github.com/civboot/civc 40 | [bootstrappable.org]: http://bootstrappable.org 41 | -------------------------------------------------------------------------------- /notes/shrm.md: -------------------------------------------------------------------------------- 1 | # shrm: the fngi shell language 2 | 3 | > **WARNING:** shrm does not yet exist. This document simply lays out what 4 | > _will_ exist. 5 | 6 | shrm is a library of [fngi] which implements a re-imagined shell. 7 | 8 | Forget about sh/bash/zsh/python. What features **must** a good shell language 9 | have: 10 | 11 | * Interactive: you must be able to run commands dynamically and work with the 12 | output directly. 13 | * Dynamic: types and functions must be changeable, data must be mutable, memory 14 | management must be automatic. You must be able to define functions and then 15 | change their definition. 16 | * Composable: it should be easy to break appart data and put it back together. 17 | It should be easy to transform data from one function/script" into the 18 | arguments for another. 19 | * Multi-process: it must be possible to spin up multiple processes and 20 | work with the data (at least pipe it) 21 | * Error handling: it must be possible to cleanly handle any errors. Resources 22 | must be automatically closed. 23 | * Writeable and Readable: the base (non-extended) syntax must be extremely easy 24 | to write while still being fairly readable. Writeability is prioritize due 25 | to how it is driven interactively and often thrown away. 26 | 27 | But what is the ultimate feature that a shell is missing: 28 | 29 | * Text-editor functionality to run a "block" of code (code with no newlines), 30 | query/view the output+logs, then make changes to the block and run again. 31 | * Stream handling: should easily handle streams of data. Open 32 | files/scripts/tables and work with the structured data output by filtering, 33 | joining, aggregating, etc. 34 | 35 | But now we are almost talking about a data query language in something like a 36 | Jupiter notebook. Indeed, I want shrm to be directly useable as a data query 37 | language for Civboot databases. Therefore I want it to follow as many principles 38 | of the [D database language] as possible for a conrete syntax language. 39 | 40 | [D database language]: https://www.dcs.warwick.ac.uk/~hugh/TTM/DTATRM.pdf 41 | 42 | ## How fngi will grow into shrm 43 | 44 | fngi is a compiled, typesafe, manually memory managed language. How can it 45 | possibly evolve into shrm from a simple library import? First of all: fngi's 46 | syntax is completely controlled by `syn` functions. Syntax can be implemented by 47 | simply calling such a function, i.e. `$shrm`. 48 | 49 | Even with this though, how do we handle the differences in types? 50 | 51 | * fngi public types are exported with a module/library. Converting to/from 52 | dynamic, memory managed types should be fairly trivial... if well architected 53 | for that purpose. 54 | * fngi makes use of an "arena stack". A dynamic language can simply push a 55 | new arena (allocator), call a fngi function, copy out the returned data and 56 | then pop & drop the arena from the stack. Types that are not just plain-old-data 57 | (POD) will not be allowed or will require special handling; i.e. must be a 58 | dynamic type returned by fngi using a separate (non-stack) passed-in arena. 59 | 60 | shrm values will have pointers to their allocator and type embedded with them. 61 | They are pushed onto a global LL for garbage collection. The GC knows how to 62 | walk the type tree and visit each shrm value, so no refcounts/etc need to be 63 | held. 64 | 65 | shrm is _not_ a high performance language. For that, use fngi. However, besides 66 | the stop-the-world GC, most libraries will be implemented in fngi, while scripts 67 | will often (but not always!) be implemented in shrm. Keep in mind that shrm is 68 | just a library of fngi, so fngi functions and modules can easily be implemented 69 | in-line when needed. 70 | 71 | [fngi]: http://github.com/civboot/fngi 72 | 73 | ## How to make a database language 74 | I believe a database language can be represented as streams of data processing. 75 | The streams and processing can be represented as a series of transforms 76 | represented in Role (interface) types. 77 | 78 | What roles would be necessary? 79 | 80 | > Note: More thought is necessary on how shrm will handle these dynamic types 81 | > role types. It's very possible that is one purpose of shrm -- to enable 82 | > dynamic types for purposes like this! 83 | 84 | ``` 85 | enum Agg { 86 | any, 87 | count, 88 | countDistinct, 89 | sum, 90 | min, 91 | max, 92 | ... 93 | } 94 | 95 | \ The role that all database types must implement. 96 | \ Note: some methods can be NULL if not supported. 97 | role DataType { 98 | \ The full key for this data type 99 | fn tuple(self) -> List[DataType]; 100 | 101 | \ Convert to/from bytes 102 | fn serialize(self) -> bytes; 103 | fn deserialize(b: bytes) -> Self; 104 | 105 | \ Compare two of the same type 106 | fn compare(other: Self, self) -> ISlot; 107 | 108 | \ Match against a user-defined string (i.e. regex) 109 | fn match(other: str, self) -> bool; 110 | 111 | \ Aggregate two of the same type, returning the new type. 112 | fn aggregate(agg: Agg, other: Self, self) -> Self; 113 | } 114 | 115 | \ The role that all data sources (i.e. tables, functions) 116 | \ must implement. 117 | role DataSource[D] { 118 | \ Walk the data source using a key of an array of DataTypes. 119 | \ 120 | \ If the key matches the primary key of the source, this should return a 121 | \ stream with ordered=true, enabling a merge join. 122 | fn walk(key: List[D], self) -> Stream[D]; 123 | 124 | \ Join two data sources with given constraints to create a new data source. 125 | fn join( 126 | key: List[DataType], 127 | filter: List[Filter[D]], 128 | aggregation: List[Aggregation[D]], 129 | other: Self, 130 | self) -> Self; 131 | } 132 | ``` 133 | 134 | 135 | -------------------------------------------------------------------------------- /notes/spor_future.md: -------------------------------------------------------------------------------- 1 | # Is Spor Syntax necessary? 2 | 3 | > **Note:** this will eliminate the spor language (i.e. `#2$L0 %ADD`), not the 4 | > bytecode. The bytecode is still extremely valuable for dynamic execution, 5 | > safety and optimizations. 6 | 7 | One of the primary benefits of FORTH is it's ability to transition to/from 8 | native execution. You can write code natively (i.e. assembly) and execute it 9 | from within FORTH. 10 | 11 | Why don't I have this? I think it is primarily because I developed spor and DV 12 | instructions. This may have been a mistake. 13 | 14 | I could add an `XN` function. It would execute a native function pointer of type 15 | `void (*)()`. This would still interact just fine with my error handling, and 16 | everything else I've implemented. 17 | 18 | This is so stupid-simple it's almost embarassing. I got very distracted by DV. 19 | This would permit me several very important things: 20 | 21 | 1. I could develop C libraries for components of fngi. I was going to do this 22 | with `civ_h` anyway, but this makes an even stronger case for it. 23 | 2. Native execution, as stated above. 24 | 3. Eliminate a rather anoying syntax. I don't like spor that much and it's not 25 | fun to write. 26 | 4. The development of fngi from C would be more streamlined. Since it would be 27 | more common to call fngi functions from C, I would create a better framework 28 | for doing so. 29 | 30 | The other thing is that I think I was too obsessed with a "single file" library. 31 | I'm now starting to embrace a few other healthy approaches to programming: 32 | 33 | 1. Create sub-languages (i.e. zty) which can be parsed by dynamic compilers like 34 | fngi and python, and generate code for static compilers like C. 35 | 2. Initially I wanted to bootstrap from x86 assembly. That died (I used C), so 36 | then I wanted to bootstrap as minimally as possible. That died (I added a 37 | block allocator, arenas, BST, etc), but I kept spor around. I don't think 38 | any of this was truly helpful. I've bloated my DV's to the point of 39 | absurdity and created way too much mis-direction to be good for me. 40 | 41 | ## Some Awesome Sauce 42 | With the use of zty I could have so much generated code that is later consumed 43 | by the fngi zty parser directly. 44 | 45 | 1. Auto-generate the fngi functions (in C) for the natives, i.e. `+ - shl`. 46 | There is no reason for this to be (human written) boilerplate! 47 | 2. Generate `_fromStack` and `_toStk` methods for structs for easy interaction. 48 | This has the added benefit that everything can be serialized for logging to 49 | python! 50 | 51 | ## Embedding with C 52 | Perhaps the coolest feature is that this would enable instant C embedding. C 53 | could register a function to be callable from fngi by just wrapping it in a 54 | `void (*)()` function and adding it to the fngi dictionary directly. 55 | 56 | -------------------------------------------------------------------------------- /notes/str.md: -------------------------------------------------------------------------------- 1 | # How to format strings 2 | 3 | A StrBuf is really just a subtype of Buf[U1] -- that's easy enough. 4 | 5 | C++'s method of string formatting is probably best here: 6 | 7 | ``` 8 | StrBuf s = StrBuf.withCap(allocTop(), 128); 9 | s << |Hello | << name << | your age is | << age; 10 | ``` 11 | 12 | What this _really_ gets down to is not strings, but methods. 13 | 14 | ## Methods 15 | 16 | Methods are a sub-type of `TY_FN` 17 | -------------------------------------------------------------------------------- /notes/struct.md: -------------------------------------------------------------------------------- 1 | # Design of struct data types 2 | 3 | STRUCT's are defined with the following syntax: 4 | 5 | ```fngi 6 | STRUCT 0x40 Name [ // 64=size of TY_DICT dictionary for this struct. 7 | field1: U4 8 | field2: U4 9 | field3: OtherStruct 10 | ] 11 | ``` 12 | 13 | - Alignment is C-like (bit packed while respecting system alignment). 14 | - `Name` appears as a dictionary entry of type `TY_DICT`. This means 15 | that it is actually a pointer to another dictionary. 16 | - field1-3 are keys in this other dictionary. They are `TY_FIELD` 17 | and have a pointer to their type and offset, among other things. 18 | 19 | Before `TY_DICT` can be implemented I must implement both block and buddy-arena 20 | alloctors. 21 | 22 | In the long-term (with the dot compiler) structs can be created using something 23 | like the following: 24 | 25 | ```fngi 26 | .foo = { 27 | field1 = 4; 28 | field2 = 5; 29 | field3 = {.a = 7}; 30 | } 31 | ``` 32 | 33 | To actually use struct's and their fields in the short-term, it is necessary to 34 | implement `VAR`, an untyped pre-cursor to the `.` compiler. This automatically 35 | handles recursive lookups. It does NOT handle assigning structs to a struct 36 | constructor, type checking, or many other things. 37 | 38 | ```fngi 39 | // foo (concrete local struct) 40 | VAR foo.field; 41 | VAR foo.field = 7; 42 | VAR &foo.field; 43 | 44 | // Or rFoo (reference struct) 45 | VAR rFoo.field; 46 | VAR rFoo.field = 7; 47 | VAR &rFoo.field; 48 | ``` 49 | -------------------------------------------------------------------------------- /notes/type_stack.md: -------------------------------------------------------------------------------- 1 | # Type Stack 2 | 3 | Fngi will bootstrap itself into types. Types are _entirely optional_ for fngi, 4 | meaning you can have a version of the language which has no type checking and 5 | you can define un-typechecked functions even when the language does have type 6 | checking. However, types are typically _extremely useful_ and therefore 7 | recommended for most cases. 8 | 9 | Fngi has a compile-time "ty stack" (type stack), which is just an array of 10 | pointers who's length is the system stack size (typically 12 - 32). There are 11 | two recognized types in fngi: 12 | 13 | - **concrete** type: native (i.e. U4), Struct, Enum, and Role instance. 14 | - **executable** type: the type _itself_ is a role instance of ExecTy. The 15 | type checker calls it's methods depending on context. 16 | 17 | ## Quick Example 18 | 19 | A quick example (using only native concrete types), say you had the following 20 | definitions: 21 | 22 | ```fngi 23 | fn add3[\a:U4, b:U4 -> out:U4] do ( 24 | \a + 3 + .b 25 | ) 26 | ``` 27 | 28 | A few important points: 29 | 30 | - `\a` in the type name causes it to _remain on the stack_. 31 | - `b` is a local variable, it gets pushed to the locals stack. 32 | - In the code block, `\a` before the `+` is purely for documentation, it has no 33 | effect on the ty stack. 34 | 35 | Type stack changes: 36 | 37 | - At the function start, the ty stack contains `[U4]`, which is `\a` 38 | - `+` is a `PRE` function, so encountering it the syntax does not (yet) change 39 | the ty stack. _It is only when a function is executed that the ty stack is 40 | changed_. 41 | - The compiler knows the type of literal `3` and pushes `U4` on the ty stack. 42 | The ty stack is now `[U4 U4]` 43 | - `+` is now evaluated. It's type signature is `[U4, U4 -> U4]`. It's inputs are 44 | popped and checked (which they pass) then the result is pushed on the type 45 | stack. It is now `[U4]` 46 | - The next `+` is again a `PRE`, so doesn't (yet) affect the ty stack. 47 | - `.b` is a known type (from the fn signature). The dot compiler pushes `U4` 48 | onto the ty stack, so it is again `[U4 U4]` 49 | - The `+` is evaluated and checked. The ty stack is `[U4]` 50 | - The function ends, and the remaining ty stack is compared to the function 51 | signature. It checks out! 52 | 53 | ## If Statements 54 | 55 | Conditionals look like below. Note a few things: 56 | 57 | - Who knows for sure why this code was written, don't focus on that. 58 | - `U4` is documented as the return value (inferred). 59 | - Note that every conditional leaves a U4 on the stack _or_ it returns from the 60 | function (and it's return value matches the function) 61 | 62 | ```fngi 63 | fn doSomething[x: U4, y:U4 -> a:U4, b:Bool] do ( 64 | if (.x) do (.y + 1) \ [ -> U4] 65 | elif (.x - 1) do (.y + 2) 66 | else ( ret (.y+3, FALSE); ) 67 | ret (_, TRUE); 68 | ) 69 | ``` 70 | 71 | How does the type stack do this? 72 | 73 | - Before the `if` statement executes anything (including it's conditional check) 74 | it clones the type stack. Call this `ifBefore` 75 | - After it's conditional check, it makes sure that the type stack is identical 76 | (the conditional check cannot change the type stack). 77 | - After it's code block, it clones the type stack again. Call this `ifAfter` 78 | - Exception: if the code block ends in a `ret` of any kind then type checking 79 | of the function is enforced, but type checking of the if statement is not. 80 | Therefore anytime a `ret` is compiled it updates a global variable for 81 | scope-checkers like this. 82 | - elif's conditional check starts with `ifBefore` and is checked to make sure it 83 | doesn't change. 84 | - elif's code block starts with `ifBefore` and checked to make sure it's 85 | identical to `ifAfter`. 86 | - the `else` clause ends in ret. It starts with `ifBefore`, but per the 87 | exception above it is only checked for the function type. 88 | - The if block then "returns" it's type stack by setting the main type stack to 89 | `ifAfter`. 90 | 91 | > Note: panics also avoid a type check obviously. 92 | 93 | ## While Loop / Switch 94 | 95 | All `loop` statements must have the same type at their beginning as their end. 96 | All of their `break` statements must have the same type as well. However, values 97 | can be returned from the `break` statements, the logic is similar to `if`. 98 | 99 | Switch statements are the same. 100 | 101 | ## Native Types 102 | 103 | Native types are stored in the native dictionary. This contains only enough 104 | information to write untyped fngi code, it does _not_ support type checking 105 | beyond the most primitive aspects (i.e. de-reference count, calling an large 106 | function like a small, etc). 107 | 108 | There are 5 `TY_*` bitmaps stored in the dictionary meta. They are: 109 | 110 | - `TY_CONST`: a UA constant. 111 | - `TY_FN`: a function `NORMAL|NOW|SYN|SYN_I`, `POST|PRE` 112 | - If defined on a Struct/Enum/Role the fn is considered a method. When type 113 | checked, the dot compiler will auto-insert the instance reference if it is the 114 | last input type (top of stack). 115 | - `TY_MODULE`: a module with a pointer to it's sub-dictionary, with ty bits for 116 | whether it is a Module, Struct, Enum, Role, or RoleImpl (implemented role). 117 | - `TY_GLOBAL`: a global variable offset, sz, and reference count 118 | - `TY_VAR`: (TODO: rename `TY_LOCAL -> TY_VAR`) 119 | - if defined in the locals dict this is a local variable offset, sz, reference 120 | count and input bit. 121 | - if defined in a MODULE=struct this is a struct field with an offset, sz and 122 | reference count. 123 | - if defined in a MODULE=enum this is an enum field with the variant number, 124 | sz and reference count (of the embedded type). 125 | 126 | ## Concrete Types 127 | 128 | Concrete types are stored in a BTree with [key=&dictKey value=type]. This allows 129 | you to look up a type if you know the dictionary pointer. 130 | 131 | - Fns store SLLs of their input and output types. Locals are not stored. 132 | - The base Struct/Enum/Role Ty just stores its size, and it's field members store a 133 | pointer to their concrete types. 134 | - RoleImpl's are just a Role struct constant which has the function methods 135 | filled in. 136 | 137 | ## Executable Types 138 | 139 | > Cancel all of this: "executable types" are just SYN. The SYN function can 140 | > check if it is being called during a type block and act accordingly. 141 | 142 | Executable types do not have a dictionary entry. Instead, they are a role 143 | instance of the following form: 144 | 145 | ```fngi 146 | role ExecTy [ 147 | register: &fn [e:#ExecTy -> &CompTy]; 148 | ] 149 | ``` 150 | 151 | `register` will "register" a new instance. It will do this by compiling tokens 152 | to determine what the sub-types are. By convention this would look like below. 153 | `List` in this example is an ExecTy, and it will compile `[U4]` to determine 154 | what it's supposed to be "generic" over. 155 | 156 | ```fngi 157 | fn foo [a:List[U4]] do ( ... ) 158 | ``` 159 | 160 | Some important points: 161 | 162 | - `register` is supposed to create a globally-unique instance (i.e. a struct) of 163 | the types it consumes, so that if it is called again with the same types it 164 | will return the same pointer. This allows you to compare the types! 165 | - ExecTy are kind of like `NOW` or `SYN` functions, except for the type 166 | stack. Instead of being "compiled" they are "executed." 167 | - ExecTy's are very powerful. They can consume any syntax, they can store 168 | whatever context they want, etc. 169 | - However, ExecTy's cannot cause the function to _emit different code for 170 | different types_. fngi still has a "concrete syntax" and functions are 171 | compiled in a streaming fashion. 172 | - Because of the above constaint, ExecTys are really only a way to have 173 | _generic references_. They are only actually useful for implementing 174 | containers (i.e. BTree, List, etc) or similar use-cases where you want to put 175 | a generic type in and then get it out (and you want the type checker to 176 | guarantee the types). 177 | - However, theoretically an ExecTy could probably be paired in some way with a 178 | Role to allow _methods_ be tied to the data, allowing for something closer to 179 | generics. I haven't seriously considered this, but I think it's largely not 180 | recommended (although there may be certain use cases). 181 | - If code generation is what you want/need I'd recommend a macro for 182 | accomplishing it: not using ExecTy's. 183 | 184 | -------------------------------------------------------------------------------- /notes/why-not-wasm.md: -------------------------------------------------------------------------------- 1 | # Why Not WASM? 2 | 3 | I have previously made comments, here and elsewhere, that web assembly seems like 4 | a decent language to bootstrap from. After implementing an interpreter for a 5 | subset of an early-version of the fngi language (including i32 bit integers, 6 | function calls with inputs and locals and many other features) I now think I 7 | was wrong. Here are the major issues: 8 | 9 | 1. WASM function inputs are part of the locals. This means that for _each 10 | function call_ a naive interpreter must first pop values from the stack and 11 | populate the inputs. This is the opposite of "inline". Clearly a complicated 12 | enough compiler can reason about these things, but this kind of complicaiton is 13 | exactly the kind of thing that Civboot wants to avoid. 14 | 15 | 2. WASM's core type system is 32 bit integers. This was obvious from the 16 | beginning, but I now believe that being able to support 16bit environments is 17 | highly important to Civboot, not least of all because it is simpler and 18 | lighter-weight for the initial language. 19 | 20 | 3. WASM relies heavily on indexes. Each module, global, function and local has an 21 | index. Allocating a bunch of memory for indexes is a hassle and non-optimal. 22 | 23 | 4. WASM locals act more like "registers" than "values on the local stack". 24 | Specifically, there is no way to (1) store anything larger than an i64 in a 25 | local or (2) get a reference to a local. Instead you must do this "virtually" 26 | by having a global value which represents your stack pointer and 27 | increment/decrement it when calling functions. But wait... isn't that the 28 | kind of task such a complicated VM should already be doing? I have all this 29 | machinery to deal with locals but can't actually get a reference to my 30 | locals? Not great. 31 | 32 | WASM is not bad, it is just not the right design to run inline for simple or 33 | memory-constrained targets. It's intended target is an optimizing compiler 34 | **not** an embedded system which executes it at runtime. 35 | 36 | -------------------------------------------------------------------------------- /src/boot.fn: -------------------------------------------------------------------------------- 1 | \ require[dat.fn comp.fn] 2 | 3 | global lgr:Logger = comp.g.log 4 | 5 | \ fnMeta:TY_FN_SYN 6 | \ fn while[stk:U1] do ( 7 | \ comp.compile:blk; 8 | \ 9 | \ ) 10 | -------------------------------------------------------------------------------- /src/comp.fn: -------------------------------------------------------------------------------- 1 | \ Compiler constants 2 | \ Note: etc/gen.py converts this into gen/comp.h 3 | \ TODO: Stk, CStr, Sll 4 | 5 | fileUse comp 6 | fileImpl comp 7 | 8 | \ Auto-generates bin/const.h 9 | const CSZ_CATCH : U2 = 0xFF 10 | 11 | \ Token types 12 | const T_NUM : U2 = 0x0 13 | const T_HEX : U2 = 0x1 14 | const T_ALPHA : U2 = 0x2 15 | const T_SINGLE : U2 = 0x3 16 | const T_SYMBOL : U2 = 0x4 17 | const T_WHITE : U2 = 0x5 18 | 19 | const C_UNTY :U4 = 0x0008 \ G_cstate: do not perform type checking. 20 | const C_FN_STATE :U4 = 0x0007 \ G_cstate: mask of current FN position 21 | const FN_STATE_NO :U4 = 0x0000 \ not compiling a fn 22 | const FN_STATE_BODY :U4 = 0x0001 \ compiling FN body 23 | const FN_STATE_STK :U4 = 0x0002 \ can declare stk inputs 24 | const FN_STATE_INP :U4 = 0x0003 \ can declare inp inputs 25 | const FN_STATE_OUT :U4 = 0x0004 \ can declare outputs 26 | 27 | const TY_UNSIZED :U2 = 0xFFFF \ the "arrLen" of an unsized type 28 | 29 | \ *********** 30 | \ * Dict Ty Bits (meta byte): [TTXX XXXX] T=TY_MASK 31 | const TY_MASK :U1 = 0xC0 \ upper two bits determine type 32 | const TY_VAR :U1 = 0x40 \ variable (local, global, struct field, etc). Has varMeta 33 | const TY_FN :U1 = 0x80 \ function, can be called and has an fnMeta 34 | const TY_DICT :U1 = 0xC0 \ a "dictionary" type which has dictMeta. 35 | 36 | \ TY_FN meta bits: [01N- FFFF] N=native M=method F=fnTy 37 | const TY_FN_NATIVE :U1 = 0x20 \ is a native function (i.e. C) 38 | 39 | const TY_FN_TY_MASK :U1 = 0x0F \ Function type mask 40 | const TY_FN_NORMAL :U1 = 0x00 \ Normally compiled, can use 'imm#' to make IMM 41 | const TY_FN_IMM :U1 = 0x01 \ Required to be run as IMM (must use 'imm#') 42 | const TY_FN_SYN :U1 = 0x02 \ (syntactical) always run imm (knowing asImm) 43 | const TY_FN_SYNTY :U1 = 0x03 \ syntactical type: can implement generics 44 | const TY_FN_INLINE :U1 = 0x04 \ Inline function, copies bytes when compiled. 45 | const TY_FN_COMMENT :U1 = 0x05 \ Comment function. Executed immediately. 46 | const TY_FN_METH :U1 = 0x06 \ Struct method 47 | const TY_FN_ABSMETH :U1 = 0x07 \ Role method to be overriden 48 | const TY_FN_SIG :U1 = 0x0F \ Fn signature 49 | 50 | \ TY_VAR meta bits: [10TT ----] A=alias G=global C=constant 51 | \ G=0 on a struct/enum is a field, G=0 on a fn is a local 52 | const TY_VAR_MSK :U1 = 0x30 53 | const TY_VAR_LOCAL :U1 = 0x00 54 | const TY_VAR_GLOBAL :U1 = 0x10 55 | const TY_VAR_CONST :U1 = 0x20 56 | const TY_VAR_ALIAS :U1 = 0x30 57 | 58 | \ TY_DICT meta bits: [11-- -DDD] D=dictType 59 | const TY_DICT_MSK :U1 = 0x07 60 | const TY_DICT_NATIVE :U1 = 0x00 61 | const TY_DICT_MOD :U1 = 0x01 62 | const TY_DICT_BITMAP :U1 = 0x02 63 | const TY_DICT_STRUCT :U1 = 0x03 64 | const TY_DICT_ENUM :U1 = 0x04 65 | const TY_DICT_ROLE :U1 = 0x05 66 | 67 | const TY_NATIVE_SIGNED :U1 = 0x08 68 | const TY_REFS :U1 = 0x03 69 | 70 | struct FileInfo [ path:&CStr; line:U2 ] 71 | 72 | struct TyBase [ 73 | parent:Bst 74 | meta:U2 75 | ] 76 | 77 | struct TyI [ 78 | parent:Sll 79 | meta:U2 arrLen:U2 80 | name:&CStr 81 | ty:&TyBase 82 | ] 83 | struct TyIBst [ parent: Bst; tyI: TyI ] 84 | struct Key [ name: Slc; tyI: &TyI ] 85 | 86 | struct TyDict declared 87 | 88 | struct Ty [ 89 | parent:TyBase; 90 | line:U2 \ src code line definition 91 | name:&CStr; tyKey:&TyI 92 | container:&TyDict 93 | file:&FileInfo 94 | ] 95 | 96 | struct TyDict [ 97 | parent:Ty 98 | children:&Ty 99 | fields:&TyI 100 | sz:U2 101 | ] 102 | 103 | struct DictStk [ dat:&&TyDict sp:U2 cap:U2 ] 104 | 105 | struct TyVar [ parent:Ty; v:S; tyI:&TyI ] 106 | struct InOut [ inp:&TyI; out:&TyI ] 107 | struct FnSig [ parent:TyBase; io:InOut ] 108 | 109 | struct TyFn [ 110 | parent:Ty 111 | locals:&Ty 112 | code:&U1 113 | inp:&TyI; out:&TyI 114 | len:U2 115 | lSlots:U1 116 | ] 117 | 118 | struct TyDb [ 119 | bba:&BBA 120 | tyIs:Stk\(&TyI) 121 | done:Stk 122 | ] 123 | 124 | struct Blk [ 125 | parent:Sll 126 | start:S 127 | breaks:&Sll \ store breaks (to update at end of block) 128 | startTyI:&TyI endTyI:&TyI \ types at start+end(incl break) of block 129 | ] 130 | 131 | struct GlobalsCode [ 132 | src:Reader srcInfo:&FileInfo 133 | token:Buf 134 | code:Buf 135 | metaNext:U2 \ meta of next fn 136 | cstate:U2; 137 | fnLocals:U2 \ locals size 138 | fnState:U1 139 | blk_:&Blk 140 | compFn:&TyFn \ current function that does compilation 141 | ] 142 | 143 | 144 | struct Globals [ 145 | c:GlobalsCode 146 | log:Logger 147 | bbaDict:&BBA 148 | rootDict:TyDict 149 | dictStk:DictStk implStk:DictStk 150 | cBst:&CBst tyIBst:&TyIBst fnSigBst:&FnSig 151 | tyDb:TyDb tyDbImm:TyDb bbaTyImm:BBA 152 | ] 153 | 154 | global g:&Globals = NULL \ initialized by native code 155 | -------------------------------------------------------------------------------- /src/dat.fn: -------------------------------------------------------------------------------- 1 | \ Core types operating directly on data 2 | 3 | \ Note: declared by native 4 | \ struct Slc [ dat:&U1 len:U2 ] 5 | 6 | unty const NULL:&Any = 0 7 | const FALSE:S = 0 8 | const TRUE:S = 1 9 | 10 | const TRACE:U1 = 0x10 11 | const DEBUG:U1 = 0x08 12 | const INFO :U1 = 0x04 13 | const WARN :U1 = 0x02 14 | const ERROR:U1 = 0x01 15 | const LOG_NEVER:U1 = 0x00 16 | 17 | struct Buf [ parent:Slc cap:U2 ] 18 | struct CStr [ len:U2 dat:Arr[ ? U1] ] 19 | struct Ring [ dat:&U1 head:U2 tail:U2 _cap:U2 ] 20 | 21 | struct Stk [ dat:&S sp:U2 cap:U2 ] 22 | struct Sll [ next:&Self ] 23 | struct SllS [ parent:Sll v:S ] 24 | struct Bst [ l:&Self r:&Self] 25 | struct CBst [ parent:Bst key:&CStr ] 26 | 27 | 28 | impl S ( 29 | fn min[a:S, b:S -> S] do ( if(a < b) do a else b ) 30 | fn max[a:S, b:S -> S] do ( if(a < b) do b else a ) 31 | ) 32 | 33 | impl U2 ( 34 | fn min[a:U2, b:U2 -> U2] do ( if(a < b) do a else b ) 35 | fn max[a:U2, b:U2 -> U2] do ( if(a < b) do b else a ) 36 | ) 37 | 38 | impl SI ( 39 | \ TODO: make inline 40 | fn < [stk:SI, stk:SI -> S] do ( lt_s; ) 41 | fn >= [stk:SI, stk:SI -> S] do ( ge_s; ) 42 | 43 | fn min[a:SI, b:SI -> SI] do ( if(a.< b) do a else b ) 44 | fn max[a:SI, b:SI -> SI] do ( if(a SI.< b) do b else a ) 45 | ) 46 | 47 | impl Slc ( 48 | meth @ [self:&Self i:S -> U1] do ( 49 | root.@ptrAdd(self.dat, i, self.len) 50 | ) 51 | 52 | meth cmp [self:&Self, s:Slc -> I4] do ( 53 | var len:U2 = U2.min(self.len, s.len) 54 | var l:&U1 = self.dat; var r:&U1 = s.dat 55 | var i:U2 = 0; 56 | blk( 57 | if(i >= len) do break; 58 | if(@l == @r) do ( 59 | \ unty (l = inc l; r = inc r); 60 | l = ptrAdd(l, 1, len) r = ptrAdd(r, 1, len) 61 | i = U2(inc i) 62 | cont; 63 | ) 64 | if(@l < @r) do ret (--1) 65 | else ret ( 1); 66 | ) 67 | if (self.len == s.len) do (SI 0) 68 | elif(self.len < s.len) do ( --1) 69 | else (SI 1) 70 | ) 71 | ) 72 | 73 | \ ************ 74 | \ * Arena 75 | 76 | struct Block [ 77 | const SIZE: U2 = 0x1000; 78 | dat:Arr[(Block.SIZE - 4) U1]; bot:U2; top:U2 79 | ] 80 | struct BANode [ next:&BANode; prev:&BANode; block:&Block ] 81 | struct BA [ free:&BANode; len:S ] 82 | struct BBA [ ba:&BA; dat:&BANode ] 83 | 84 | role Arena [ 85 | absmeth drop [&Self] 86 | absmeth free [&Self, &Any, S\sz, U2\alignment -> &Slc] 87 | absmeth alloc[&Self, S\sz, U2\alignment -> &Any] 88 | absmeth maxAlloc[&Self -> S] 89 | ] 90 | 91 | \ Note: method implementations are native (see fngi.c) 92 | unty impl BBA ( 93 | meth drop [stk:&Self ] do; 94 | meth free [stk:&Self stk\dat:&Any stk\sz:S stk\alignment:U2 -> &Slc ] do; 95 | meth alloc [stk:&Self stk\sz:S stk\alignment:U2 -> &Any] do; 96 | meth maxAlloc [stk:&Self -> S ] do; 97 | ) 98 | 99 | impl BBA:Arena { 100 | drop = &BBA.drop 101 | free = &BBA.free 102 | alloc = &BBA.alloc 103 | maxAlloc = &BBA.maxAlloc 104 | } 105 | 106 | \ ************ 107 | \ * File + Fmt 108 | 109 | struct BaseFile [ 110 | ring: Ring \ buffer for reading or writing data 111 | code: U2 \ status or error (File_*) 112 | ] 113 | 114 | \ Note: method implementations are native (see fngi.c) 115 | role Reader [ 116 | absmeth read [&Self] 117 | absmeth asBase [&Self -> &BaseFile] 118 | ] 119 | 120 | role Writer [ 121 | absmeth asBase [&Self -> &BaseFile] 122 | absmeth write [&Self] 123 | ] 124 | 125 | struct FmtState [ arena:Arena; pretty:U2; ] 126 | 127 | \ TODO: embed roles 128 | \ role Fmt [ 129 | \ absmeth asBase [&Self -> &BaseFile] 130 | \ absmeth write [&Self] 131 | \ absmeth fmtState [&Self -> &FmtState] 132 | \ ] 133 | 134 | \ ************ 135 | \ * Logger 136 | 137 | struct LogConfig [ lvl:U1 ] 138 | 139 | role Logger [ 140 | absmeth asBase [&Self -> &BaseFile] 141 | absmeth write [&Self] 142 | absmeth fmtState [&Self -> &FmtState] 143 | absmeth logConfig [&Self -> &LogConfig] 144 | absmeth start [&Self S -> U1 ] 145 | absmeth add [&Self Slc] 146 | absmeth end [&Self] 147 | ] 148 | -------------------------------------------------------------------------------- /src/fngi.h: -------------------------------------------------------------------------------- 1 | #ifndef __FNGI_H 2 | #define __FNGI_H 3 | 4 | #include "civ_unix.h" 5 | #include "comp.h" // from gen/ 6 | #include "spor.h" // from gen/ 7 | 8 | #define FNGI_VERSION "0.1.0" 9 | 10 | #define SZR SZ4 11 | 12 | #define WS_DEPTH 16 13 | #define RS_DEPTH 128 14 | #define TOKEN_SIZE 128 15 | #define DICT_DEPTH 10 16 | #define FN_ALLOC 256 17 | 18 | #define SLIT_MAX 0x2F 19 | 20 | // To use: function must accept a "Kernel* k" pointer 21 | #define cfb (k->fb) 22 | 23 | #define WS (&cfb->ws) 24 | #define RS (&cfb->rs) 25 | 26 | #define WS_POP() Stk_pop(WS) 27 | #define WS_POP2(A, B) Stk_pop2(WS, A, B) 28 | #define WS_POP3(A, B, C) Stk_pop3(WS, A, B, C) 29 | #define WS_ADD(V) Stk_add(WS, V) 30 | #define WS_ADD2(A, B) Stk_add2(WS, A, B) 31 | #define WS_ADD3(A, B, C) Stk_add3(WS, A, B, C) 32 | #define RS_ADD(V) Stk_add(RS, V) 33 | #define RS_POP() Stk_pop(RS) 34 | #define INFO_ADD(V) Stk_add(&cfb->info, V) 35 | #define INFO_POP(V) Stk_pop(&cfb->info) 36 | 37 | #define TASSERT_WS(E) TASSERT_STK(E, WS) 38 | #define TASSERT_EMPTY() do { \ 39 | if(Stk_len(WS)) { \ 40 | eprintf("!!! Not empty: "); dbgWs(k); eprintf("\n"); \ 41 | TASSERT_EQ(0, Stk_len(WS)) \ 42 | } } while(0) 43 | 44 | 45 | 46 | #define FNGI_EXPECT_ERR(CODE, MSG) \ 47 | civ.fb->state |= Fiber_EXPECT_ERR; \ 48 | HANDLE_ERR( \ 49 | { CODE; \ 50 | eprintf("!!! expected error never happend\n"); assert(false); } \ 51 | , { handleExpectedErr(SLC(MSG)); Kern_errCleanup(k); }) 52 | 53 | #define Ty_fmt(TY) CStr_fmt((TY)->name) 54 | #define Instr_fmt(I) Dat_fmt(instrName(I)) 55 | 56 | #define TEST_FNGI(NAME, numBlocks) \ 57 | TEST_UNIX(NAME, numBlocks) \ 58 | civ.errPrinter = &fngiErrPrinter; \ 59 | FnFiber fnFb = {0}; \ 60 | Fiber_init((Fiber*)&fnFb, &localErrJmp); \ 61 | assert(FnFiber_init(&fnFb)); \ 62 | civ.fb = (Fiber*)&fnFb; \ 63 | Kern _k = {0}; Kern* k = &_k; fngiK = k; \ 64 | Kern_init(k, &fnFb); _k.isTest = true; 65 | 66 | #define END_TEST_FNGI TASSERT_EMPTY(); END_TEST_UNIX 67 | 68 | // ################################ 69 | // # From gen/name.c 70 | extern U1* unknownInstr; 71 | Slc instrName(U1 instr); 72 | U1* szName(U1 szI); 73 | 74 | // ################################ 75 | // # Fngi Roles 76 | struct _Kern; 77 | // struct _TyFn; 78 | 79 | // Sp_XrN: Execute Spor Role with N arguments 80 | #define Sp_Xr0(ROLE, METHOD) \ 81 | do { WS_ADD((S) (ROLE).d); executeFn(k, (ROLE).m->METHOD); } \ 82 | while (0) 83 | 84 | #define Sp_Xr1(ROLE, METHOD, A0) \ 85 | do { WS_ADD((S) (ROLE).d); WS_ADD(A0); executeFn(k, (ROLE).m->METHOD); } \ 86 | while (0) 87 | 88 | #define Sp_Xr2(ROLE, METHOD, A0, A1) \ 89 | do { WS_ADD((S) (ROLE).d); WS_ADD2(A0, A1); executeFn(k, (ROLE).m->METHOD); } \ 90 | while (0) 91 | 92 | #define Sp_Xr3(ROLE, METHOD, A0, A1, A2) \ 93 | do { WS_ADD((S) (ROLE).d); WS_ADD3(A0, A1, A2); executeFn(k, (ROLE).m->METHOD); } \ 94 | while (0) 95 | 96 | // Native role implemntations 97 | extern MSpArena mSpArena_BBA; 98 | extern MSpReader mSpReader_UFile; 99 | extern MSpReader mSpReader_BufFile; 100 | extern MSpLogger mSpLogger_File; 101 | 102 | static inline SpArena BBA_asSpArena(BBA* bba) { 103 | return (SpArena) { .d = bba, .m = &mSpArena_BBA }; 104 | } 105 | 106 | BaseFile* SpReader_asBase(struct _Kern* k, SpReader r); 107 | U1* SpReader_get(struct _Kern* k, SpReader r, U2 i); 108 | 109 | // ################################ 110 | // # Types 111 | 112 | 113 | // A Ty can be a const, function, variable or dict depending on meta. See 114 | // TY_MASK in const.zty. 115 | 116 | #define KEY(NAME) (Key){ .name = SLC(NAME) } 117 | #define TY_KEY(TY) (Key){ .name = Slc_frCStr((TY)->name), .tyI = (TY)->tyKey } 118 | 119 | extern TyFn _TyFn_imm; 120 | #define START_IMM(AS_IMM) \ 121 | TyFn* cfn = k->g.c.compFn; if(AS_IMM) k->g.c.compFn = &_TyFn_imm 122 | 123 | #define END_IMM k->g.c.compFn = cfn 124 | 125 | static inline InOut* TyFn_asInOut(TyFn* fn) { return (InOut*) &fn->inp; } 126 | 127 | #define TyFn_native(CNAME, META, NFN, INP, OUT) { \ 128 | .name = (CStr*) ( CNAME ), \ 129 | .meta = TY_FN | TY_FN_NATIVE | (META), \ 130 | .code = NFN, \ 131 | .inp = INP, .out = OUT \ 132 | } 133 | 134 | #define TyFn_static(NAME, META, LSLOTS, DAT) \ 135 | CStr_ntVar(LINED(name), "\x08", ""); \ 136 | static TyFn NAME; \ 137 | NAME = (TyFn) { \ 138 | .name = LINED(name), \ 139 | .meta = TY_FN | META, \ 140 | .code = DAT, \ 141 | .lSlots = LSLOTS, \ 142 | }; 143 | 144 | static inline Sll* TyI_asSll(TyI* this) { return (Sll*)this; } 145 | 146 | static inline TyFn litFn(U1* code, U2 meta, U2 lSlots) { 147 | return (TyFn) { 148 | .meta = TY_FN | meta, 149 | .code = code, 150 | .lSlots = lSlots, 151 | }; 152 | } 153 | 154 | static inline Sll** TyDict_fieldsRoot(TyDict* ty) { return (Sll**) &ty->fields; } 155 | 156 | typedef struct _Ownership { 157 | struct _Ownership* next; 158 | U2 meta; U2 offset; 159 | S len; 160 | } Ownership; 161 | typedef struct { void* ref; TyDict* ty; Ownership* ownership; } OwnedValue; 162 | 163 | typedef enum { NOT_DONE, BLK_DONE, RET_DONE } HowDone; 164 | 165 | #define TYDB_DEPTH 16 166 | typedef struct { S tyIsDat[TYDB_DEPTH]; S doneDat[TYDB_DEPTH]; } TyDbDat; 167 | 168 | // Flow Block (loop/while/etc) 169 | static inline Sll* Blk_asSll(Blk* this) { return (Sll*)this; } 170 | 171 | typedef struct _SllSpArena 172 | { struct _SllSpArena* next; SpArena arena; } SllSpArena; 173 | 174 | static inline Sll* SllSpArena_asSll(SllSpArena* this) { return (Sll*)this; } 175 | 176 | typedef struct { 177 | Fiber fb; 178 | SllSpArena* sllArena; 179 | U1* ep; // execution pointer 180 | Stk ws; Stk rs; // working and return stack 181 | Stk info; // info stack 182 | } FnFiber; 183 | 184 | typedef struct _Kern { 185 | U4 _null; 186 | bool isTest; 187 | BBA bbaCode; BBA bbaDict; 188 | BBA bbaRepl; BBA bbaTmp; 189 | BBA bbaSllArena; 190 | U1 tokenDat[128]; 191 | TyDict* dictBuf[DICT_DEPTH]; TyDict* implBuf[DICT_DEPTH]; 192 | TyDbDat tyDbDat; TyDbDat tyDbImmDat; 193 | SllSpArena sllArena; 194 | Globals g; // kernel globals 195 | FnFiber* fb; // current fiber. 196 | } Kern; 197 | 198 | extern Kern* fngiK; 199 | void Kern_errCleanup(Kern*); 200 | 201 | 202 | // ################################ 203 | // # Kernel 204 | void dbgWs(Kern *k); 205 | static inline S RS_topRef(Kern* k) { Stk* rs = RS; return (S)&rs->dat[rs->sp]; } 206 | void dbgRs(Kern* k); 207 | void Kern_handleSig(Kern* k, int sig, struct sigcontext* ctx); 208 | void fngiErrPrinter(); 209 | void fngiHandleSig(int sig, struct sigcontext ctx); 210 | 211 | void DictStk_reset(Kern* k); 212 | void DictStk_add(DictStk* stk, TyDict* r); 213 | void Kern_init(Kern* k, FnFiber* fb); 214 | 215 | // Initialze FnFiber (beyond Fiber init). 216 | bool FnFiber_init(FnFiber* fb); 217 | 218 | static inline U1* kFn(void(*native)(Kern*)) { return (U1*) native; } 219 | 220 | #define ARENA_TOP (k->fb->sllArena->arena) 221 | 222 | #define LOCAL_TYDB_BBA(NAME) \ 223 | BBA bba_##NAME = (BBA) { &civ.ba }; \ 224 | BBA* prevBba_##NAME = k->g.NAME.bba; \ 225 | k->g.NAME.bba = &bba_##NAME; 226 | 227 | #define END_LOCAL_TYDB_BBA(NAME) \ 228 | BBA_drop(&bba_##NAME); \ 229 | k->g.NAME.bba = prevBba_##NAME; 230 | 231 | #define REPL_START \ 232 | TyDb_new(&k->g.tyDb); LOCAL_TYDB_BBA(tyDb); 233 | 234 | #define REPL_END \ 235 | TyDb_drop(k, &k->g.tyDb); END_LOCAL_TYDB_BBA(tyDb); \ 236 | DictStk_reset(k); 237 | 238 | 239 | 240 | // ################################ 241 | // # Execute 242 | 243 | void executeFn(Kern* k, TyFn* fn); 244 | 245 | // ################################# 246 | // # Scan 247 | // scan fills g.c.token with a token. If one already exists it is a noop. 248 | // scan does NOT affect g.c.src's ring buffer, except to increment tail with 249 | // characters. 250 | // When the token is used, tokenDrop should be called. This will update 251 | // src's ring buffer as well so the next token can be scanned. 252 | void scan(Kern* k); 253 | void tokenDrop(Kern* k); // clear token and update src.ring 254 | U1 cToU1(U1 c); // return 0-15 or 0xFF if not a hex integer. 255 | typedef struct { bool isNum; U4 v; } ParsedNumber; 256 | ParsedNumber parseU4(Kern* k, Slc t); 257 | 258 | // ################################# 259 | // # Compiler 260 | #define IS_TY(M) return ty && ((M) == (TY_MASK & ty->meta)) 261 | static inline bool isTyVar(Ty* ty) { IS_TY(TY_VAR); } 262 | static inline bool isTyDict(Ty* ty) { IS_TY(TY_DICT); } 263 | static inline bool isTyDictB(TyBase* ty) { IS_TY(TY_DICT); } 264 | static inline bool isTyFn(Ty* ty) { 265 | if((TY_FN || TY_FN_SIG) == ty->meta) return false; else IS_TY(TY_FN); } 266 | static inline bool isTyFnB(TyBase* ty) { return isTyFn((Ty*)ty); } 267 | #undef IS_TY 268 | #define IS_FN(M) { return (M) & fn->meta; } 269 | static inline bool isFnNative(TyFn* fn) IS_FN(TY_FN_NATIVE) 270 | #undef IS_FN 271 | #define IS_FN(M) { return (TY_FN_TY_MASK & fn->meta) == (M); } 272 | static inline bool isFnNormal(TyFn* fn) IS_FN(TY_FN_NORMAL) 273 | static inline bool isFnImm(TyFn* fn) IS_FN(TY_FN_IMM) 274 | static inline bool isFnSyn(TyFn* fn) IS_FN(TY_FN_SYN) 275 | static inline bool isFnSynty(TyFn* fn) IS_FN(TY_FN_SYNTY) 276 | static inline bool isFnInline(TyFn* fn) IS_FN(TY_FN_INLINE) 277 | static inline bool isFnComment(TyFn* fn) IS_FN(TY_FN_COMMENT) 278 | static inline bool isFnMeth(TyFn* fn) IS_FN(TY_FN_METH) 279 | static inline bool isFnAbsmeth(TyFn* fn) IS_FN(TY_FN_ABSMETH) 280 | #undef IS_FN 281 | #define FN_SIG (TY_FN | TY_FN_SIG) 282 | static inline bool isFnSig(TyBase* ty) { return FN_SIG == ty->meta; } 283 | #define IS_DICT(M) { return (M) == (TY_DICT_MSK & ty->meta); } 284 | static inline bool isDictNative(TyDict* ty) IS_DICT(TY_DICT_NATIVE) 285 | static inline bool isDictMod(TyDict* ty) IS_DICT(TY_DICT_MOD) 286 | static inline bool isDictStruct(TyDict* ty) IS_DICT(TY_DICT_STRUCT) 287 | static inline bool isDictRole(TyDict* ty) IS_DICT(TY_DICT_ROLE) 288 | #undef IS_DICT 289 | #define IS_VAR(M) { return (M) == (TY_VAR_MSK & v->meta); } 290 | static inline bool isVarAlias(TyVar* v) IS_VAR(TY_VAR_ALIAS) 291 | static inline bool isVarLocal(TyVar* v) IS_VAR(TY_VAR_LOCAL) 292 | static inline bool isVarGlobal(TyVar* v) IS_VAR(TY_VAR_GLOBAL) 293 | static inline bool isVarConst(TyVar* v) IS_VAR(TY_VAR_CONST) 294 | static inline U1 TyI_refs(TyI* tyI) { return TY_REFS & tyI->meta; } 295 | #undef IS_VAR 296 | 297 | static inline TyFn* tyFn(void* p) { 298 | ASSERT(isTyFn((Ty*)p), "invalid TyFn"); 299 | return (TyFn*)p; 300 | } 301 | 302 | static inline TyDict* tyDict(Ty* ty) { 303 | ASSERT(isTyDict(ty), "invalid TyDict"); 304 | return (TyDict*) ty; 305 | } 306 | 307 | static inline TyDict* tyDictB(TyBase* ty) { return tyDict((Ty*)ty); } 308 | 309 | static inline TyVar* tyVar(Ty* ty) { 310 | ASSERT(isTyVar(ty), "expected TyVar"); 311 | return (TyVar*) ty; 312 | } 313 | 314 | Ty* Kern_findTy(Kern* k, Key* key); 315 | void Kern_addTy(Kern* k, Ty* ty); 316 | 317 | void Kern_fns(Kern* k); 318 | void Core_mod(Kern* k); 319 | void single(Kern* k, bool asImm); 320 | void compileSrc(Kern* k); 321 | 322 | // Compile a stream to bbaRepl, returning the start. 323 | // 324 | // Any functions/etc will still be compiled to the normal locations. 325 | U1* compileRepl(Kern* k, bool withRet); 326 | void compilePath(Kern* k, CStr* path); 327 | 328 | // ################################# 329 | // # Misc 330 | 331 | #define WS_SLC() Slc_fromWs(k) 332 | static inline Slc Slc_fromWs(Kern* k) { 333 | U1* dat = (U1*)WS_POP(); 334 | return (Slc){dat, .len=WS_POP()}; 335 | } 336 | 337 | static inline S ftSzI(U1* addr, U1 szI) { 338 | S out; 339 | switch(szI) { 340 | case SZ1: out = *(U1*)addr; break; 341 | case SZ2: out = *(U2*)addr; break; 342 | case SZ4: out = *(U4*)addr; break; 343 | default: assert(false); 344 | } 345 | return out; 346 | } 347 | 348 | static inline void srSzI(U1* addr, U1 szI, S v) { 349 | switch(szI) { 350 | case SZ1: *(U1*)addr = v; return; 351 | case SZ2: *(U2*)addr = v; return; 352 | case SZ4: *(U4*)addr = v; return; 353 | } 354 | assert(false); 355 | } 356 | 357 | // ################################# 358 | // # Test Helpers 359 | 360 | #define SET_SRC(CODE) \ 361 | BufFile_varNt(LINED(bf), 64, CODE); \ 362 | k->g.c.src = (SpReader) {.m = &mSpReader_BufFile, .d = &LINED(bf) }; 363 | 364 | #define COMPILE_NAMED(NAME, CODE, withRet) \ 365 | SET_SRC(CODE); U1* NAME = compileRepl(k, withRet) 366 | #define COMPILE(CODE, withRet) \ 367 | SET_SRC(CODE); compileRepl(k, withRet) 368 | #define COMPILE_EXEC(CODE) \ 369 | COMPILE_NAMED(LINED(code), CODE, true); executeInstrs(k, LINED(code)); 370 | 371 | void executeInstrs(Kern* k, U1* instrs); 372 | 373 | void simpleRepl(Kern* k); 374 | 375 | // Get the current snapshot 376 | static inline TyI* TyDb_index(TyDb* db, U2 i) { 377 | ASSERT(i < Stk_len(&db->tyIs), "TyDb OOB index"); 378 | return (TyI*) db->tyIs.dat[db->tyIs.sp + i]; 379 | } 380 | 381 | static inline TyI* TyDb_top(TyDb* db) { return (TyI*) Stk_top(&db->tyIs); } 382 | 383 | // Get a reference to the current snapshot 384 | static inline TyI** TyDb_root(TyDb* db) { return (TyI**) Stk_topRef(&db->tyIs); } 385 | 386 | // Get a reference to the current snapshot 387 | static inline Sll** TyDb_rootSll(TyDb* db) { return (Sll**) Stk_topRef(&db->tyIs); } 388 | 389 | // Get/set whether current snapshot is done (guaranteed ret) 390 | static inline HowDone TyDb_done(TyDb* db) { return Stk_top(&db->done); } 391 | void TyDb_setDone(TyDb* db, HowDone done); 392 | 393 | // Free from the current snapshot. 394 | // 395 | // "stream" can be either TyDb_top (freeing the entire snapshot), or a 396 | // separate type stream which indicates the length of items to drop. 397 | void TyDb_free(Kern* k, TyDb* db, TyI* stream); 398 | 399 | // Drop the current snapshot 400 | void TyDb_drop(Kern* k, TyDb* db); 401 | 402 | // Create a new snapshot 403 | static inline void TyDb_new(TyDb* db) { 404 | Stk_add(&db->tyIs, 0); 405 | Stk_add(&db->done, false); 406 | } 407 | 408 | static inline TyDb* tyDb(Kern* k, bool asImm) { return asImm ? &k->g.tyDbImm : &k->g.tyDb; } 409 | void tyCheck(TyI* require, TyI* given, bool sameLen, Slc errCxt); 410 | void tyCall(Kern* k, TyDb* db, TyI* inp, TyI* out); 411 | void tyRet(Kern* k, TyDb* db, HowDone done); 412 | void tySplit(Kern* k); 413 | void tyMerge(Kern* k, TyDb* db, Slc* msg); 414 | void TyI_print(TyI* tyI); 415 | void TyI_printAll(TyI* tyI); 416 | Ty* TyDict_find(TyDict* dict, Key* s); 417 | S TyDict_sz(TyDict* ty); 418 | 419 | typedef enum { ROLE_NONE, ROLE_DAT, ROLE_METH } CompRole; 420 | 421 | typedef struct _DotPath { 422 | struct _DotPath* next; 423 | TyI* tyI; 424 | TyVar* global; // TyVarGlobal (optional) 425 | U2 offset; 426 | U1 op; // operation 427 | U1 compRole; 428 | } DotPath; 429 | 430 | DotPath* dotPath(Kern* k, TyI* cur, TyVar* global, U2 offset, U1 op); 431 | void DotPath_print(DotPath* st); 432 | 433 | #define TYI_VOID NULL 434 | 435 | #define TYIS(PRE) \ 436 | PRE TyDict Ty_UNSET; \ 437 | PRE TyDict Ty_Any; \ 438 | PRE TyDict Ty_Unsafe; \ 439 | PRE TyDict Ty_Self; \ 440 | PRE TyDict Ty_RoleMeth; \ 441 | PRE TyDict Ty_RoleField;\ 442 | PRE TyI TyIs_UNSET; \ 443 | PRE TyI TyIs_Unsafe; \ 444 | PRE TyI TyIs_rSelf; \ 445 | PRE TyI TyIs_RoleField;\ 446 | PRE TyI TyIs_rAny; \ 447 | PRE TyI TyIs_rAnyS; \ 448 | PRE TyI TyIs_rAnySS; \ 449 | PRE TyDict Ty_U1; \ 450 | PRE TyDict Ty_U2; \ 451 | PRE TyDict Ty_U4; \ 452 | PRE TyDict Ty_S; \ 453 | PRE TyDict Ty_I1; \ 454 | PRE TyDict Ty_I2; \ 455 | PRE TyDict Ty_I4; \ 456 | PRE TyDict Ty_SI; \ 457 | PRE TyI TyIs_S; /* S */ \ 458 | PRE TyI TyIs_SS; /* S, S */ \ 459 | PRE TyI TyIs_SSS; /* S, S, S */ \ 460 | PRE TyI TyIs_U1; /* U1 */ \ 461 | PRE TyI TyIs_U2; /* U2 */ \ 462 | PRE TyI TyIs_U4; /* U4 */ \ 463 | PRE TyI TyIs_U4x2; /* U4 U4 */ \ 464 | PRE TyI TyIs_SI; /* SI */ \ 465 | PRE TyI TyIs_SI_SI; /* SI SI */ \ 466 | PRE TyI TyIs_rU1; /* &U1 */ \ 467 | PRE TyI TyIs_rU2; /* &U2 */ \ 468 | PRE TyI TyIs_rU4; /* &U4 */ \ 469 | PRE TyI TyIs_rU1_U4; /* &U1, U4 */ \ 470 | 471 | TYIS(extern) 472 | 473 | void N_assertWsEmpty(Kern* k); 474 | 475 | #endif // __FNGI_H 476 | -------------------------------------------------------------------------------- /src/spor.fn: -------------------------------------------------------------------------------- 1 | \ Spor Constants 2 | \ Note: etc/gen.py converts this into gen/spor.h 3 | \ 4 | \ # Table of Contents 5 | \ Search for these headings to find information 6 | \ 7 | \ [1] Instructions: contains definition of spore assembly instructions and 8 | \ documentation. 9 | \ [1.a] Operations: Special 10 | \ [1.b] Operations: One Inp -> One Out 11 | \ [1.c] Operations: Two Inp -> One Out 12 | \ [1.d] Sizes: SZ1, SZ2, SZ4, SZR 13 | \ [1.e] Mem: fetch, store, locals, globals 14 | \ [1.f] Jmp: jumping, execution, tables 15 | \ [1.g] Small Literal [0x40 - 0x80) 16 | \ [2] Registers and Device Operations (RGFT, RGSR, DVFT, DVSR) 17 | \ [3] Constants 18 | \ [3.a] Dict Ty Bits 19 | \ [3.b] Zoab 20 | \ [3.c] Log Levels 21 | \ [3.d] Errors 22 | \ [4] Globals 23 | \ 24 | \ ********** 25 | \ * [1] Instructions: these are constants that can be used directly by: % ^ 26 | \ Spor uses 8 bit instructions with the following bit layout (S=size bit): 27 | \ [00-40] 00-- ----: operation 28 | \ [40-7F] 01SS ----: mem 29 | \ [80-BF] 10SS ----: jmp 30 | \ [C0-FF] 11-- ----: small literal value [0x00 - 0x3F] 31 | 32 | mod spor; fileUse spor; 33 | 34 | \ # [1.b] Operations: Special 35 | const NOP :U1 = 0x00 \ {} no operation 36 | const RETZ :U1 = 0x01 \ {l} return if zero 37 | const RET :U1 = 0x02 \ {} return 38 | const YLD :U1 = 0x03 \ {} yield control to another fiber 39 | const SWP :U1 = 0x04 \ {l r -> r l} swap 40 | const DRP :U1 = 0x05 \ {l -> } drop 41 | const OVR :U1 = 0x06 \ {l r -> l r l} over 42 | const DUP :U1 = 0x07 \ {l -> l l} duplicate 43 | const DUPN :U1 = 0x08 \ {l -> l l==0} DUP then NOT 44 | const RG :U1 = 0x09 \ {-> v} Register (U1 literal) 45 | const OWR :U1 = 0x0A \ {-> &OwnedValue} owned reference 46 | const LR :U1 = 0x0B \ {-> &local} local reference (U2 literal) 47 | const GR :U1 = 0x0C \ {-> &global} global reference (U4+U2 literals) 48 | const XR :U1 = 0x0F \ {-> &fn} function reference (U4 literal) 49 | 50 | \ # [1.b] Operations: One Inp -> One Out 51 | const INC :U1 = 0x10 \ {l+1} increment 1 52 | const INC2 :U1 = 0x11 \ {l+2} increment 2 53 | const INC4 :U1 = 0x12 \ {l+4} increment 4 54 | const DEC :U1 = 0x13 \ {l-4} decrement 1 55 | const INV :U1 = 0x14 \ {~l} Bitwise Inversion 56 | const NEG :U1 = 0x15 \ {-l} Negate (2's compliment) 57 | const NOT :U1 = 0x16 \ {l==0} Logical NOT 58 | const CI1 :U1 = 0x17 \ {ISz} Convert I1 to ISz 59 | const CI2 :U1 = 0x18 \ {ISz} Convert I2 to ISz 60 | \ future: leading 0's, trailing 0's, count of 1's 61 | \ Some single-arg extension commands might be: 62 | \ (7) floating point abs, negative, ceil, floor, trunc, nearest, and sqrt 63 | \ (1) i -> f conversion 64 | \ (1) f -> i conversion 65 | 66 | \ # [1.c] Operations: Two Inp -> One Out 67 | const ADD :U1 = 0x20 \ {l + r } add 68 | const SUB :U1 = 0x21 \ {l - r } subtract 69 | const MOD :U1 = 0x22 \ {l % r } integer modulo (remainder) 70 | const SHL :U1 = 0x23 \ {l << r } bit shift left 71 | const SHR :U1 = 0x24 \ {l >> r } bit shift right 72 | const MSK :U1 = 0x25 \ {l & r } bitwise and 73 | const JN :U1 = 0x26 \ {l | r } bitwise or 74 | const XOR :U1 = 0x27 \ {l ^ r } bitwise xor 75 | const AND :U1 = 0x28 \ {l && r } logical and 76 | const OR :U1 = 0x29 \ {l || r } logical or 77 | const EQ :U1 = 0x2A \ {l == r } equal 78 | const NEQ :U1 = 0x2B \ {l != r } not equal 79 | const GE_U :U1 = 0x2C \ {l >= r } unsigned greater than or equal 80 | const LT_U :U1 = 0x2D \ {l < r } unsigned less than 81 | const GE_S :U1 = 0x2E \ {l >= r } signed greater than or equal 82 | const LT_S :U1 = 0x2F \ {l < r } signed less than 83 | const MUL :U1 = 0x30 \ {l * r } multiplication 84 | const DIV_U:U1 = 0x31 \ {l / r } unsigned division 85 | const DIV_S:U1 = 0x32 \ {l / r } signed division 86 | \ Double-arg extension commands might be: 87 | \ floating point: add,sub,mul,div,ge,lt 88 | 89 | \ # [1.a] Sizes: --SS ---- 90 | const SZ_MASK :U1 = 0x30 \ size bit mask (for instr and meta) 91 | const SZ1 :U1 = 0x00 92 | const SZ2 :U1 = 0x10 93 | const SZ4 :U1 = 0x20 94 | 95 | \ # [1.d] Mem 96 | \ unsized 97 | const MMV :U1 = 0x40 \ {&to &from [U2 lit:sz]} memmove 98 | 99 | \ sized 100 | const FT :U1 = 0x41 \ {addr} -> {value} |FeTch value from addr 101 | const FTBE :U1 = 0x42 \ {addr} -> {value} |FeTch value from addr (big endian) 102 | const FTO :U1 = 0x43 \ {addr} -> {value} |FeTch value from addr + U1 literal offset 103 | const FTLL :U1 = 0x44 \ {} -> {local} |FeTch Literal Local 104 | const FTGL :U1 = 0x45 \ {} -> {local} |FeTch Global Literal 105 | const SR :U1 = 0x46 \ {addr value} -> {} |Store value at addr 106 | const SRBE :U1 = 0x47 \ {addr value} -> {} |Store value at addr (big endian) 107 | const SRO :U1 = 0x48 \ {addr value} -> {} |Store value at addr + U1 literal offset 108 | const SRGL :U1 = 0x49 \ {addr value} -> {} |Store Global Literal 109 | const SRLL :U1 = 0x4A \ {value} -> {} |StoRe Literal Local 110 | const FTOK :U1 = 0x4B \ {addr} -> {value addr} |FTO but keep addr on stack 111 | const LIT :U1 = 0x4C \ {} -> {literal} |Literal (U1, U2 or U4) 112 | 113 | \ # [1.e] Jmp 114 | \ 115 | \ Jumps can be to either a literal (L) or to an item on the working stack (W). 116 | \ LCL is put here because it's related to functions. 117 | 118 | \ Unsized jumps: 119 | const LCL :U1 = 0x80 \ Grow Local stack by U1 Literal 120 | const XL :U1 = 0x81 \ Execute U4 Literal (normal execute) 121 | const XW :U1 = 0x90 \ Execute from Working Stack 122 | const XLL :U1 = 0x91 \ Execute local literal offset 123 | const XRL :U1 = 0xA0 \ Execute role method local offset 124 | 125 | \ Sized jumps: 126 | const JL :U1 = 0x82 \ Jmp to Literal 127 | const JLZ :U1 = 0x83 \ Jmp to Literal if 0 (popping WS) 128 | const JLNZ :U1 = 0x84 \ Jmp to Literal if not 0 (popping WS) 129 | const JTBL :U1 = 0x85 \ Jump to Table index using size=Literal 130 | const SLIC :U1 = 0x86 \ Inline Slc, jmp sz and push {ref, len} 131 | 132 | \ # [1.f] Small Literal [0xC0 - 0xFF] 133 | const SLIT :U1 = 0xC0 134 | -------------------------------------------------------------------------------- /tests/basic.fn: -------------------------------------------------------------------------------- 1 | \ Test basic operations 2 | imm#assertWsEmpty; 3 | 4 | imm#tAssertEq(1, 1) 5 | 6 | fn ret1 [ -> S] do 1 7 | imm#tAssertEq(ret1, 1); 8 | 9 | struct A [ a1: S ] 10 | struct B [ b1: A; b2: S] 11 | struct C [ c1: &B; c2: B] 12 | 13 | \ Test structs and accessing references 14 | fn testStructs[ ] do ( 15 | var b: B = B(A 5, 7) 16 | var c: C = { c1 = &b, c2=b } 17 | tAssertEq(b.b1.a1, 5) 18 | tAssertEq(b.b2, 7) 19 | tAssertEq(c.c1.b1.a1, 5) 20 | tAssertEq(c.c2.b1.a1, 5) 21 | 22 | c.c1.b1.a1 = 9; tAssertEq(c.c1.b1.a1, 9) 23 | tAssertEq(c.c2.b1.a1, 5) 24 | tAssertEq(b.b1.a1, 9) \ ref updated 25 | c.c2.b1.a1 = 8; tAssertEq(c.c1.b1.a1, 9) 26 | tAssertEq(c.c2.b1.a1, 8) 27 | tAssertEq(b.b1.a1, 9) 28 | 29 | var r: &B = &b; tAssertEq(r.b2, 7) 30 | r.b2 = 2 ; tAssertEq(r.b2, 2); tAssertEq(b.b2, 2) 31 | ) 32 | imm#testStructs; 33 | 34 | imm#assertWsEmpty; 35 | 36 | \ Test globals 37 | global x:S = 7 38 | imm#tAssertEq(7, x) 39 | 40 | global b: B = B(A 9, 2) 41 | global c: C = { c1 = &b, c2=b } 42 | 43 | imm#( 44 | tAssertEq(9, b.b1.a1) 45 | tAssertEq(9, @ &b.b1.a1) 46 | tAssertEq(2, b.b2) 47 | tAssertEq(9, c.c1.b1.a1) 48 | tAssertEq(9, c.c2.b1.a1) 49 | ) 50 | 51 | imm#tAssertEq(2, if(0) do 1 else 2) 52 | -------------------------------------------------------------------------------- /tests/example_data.txt: -------------------------------------------------------------------------------- 1 | This is just some example data. 2 | 3 | It is used to test readers. 4 | -------------------------------------------------------------------------------- /tests/lua_test.lua: -------------------------------------------------------------------------------- 1 | -- this is just for me to understand lua better 2 | 3 | function myIter(i) 4 | return function(newI) 5 | if newI then i = newI end 6 | i = i + 1 7 | if i < 10 then 8 | return i, i * 10 9 | end 10 | end 11 | end 12 | 13 | function list(iter) 14 | local l = {} 15 | for v in iter do 16 | table.insert(l, v) 17 | end 18 | return l 19 | end 20 | 21 | function join(l) 22 | local b = {} 23 | for _, v in ipairs(l) do 24 | table.insert(b, tostring(v)) 25 | end 26 | return table.concat(b, ' ') 27 | end 28 | 29 | 30 | myI = myIter(5) 31 | print("myI", join(list(myI))) 32 | print("myI", join(list(myI))) 33 | print("myI", join(list(myI))) 34 | 35 | myI2 = {i=0} 36 | setmetatable(myI2, {__call=function(self) 37 | self.i = self.i + 1 38 | if self.i < 10 then 39 | return self.i, self.i * 10 40 | end 41 | end}) 42 | 43 | 44 | print("myI2", join(list(myI2))) 45 | myI2.i = 3 46 | print("myI2", join(list(myI2))) 47 | 48 | -- ... expression causes a 49 | -- table.unwrap(additionalArgs) anywhere it is used. 50 | function aPrint(a, ...) 51 | print('...', type('...'), ...) 52 | print('select(1, ...)', select(1, ...)) 53 | local b, c, d = ... 54 | print('b', b, 'c', c, 'd', d) 55 | local args = {..., z=7} 56 | for k, v in pairs(args) do 57 | print('args', k, v) 58 | end 59 | print( 60 | string.format('a=%s; ', a), 61 | table.unpack(args)) 62 | end 63 | aPrint('thisIsA', 'and this is', 'something', 1, {}) 64 | -------------------------------------------------------------------------------- /tests/main.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | #include "fngi.h" 5 | 6 | #define NL eprintf("\n"); 7 | 8 | void disassemble(void* fn, U2 len) { 9 | U1* c = fn; eprintf("## Disassemble %p [len=%u]\n", c, len); 10 | for(U2 i = 0; i < len; i++) { 11 | U1 instr = c[i]; Slc name = instrName(instr); 12 | eprintf("%.2X [%.*s]\n", instr, Dat_fmt(name)); 13 | } 14 | } 15 | #define DISASSEMBLE(FN) \ 16 | TyFn* LINED(fn) = tyFn(Kern_findTy(k, &KEY(FN))); \ 17 | eprintf("DISASSEMBLED %.*s [len=%u]:\n", Ty_fmt(LINED(fn)), LINED(fn)->len); \ 18 | disassemble(LINED(fn)->code, LINED(fn)->len); 19 | 20 | TEST(basic) 21 | TASSERT_EQ(7, cToU1('7')); 22 | TASSERT_EQ(0xB, cToU1('b')); 23 | TASSERT_EQ(0xB, cToU1('B')); 24 | TASSERT_EQ(0xFF, cToU1('-')); 25 | END_TEST 26 | 27 | TEST_FNGI(init, 10) 28 | TASSERT_EQ(WS_DEPTH, WS->cap); 29 | TASSERT_EQ(WS->sp, WS->cap); 30 | WS_ADD(4); TASSERT_WS(4); 31 | END_TEST_FNGI 32 | 33 | void N_add42(Kern* k) { 34 | WS_ADD(WS_POP() + 42); 35 | } 36 | 37 | // Helpers to execute functions 38 | static inline void _xFn(Kern* k, TyFn fn) { executeFn(k, &fn); } 39 | 40 | // XFN(dat, meta, lSlots) 41 | #define XFN(...) _xFn(k, litFn(__VA_ARGS__)) 42 | 43 | TEST_FNGI(call, 1) 44 | // Running some code 45 | 46 | TyFn_static(fiveFn, 0, 0, ((U1[]){SLIT + 2, SLIT + 3, ADD, RET}) ); 47 | executeFn(k, &fiveFn); TASSERT_WS(5); 48 | 49 | // Calling a function 50 | Buf_var(call5, 16); 51 | Buf_add(&call5, XL); Buf_addBE4(&call5, (S)&fiveFn); Buf_add(&call5, RET); 52 | XFN(call5.dat, 0, 0); TASSERT_WS(5); 53 | 54 | // Executing a native 55 | TyFn_static(add42, TY_FN_NATIVE, 0, kFn(N_add42)); 56 | Buf_var(callAdd42, 16); 57 | Buf_add(&callAdd42, XL); Buf_addBE4(&callAdd42, (S)&add42); Buf_add(&callAdd42, RET); 58 | WS_ADD(3); XFN(callAdd42.dat, 0, 0); TASSERT_WS(45); 59 | TASSERT_EMPTY(); 60 | END_TEST_FNGI 61 | 62 | #define TASSERT_TOKEN(T) \ 63 | tokenDrop(k); scan(k); \ 64 | TASSERT_SLC_EQ(T, *Buf_asSlc(&k->g.c.token)); 65 | 66 | TEST_FNGI(scan, 1) 67 | U1 dat[32]; k->g.c.token = (Buf){.dat = dat, .cap = 32}; 68 | BufFile_varNt(bf, 8, " this-has(*) eight tokens"); 69 | k->g.c.src = (SpReader) {.m = &mSpReader_BufFile, .d = &bf }; 70 | 71 | TASSERT_TOKEN("this"); TASSERT_TOKEN("-"); TASSERT_TOKEN("has"); 72 | TASSERT_TOKEN("("); TASSERT_TOKEN("*"); TASSERT_TOKEN(")"); 73 | TASSERT_TOKEN("eight"); TASSERT_TOKEN("tokens"); 74 | END_TEST_FNGI 75 | 76 | Slc testCxt = SLC("test"); 77 | #define TY_CHECK(REQ, GIV, SAMELEN) tyCheck(REQ, GIV, SAMELEN, testCxt) 78 | 79 | TEST_FNGI(compile0, 4) 80 | k->g.c.fnState |= C_UNTY; 81 | Kern_fns(k); 82 | TASSERT_EMPTY(); 83 | 84 | // Very explicit memory layout 85 | BufFile_varNt(bf, 8, "42 + 7 ret;"); 86 | k->g.c.src = (SpReader) {.m = &mSpReader_BufFile, .d = &bf }; 87 | U1 codeDat[256]; k->g.c.code = (Buf){.dat=codeDat, .cap=256}; 88 | compileSrc(k); 89 | XFN(codeDat, 0, 0); 90 | TASSERT_WS(49); 91 | TASSERT_EMPTY(); 92 | 93 | COMPILE_EXEC("inc 4"); TASSERT_WS(5); 94 | COMPILE_EXEC("inc2 4"); TASSERT_WS(6); 95 | COMPILE_EXEC("dec 4"); TASSERT_WS(3); 96 | COMPILE_EXEC("1 + 2"); TASSERT_WS(3); 97 | COMPILE_EXEC("1 shl 4"); TASSERT_WS(1 << 4); 98 | TASSERT_EMPTY(); 99 | COMPILE_EXEC("7 - (3 + 2)"); TASSERT_WS(2); 100 | TASSERT_EMPTY(); 101 | 102 | COMPILE("fn answer[ ] do 0x42", false); 103 | TyFn* answer = tyFn(Kern_findTy(k, &KEY("answer"))); 104 | executeFn(k, answer); TASSERT_WS(0x42); 105 | COMPILE_EXEC("answer;"); TASSERT_WS(0x42); 106 | TASSERT_EMPTY(); 107 | 108 | COMPILE_EXEC("0x44"); TASSERT_WS(0x44); 109 | COMPILE_EXEC("ch:d"); TASSERT_WS('d'); 110 | COMPILE_EXEC("ch:\\n"); TASSERT_WS('\n'); 111 | COMPILE_EXEC("ch:\\t"); TASSERT_WS('\t'); 112 | COMPILE_EXEC("ch: "); TASSERT_WS(' '); 113 | COMPILE_EXEC("ch:\\ "); TASSERT_WS(' '); 114 | END_TEST_FNGI 115 | 116 | TEST_FNGI(inlineFns, 4) 117 | k->g.c.fnState |= C_UNTY; 118 | Kern_fns(k); 119 | COMPILE_EXEC("swp(1, 4)"); TASSERT_WS(1); TASSERT_WS(4); 120 | 121 | TASSERT_EMPTY(); 122 | END_TEST_FNGI 123 | 124 | TEST_FNGI(compile1, 10) 125 | Kern_fns(k); 126 | k->g.c.fnState |= C_UNTY; 127 | 128 | COMPILE_EXEC("fn maths[ ] do (_ + 7 + (3 * 5)) maths(4)"); TASSERT_WS(26); 129 | COMPILE_EXEC("1 \\comment \\(3 + 4) + 7"); TASSERT_WS(8) 130 | COMPILE_EXEC("fn maths2[stk:U2 -> U2] do (_ + 10) maths2(6)"); TASSERT_WS(16); 131 | TyFn* maths2 = tyFn(Kern_findTy(k, &KEY("maths2"))); 132 | TASSERT_EQ(&Ty_U2, (TyDict*)maths2->inp->ty); TASSERT_EQ(NULL , maths2->inp->name); 133 | TASSERT_EQ(&Ty_U2, (TyDict*)maths2->out->ty); TASSERT_EQ(NULL , maths2->out->name); 134 | TASSERT_EQ(NULL, (TyDict*)maths2->inp->next); TASSERT_EQ(NULL, maths2->out->next); 135 | 136 | COMPILE_EXEC("fn maths3[x:U2 -> U2] do (x + 4) maths3(3)"); 137 | TyFn* maths3 = tyFn(Kern_findTy(k, &KEY("maths3"))); 138 | TASSERT_EQ(&Ty_U2, (TyDict*)maths3->inp->ty); 139 | TASSERT_SLC_EQ("x" , CStr_asSlc(maths3->inp->name)); 140 | TASSERT_EQ(&Ty_U2, (TyDict*)maths3->out->ty); TASSERT_EQ(NULL , maths3->out->name); 141 | TASSERT_EQ(NULL, maths3->inp->next); TASSERT_EQ(NULL, maths3->out->next); 142 | TASSERT_EQ(1, maths3->lSlots); 143 | TASSERT_WS(7); 144 | 145 | COMPILE_EXEC("fn pop2[a:U1 b:U2 c:S -> \\a:S] do (a); pop2(0x1234 2 3)"); 146 | TASSERT_WS(0x34); 147 | TyFn* pop2 = tyFn(Kern_findTy(k, &KEY("pop2"))); 148 | TASSERT_SLC_EQ("c" , CStr_asSlc(pop2->inp->name)); 149 | TASSERT_SLC_EQ("b" , CStr_asSlc(pop2->inp->next->name)); 150 | TASSERT_SLC_EQ("a" , CStr_asSlc(pop2->inp->next->next->name)); 151 | TASSERT_EQ(&Ty_S, (TyDict*)pop2->inp->ty); 152 | TASSERT_EQ(2, pop2->lSlots); 153 | 154 | COMPILE_EXEC("tAssertEq(42, 42)"); 155 | COMPILE_EXEC("tAssertEq(0x10 + 3, 0x13)"); 156 | COMPILE_EXEC("tAssertEq(1 shl 4, 0x10)"); 157 | COMPILE_EXEC("tAssertEq(0x10 shr 4, 1)"); 158 | END_TEST_FNGI 159 | 160 | TEST_FNGI(comment, 10) 161 | Kern_fns(k); 162 | COMPILE_EXEC("\\1 \\(foo bar) \\(3 4 5)\n\\ 1 2 3 this is a line\n\\3"); 163 | TASSERT_EQ(0, Stk_len(WS)); 164 | END_TEST_FNGI 165 | 166 | TEST_FNGI(tyDb, 4) 167 | Kern_fns(k); 168 | LOCAL_TYDB_BBA(tyDb); 169 | 170 | TY_CHECK(&TyIs_S, &TyIs_S, false); 171 | TY_CHECK(&TyIs_S, &TyIs_S, true); 172 | TY_CHECK(&TyIs_S, &TyIs_SS, false); 173 | 174 | EXPECT_ERR(TY_CHECK(&TyIs_S, &TyIs_SS, true), "test"); 175 | EXPECT_ERR(TY_CHECK(&TyIs_U4x2, &TyIs_rU1_U4, true), "test"); 176 | 177 | TyDb* db = &k->g.tyDb; 178 | 179 | TyDb_new(db); 180 | 181 | // Call a function which returns [S], the type gets put on 182 | tyCall(k, db, NULL, &TyIs_S); TY_CHECK(&TyIs_S, TyDb_top(db), true); 183 | 184 | // Now call a function which consumes [S], the type stack empties 185 | tyCall(k, db, &TyIs_S, NULL); TY_CHECK(NULL, TyDb_top(db), true); 186 | 187 | // ret[done] causes errors on future operations 188 | DictStk_add(&k->g.implStk, (TyDict*)Kern_findTy(k, &KEY("+"))); 189 | tyCall(k, db, NULL, &TyIs_S); 190 | tyRet(k, db, RET_DONE); TASSERT_EQ(RET_DONE, TyDb_done(db)); 191 | FNGI_EXPECT_ERR(tyCall(k, db, &TyIs_S, NULL), "Code after guaranteed 'ret'"); 192 | END_LOCAL_TYDB_BBA(tyDb); 193 | END_TEST_FNGI 194 | 195 | TEST_FNGI(compileTy, 7) 196 | Kern_fns(k); 197 | REPL_START 198 | COMPILE_EXEC("fn pop2 [ a:U1 b:U2 c:S -> \\a:U1] do (a)"); 199 | COMPILE_EXEC("fn pop2_ [stk:U1 b:U2 c:S -> \\a:U1] do (_)"); 200 | COMPILE_EXEC("fn add [stk:S b:S -> \\sum:S] do (_ + b)"); 201 | COMPILE_EXEC("add(42, 7)"); TASSERT_WS(49); 202 | 203 | COMPILE_EXEC("fn addUs[stk:U1 b:U2 -> \\a:S ] do (_ + b)"); 204 | COMPILE_EXEC("addUs(cast:U1(0x12) cast:U2(0x34))"); TASSERT_WS(0x46); 205 | REPL_END 206 | END_TEST_FNGI 207 | 208 | #define TY_LEN Sll_len((Sll*)TyDb_top(&k->g.tyDb)) 209 | TEST_FNGI(compileIf, 10) 210 | Kern_fns(k); 211 | REPL_START 212 | 213 | k->g.c.fnState |= C_UNTY; 214 | 215 | COMPILE_EXEC("if(1) do ;"); TASSERT_EQ(0, Stk_len(WS)); 216 | COMPILE_EXEC("if(1) do 0x42 else 0x33"); TASSERT_WS(0x42); 217 | COMPILE_EXEC("if(0) do 0x42 else 0x33"); TASSERT_WS(0x33); 218 | COMPILE_EXEC("if(1) do 0x42 elif(1) do 0x11 else 0x33"); TASSERT_WS(0x42); 219 | COMPILE_EXEC("if(0) do 0x42 elif(1) do 0x11 else 0x33"); TASSERT_WS(0x11); 220 | COMPILE_EXEC("if(0) do 0x42 elif(0) do 0x11 else 0x33"); TASSERT_WS(0x33); 221 | TASSERT_EQ(0, Stk_len(WS)); 222 | TASSERT_EQ(0, TY_LEN); 223 | 224 | k->g.c.fnState = (~C_UNTY) & k->g.c.fnState; 225 | TASSERT_EQ(0, TY_LEN); TASSERT_EQ(0, Stk_len(WS)); 226 | 227 | COMPILE_EXEC("if(1) do ;"); 228 | TASSERT_EQ(0, TY_LEN); TASSERT_EQ(0, Stk_len(WS)); 229 | 230 | COMPILE_EXEC("if(1) do 0x42 else 0x33"); TASSERT_EQ(1, TY_LEN); 231 | COMPILE_EXEC("tAssertEq 0x42"); TASSERT_EQ(0, TY_LEN); 232 | 233 | COMPILE_EXEC("fn ifRet[stk:S -> S] do ( if(_) do (0x22 ret;) else 0x33 )") 234 | COMPILE_EXEC("tAssertEq(0x22, ifRet(1))"); 235 | COMPILE_EXEC("tAssertEq(0x33, ifRet(0))"); 236 | 237 | COMPILE_EXEC("fn ifElifRet[x:S -> S] do (" 238 | " if( x == 0x42) do (x ret;)" 239 | " elif(x == 0x11) do (0 ret;)" 240 | " else 0x33" 241 | ")") 242 | COMPILE_EXEC("tAssertEq(0x42, ifElifRet(0x42))"); 243 | COMPILE_EXEC("tAssertEq(0, ifElifRet(0x11))"); 244 | COMPILE_EXEC("tAssertEq(0x33, ifElifRet(7))"); 245 | 246 | COMPILE_EXEC("fn ifElifRetMid[x:S -> S] do (" 247 | " if (x == 0x42) do (0x42)" 248 | " elif(x == 0x11) do (0 ret;)" 249 | " else 0x33" 250 | ")") 251 | COMPILE_EXEC("tAssertEq(0x42, ifElifRetMid(0x42))"); 252 | COMPILE_EXEC("tAssertEq(0, ifElifRetMid(0x11))"); 253 | COMPILE_EXEC("tAssertEq(0x33, ifElifRetMid(7))"); 254 | 255 | COMPILE_EXEC("fn ifElifStk[stk:S -> S] do (" 256 | " if (dup; == 0x42) do (_)" 257 | " elif(dup; == 0x11) do (drp; 0 ret;)" 258 | " else (drp; 0x33)" 259 | ")") 260 | COMPILE_EXEC("tAssertEq(0x42, ifElifStk(0x42))"); 261 | COMPILE_EXEC("tAssertEq(0, ifElifStk(0x11))"); 262 | COMPILE_EXEC("tAssertEq(0x33, ifElifStk(7))"); 263 | 264 | #define IF_ALL_RET \ 265 | "if(0) do (0 ret;) elif(0) do (0 ret;) else (1 ret;)" 266 | 267 | COMPILE_EXEC("fn one[ -> S] do (" IF_ALL_RET ")"); 268 | COMPILE_EXEC("tAssertEq(1, one;)"); 269 | COMPILE_EXEC("fn ifRet1[ -> S] do ( if(1) do ret 2; ret 4; )"); 270 | COMPILE_EXEC("tAssertEq(2, ifRet1,)"); 271 | 272 | TASSERT_EQ(1, Stk_len(&k->g.tyDb.tyIs)); 273 | SET_SRC("fn bad[ -> S] do (" IF_ALL_RET "4 )"); 274 | FNGI_EXPECT_ERR(compileRepl(k, true), "Code after guaranteed 'ret'"); 275 | TASSERT_EQ(1, Stk_len(&k->g.tyDb.tyIs)); 276 | 277 | REPL_END 278 | END_TEST_FNGI 279 | 280 | TEST_FNGI(compileBlk, 10) 281 | Kern_fns(k); 282 | REPL_START 283 | COMPILE_EXEC("S(0) blk( drp; )"); TASSERT_EMPTY(); 284 | 285 | SET_SRC("S(0) blk( drp; cont; )"); 286 | FNGI_EXPECT_ERR(compileRepl(k, true), "cont not identical"); 287 | 288 | COMPILE_EXEC( 289 | "fn loop[ -> S] do (\n" 290 | " S 0 -> blk(\n" 291 | " if(dup, >= 5) do (drp; break 0x15)\n" 292 | " inc; cont;\n" 293 | " )\n" 294 | ") loop()"); TASSERT_WS(0x15); 295 | 296 | COMPILE_EXEC( 297 | "fn useContIf[ -> S] do (\n" 298 | " S 0 -> blk(\n" 299 | " inc; contIf(dup, < 5)\n" 300 | " )\n" 301 | ") useContIf()"); TASSERT_WS(5); 302 | 303 | COMPILE_EXEC( 304 | "fn useWhile[ -> S] do (\n" 305 | " S 0 -> blk ( inc; ) while ( dup, < 7 )\n" 306 | ") useWhile()"); TASSERT_WS(7); 307 | REPL_END 308 | TASSERT_EQ(0, Stk_len(&k->g.tyDb.tyIs)); 309 | END_TEST_FNGI 310 | 311 | TEST_FNGI(compileVar, 10) 312 | Kern_fns(k); REPL_START; 313 | COMPILE_EXEC( 314 | "fn useVar[stk:S -> S] do (\n" 315 | " var a: S = (_ + 7); ret inc(a);\n" 316 | ") useVar(0x42)"); TASSERT_WS(0x4A); 317 | 318 | TASSERT_EMPTY(); 319 | 320 | COMPILE_EXEC("fn idenRef[a:&S -> &S] do ( a )\n"); 321 | COMPILE_EXEC("fn ftRef [a:&S -> S ] do ( @a )\n"); 322 | TyFn* ftRef = (TyFn*) Kern_findTy(k, &KEY("ftRef")); 323 | TASSERT_EQ(1, ftRef->inp->meta); 324 | TASSERT_EQ(0, ftRef->out->meta); 325 | 326 | COMPILE_EXEC("fn useRef[a:S -> S ] do ( ftRef(&a) )\n"); 327 | COMPILE_EXEC("useRef(0x29)"); 328 | COMPILE_EXEC("tAssertEq(0x29)"); 329 | COMPILE_EXEC("fn getRefs[a:S -> &S &S] do ( var b:U4; &a, &b )\n"); 330 | COMPILE_EXEC("getRefs(2);"); 331 | WS_POP2(S a, S b); 332 | U4 localBot = RS_topRef(k) - 12; // 12 == size(ret addr) + size(A) + size(B) 333 | TASSERT_EQ(localBot , a); 334 | TASSERT_EQ(localBot + 4, b); 335 | REPL_END 336 | END_TEST_FNGI 337 | 338 | void assertDotPath(DotPath* p, U1 op, U1 meta, U2 offset, void* ty) { 339 | TASSERT_EQ(op, p->op); 340 | TASSERT_EQ(offset, p->offset); 341 | TASSERT_EQ(meta, p->tyI->meta); 342 | TASSERT_EQ(ty, (TyBase*)p->tyI->ty); 343 | } 344 | 345 | TEST_FNGI(dotPath, 10) 346 | Kern_fns(k); REPL_START; 347 | 348 | COMPILE_EXEC( 349 | " struct A [ a0:U2 a1:U2 ]\n" 350 | " struct B [ b0:A b1:&A b2:U4]\n" 351 | " struct C [ c0:B c1:&B c2:A ]\n"); 352 | TyDict* A = tyDict(Kern_findTy(k, &KEY("A"))); 353 | TyDict* B = tyDict(Kern_findTy(k, &KEY("B"))); 354 | TyDict* C = tyDict(Kern_findTy(k, &KEY("C"))); 355 | 356 | TyI TyI_A = { .ty = (TyBase*) A }; 357 | TyI TyI_B = { .ty = (TyBase*) B }; 358 | TyI TyI_C = { .ty = (TyBase*) C }; 359 | 360 | TyI TyI_Ar = { .ty = (TyBase*) A, .meta = 1 }; 361 | TyI TyI_Br = { .ty = (TyBase*) B, .meta = 1 }; 362 | 363 | TyVar* g = (TyVar*)0xF00F; 364 | 365 | SET_SRC(".a0"); 366 | DotPath* p = dotPath(k, &TyI_A, g, 0, FTGL); 367 | TASSERT_EQ(NULL, p->next); assertDotPath(p, FTGL, /*meta*/0, /*off*/0, &Ty_U2); 368 | TASSERT_EQ(g, p->global); 369 | 370 | SET_SRC(".b0"); // b.b0 returns a struct 371 | p = dotPath(k, &TyI_B, g, 0, FTGL); 372 | TASSERT_EQ(NULL, p->next); assertDotPath(p, FTGL, /*meta*/0, /*off*/0, A); 373 | TASSERT_EQ(g, p->global); 374 | 375 | SET_SRC(".b0.a0"); // b.b0.a0 is basically identical to A.a0 376 | p = dotPath(k, &TyI_B, g, 0, FTGL); 377 | TASSERT_EQ(NULL, p->next); assertDotPath(p, FTGL, /*meta*/0, /*off*/0, &Ty_U2); 378 | TASSERT_EQ(g, p->global); 379 | 380 | SET_SRC(".b0.a1"); // b.b0.a1 is basically identical to A.a2 381 | p = dotPath(k, &TyI_B, g, 0, FTGL); 382 | TASSERT_EQ(NULL, p->next); assertDotPath(p, FTGL, /*meta*/0, /*off*/2, &Ty_U2); 383 | TASSERT_EQ(g, p->global); 384 | 385 | SET_SRC(".b1.a1"); // b.b1.a1 has to go through a reference 386 | p = dotPath(k, &TyI_B, g, 0, FTGL); 387 | assertDotPath(p, FTO, /*meta*/0, /*off*/2, &Ty_U2); 388 | TASSERT_EQ(NULL, p->global); 389 | p = p->next; TASSERT_EQ(NULL, p->next); 390 | assertDotPath(p, FTGL, /*meta*/1, /*off*/4, A); 391 | TASSERT_EQ(g, p->global); 392 | 393 | SET_SRC(".b1.a1"); // it matters little whether the first value is a ref 394 | p = dotPath(k, &TyI_Br, NULL, 0, FTO); 395 | assertDotPath(p, FTO, /*meta*/0, /*off*/2, &Ty_U2); 396 | TASSERT_EQ(NULL, p->global); 397 | p = p->next; TASSERT_EQ(NULL, p->next); 398 | assertDotPath(p, FTO, /*meta*/1, /*off*/4, A); 399 | TASSERT_EQ(NULL, p->global); 400 | 401 | SET_SRC(".c0.b0.a0"); // basically identical to A.a0 402 | p = dotPath(k, &TyI_C, g, 0, FTGL); 403 | TASSERT_EQ(NULL, p->next); assertDotPath(p, FTGL, /*meta*/0, /*off*/0, &Ty_U2); 404 | 405 | COMPILE_EXEC( 406 | "impl A (\n" 407 | " meth aMeth[self:&Self -> U4 ] do (self.a0 + self.a1 )\n" 408 | ")\n"); 409 | TyFn* aMeth = tyFn(TyDict_find(A, &KEY("aMeth"))); 410 | SET_SRC(".aMeth"); 411 | p = dotPath(k, &TyI_A, g, 0, FTGL); 412 | assertDotPath(p, XL, /*meta*/0, /*off*/0, aMeth); TASSERT_EQ(NULL, p->global); 413 | p = p->next; // where to get &Self 414 | TASSERT_EQ(NULL, p->next); assertDotPath(p, FTGL, /*meta*/0, /*off*/0, A); 415 | TASSERT_EQ(g, p->global); 416 | 417 | SET_SRC(".c0.b0.aMeth"); 418 | p = dotPath(k, &TyI_C, g, 0, FTGL); 419 | assertDotPath(p, XL, /*meta*/0, /*off*/0, aMeth); TASSERT_EQ(NULL, p->global); 420 | p = p->next; // where to get &Self 421 | TASSERT_EQ(NULL, p->next); assertDotPath(p, FTGL, /*meta*/0, /*off*/0, A); 422 | TASSERT_EQ(g, p->global); 423 | 424 | SET_SRC(".c1.b0.aMeth"); 425 | p = dotPath(k, &TyI_C, g, 0, FTGL); 426 | assertDotPath(p, XL, /*meta*/0, /*off*/0, aMeth); TASSERT_EQ(NULL, p->global); 427 | TASSERT_EQ(NULL, p->global); 428 | p = p->next; // where to get &Self from &B 429 | assertDotPath(p, FTO, /*meta*/0, /*off*/0, A); TASSERT_EQ(NULL, p->global); 430 | p = p->next; // where to get B (.c1) from C 431 | TASSERT_EQ(NULL, p->next); assertDotPath(p, FTGL, /*meta*/1, /*off*/TyDict_sz(B), B); 432 | TASSERT_EQ(g, p->global); 433 | 434 | // Through reference 435 | SET_SRC(".a1"); p = dotPath(k, &TyI_Ar, g, 0, FTGL); 436 | assertDotPath(p, FTO, /*meta*/0, /*off*/2, &Ty_U2); 437 | p = p->next; TASSERT_EQ(NULL, p->next); 438 | assertDotPath(p, FTGL, /*meta*/1, /*off*/0, A); 439 | 440 | SET_SRC(".b1"); p = dotPath(k, &TyI_Br, NULL, 0, FTO); 441 | assertDotPath(p, FTO, /*meta*/1, /*off*/4, A); 442 | TASSERT_EQ(NULL, p->next); 443 | 444 | SET_SRC(".b1.a1"); p = dotPath(k, &TyI_Br, NULL, 0, FTO); 445 | assertDotPath(p, FTO, /*meta*/0, /*off*/2, &Ty_U2); 446 | p = p->next; TASSERT_EQ(NULL, p->next); 447 | assertDotPath(p, FTO, /*meta*/1, /*off*/4, A); 448 | 449 | REPL_END 450 | END_TEST_FNGI 451 | 452 | TEST_FNGI(compileStruct, 10) 453 | Kern_fns(k); REPL_START; 454 | 455 | COMPILE_EXEC( 456 | "fn chTy[stk:S -> U2] do ( ret U2; )" 457 | "chTy(0x42)"); TASSERT_WS(0x42); 458 | 459 | COMPILE_EXEC("struct Foo [ a: U2; b: U1; c: U4 ]"); 460 | TyDict* Foo = (TyDict*) Kern_findTy(k, &KEY("Foo")); 461 | assert(Foo); 462 | TASSERT_EQ(TY_DICT | TY_DICT_STRUCT, Foo->meta); 463 | TASSERT_EQ(8, Foo->sz); 464 | assert(TyDict_find(Foo, &KEY("a"))); 465 | 466 | COMPILE_EXEC("struct A [ a: S ]"); 467 | COMPILE_EXEC("struct B [ a: A; b: S ]"); 468 | COMPILE_EXEC( 469 | "fn simpleCreate[a:S -> B] do (" 470 | " B(A(a), 0x42)" 471 | "); simpleCreate(0x33)"); TASSERT_WS(0x42); TASSERT_WS(0x33); 472 | 473 | COMPILE_EXEC("destruct(simpleCreate(0x35)) tAssertEq(0x42)") 474 | TASSERT_WS(0x35); 475 | TASSERT_EQ(0, Stk_len(WS)); 476 | 477 | COMPILE_EXEC( 478 | "fn useField[a:A -> S] do ( a.a + 7 )" 479 | " tAssertEq(0x18, useField(A 0x11))"); 480 | 481 | COMPILE_EXEC("fn getStruct[b:B -> B] do ( b ); getStruct(B(A 0x4321, 0x321))"); 482 | TASSERT_WS(0x321); TASSERT_WS(0x4321); 483 | REPL_END 484 | END_TEST_FNGI 485 | 486 | TEST_FNGI(fnSig, 10) 487 | Kern_fns(k); REPL_START; 488 | 489 | COMPILE_EXEC( 490 | "fn add1[a:S -> S] do (\n" 491 | " a + 1\n" 492 | "); tAssertEq(add1(4), 5)"); 493 | 494 | COMPILE_EXEC( 495 | "fn useFn [ f:&fnSig[S->S] -> S] do (\n" 496 | " @f(8)\n" 497 | ")"); 498 | COMPILE_EXEC("tAssertEq(9, useFn(&add1))"); 499 | 500 | REPL_END 501 | END_TEST_FNGI 502 | 503 | TEST_FNGI(alias, 10) 504 | Kern_fns(k); REPL_START; 505 | 506 | COMPILE_EXEC("struct A [ a: S ] alias Aa:A\n" 507 | "fn useAlias[a:Aa -> A] do (\n" 508 | " Aa(a.a)\n" 509 | "); useAlias(A(0x30))\n"); TASSERT_WS(0x30); 510 | 511 | REPL_END 512 | END_TEST_FNGI 513 | 514 | 515 | TEST_FNGI(structBrackets, 10) 516 | Kern_fns(k); REPL_START 517 | COMPILE_EXEC("struct A [ a1: S, a2: S ]"); 518 | COMPILE_EXEC("struct B [ b1: A, b2: S ]"); 519 | COMPILE_EXEC("fn buildA[ -> A] do ( var a: A = { a1 = 0x33 a2 = 0x22 } a )") 520 | COMPILE_EXEC("buildA()"); TASSERT_WS(0x22); TASSERT_WS(0x33); 521 | COMPILE_EXEC("fn buildB[ -> B] do ( var b: B = {\n" 522 | " b1 = { a1 = 0x35 a2 = 0x25 }\n" 523 | " b2 = 0x11\n" 524 | "} b )") 525 | COMPILE_EXEC("buildB()"); TASSERT_WS(0x11); TASSERT_WS(0x25); TASSERT_WS(0x35); 526 | COMPILE_EXEC("fn buildBwA[ -> B] do ( var b: B = {\n" 527 | " b1 = buildA()\n" 528 | " b2 = 0x11\n" 529 | "} b )") 530 | COMPILE_EXEC("buildBwA()"); TASSERT_WS(0x11); TASSERT_WS(0x22); TASSERT_WS(0x33); 531 | 532 | 533 | REPL_END 534 | END_TEST_FNGI 535 | 536 | TEST_FNGI(global, 10) 537 | Kern_fns(k); REPL_START 538 | COMPILE_EXEC("global myA:S = 32"); 539 | TyVar* a = tyVar(Kern_findTy(k, &KEY("myA"))); 540 | TASSERT_EQ(0, Stk_len(WS)); 541 | TASSERT_EQ(32, ftSzI((U1*)a->v, SZR)); 542 | COMPILE_EXEC("imm#tAssertEq(32, myA)"); 543 | COMPILE_EXEC("tAssertEq(32, myA)"); 544 | 545 | COMPILE_EXEC("struct Foo [ a: S; b: S; c: S]"); 546 | COMPILE_EXEC("global foo:Foo = Foo(7, 3, 12)"); 547 | TASSERT_EQ(0, Stk_len(WS)); 548 | TyVar* foo = tyVar(Kern_findTy(k, &KEY("foo"))); 549 | TASSERT_EQ(7, ftSzI((U1*)foo->v, SZR)); 550 | 551 | COMPILE_EXEC("tAssertEq(7, foo.a) tAssertEq(3, foo.b)"); 552 | COMPILE_EXEC("@ &foo.a"); TASSERT_WS(7); 553 | COMPILE_EXEC("(&foo).a"); TASSERT_WS(7); 554 | 555 | COMPILE_EXEC("imm#tAssertEq(7, foo.a)"); 556 | COMPILE_EXEC("foo.a = 0x444 tAssertEq(0x444, foo.a)"); 557 | COMPILE_EXEC("imm#( tAssertEq(0x444, foo.a); tAssertEq(3, foo.b) )"); 558 | COMPILE_EXEC("assertWsEmpty;"); 559 | 560 | COMPILE_EXEC("const c1:S = 12"); 561 | COMPILE_EXEC("tAssertEq(12, c1)"); 562 | 563 | COMPILE_EXEC("const cFoo:Foo = foo;"); 564 | COMPILE_EXEC("tAssertEq(0x444, cFoo.a); tAssertEq(3, foo.b)"); 565 | REPL_END 566 | END_TEST_FNGI 567 | 568 | TEST_FNGI(mod, 10) 569 | Kern_fns(k); REPL_START 570 | COMPILE_EXEC("mod foo ( fn one[ -> S] do 1 )"); 571 | COMPILE_EXEC("imm#tAssertEq(1, foo.one())"); 572 | COMPILE_EXEC("use foo ( imm#tAssertEq(1, one()) )"); 573 | REPL_END 574 | END_TEST_FNGI 575 | 576 | TEST_FNGI(structDeep, 12) 577 | Kern_fns(k); REPL_START 578 | COMPILE_EXEC("struct A [ a: S ]"); 579 | COMPILE_EXEC("struct B [ a: A; b: S; rA: &A ]"); 580 | COMPILE_EXEC("struct C [a: &A, b: &B]") 581 | COMPILE_EXEC("fn cGetA[c:&C -> S] do ( c.b.a.a );"); 582 | COMPILE_EXEC("fn useC[ -> S S] do (\n" 583 | " var a: A = A 1\n" 584 | " var b: B = B(A 2, 3, &a)\n" 585 | " tAssertEq(b.a.a, 2)\n" 586 | " tAssertEq(b.rA.a, 1)\n" 587 | " var c: C = C(&a, &b)\n" 588 | " tAssertEq(@(&b.rA.a), 1)\n" 589 | " c.b.a.a, cGetA(&c)\n" 590 | ")"); 591 | COMPILE_EXEC("useC;"); 592 | TASSERT_WS(2); TASSERT_WS(2); 593 | 594 | COMPILE_EXEC("fn assign[ -> S] do (\n" 595 | " var a: A;\n" 596 | " a.a = 0x42\n" 597 | " a.a\n" 598 | ")"); 599 | COMPILE_EXEC("assign()"); TASSERT_WS(0x42); 600 | 601 | COMPILE_EXEC("fn assignRef[a:&A] do (a.a = 0x44)") 602 | 603 | COMPILE_EXEC("fn useAssignRef[ -> S] do (var a:A; assignRef(&a); a.a)"); 604 | 605 | COMPILE_EXEC("useAssignRef()"); 606 | TASSERT_WS(0x44); 607 | 608 | REPL_END 609 | END_TEST_FNGI 610 | 611 | TEST_FNGI(structSuper, 12) 612 | Kern_fns(k); REPL_START 613 | COMPILE_EXEC("struct A [ a: S ]"); 614 | COMPILE_EXEC("struct B [ parent: A; b: S ]"); 615 | 616 | COMPILE_EXEC("fn useB[b:B -> S S] do (\n" 617 | " b.b, b.a\n" 618 | ")"); 619 | COMPILE_EXEC("useB(B(A(0x11), 0x12))"); TASSERT_WS(0x11); TASSERT_WS(0x12); 620 | 621 | COMPILE_EXEC("fn thing[ ] do ( var a:A = { a = 0x15 } ) "); 622 | COMPILE_EXEC("fn newB[ -> B] do ( var b:B = { a = 0x15 b = 0x16 } b)"); 623 | COMPILE_EXEC("useB(newB;)"); TASSERT_WS(0x15); TASSERT_WS(0x16); 624 | 625 | COMPILE_EXEC("struct _Slc [ dat:&U1 len:U2 ]"); 626 | COMPILE_EXEC("struct _Buf [ parent:_Slc cap:U2 ]"); 627 | TyDict* s = tyDict(Kern_findTy(k, &KEY("_Slc"))); 628 | TyDict* b = tyDict(Kern_findTy(k, &KEY("_Buf"))); 629 | TASSERT_EQ(6, s->sz); TASSERT_EQ(8, b->sz); 630 | 631 | // Prove the type-checker allows either a Slc or Buf pointer. 632 | COMPILE_EXEC("fn ignoreSlc[s:&_Slc] do ()"); 633 | COMPILE_EXEC("fn ignoreBuf[s:&_Buf] do ()"); 634 | COMPILE_EXEC( 635 | "fn passStuff[ ] do (\n" 636 | " var s:_Slc; var b:_Buf;\n" 637 | " ignoreSlc(&s)\n" // note: ignoreBuf(&s) causes a compiler error 638 | " ignoreBuf(cast:&_Buf(&s))\n" // cast allows it (not really valid) 639 | " ignoreSlc(&b) ignoreBuf(&b)\n" 640 | ")"); 641 | REPL_END 642 | END_TEST_FNGI 643 | 644 | TEST_FNGI(method, 20) 645 | Kern_fns(k); REPL_START 646 | COMPILE_EXEC("struct A [\n" 647 | " v:S;\n" 648 | " meth aDo [self: &Self, x: S -> S] \n" 649 | " do ( self.v + x )\n" 650 | "]") 651 | COMPILE_EXEC("fn callADo[x:S a:A -> S] do ( a.aDo(x) )"); 652 | COMPILE_EXEC("tAssertEq(8, callADo(3, A 5)) assertWsEmpty;"); 653 | 654 | COMPILE_EXEC("impl A( fn nonMeth[x:S -> S] do ( 7 + x ) )") 655 | COMPILE_EXEC("fn callNonMeth[x:S a:A -> S] do ( A.nonMeth(x) )"); 656 | COMPILE_EXEC("tAssertEq(10, callNonMeth(3, A 1)) assertWsEmpty;"); 657 | 658 | COMPILE_EXEC("global a:A = A 5"); 659 | COMPILE_EXEC("tAssertEq(5, a.v)"); 660 | COMPILE_EXEC("tAssertEq(13, a.aDo(8))"); 661 | COMPILE_EXEC("tAssertEq(14, A.nonMeth(7))"); 662 | 663 | COMPILE_EXEC("imm#tAssertEq(13, a.aDo(8))"); 664 | COMPILE_EXEC("imm#tAssertEq(14, A.nonMeth(7))"); 665 | 666 | COMPILE_EXEC("impl A( meth aDoSelf [self: &Self, x: S -> S] do ( self.v + x ) )") 667 | COMPILE_EXEC("imm#tAssertEq(13, a.aDoSelf(8))"); 668 | 669 | TyDict* A = tyDict(Kern_findTy(k, &KEY("A"))); 670 | TyFn* aDo = tyFn(TyDict_find(A, &KEY("aDo"))); 671 | COMPILE_EXEC("&A.aDo;"); TASSERT_WS((S)aDo); 672 | 673 | COMPILE_EXEC("impl A( meth assignInc [self: &Self, x: S -> S] do\n" 674 | " ( self.v = (x + 1); self.v ) )") 675 | COMPILE_EXEC("imm#tAssertEq(6, a.assignInc(5))"); 676 | COMPILE_EXEC("imm#tAssertEq(6, a.v)"); 677 | 678 | COMPILE_EXEC("impl A( meth inc [self: &Self -> S] do\n" 679 | " ( self.v = (self.v + 1); self.v ) )") 680 | COMPILE_EXEC("imm#tAssertEq(7, a.inc;)"); 681 | REPL_END 682 | END_TEST_FNGI 683 | 684 | TEST_FNGI(ptr, 20) // tests necessary for libraries 685 | Kern_fns(k); REPL_START 686 | // Get the value at a pointer 687 | COMPILE_EXEC("fn ftPtr[a:S -> S] do ( @(&a) ) ftPtr(0x42)"); 688 | TASSERT_WS(0x42); 689 | 690 | // Set the value at a pointer, then get it 691 | COMPILE_EXEC("fn srPtr[a:S -> S] do ( \\set @(&a) = (a + 3); a ) srPtr(3)"); 692 | TASSERT_WS(6); 693 | 694 | // Just get pointer and an offset of 1 695 | COMPILE_EXEC("fn getPtrs[x:S -> &S, &S] do (&x, ptrAdd(&x, 1, 10)) getPtrs(5)") 696 | WS_POP2(S x, S xPlus1); 697 | TASSERT_EQ(x + sizeof(S), xPlus1); 698 | 699 | // Get a value at the pointer index 1 700 | COMPILE_EXEC("fn ftPtr1[a:S b:S -> S] do ( @ptrAdd(&a, 1, 2) ) ftPtr1(0xBAD, 0x733) "); 701 | TASSERT_WS(0x733); 702 | REPL_END 703 | END_TEST_FNGI 704 | 705 | TEST_FNGI(arr, 20) // tests necessary for libraries 706 | Kern_fns(k); 707 | REPL_START 708 | COMPILE_EXEC( 709 | "fn simpleArr[x:S -> S] do (\n" 710 | " var a: Arr [3 S]\n" 711 | " var b: S = 0x42\n" 712 | " a = 3;\n" 713 | " @ptrAdd(&a, 1, 3) = 4; @ptrAdd(&a, 2, 3) = 5;\n" 714 | " tAssertEq(0x42, b)\n" 715 | " @ptrAdd(&a, 3, 4) = 9; \\ note: setting b\n" 716 | " tAssertEq(9, b)\n" 717 | " @ptrAdd(&a, x, 3)\n" 718 | // "3\n" 719 | ")"); 720 | COMPILE_EXEC("simpleArr 0"); TASSERT_WS(3); 721 | COMPILE_EXEC("simpleArr 1"); TASSERT_WS(4); 722 | COMPILE_EXEC("simpleArr 2"); TASSERT_WS(5); 723 | 724 | COMPILE_EXEC("fn unknown[x:?&S] do ( )") 725 | TyFn* unknown = tyFn(Kern_findTy(k, &KEY("unknown"))); 726 | assert(TY_UNSIZED == unknown->inp->arrLen); 727 | 728 | COMPILE_EXEC("struct UnsizedThing [ len:U2 dat:Arr[ ? U1] ]"); 729 | REPL_END 730 | END_TEST_FNGI 731 | 732 | TEST_FNGI(role, 20) 733 | Kern_fns(k); 734 | REPL_START 735 | 736 | CStr_ntVar(aaa, "\x03", "aaa"); 737 | TyI fakeKey = { .name = aaa, .ty = NULL }; 738 | TyDict tyKeyDict = { 739 | .name = aaa, .tyKey = &fakeKey, 740 | .meta = TY_DICT | TY_DICT_STRUCT, 741 | }; 742 | Kern_addTy(k, (Ty*)&tyKeyDict); 743 | Key key = { .name = SLC("aaa"), .tyI = &fakeKey }; 744 | TyDict* found = (TyDict*)Kern_findTy(k, &key); 745 | TASSERT_EQ(&tyKeyDict, found); 746 | 747 | COMPILE_EXEC( 748 | "role Adder [\n" 749 | " absmeth add [ &Self \\(b)S -> S ]\n" 750 | "]"); 751 | TyDict* Adder = tyDict(Kern_findTy(k, &KEY("Adder"))); 752 | TyFn* add = tyFn(TyDict_find(Adder, &KEY("add"))); 753 | TASSERT_EQ(NULL, add->inp->name); 754 | assert(add->inp->ty == (TyBase*) &Ty_S); 755 | TyVar* addV = tyVar(TyDict_find(Adder, &(Key){ 756 | .name = SLC("add"), .tyI = &TyIs_RoleField })); 757 | assert(isFnSig(addV->tyI->ty)); 758 | 759 | COMPILE_EXEC( 760 | // "unty fn add [ self:&Self, b:S -> S ] do;\n" 761 | "struct A [\n" 762 | " a: S\n" 763 | " meth add [ self:&Self, b:S -> S ] do (\n" 764 | " self.a + b\n" 765 | " )\n" 766 | "]"); 767 | TyDict* A = tyDict(Kern_findTy(k, &KEY("A"))); 768 | TyFn* A_add = tyFn(TyDict_find(A, &KEY("add"))); 769 | 770 | COMPILE_EXEC("global myA: A = A(5)\n tAssertEq(8, myA.add(3))\n") 771 | COMPILE_EXEC("impl A:Adder { add = &A.add }"); 772 | 773 | // Test that it's actually implemented 774 | TyVar* A_Adder = tyVar(TyDict_find(A, &(Key) { 775 | .name = SLC("Adder"), .tyI = &(TyI) { .ty = (TyBase*)Adder, } })); 776 | assert(A_Adder); TASSERT_EQ((S)A_add, *(S*)A_Adder->v); 777 | 778 | COMPILE_EXEC( 779 | "global a:A = A 3\n" 780 | "global ad:Adder = Adder &a\n"); 781 | COMPILE_EXEC("ad.add(5)"); TASSERT_WS(8); 782 | 783 | COMPILE_EXEC( 784 | "fn useAdder[a: Adder -> S] do (\n" 785 | " a.add(2)\n" 786 | ")"); 787 | COMPILE_EXEC("useAdder ad"); TASSERT_WS(5); 788 | 789 | COMPILE_EXEC( 790 | "fn createUseAdder[a: A -> S] do (\n" 791 | " useAdder(Adder(&a))" 792 | ")"); 793 | COMPILE_EXEC("createUseAdder(A 3)"); TASSERT_WS(5); 794 | 795 | COMPILE_EXEC( 796 | "fn useAdderRef[aR:&Adder -> S] do (\n" 797 | " var a:Adder = @aR\n" 798 | " a.add(5)\n" 799 | ")"); 800 | COMPILE_EXEC("useAdderRef(&ad)"); TASSERT_WS(8); 801 | REPL_END 802 | END_TEST_FNGI 803 | 804 | TEST_FNGI(misc, 20) 805 | Kern_fns(k); REPL_START 806 | COMPILE_EXEC("imm#tAssertEq(5, 1 + 4)"); 807 | COMPILE_EXEC("fn useLit[a: S -> S] do ( a + lit#(4 + 12) )"); 808 | COMPILE_EXEC("tAssertEq(0x15, useLit(5))"); 809 | REPL_END 810 | END_TEST_FNGI 811 | 812 | TEST_FNGI(file_basic, 20) 813 | Kern_fns(k); 814 | N_assertWsEmpty(k); 815 | CStr_ntVar(path, "\x0E", "tests/basic.fn"); 816 | compilePath(k, path); 817 | END_TEST_FNGI 818 | 819 | #define TYDICT_SZ(MOD, NAME) \ 820 | TyDict_sz(tyDict(TyDict_find(MOD, &KEY(NAME)))) 821 | 822 | TEST_FNGI(core, 20) 823 | Kern_fns(k); Core_mod(k); 824 | REPL_START 825 | COMPILE_EXEC( 826 | "fn useSlc[ -> U1] do (\n" 827 | " var dat:Arr[12 U1]\n" 828 | " dat = ch:h \n" 829 | " @ptrAdd(&dat, 1, 12) = ch:i \n" 830 | " @ptrAdd(&dat, 2, 12) = ch:p \n" 831 | " var s: Slc = Slc(&dat, U2(12))\n" 832 | " tAssertEq(ch:h, s.@0)\n" 833 | " tAssertEq(ch:i, s.@1)\n" 834 | " s.@2\n" 835 | ")"); 836 | 837 | COMPILE_EXEC("useSlc;"); TASSERT_WS('p'); 838 | 839 | COMPILE_EXEC( 840 | "fn getSlc[ -> Slc] do (\n" 841 | " |hello \\| slc|\n" 842 | ")"); 843 | COMPILE_EXEC("getSlc;"); WS_POP2(S dat, U2 len); 844 | Slc result = (Slc) {(U1*)dat, len}; 845 | TASSERT_SLC_EQ("hello | slc", result); 846 | 847 | TyDict* comp = tyDict(Kern_findTy(k, &KEY("comp"))); 848 | TASSERT_EQ(sizeof(TyBase) - 2, TYDICT_SZ(comp, "TyBase")); 849 | TASSERT_EQ(sizeof(Ty), TYDICT_SZ(comp, "Ty")); 850 | TASSERT_EQ(sizeof(TyDict) - 2, TYDICT_SZ(comp, "TyDict")); 851 | TASSERT_EQ(sizeof(Globals), TYDICT_SZ(comp, "Globals")); 852 | 853 | TASSERT_EQ(1, Slc_cmp(SLC("world"), SLC("aliens"))); 854 | 855 | CStr_ntVar(testDat, "\x11", "tests/test_dat.fn"); compilePath(k, testDat); 856 | 857 | COMPILE_EXEC( 858 | "struct A [ a1:S a2:U2 a3:U1 ]\n" 859 | "global a1:A = A(0xFFFF, 42, 3);\n" 860 | "global a2:A; &a2 @= &a1\n" 861 | "tAssertEq(0xFFFF, a2.a1)\n" 862 | "tAssertEq(42, a2.a2)\n" 863 | "tAssertEq(3, a2.a3)\n"); 864 | 865 | COMPILE_EXEC( 866 | "global a3:A @= &a2;\n" 867 | "tAssertEq(0xFFFF, a3.a1)\n" 868 | "tAssertEq(3, a3.a3)\n"); 869 | 870 | TyVar* g = tyVar(TyDict_find(comp, &KEY("g"))); 871 | TASSERT_EQ(&k->g, *(void**)g->v); 872 | 873 | COMPILE_EXEC("fn getGlobals[ -> &comp.Globals] do ( comp.g )"); 874 | COMPILE_EXEC("getGlobals;"); 875 | TASSERT_WS((S)&k->g); 876 | 877 | COMPILE_EXEC("fn getLog[ -> Logger] do ( comp.g.log )"); 878 | COMPILE_EXEC("getLog;"); 879 | TASSERT_WS((S)k->g.log.m); TASSERT_WS((S)k->g.log.d); 880 | 881 | COMPILE_EXEC("global l:Logger = comp.g.log"); 882 | TyVar* l = tyVar(Kern_findTy(k, &KEY("l"))); 883 | void* dLocal = *(void**)(l->v); 884 | void* mLocal = *(void**)(l->v + RSIZE); 885 | TASSERT_EQ(&mSpLogger_File, *(void**)(l->v + RSIZE)); 886 | TASSERT_EQ(k->g.log.d, dLocal); 887 | TASSERT_EQ(k->g.log.m, mLocal); 888 | TASSERT_EQ(civ.log.d, k->g.log.d); 889 | 890 | COMPILE_EXEC( 891 | "fn doLog[ ] do (\n" 892 | " tAssert(l.start INFO) l.add|testing compiled log| l.end;\n" 893 | ") doLog;"); 894 | COMPILE_EXEC("tAssert(l.start INFO) l.add|testing global log| l.end;"); 895 | COMPILE_EXEC("imm#(tAssert(l.start INFO) l.add|testing global imm log| l.end;)"); 896 | REPL_END 897 | END_TEST_FNGI 898 | 899 | #define USE_ARENA_FNGI \ 900 | " var a: &Any; a = arena.alloc(10, 4); tAssert(S a)\n" \ 901 | " var b: &Any; b = arena.alloc(12, 4); tAssert(S b)\n" \ 902 | " tAssert(not S arena.free(b, 12, 4))\n" \ 903 | " tAssert(not S arena.free(a, 10, 4))\n" \ 904 | " (a, b)\n" 905 | 906 | TEST_FNGI(bba, 20) 907 | Kern_fns(k); Core_mod(k); 908 | REPL_START 909 | 910 | COMPILE_EXEC("fn useBBA[ arena:&BBA -> &Any &Any] do (\n" USE_ARENA_FNGI ")"); 911 | COMPILE_EXEC("comp.g.bbaDict"); TASSERT_WS((S)k->g.bbaDict); 912 | COMPILE_EXEC("useBBA(comp.g.bbaDict)"); 913 | void* a = BBA_alloc(k->g.bbaDict, 10, 4); 914 | void* b = BBA_alloc(k->g.bbaDict, 12, 4); 915 | assert(not BBA_free(k->g.bbaDict, b, 12, 4)); 916 | assert(not BBA_free(k->g.bbaDict, a, 10, 4)); 917 | TASSERT_WS((S)b); TASSERT_WS((S)a); 918 | 919 | Arena arena = BBA_asArena(k->g.bbaDict); 920 | 921 | COMPILE_EXEC("fn useArena[ arena:Arena -> &Any &Any] do (\n" USE_ARENA_FNGI ")"); 922 | COMPILE_EXEC("useArena(Arena comp.g.bbaDict)"); 923 | a = Xr(arena,alloc, 10, 4); 924 | b = Xr(arena,alloc, 12, 4); 925 | assert(not Xr(arena,free, b, 12, 4)); 926 | assert(not Xr(arena,free, a, 10, 4)); 927 | TASSERT_WS((S)b); TASSERT_WS((S)a); 928 | REPL_END 929 | END_TEST_FNGI 930 | 931 | TEST_FNGI(learn, 26) 932 | Kern_fns(k); Core_mod(k); 933 | CStr_ntVar(learn, "\x0D", "learn_in_y.fn"); compilePath(k, learn); 934 | END_TEST_FNGI 935 | 936 | TEST_FNGI(repl, 20) 937 | Kern_fns(k); 938 | simpleRepl(k); 939 | END_TEST_FNGI 940 | 941 | int main(int argc, char* argv[]) { 942 | ARGV = argv; 943 | SETUP_SIG((void *)fngiHandleSig); 944 | 945 | char* arg; bool repl = false; 946 | for(int i = 1; i < argc; arg = argv[i++]) { 947 | if(strcmp("--repl", arg)) repl = true; 948 | else { 949 | eprintf("Unrecognized argument: %s\n", arg); 950 | } 951 | } 952 | 953 | eprintf("# Running tests\n"); 954 | test_basic(); 955 | test_init(); 956 | test_call(); 957 | test_scan(); 958 | test_compile0(); 959 | test_compile1(); 960 | test_inlineFns(); 961 | test_comment(); 962 | test_tyDb(); 963 | test_compileTy(); 964 | test_compileIf(); 965 | test_compileBlk(); 966 | test_compileVar(); 967 | test_dotPath(); 968 | test_compileStruct(); 969 | test_fnSig(); 970 | test_alias(); 971 | test_structBrackets(); 972 | test_global(); 973 | test_mod(); 974 | test_structDeep(); 975 | test_structSuper(); 976 | test_method(); 977 | test_ptr(); 978 | test_arr(); 979 | test_role(); 980 | test_misc(); 981 | test_file_basic(); 982 | test_core(); 983 | test_bba(); 984 | test_learn(); 985 | eprintf("# Tests complete\n"); 986 | 987 | if(repl) test_repl(); 988 | 989 | return 0; 990 | } 991 | -------------------------------------------------------------------------------- /tests/test_dat.fn: -------------------------------------------------------------------------------- 1 | mod testDat; fileUse testDat; fileImpl testDat; 2 | 3 | global world: Slc = |hello world!| 4 | global aliens: Slc = |hello aliens!| 5 | 6 | imm#tAssertEq(12, world.len) 7 | imm#tAssertEq(0, S world.cmp(world)) 8 | imm#tAssertEq(0, S aliens.cmp(aliens)) 9 | imm#tAssertEq(1, S world.cmp(aliens)) 10 | \ imm#tAssertEq(1, S aliens.cmp(world)) 11 | imm#tAssertEq(ch:h, world.@0) 12 | imm#tAssertEq(ch:e, world.@1) 13 | --------------------------------------------------------------------------------