diff --git a/src/README.md b/src/README.md new file mode 100644 index 00000000..f6fc2a91 --- /dev/null +++ b/src/README.md @@ -0,0 +1,220 @@ +# Overview of the CBQN source + +## Conventions + +Functions starting with `m_` allocate a new object. +Functions starting with `q_` are queries/predicates, and return a boolean. +Functions ending with `R` are either supposed to be called rarely, or the caller expects that a part of it happens rarely. +Functions ending with `U` return (or take) a non-owned object (`U` = "unincremented"). +Variables starting with `bi_` are builtins (primitives or special values). +Which arguments are consumed usually is described in a comment after the function or its prototype. Otherwise, check the source. + +``` +src/ + builtins/ + sfns.c structural functions + fns.c other functions + arithd.c dyadic arithmetic functions + arithm.c monadic arithmetic functions (incl •math stuff) + cmp.c dyadic comparison functions + sort.c sort/grade/bins + md1.c 1-modifiers + md2.c 2-modifiers + sysfn.c •-definitions + internal.c •internal + opt/ files which aren't needed for every build configuration + gen/ generated files + core/ things included everywhere + utils/ utilities included as needed +``` + +## Base + +`B` represents any BQN object. Some are heap-allocated, some are not. +```C +Type checks (all are safe to execute on any B instance): + test tag instance description + isVal(x) [many] any heap-allocated object + isFun(x) FUN_TAG a function (guaranteed heap-allocated) + isMd1(x) MD1_TAG a 1-modifier (guaranteed heap-allocated) + isMd2(x) MD2_TAG a 2-modifier (guaranteed heap-allocated) + isMd (x) [many] any modifier (guaranteed heap-allocated) + isCallable(x) [many] isFun|isMd (guaranteed heap-allocated) + isArr(x) ARR_TAG an array type (guaranteed heap-allocated) + isAtm(x) [many] !isArr(x) can be either heap-allocated or tagged + isChr(x) C32_TAG a character (never heap-allocated) + isF64(x) F64_TAG a number (never heap-allocated) + isNsp(x) NSP_TAG a namespace (guaranteed heap-allocated) + isObj(x) OBJ_TAG internal (guaranteed heap allocated) + and then there are some extra types for variables for the VM & whatever; see h.h *_TAG definitions + +Extra functions for converting types: + m_f64(x) // f64 → B + m_c32(x) // codepoint → B + m_i32(x) // i32 → B + m_usz(x) // usz → B + o2i(x) // B → i32, throw if impossible + o2i64(x) // B → i64, throw if impossible + o2u64(x) // B → u64, throw if impossible + o2s(x) // B → usz, throw if impossible + o2c(x) // B → c32, throw if impossible + o2f(x) // B → f64, throw if impossible + o2iu(x) // B → i32, assumes is valid (u ≡ unchecked) + o2i64u(x) // B → i64, assumes is valid + o2su(x) // B → usz, assumes is valid + o2cu(x) // B → c32, assumes is valid + o2fu(x) // B → f64, assumes is valid + o2b(x) // B → bool, throw if impossible + q_i32(x) // query if x is convertible to i32 + q_i64(x) // query if x is convertible to i64 + q_f64(x) // query if x is convertible to f64 (equivalent to isF64) + q_N(x) // query if x is · (≡ bi_N) + noFill(x) // if x represents undefined fill (returned by getFill*; ≡ bi_noFill) + tag(x,*_TAG) // pointer → B + +See src/h.h for more basic functions +``` + +An object can be allocated with `mm_alloc(sizeInBytes, t_something)`. The returned object is a `Value*`, but should be used as some subtype. `mm_free` can be used to force-free an object regardless of its reference count. + +A heap-allocated object can be cast to a `Value*` with `v(x)`, to an `Arr*` with `a(x)`, or to any pointer type with `c(Type,x)`. `v(x)->type` stores the type of an object (see `enum Type` in `src/h.h`), which is used by runtime functions to decide how to interpret an object. + +Reference count of any `B` object can be incremented/decremented with `inc(x)`/`dec(x)` (`inc(x)` also returns `x`, so you can use it inline in the consumer). A pointer type has `ptr_inc(x)`/`ptr_dec(x)`. `dec`/`ptr_dec` will return the object to the memory manager if the refcount goes to zero. + +Since reference counting is hard, there's `make heapverify` that verifies that code executed does it right (and screams unreadable messages when it doesn't). After any changes, I'd suggest running: +```bash +#!/usr/bin/env bash +make rtverify && echo 'rtverify:' && ./BQN -M 1000 path/to/mlochbaum/BQN/test/this.bqn +make heapverify && echo 'heapverify:' && ./BQN -M 1000 path/to/mlochbaum/BQN/test/this.bqn -noerr bytecode header identity literal namespace prim simple syntax token under undo +``` + +Temporary allocations can be made with `utils/talloc.h`: +```C +#include "utils/talloc.h" +TALLOC(char, buf, 123); // allocate char* buf with 123 elements +// buf is now a regular char* and can be stored/passed around as needed +TREALLOC(buf, 456); // extend buf +TFREE(buf); // free buf +// if the size is guaranteed to be small enough, using VLAs is fine i guess + +TSALLOC(i32, stack, 10); // allocate an i32 stack with initially reserved 10 items (initial reserve must be positive!) +TSADD(stack, 15); // add a single item +TSADD(stack, (i32*){1,2,3}, 3); // add many items +usz sz = TSSIZE(stack); // get the current height of the stack +i32 item = stack[1]; // get the 2nd item +TSFREE(stack); // free the stack +// note that TSALLOC creates multiple local variables, and as such cannot be passed around to other functions +``` + +## Virtual functions + +All virtual method accesses require that the argument is heap-allocated. + +You can get a virtual function of a `B` instance with `TI(x, something)`. There's also `TIv(x, something)` for a pointer `x` instead. See `#define FOR_TI` in `src/h.h` for available functions. + +Call a BQN function with `c1(f, x)` or `c2(f, w, x)`. A specific builtin can be called by looking up the appropriate name in `src/utils/builtins.h` (and adding the `bi_` prefix). + +Calling a modifier involves deriving it with `m1_d`/`m2_d`, using a regular `c1`/`c2`, and managing the refcounts of everything while at that. + +## Arrays + +If you know that `x` is an array (e.g. by testing `isArr(x)` beforehand), `a(x)->ia` will give you the product of the shape, `rnk(x)` will give you the rank, and `a(x)->sh` will give you a `usz*` to the full shape. + +Allocating an array: +```C +i32* rp; B r = m_i32arrv(&rp, 123); // allocate a 123-element i32 vector +i32* rp; B r = m_i32arrc(&rp, x); // allocate an array with the same shape as x (x must be an array; x isn't consumed) + +i32* rp; Arr* r = m_i32arrp(&rp, 123); // allocate a 123-element i32-array without allocating shape +// then do one of these: +arr_shVec(r); // set shape of r to a vector +usz* sh = arr_shAlloc(r, 4); // allocate a rank 4 shape; write to sh the individual items; sh will be NULL for ranks 0 and 1 +arr_shCopy(r, x); // copy the shape object of x (doesn't consume x) +B result = taga(r); +// see stuff.h for m_shArr/arr_shSetI/arr_shSetU for ways to batch-assign a single shape object to multiple objects + +u32* rp; B r = m_c32arrv(%rp, 10); // 10-char string +// etc for m_(c32|f64|i32)arr[vcp] + +usz ri=0; HArr_p r = m_harrs(123, &ri); // allocate 123-item arbitrary type array +// write items with r.a[ri++] or equivalent. Whenever GC could happen, ri must point to after the end of the currently set items +// then do one of these to finish the array: +B result = harr_fv(r); // sets shape to a vector +B result = harr_fc(r, x); // copies the shape of x, doesn't consume x +B result = harr_fcd(r, x); // copies the shape of x and consumes it +usz* sh = harr_fa(r, 4); // allocate shape for a rank 4 array. To get the result `B` object, just do r.b +// If at any point you want to free the object before finishing it, use harr_abandon(r) + +// If you're sure GC cannot happen before all items in the array are set, you can use: (U means "unsafe") +HArr_p r = m_harrUv(10); // 10-item vector +HArr_p r = m_harrUc(10, x); // 10-item array with the same shape as x +HArr_p r = m_harrUp(10); // 10-item array without any set shape. Use the arr_shWhatever(r.c, …) + +// you can use withFill to add a fill element to a created array (or manually create a fillarr, see src/core/fillarr.h) + +B r = m_str32(U"⟨1⋄2⋄3⟩"); // a constant string with unicode chars +B r = m_str8(5, "hello"); // an ASCII string with specified length +B r = m_str8l("hello"); // an ASCII string, read until null byte +#include "utils/utf.h" +B r = fromUTF8l("⟨1⋄2⋄3⟩") // parse UTF-8 from a char* +B r = fromUTF8("⟨1⋄2⋄3⟩", 17) // ↑ but with a specified length +u64 sz = utf8lenB(x); TALLOC(char, buf, sz+1); toUTF8(x, buf); buf[sz]=0; /*use buf as a C-string*/ TFREE(buf); + +// src/utils/mut.h provides a way to build an array by copying parts of other arrays + +// some functions for specific cases: +B r = m_unit(x); // equivalent to <𝕩 +B r = m_hunit(x); // like the above, except no fill is set +B r = m_atomUnit(x); // if x is likely to be an atom, this is a better alternative to m_unit +B r = m_v1(a); // ⟨a⟩ +B r = m_v2(a,b); // ⟨a,b⟩ +B r = m_v3(a,b,c); // ⟨a,b,c⟩ +B r = m_v4(a,b,c,d); // ⟨a,b,c,d⟩ +B r = emptyHVec(); // an empty vector with no fill +B r = emptyIVec(); // an empty integer vector +B r = emptyCVec(); // an empty character vector +B r = emptySVec(); // an empty string vector +// rank can be manually set for an exsiting object with sprnk or srnk, but be careful to keep the shape object in sync! +``` + + +Retrieving data from arrays: +```C +// generic methods: +BS2B xget = TI(x,get); // and then (presumably in a loop) call xget(x,i) to get the i-th element of the ravel. This gives owned objects +BS2B xgetU = TI(x,getU); // returns non-owned objects instead, useful if you're only gonna retrieve atoms or won't keep them around (e.g. only use in `equals` or `hash` or sth) +// for specific array types: +if (TI(x,elType)==el_i32) i32* xp = i32any_ptr(x); // for either t_i32arr or t_i32slice; for t_i32arr only, there's i32arr_ptr(x) +if (TI(x,elType)==el_c32) u32* xp = c32any_ptr(x); // ↑ +if (TI(x,elType)==el_f64) f64* xp = f64any_ptr(x); // ↑ +if (v(x)->type==t_harr) B* xp = harr_ptr(x); +if (v(x)->type==t_harr || v(x)->type==t_hslice) B* xp = hany_ptr(x); // note that elType==el_B doesn't imply hany_ptr is safe! +if (v(x)->type==t_fillarr) B* xp = fillarr_ptr(x); +``` + +## Errors + +Throw an error with `thrM("some message")` or `thr(some B instance)` or `thrOOM()`. What to do with active references at the time of the error is TBD when GC is actually completed, but you can just not worry about that for now. + +A fancier message can be created with `thrF(message, …)` with printf-like (but different!!) varargs (source in `do_fmt`): +``` +%i decimal i32 +%l decimal i64 +%ui decimal u32 +%ul decimal u64 +%xi hex u32 +%xl hex u64 +%s decimal usz +%f f64 +%p pointer +%c char +%S char* C-string consisting of ASCII +%U char* of UTF-8 data +%R a B instance of a string or number +%H the shape of a B instance +%% "%" +``` +See `#define CATCH` in `src/h.h` for how to catch errors. + +Use `assert(predicate)` for checks (for optimized builds they're replaced with `if (!predicate) invoke_undefined_behavior();` so it's still invoked!!). +There's also `err("message")` that (at least currently) is kept in optimized builds as-is, and always kills the process on being called. \ No newline at end of file diff --git a/src/builtins/md1.c b/src/builtins/md1.c index e53c40c5..1a425635 100644 --- a/src/builtins/md1.c +++ b/src/builtins/md1.c @@ -248,7 +248,7 @@ B fold_c1(B d, B x) { B f = c(Md1D,d)->f; dec(x); if (isFun(f)) { B r = TI(f,identity)(f); - if (!isNothing(r)) return inc(r); + if (!q_N(r)) return inc(r); } thrM("´: No identity found"); } diff --git a/src/builtins/sysfn.c b/src/builtins/sysfn.c index 706990e1..e1b63008 100644 --- a/src/builtins/sysfn.c +++ b/src/builtins/sysfn.c @@ -596,7 +596,7 @@ B sys_c1(B t, B x) { else if (eqStr(c, U"flines")) r.a[i] = m_nfn(fLinesDesc, path_dir(inc(comp_currPath))); else if (eqStr(c, U"import")) r.a[i] = m_nfn(importDesc, path_dir(inc(comp_currPath))); else if (eqStr(c, U"args")) { - if(isNothing(comp_currArgs)) thrM("No arguments present for •args"); + if(q_N(comp_currArgs)) thrM("No arguments present for •args"); r.a[i] = inc(comp_currArgs); } else { dec(x); thrF("Unknown system function •%R", c); } } diff --git a/src/core/stuff.h b/src/core/stuff.h index 4967e454..2d572f6d 100644 --- a/src/core/stuff.h +++ b/src/core/stuff.h @@ -1,10 +1,5 @@ // memory defs -static void* mm_alloc(usz sz, u8 type); -static void mm_free(Value* x); -static u64 mm_size(Value* x); -static void mm_visit(B x); -static void mm_visitP(void* x); NORETURN void bqn_exit(i32 code); u64 mm_heapUsed(void); void printAllocStats(void); diff --git a/src/h.h b/src/h.h index 3096e425..e61e8be2 100644 --- a/src/h.h +++ b/src/h.h @@ -1,6 +1,5 @@ #pragma once -// #define ATOM_I32 #ifdef DEBUG // #define DEBUG_VM #endif @@ -53,6 +52,7 @@ // #define RT_VERIFY // compare native and runtime versions of primitives // #define NO_RT // whether to completely disable self-hosted runtime loading // #define PRECOMP // execute just precompiled code at src/gen/interp +// #define ATOM_I32 // not a thing; ignore pls #ifdef __OpenBSD__ @@ -107,6 +107,7 @@ #define AUTO __auto_type #define LIKELY(X) __builtin_expect(X,1) #define RARE(X) __builtin_expect(X,0) +#define fsizeof(T,F,E,N) (offsetof(T, F) + sizeof(E)*(N)) // type, flexible array member name, flexible array member type, item amount #define RFLD(X,T,F) ((T*)((char*)(X) - offsetof(T,F))) // value, result type, field name; reverse-read field: `T* x = …; E v = x->f; x == RFLD(v, T, f)` #define N64x "%"SCNx64 #define N64d "%"SCNd64 @@ -123,7 +124,6 @@ typedef u8 ur; CTR_FOR(F) #undef F -#define fsizeof(T,F,E,N) (offsetof(T, F) + sizeof(E)*(N)) // type, flexible array member name, flexible array member type, item amount // .FF0 .111111111110000000000000000000000000000000000000000000000000000 infinity // .FF8 .111111111111000000000000000000000000000000000000000000000000000 qNaN // .FF. .111111111110nnn................................................ sNaN aka tagged aka not f64, if nnn≠0 @@ -148,7 +148,6 @@ void cbqn_init(void); typedef union B { u64 u; - i64 s; f64 f; } B; #define b(x) ((B)(x)) @@ -234,6 +233,11 @@ extern B bi_emptyHVec, bi_emptyIVec, bi_emptyCVec, bi_emptySVec; #define emptyIVec() ({ B t = bi_emptyIVec; ptr_inc(v(t)); t; }) #define emptyCVec() ({ B t = bi_emptyCVec; ptr_inc(v(t)); t; }) #define emptySVec() ({ B t = bi_emptySVec; ptr_inc(v(t)); t; }) +static void* mm_alloc(usz sz, u8 type); +static void mm_free(Value* x); +static u64 mm_size(Value* x); +static void mm_visit(B x); +static void mm_visitP(void* x); static void dec(B x); static B inc(B x); static void ptr_dec(void* x); @@ -263,7 +267,7 @@ NOINLINE NORETURN void thrF(char* s, ...); NOINLINE NORETURN void thrOOM(void); jmp_buf* prepareCatch(void); #if CATCH_ERRORS -#define CATCH setjmp(*prepareCatch()) // use as `if (CATCH) { /*handle error*/ dec(catchMessage); } /*potentially erroring thing*/ popCatch();` +#define CATCH setjmp(*prepareCatch()) // use as `if (CATCH) { /*handle error*/ dec(catchMessage); return; } /*potentially erroring thing*/ popCatch(); /*no errors yay*/` #else // note: popCatch() must always be called if no error was caught, so no returns before it! #define CATCH false #endif @@ -370,7 +374,7 @@ typedef B (*BBBBBB2B)(B, B, B, B, B, B); F(BS2B, getU) /* like get, but doesn't increment result (mostly equivalent to `B t=get(…); dec(t); t`) */ \ F(BB2B, m1_d) /* consume all args; (m, f) */ \ F(BBB2B, m2_d) /* consume all args; (m, f, g) */ \ - F(BSS2A, slice) /* consumes; create slice from given starting position and length; add shape & rank yourself; may not actually be a Slice object; preserves fill */ \ + F(BSS2A, slice) /* consumes; create slice from a starting position and length; add shape & rank yourself; may not actually be a Slice object; preserves fill */ \ F(B2B, identity) /* return identity element of this function; doesn't consume */ \ \ F( BBB2B, fn_uc1) /* t,o, x→r; r≡O⌾( T ) x; consumes x */ \ @@ -398,8 +402,7 @@ typedef B (*BBBBBB2B)(B, B, B, B, B, B); #define TI(X,V) (ti_##V[v(X)->type]) -static bool isNothing(B b) { return b.u==bi_N.u; } -static void mm_free(Value* x); +static bool q_N(B b) { return b.u==bi_N.u; } // refcount static bool reusable(B x) { return v(x)->refc==1; } @@ -427,7 +430,7 @@ static B inc(B x) { if (isVal(VALIDATE(x))) v(x)->refc++; return x; } -static B incBy(B x, i64 am) { +static B incBy(B x, i64 am) { // am mustn't be negative! if (isVal(VALIDATE(x))) v(x)->refc+= am; return x; } diff --git a/src/jit/nvm_x86_64.c b/src/jit/nvm_x86_64.c index 5e3b7284..e534f0ee 100644 --- a/src/jit/nvm_x86_64.c +++ b/src/jit/nvm_x86_64.c @@ -53,7 +53,7 @@ INS B i_FN1C(B f, B x, u32* bc) { POS_UPD; // TODO figure out a way to instead p dec(f); return r; } INS B i_FN1O(B f, B x, u32* bc) { POS_UPD; - B r = isNothing(x)? x : c1(f, x); + B r = q_N(x)? x : c1(f, x); dec(f); return r; } INS B i_FN2C(B w, B f, B x, u32* bc) { POS_UPD; @@ -62,18 +62,18 @@ INS B i_FN2C(B w, B f, B x, u32* bc) { POS_UPD; } INS B i_FN2O(B w, B f, B x, u32* bc) { POS_UPD; B r; - if (isNothing(x)) { dec(w); r = x; } - else r = isNothing(w)? c1(f, x) : c2(f, w, x); + if (q_N(x)) { dec(w); r = x; } + else r = q_N(w)? c1(f, x) : c2(f, w, x); dec(f); return r; } INS B i_FN1Oi(B x, BB2B fm, u32* bc) { POS_UPD; - B r = isNothing(x)? x : fm(b((u64)0), x); + B r = q_N(x)? x : fm(b((u64)0), x); return r; } INS B i_FN2Oi(B w, B x, BB2B fm, BBB2B fd, u32* bc) { POS_UPD; - if (isNothing(x)) { dec(w); return x; } - else return isNothing(w)? fm(b((u64)0), x) : fd(b((u64)0), w, x); + if (q_N(x)) { dec(w); return x; } + else return q_N(w)? fm(b((u64)0), x) : fd(b((u64)0), w, x); } INS B i_ARR_0() { // TODO combine with ADDI return emptyHVec(); @@ -95,7 +95,7 @@ INS B i_OP2D(B f,B m, B g, u32* bc) { POS_UPD; return m2_d (m,f,g); } INS B i_OP2H(B m, B g ) { return m2_h (m, g); } INS B i_TR2D(B g, B h ) { return m_atop( g,h); } INS B i_TR3D(B f,B g, B h ) { return m_fork(f,g,h); } -INS B i_TR3O(B f,B g, B h ) { return isNothing(f)? m_atop(g,h) : m_fork(f,g,h); } +INS B i_TR3O(B f,B g, B h ) { return q_N(f)? m_atop(g,h) : m_fork(f,g,h); } INS B i_NOVAR(u32* bc, B* cStack) { POS_UPD; GS_UPD; thrM("Reading variable before its defined"); } @@ -143,7 +143,7 @@ INS B i_NSPM(B o, u32 l) { return tag(a,OBJ_TAG); } INS B i_CHKV(B x, u32* bc, B* cStack) { - if(isNothing(x)) { POS_UPD; GS_UPD; thrM("Unexpected Nothing (·)"); } + if(q_N(x)) { POS_UPD; GS_UPD; thrM("Unexpected Nothing (·)"); } return x; } INS B i_FAIL(u32* bc, B* cStack) { @@ -268,7 +268,7 @@ static OptRes opt(u32* bc0) { } case TR3D: case TR3O: { S(f,0) S(g,1) S(h,2) if (f.p==-1 | g.p==-1 | h.p==-1) goto defIns; - if (isNothing(f.v)) thrM("JIT optimization: didn't expect constant ·"); + if (q_N(f.v)) thrM("JIT optimization: didn't expect constant ·"); B d = m_fork(inc(f.v), inc(g.v), inc(h.v)); cact = 5; RM(f.p); RM(g.p); RM(h.p); TSADD(data, d.u); @@ -563,7 +563,7 @@ Nvm_res m_nvm(Body* body) { } break; case LOCU: TOPs; { u64 d=*bc++; u64 p=*bc++; LSC(R_A1,d); MOV8rmo(R_RES,R_A1,p*8+offsetof(Scope,vars)); // read variable - IMM(R_A2, bi_optOut.u); MOV8mro(R_A1, R_A2,p*8+offsetof(Scope,vars)); // set to + IMM(R_A2, bi_optOut.u); MOV8mro(R_A1, R_A2,p*8+offsetof(Scope,vars)); // set to bi_optOut } break; case EXTO: TOPs; { u64 d=*bc++; IMM(R_A0,*bc++); LSC(R_A1,d); IMM(R_A2,off); INV(3,1,i_EXTO); } break; // (u32 p, Scope* sc, u32* bc, S) // case LOCU: TOPs; { u64 d=*bc++; IMM(R_A0,*bc++); LSC(R_A1,d); CCALL(i_LOCU); } break; // (u32 p, Scope* sc) diff --git a/src/jit/x86_64.h b/src/jit/x86_64.h index 8673f4f1..946a47fb 100644 --- a/src/jit/x86_64.h +++ b/src/jit/x86_64.h @@ -23,7 +23,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. // 0 rax V result // 1 rcx V arg 3 // 2 rdx V arg 2 -// 3 rbx +// 3 rbx // 4 rsp stack // 5 rbp base // 6 rsi V arg 1 diff --git a/src/load.c b/src/load.c index 2b34c9df..63f64730 100644 --- a/src/load.c +++ b/src/load.c @@ -281,7 +281,7 @@ void load_init() { // very last init function #else B r = rtComplete[i]? inc(fruntime[i]) : rtObjGet(rtObjRaw, i); #endif - if (isNothing(r)) { printf("· in runtime!\n"); exit(1); } + if (q_N(r)) { printf("· in runtime!\n"); exit(1); } if (isVal(r)) v(r)->flags|= i+1; #ifdef RT_WRAP r = rtWrap_wrap(r); diff --git a/src/utils/file.c b/src/utils/file.c index 9185357a..b85869b3 100644 --- a/src/utils/file.c +++ b/src/utils/file.c @@ -71,14 +71,14 @@ B file_lines(B path) { // consumes B path_resolve(B base, B rel) { // consumes rel; assumes base is a char vector or bi_N - assert((isArr(base) || isNothing(base)) && isArr(rel)); + assert((isArr(base) || q_N(base)) && isArr(rel)); BS2B rgetU = TI(rel,getU); usz ria = a(rel)->ia; if (rnk(rel)!=1) thrM("Paths must be character vectors"); for (usz i = 0; i < ria; i++) if (!isC32(rgetU(rel, i))) thrM("Paths must be character vectors"); if (ria==0) { dec(rel); return inc(base); } if (o2cu(rgetU(rel, 0))=='/') return rel; - if (isNothing(base)) thrM("Using relative path with no absolute base path known"); + if (q_N(base)) thrM("Using relative path with no absolute base path known"); BS2B bgetU = TI(base,getU); usz bia = a(base)->ia; bool has = bia && o2cu(bgetU(base, bia-1))=='/'; @@ -92,8 +92,8 @@ B path_resolve(B base, B rel) { // consumes rel; assumes base is a char vector o } B path_dir(B path) { // consumes; returns directory part of file path with trailing slash, or · - assert(isArr(path) || isNothing(path)); - if (isNothing(path)) return path; + assert(isArr(path) || q_N(path)); + if (q_N(path)) return path; BS2B pgetU = TI(path,getU); usz pia = a(path)->ia; if (pia==0) thrM("Empty file path"); diff --git a/src/utils/mut.h b/src/utils/mut.h index 82f0f1d7..5ec76f8c 100644 --- a/src/utils/mut.h +++ b/src/utils/mut.h @@ -1,5 +1,18 @@ #pragma once +/* Usage: + +Start with MAKE_MUT(name, itemAmount); +MAKE_MUT allocates the object on the stack, so everything must happen within the scope of it. +Optionally, call mut_init(name, el_something) with an appropriate ElType +End with mut_f(v|c|cd|p); + +There must be no allocations while a mut object is being built so GC doesn't do bad things. +mut_pfree must be used to free a partially finished `mut` instance safely (e.g. before throwing an error) +methods ending with G expect that mut_init has been called with a type that can fit the elements that it'll set + +*/ + typedef struct Mut { u8 type; usz ia; diff --git a/src/vm.c b/src/vm.c index 4dc9abef..156f44be 100644 --- a/src/vm.c +++ b/src/vm.c @@ -380,13 +380,13 @@ NOINLINE Block* compile(B bcq, B objs, B allBlocks, B allBodies, B indices, B to comp->objs = toHArr(objs); comp->blockAm = 0; B nameList; - if (isNothing(tokenInfo)) { + if (q_N(tokenInfo)) { nameList = bi_emptyHVec; } else { B t = TI(tokenInfo,getU)(tokenInfo,2); nameList = TI(t,getU)(t,0); } - if (!isNothing(src) && !isNothing(indices)) { + if (!q_N(src) && !q_N(indices)) { if (isAtm(indices) || rnk(indices)!=1 || a(indices)->ia!=2) thrM("VM compiler: Bad indices"); for (i32 i = 0; i < 2; i++) { B ind = TI(indices,getU)(indices,i); @@ -546,7 +546,7 @@ B evalBC(Block* bl, Body* b, Scope* sc) { // doesn't consume } case FN1O: { P(f)P(x) GS_UPD;POS_UPD; - ADD(isNothing(x)? x : c1(f, x)); dec(f); + ADD(q_N(x)? x : c1(f, x)); dec(f); break; } case FN2C: { P(w)P(f)P(x) @@ -556,8 +556,8 @@ B evalBC(Block* bl, Body* b, Scope* sc) { // doesn't consume } case FN2O: { P(w)P(f)P(x) GS_UPD;POS_UPD; - if (isNothing(x)) { dec(w); ADD(x); } - else ADD(isNothing(w)? c1(f, x) : c2(f, w, x)); + if (q_N(x)) { dec(w); ADD(x); } + else ADD(q_N(w)? c1(f, x) : c2(f, w, x)); dec(f); break; } @@ -592,7 +592,7 @@ B evalBC(Block* bl, Body* b, Scope* sc) { // doesn't consume case TR2D: { P(g)P(h) ADD(m_atop( g,h)); break; } case TR3D: { P(f)P(g)P(h) ADD(m_fork(f,g,h)); break; } case TR3O: { P(f)P(g)P(h) - if (isNothing(f)) { ADD(m_atop(g,h)); dec(f); } + if (q_N(f)) { ADD(m_atop(g,h)); dec(f); } else ADD(m_fork(f,g,h)); break; } @@ -658,7 +658,7 @@ B evalBC(Block* bl, Body* b, Scope* sc) { // doesn't consume } case RETN: goto end; case CHKV: { - if (isNothing(PEEK(1))) { GS_UPD; POS_UPD; thrM("Unexpected Nothing (·)"); } + if (q_N(PEEK(1))) { GS_UPD; POS_UPD; thrM("Unexpected Nothing (·)"); } break; } case FAIL: thrM("No body matched"); @@ -974,7 +974,7 @@ NOINLINE B vm_fmtPoint(B src, B prepend, B path, usz cs, usz ce) { // consumes p NOINLINE void vm_printPos(Comp* comp, i32 bcPos, i64 pos) { B src = comp->src; - if (!isNothing(src) && !isNothing(comp->indices)) { + if (!q_N(src) && !q_N(comp->indices)) { B inds = TI(comp->indices,getU)(comp->indices, 0); usz cs = o2s(TI(inds,getU)(inds,bcPos)); B inde = TI(comp->indices,getU)(comp->indices, 1); usz ce = o2s(TI(inde,getU)(inde,bcPos))+1; // printf(" bcPos=%d\n", bcPos); // in case the pretty error generator is broken