initial pointer objects

This commit is contained in:
dzaima 2024-02-04 23:18:54 +02:00
parent 297302cad0
commit 7edcccdf82
4 changed files with 417 additions and 126 deletions

375
src/ffi.c
View File

@ -167,8 +167,8 @@ typedef struct BoundFn {
i32 xLen; // 0: length 0 array; else, ↑
#endif
} BoundFn;
GLOBAL NFnDesc* boundFnDesc;
GLOBAL NFnDesc* foreignFnDesc;
STATIC_GLOBAL NFnDesc* boundFnDesc;
STATIC_GLOBAL NFnDesc* foreignFnDesc;
B boundFn_c1(B t, B x) { BoundFn* c = c(BoundFn,t); return getB(((bqn_boundFn1)c->w_c1)(makeX(inc(c->obj)), makeX(x))); }
B boundFn_c2(B t, B w, B x) { BoundFn* c = c(BoundFn,t); return getB(((bqn_boundFn2)c->w_c2)(makeX(inc(c->obj)), makeX(w), makeX(x))); }
@ -234,20 +234,29 @@ DEF_FREE(ffiFn) { dec(((BoundFn*)x)->obj); }
typedef struct BQNFFIEnt {
union {
B o; // usual case
TAlloc* structData; // pointer stored in last element of cty_struct
struct { // regular BQNFFIEnt usage
union {
B o; // usual case
TAlloc* structData; // pointer stored in last element of cty_struct
};
#if FFI==2
ffi_type t;
#endif
// generic ffi_parseType ffi_parseTypeStr cty_ptr cty_repr
union { u8 extra; u8 onW; u8 canRetype; u8 mutPtr; u8 reType; };
union { u8 extra2; u8 mutates; /*mutates*/ u8 reWidth; };
union {
struct { u8 wholeArg; u8 resSingle; }; // ffi_parseType
u16 offset; // cty_struct, cty_starr
};
u16 staticOffset; // only at the top level; offset into allocation
};
struct { // second element of a pointer holder
void* ptrh_ptr; // actual pointer
ux ptrh_stride; // stride size
};
};
#if FFI==2
ffi_type t;
#endif
// generic ffi_parseType ffi_parseTypeStr cty_ptr cty_repr
union { u8 extra; u8 onW; u8 canRetype; u8 mutPtr; u8 reType; };
union { u8 extra2; u8 mutates; /*mutates*/ u8 reWidth; };
union {
struct { u8 wholeArg; u8 resSingle; }; // ffi_parseType
u16 offset; // cty_struct
};
u16 staticOffset; // only at the top level; offset into allocation
} BQNFFIEnt;
typedef struct BQNFFIType {
struct Value;
@ -328,14 +337,15 @@ static char* const sty_names[] = {
[sty_f32]="f32", [sty_f64]="f64"
};
enum CompoundTy {
cty_ptr, // *... / &...
cty_repr, // something:type
cty_struct, // {...}
cty_starr, // struct-based array
cty_tlarr, // top-level array
cty_ptr, // *... / &...; .a = {{.mutPtr = startsWith("&"), .o=eltype}}
cty_tlarr, // top-level array; {{.o=eltype}}
cty_struct, // {...}; .a = {{.o=el0}, {.o=el1}, ..., {.o=elLast}, {.structData = ffi_type**}}
cty_starr, // struct-based array; same as cty_struct
cty_repr, // something:type; {{.o=el, .t=rt, .reType=one of "icuf", .reWidth=bit width of retype}}
cty_ptrh, // data holder for pointer objects; {{.o=eltype}, {.ptrh_ptr, .ptrh_stride}}
};
static B m_bqnFFIType(BQNFFIEnt** rp, u8 ty, usz ia) {
static NOINLINE B m_bqnFFIType(BQNFFIEnt** rp, u8 ty, usz ia) {
BQNFFIType* r = mm_alloc(fsizeof(BQNFFIType, a, BQNFFIEnt, ia), t_ffiType);
r->ty = ty;
r->ia = ia;
@ -348,7 +358,7 @@ static u32 readUInt(u32** p) {
u32* c = *p;
u32 r = 0;
while (*c>='0' & *c<='9') {
if (r >= U32_MAX/10 - 10) thrM("FFI: number literal too large");
if (r >= U32_MAX/10 - 10) thrM("Type parser: number literal too large");
r = r*10 + *c-'0';
c++;
}
@ -364,19 +374,19 @@ BQNFFIEnt ffi_parseTypeStr(u32** src, bool inPtr, bool top) { // parse actual ty
u32 myWidth = 0; // used if parseRepr
switch (c0) {
default:
if (c0 == '\0') thrF("FFI: Error parsing type: Unexpected end of input");
thrF("FFI: Error parsing type: Unexpected character '%c'", c0);
if (c0 == '\0') thrF("Type parser: Unexpected end of input");
thrF("Type parser: Unexpected character '%c'", c0);
case '[': {
u32 n = readUInt(&c);
if (*c++!=']') thrM("FFI: Bad array type");
if (n==0) thrM("FFI: 0-item arrays not supported");
if (*c++!=']') thrM("Type parser: Bad array type");
if (n==0) thrM("Type parser: 0-item arrays not supported");
BQNFFIEnt e = ffi_parseTypeStr(&c, top, false);
if (top) { // largely copy-pasted from `case '*': case '&':`
myWidth = sizeof(void*);
BQNFFIEnt* rp; ro = m_bqnFFIType(&rp, cty_tlarr, 1);
rp[0] = e;
if (n>U16_MAX) thrM("FFI: Top-level array too large; limit is 65535 elements");
if (n>U16_MAX) thrM("Type parser: Top-level array too large; limit is 65535 elements");
c(BQNFFIType, ro)->arrCount = n;
mut|= rp[0].mutates;
parseRepr = rp[0].canRetype;
@ -398,8 +408,8 @@ BQNFFIEnt ffi_parseTypeStr(u32** src, bool inPtr, bool top) { // parse actual ty
rt.type = FFI_TYPE_STRUCT;
TALLOC(size_t, offsets, n);
if (ffi_get_struct_offsets(FFI_DEFAULT_ABI, &rt, offsets) != FFI_OK) thrM("FFI: Failed getting array offsets");
if (rt.size>=U16_MAX) thrM("FFI: Array too large; limit is 65534 bytes");
if (ffi_get_struct_offsets(FFI_DEFAULT_ABI, &rt, offsets) != FFI_OK) thrM("Type parser: Failed getting array offsets");
if (rt.size>=U16_MAX) thrM("Type parser: Array too large; limit is 65534 bytes");
PLAINLOOP for (usz i = 0; i < n; i++) rp[i].offset = offsets[i];
c(BQNFFIType, ro)->structSize = rt.size;
TFREE(offsets);
@ -412,7 +422,7 @@ BQNFFIEnt ffi_parseTypeStr(u32** src, bool inPtr, bool top) { // parse actual ty
if (c0=='f') {
if (n==32) rt = ffi_type_float;
else if (n==64) rt = ffi_type_double;
else thrM("FFI: Bad float width");
else thrM("Type parser: Bad float width");
ro = m_c32(n==32? sty_f32 : sty_f64);
} else {
u32 scty;
@ -420,7 +430,7 @@ BQNFFIEnt ffi_parseTypeStr(u32** src, bool inPtr, bool top) { // parse actual ty
else if (n==16) { scty = c0=='i'? sty_i16 : sty_u16; rt = c0=='i'? ffi_type_sint16 : ffi_type_uint16; }
else if (n==32) { scty = c0=='i'? sty_i32 : sty_u32; rt = c0=='i'? ffi_type_sint32 : ffi_type_uint32; }
else if (n==64) { scty = c0=='i'? sty_i64 : sty_u64; rt = c0=='i'? ffi_type_sint64 : ffi_type_uint64; }
else thrM("FFI: Bad integer width");
else thrM("Type parser: Bad integer width");
ro = m_c32(scty);
}
parseRepr = !inPtr; myWidth = sty_w[o2cG(ro)];
@ -437,7 +447,7 @@ BQNFFIEnt ffi_parseTypeStr(u32** src, bool inPtr, bool top) { // parse actual ty
case '*': case '&': {
myWidth = sizeof(void*);
if (c0=='*' && (0==*c || ':'==*c)) {
if (c0=='*' && (0==*c || ':'==*c || '}'==*c || ','==*c)) {
ro = m_c32(sty_ptr);
parseRepr = !inPtr;
canRetype = inPtr;
@ -458,10 +468,10 @@ BQNFFIEnt ffi_parseTypeStr(u32** src, bool inPtr, bool top) { // parse actual ty
TSALLOC(BQNFFIEnt, es, 4);
while (true) {
BQNFFIEnt e = TSADD(es, ffi_parseTypeStr(&c, false, false));
if (e.mutates) thrM("FFI: Structs currently cannot contain mutable references");
if (e.mutates) thrM("Type parser: Structs currently cannot contain mutable references");
u32 m = *c++;
if (m=='}') break;
if (m!=',') thrM("FFI: Improper struct separator or end");
if (m!=',') thrM("Type parser: Improper struct separator or end");
}
usz n = TSSIZE(es);
BQNFFIEnt* rp; ro = m_bqnFFIType(&rp, cty_struct, n+1);
@ -478,8 +488,8 @@ BQNFFIEnt ffi_parseTypeStr(u32** src, bool inPtr, bool top) { // parse actual ty
rt.type = FFI_TYPE_STRUCT;
TALLOC(size_t, offsets, n);
if (ffi_get_struct_offsets(FFI_DEFAULT_ABI, &rt, offsets) != FFI_OK) thrM("FFI: Failed getting struct offsets");
if (rt.size>=U16_MAX) thrM("FFI: Struct too large; limit is 65534 bytes");
if (ffi_get_struct_offsets(FFI_DEFAULT_ABI, &rt, offsets) != FFI_OK) thrM("Type parser: Failed getting struct offsets");
if (rt.size>=U16_MAX) thrM("Type parser: Struct too large; limit is 65534 bytes");
PLAINLOOP for (usz i = 0; i < n; i++) rp[i].offset = offsets[i];
c(BQNFFIType, ro)->structSize = rt.size;
TFREE(offsets);
@ -491,14 +501,14 @@ BQNFFIEnt ffi_parseTypeStr(u32** src, bool inPtr, bool top) { // parse actual ty
u8 t = *c++;
u32 n = readUInt(&c);
if (t=='i' || t=='c') {
if (n!=8 & n!=16 & n!=32) { badW: thrF("FFI: Unsupported width in :%c%i", (u32)t, n); }
if (n!=8 & n!=16 & n!=32) { badW: thrF("Type parser: Unsupported width in :%c%i", (u32)t, n); }
} else if (t=='u') {
if (n!=1) goto badW;
} else if (t=='f') {
if (n!=64) goto badW;
} else thrM("FFI: Unexpected character after \":\"");
} else thrM("Type parser: Unexpected character after \":\"");
if (isC32(ro) && n > myWidth*8) thrF("FFI: Representation wider than the value for \"%S:%c%i\"", sty_names[o2cG(ro)], (u32)t, n);
if (isC32(ro) && n > myWidth*8) thrF("Type parser: Representation wider than the value for \"%S:%c%i\"", sty_names[o2cG(ro)], (u32)t, n);
// TODO figure out what to do with i32:i32 etc
B roP = ro;
@ -508,25 +518,32 @@ BQNFFIEnt ffi_parseTypeStr(u32** src, bool inPtr, bool top) { // parse actual ty
*src = c;
return (BQNFFIEnt){.t=rt, .o=ro, .canRetype=canRetype, .extra2=mut};
}
static NOINLINE u32* parseType_pre(B arg, ux ia) { // doesn't consume; arg can be freed immediately after
TALLOC(u32, xp, ia+1);
xp[ia] = 0;
COPY_TO(xp, el_c32, 0, arg, 0, ia);
return xp;
}
static NOINLINE void parseType_post(u32* xp0, u32* xp, usz ia) {
if (xp0+ia != xp) thrM("Type parser: Garbage at end of type");
TFREE(xp0);
}
BQNFFIEnt ffi_parseType(B arg, bool forRes) { // doesn't consume; parse argument side & other global decorators
vfyStr(arg, "FFI", "type");
usz ia = IA(arg);
if (ia==0) {
if (!forRes) thrM("FFI: Argument type empty");
if (!forRes) thrM("Type parser: Type was empty");
return (BQNFFIEnt){.t = ffi_type_void, .o=m_c32(sty_void), .resSingle=false};
}
arg = chr_squeezeChk(incG(arg));
MAKE_MUT_INIT(tmp, ia+1, el_c32); MUTG_INIT(tmp);
mut_copyG(tmp, 0, arg, 0, ia);
mut_setG(tmp, ia, m_c32(0));
u32* xp = tmp->a;
u32* xpN = xp + ia;
u32* xp0 = parseType_pre(arg, ia);
u32* xp = xp0;
BQNFFIEnt t;
if (xp[0]=='&' && xp[1]=='\0') {
t = (BQNFFIEnt){.t = ffi_type_void, .o=m_c32(sty_void), .resSingle=true};
xp+= 1;
} else {
u8 side = 0;
bool whole = false;
@ -543,13 +560,14 @@ BQNFFIEnt ffi_parseType(B arg, bool forRes) { // doesn't consume; parse argument
t = ffi_parseTypeStr(&xp, false, true);
// printI(arg); printf(": "); printFFIType(stdout, t.o); printf("\n");
if (xp!=xpN) thrM("FFI: Garbage at end of type");
t.onW = side;
// keep .mutates
t.wholeArg = whole;
t.resSingle = false;
}
mut_pfree(tmp, 0);
parseType_post(xp0, xp, ia);
decG(arg);
return t;
}
@ -614,16 +632,17 @@ NOINLINE B readU8Bits(B x) { usz ia=IA(x); u8* xp=tyarr_ptr(x); i16* rp; B r=m
NOINLINE B readU16Bits(B x) { usz ia=IA(x); u16* xp=tyarr_ptr(x); i32* rp; B r=m_i32arrv(&rp, ia); for (usz i=0; i<ia; i++) rp[i]=xp[i]; return num_squeeze(r); }
NOINLINE B readU32Bits(B x) { usz ia=IA(x); u32* xp=tyarr_ptr(x); f64* rp; B r=m_f64arrv(&rp, ia); for (usz i=0; i<ia; i++) rp[i]=xp[i]; return num_squeeze(r); }
NOINLINE B readF32Bits(B x) { usz ia=IA(x); f32* xp=tyarr_ptr(x); f64* rp; B r=m_f64arrv(&rp, ia); for (usz i=0; i<ia; i++) rp[i]=xp[i]; return r; }
static void* ptrobj_checkget(B x); // doesn't consume
STATIC_GLOBAL B ffiObjsGlobal;
void genObj(B o, B c, bool anyMut, void* ptr) {
void genObj(B o, B c, bool anyMut, void* ptr) { // doesn't consume
// printFFIType(stdout,o); printf(" = "); printI(c); printf("\n");
if (isC32(o)) { // scalar
u32 t = o2cG(o);
f64 f = c.f;
switch(t) { default: UD; // thrF("FFI: Unimplemented scalar type \"%S\"", sty_names[t]);
case sty_a: *(BQNV*)ptr = makeX(inc(c)); break;
case sty_ptr: thrM("FFI: \"*\" unimplemented"); break;
case sty_ptr: *(void**)ptr = ptrobj_checkget(c); break;
case sty_u8: { if(!q_fu8 (f)) thrM("FFI: improper value for u8" ); *( u8*)ptr = ( u8)f; break; }
case sty_i8: { if(!q_fi8 (f)) thrM("FFI: improper value for i8" ); *( i8*)ptr = ( i8)f; break; }
case sty_u16: { if(!q_fu16(f)) thrM("FFI: improper value for u16"); *(u16*)ptr = (u16)f; break; }
@ -727,23 +746,24 @@ void genObj(B o, B c, bool anyMut, void* ptr) {
}
}
B readAny(BQNFFIEnt e, u8* ptr);
B readAny(B o, u8* ptr);
B readStruct(BQNFFIType* t, u8* ptr) {
usz ia = t->ia-1;
M_HARR(r, ia);
for (usz i = 0; i < ia; i++) {
void* c = ptr + t->a[i].offset;
HARR_ADD(r, i, readAny(t->a[i], c));
HARR_ADD(r, i, readAny(t->a[i].o, c));
}
return HARR_FV(r);
}
B m_ptrobj_s(void* ptr, B o); // consumes o, sets stride to size of o
B m_ptrobj(void* ptr, B o, ux stride); // consumes o
B readSimple(u8 resCType, u8* ptr) {
B r;
switch(resCType) { default: UD; // thrM("FFI: Unimplemented type");
case sty_void: r = m_c32(0); break;
case sty_ptr: thrM("FFI: \"*\" not yet implemented"); break;
case sty_ptr: r = m_ptrobj_s(*(void**)ptr, m_c32(sty_void)); break;
case sty_a: r = getB(*(BQNV*)ptr); break;
case sty_i8: r = m_i32(*( i8*)ptr); break; case sty_u8: r = m_i32(*( u8*)ptr); break;
case sty_i16: r = m_i32(*(i16*)ptr); break; case sty_u16: r = m_i32(*(u16*)ptr); break;
@ -764,18 +784,19 @@ B readRe(BQNFFIEnt e, u8* ptr) {
return makeRe(reT, reW, ptr, etw);
}
B readAny(BQNFFIEnt e, u8* ptr) {
if (isC32(e.o)) {
return readSimple(o2cG(e.o), ptr);
B readAny(B o, u8* ptr) { // doesn't consume
if (isC32(o)) {
return readSimple(o2cG(o), ptr);
} else {
BQNFFIType* t = c(BQNFFIType, e.o);
BQNFFIType* t = c(BQNFFIType, o);
if (t->ty == cty_repr) { // cty_repr, scalar:x
return readRe(t->a[0], ptr);
} else if (t->ty==cty_struct || t->ty==cty_starr) { // {...}
return readStruct(c(BQNFFIType, e.o), ptr);
} else if (t->ty==cty_struct || t->ty==cty_starr) { // {...}, [n]...
return readStruct(c(BQNFFIType, o), ptr);
}
}
thrM("FFI: Unimplemented struct field type for reading");
thrM("FFI: Unimplemented in-memory type for reading");
}
B buildObj(BQNFFIEnt ent, bool anyMut, B* objs, usz* objPos) {
@ -894,9 +915,11 @@ B libffiFn_c2(B t, B w, B x) {
BQNFFIType* t = c(BQNFFIType, ents[0].o);
if (t->ty == cty_repr) { // cty_repr, scalar:x
r = readRe(t->a[0], res);
} else { // {...}
} else if (t->ty == cty_struct) { // {...}
r = readStruct(c(BQNFFIType, ents[0].o), res);
}
} else if (t->ty == cty_ptr) { // *...
r = m_ptrobj_s(*(void**)res, inc(t->a[0].o));
} else UD;
}
TFREE(tmpAlloc);
@ -939,23 +962,25 @@ BQNFFIEnt ffi_parseType(B arg, bool forRes) {
static u64 atomSize(B chr) {
static u64 calcAtomSize(B chr) {
return o2cG(chr)==sty_a? sizeof(BQNV) : sizeof(ffi_arg)>8? sizeof(ffi_arg) : 8;
}
static u64 calcStaticSize(BQNFFIEnt e) {
if (isC32(e.o)) { // scalar
return atomSize(e.o);
} else {
BQNFFIType* t = c(BQNFFIType, e.o);
if (t->ty==cty_ptr || t->ty==cty_tlarr) return sizeof(void*); // *any / &any / top-level [n]any
else if (t->ty==cty_struct || t->ty==cty_starr) return t->structSize; // {...}
else if (t->ty==cty_repr) { // any:any
B o2 = t->a[0].o;
if (isC32(o2)) return atomSize(o2);
if (c(BQNFFIType,o2)->ty != cty_ptr) thrM("FFI: Bad type with reinterpretation");
return sizeof(void*);
} else thrM("FFI: Unimplemented type (size calculation)");
}
static NOINLINE u64 calcMemSizeComplex(B o) {
BQNFFIType* t = c(BQNFFIType, o);
if (t->ty==cty_ptr || t->ty==cty_tlarr) return sizeof(void*); // *any / &any / top-level [n]any
else if (t->ty==cty_struct || t->ty==cty_starr) return t->structSize; // {...}
else if (t->ty==cty_repr) { // any:any
B o2 = t->a[0].o;
if (isC32(o2)) return calcAtomSize(o2);
if (c(BQNFFIType,o2)->ty != cty_ptr) thrM("FFI: Bad type with reinterpretation");
return sizeof(void*);
} else thrM("FFI: Unimplemented type (size calculation)");
}
static u64 calcMemSize(B o) {
return isC32(o)? sty_w[o2cG(o)] : calcMemSizeComplex(o);
}
static u64 calcStaticSize(B o) {
return isC32(o)? calcAtomSize(o) : calcMemSizeComplex(o);
}
B ffiload_c2(B t, B w, B x) {
@ -974,20 +999,27 @@ B ffiload_c2(B t, B w, B x) {
{
B atomType;
usz size;
if (isC32(tRes.o)) { atomType = tRes.o; goto calcRes; }
if (isC32(tRes.o)) { atomType = tRes.o; goto calcAtomSize; }
BQNFFIType* t = c(BQNFFIType, tRes.o);
if (t->ty == cty_repr) {
B o2 = t->a[0].o;
if (!isC32(o2)) thrM("FFI: Unimplemented result type");
atomType = o2;
goto calcRes;
goto calcAtomSize;
}
if (t->ty==cty_struct) { size = t->structSize; goto allocRes; }
if (t->ty==cty_tlarr) thrM("FFI: Cannot return array");
if (t->ty==cty_ptr) {
size = sizeof(void*);
goto allocRes;
}
thrM("FFI: Unimplemented result type");
calcRes:; size = atomSize(atomType);
calcAtomSize:;
size = calcAtomSize(atomType);
goto allocRes;
allocRes:; STATIC_ALLOC(tRes, size);
}
@ -1000,7 +1032,7 @@ B ffiload_c2(B t, B w, B x) {
for (usz i = 0; i < argn; i++) {
BQNFFIEnt e = ffi_parseType(GetU(x,i+2), false);
STATIC_ALLOC(e, calcStaticSize(e));
STATIC_ALLOC(e, calcStaticSize(e.o));
args[i+1] = e;
if (e.wholeArg) whole[e.onW] = true;
@ -1063,7 +1095,9 @@ B ffiload_c2(B t, B w, B x) {
#define FFI_TYPE_FLDS(OBJ, PTR) \
BQNFFIType* t = (BQNFFIType*) x; \
BQNFFIEnt* arr=t->a; usz ia=t->ia; \
if (t->ty==cty_struct || t->ty==cty_starr) { \
if (t->ty==cty_ptrh) { \
ia = 1; \
} if (t->ty==cty_struct || t->ty==cty_starr) { \
ia--; \
if (arr[ia].structData!=NULL) { \
PTR(arr[ia].structData); \
@ -1089,9 +1123,184 @@ void ffiType_print(FILE* f, B x) {
fprintf(f, "");
}
STATIC_GLOBAL Body* ptrobj_ns;
STATIC_GLOBAL NFnDesc* ptrReadDesc;
STATIC_GLOBAL NFnDesc* ptrWriteDesc;
STATIC_GLOBAL NFnDesc* ptrCastDesc;
STATIC_GLOBAL NFnDesc* ptrAddDesc;
STATIC_GLOBAL NFnDesc* ptrSubDesc;
STATIC_GLOBAL NFnDesc* ptrFieldDesc;
// ptrh - pointer holder - BQNFFIType object of {{.o=eltype}, {.ptrh_ptr, .ptrh_stride}}
// eltype of sty_void is used for an untyped pointer
static B ptrh_type(B n) { return c(BQNFFIType, n)->a[0].o; }
static BQNFFIEnt* ptrh_more(B n) { return &c(BQNFFIType, n)->a[1]; }
static void* ptrh_ptr(B n) { return ptrh_more(n)->ptrh_ptr; }
static ux ptrh_stride(B n) { return ptrh_more(n)->ptrh_stride; }
B m_ptrobj_s(void* ptr, B o) {
return m_ptrobj(ptr, o, calcMemSize(o));
}
B m_ptrobj(void* ptr, B o, ux stride) {
BQNFFIEnt* op;
B obj = m_bqnFFIType(&op, cty_ptrh, 2);
op[0].o = o;
op[1].ptrh_ptr = ptr;
op[1].ptrh_stride = stride;
return m_nns(ptrobj_ns,
m_nfn(ptrReadDesc, incG(obj)),
m_nfn(ptrWriteDesc, incG(obj)),
m_nfn(ptrCastDesc, incG(obj)),
m_nfn(ptrAddDesc, incG(obj)),
m_nfn(ptrSubDesc, incG(obj)),
m_nfn(ptrFieldDesc, obj)
);
}
static B ptrobj_checkget_holder(B x) { // doesn't consume
if (!isNsp(x)) thrF("Expected pointer object, got %S", genericDesc(x));
if (c(NS,x)->desc != ptrobj_ns->nsDesc) thrM("Expected pointer object, got some other kind of namespace");
return nfn_objU(c(NS,x)->sc->vars[0]);
}
static NOINLINE void* ptrobj_checkget(B x) {
return ptrh_ptr(ptrobj_checkget_holder(x));
}
static void* ptrobj_elbase(B h, B off, bool negate) { // doesn't consume h, off doesn't matter
if (ptrh_type(h).u == m_c32(sty_void).u) thrM("Cannot offset an untyped pointer");
char* ptr = ptrh_ptr(h);
i64 el = o2i64(off);
if (negate) el = -el;
return ptr + ptrh_stride(h)*el;
}
static B ptrobjRead_c1(B t, B x) {
B h = nfn_objU(t);
return readAny(ptrh_type(h), ptrobj_elbase(h, x, false));
}
static B ptrobjWrite_c2(B t, B w, B x) {
B h = nfn_objU(t);
genObj(ptrh_type(h), x, false, ptrobj_elbase(h, w, false));
dec(x);
return m_f64(1);
}
static B ptrobjWrite_c1(B t, B x) { return ptrobjWrite_c2(t, m_f64(0), x); }
static B ptrobjCast_c1(B t, B x) {
vfyStr(x, "Type parser", "Pointer type");
usz ia = IA(x);
void* ptr = ptrh_ptr(nfn_objU(t));
if (ia==0) {
decG(x);
return m_ptrobj(ptr, m_c32(sty_void), 0);
}
u32* xp0 = parseType_pre(x, ia);
u32* xp = xp0;
decG(x);
BQNFFIEnt parsed = ffi_parseTypeStr(&xp, false, false);
B r = m_ptrobj_s(ptr, parsed.o);
parseType_post(xp0, xp, ia);
return r;
}
static B ptrobjField_c1(B t, B x) {
B h = nfn_objU(t);
u64 fld = o2u64(x);
B el = ptrh_type(h);
if (isC32(el)) thrM("Cannot get a field of a pointer to a scalar");
BQNFFIType* elc = c(BQNFFIType, el);
char* ptr = ptrh_ptr(h);
B elNew;
if (elc->ty == cty_starr || elc->ty == cty_struct) {
bool isStruct = elc->ty == cty_struct;
u64 n = elc->ia-1;
if (fld >= n) thrF(isStruct? "Cannot get field %l of a struct with %s fields" : "Cannot get pointer to element %l of an array with %s elements", fld, n);
BQNFFIEnt* fldptr = &elc->a[fld];
elNew = inc(fldptr->o);
ptr+= fldptr->offset;
} else {
thrM("Pointer object: Field must be applied to a pointer to a struct or array");
}
return m_ptrobj(ptr, elNew, ptrh_stride(h));
}
static B ptrobjAdd_c1(B t, B x) {
B h = nfn_objU(t);
return m_ptrobj(ptrobj_elbase(h, x, false), inc(ptrh_type(h)), ptrh_stride(h));
}
static B ptrty_simplify(B x) {
assert(!isC32(x));
BQNFFIType* xt = c(BQNFFIType,x);
if (xt->ty == cty_repr) return xt->a[0].o;
return x;
}
static bool ptrty_equal(B a, B b) {
if (!isC32(a)) a = ptrty_simplify(a);
if (a.u == m_c32(sty_ptr).u) return true;
if (!isC32(b)) b = ptrty_simplify(b);
if (b.u == m_c32(sty_ptr).u) return true;
if (isC32(a) || isC32(b)) return a.u==b.u; // if only one is a character, this test trivially fails
BQNFFIType* at = c(BQNFFIType,a);
BQNFFIType* bt = c(BQNFFIType,b);
if (at->ty != bt->ty) return false;
switch (at->ty) { default: UD;
case cty_ptr: case cty_tlarr: {
return ptrty_equal(at->a[0].o, bt->a[0].o);
}
case cty_repr: {
UD; // should be handled by ptrty_simplify
// if (at->a[0].reType != bt->a[0].reType) return false;
// if (at->a[0].reWidth != bt->a[0].reWidth) return false;
// if (!ptrty_equal(at->a[0].o, bt->a[0].o)) return false;
// return true;
}
case cty_struct: case cty_starr: {
if (at->ia != bt->ia) return false;
ux n = at->ia-1;
for (ux i = 0; i < n; i++) {
if (!ptrty_equal(at->a[i].o, bt->a[i].o)) return false;
}
return true;
}
}
}
static B ptrobjSub_c1(B t, B x) {
B h = nfn_objU(t);
if (isNum(x)) return m_ptrobj(ptrobj_elbase(h, x, true), inc(ptrh_type(h)), ptrh_stride(h));
if (isNsp(x)) {
B h2 = ptrobj_checkget_holder(x);
B t1 = ptrh_type(h);
B t2 = ptrh_type(h2);
if (t1.u==m_c32(sty_void).u || t2.u==m_c32(sty_void).u) thrM("(pointer).Sub ptr: Both pointers must be typed");
ux stride = ptrh_stride(h);
if (stride!=ptrh_stride(h2)) thrM("(pointer).Sub ptr: Arguments must have the same stride");
if (!ptrty_equal(t1, t2)) thrM("(pointer).Sub ptr: Arguments must have compatible types");
ptrdiff_t diff = ptrh_ptr(h) - ptrh_ptr(h2);
ptrdiff_t eldiff = diff / (ptrdiff_t)stride;
if (eldiff*stride != diff) thrM("(pointer).Sub ptr: Distance between pointers isn't an exact multiple of stride");
decG(x);
return m_f64(eldiff);
}
thrF("(pointer).Sub: Unexpected argument type: %S", genericDesc(x));
}
void ffi_init(void) {
boundFnDesc = registerNFn(m_c8vec_0("(foreign function)"), boundFn_c1, boundFn_c2);
foreignFnDesc = registerNFn(m_c8vec_0("(foreign function)"), directFn_c1, directFn_c2);
ptrobj_ns = m_nnsDesc("read","write","cast","add","sub","field"); // first field must be an nfn whose object is the ptrh (needed for ptrobj_checkget_holder)
ptrReadDesc = registerNFn(m_c8vec_0("(pointer).Read"), ptrobjRead_c1, c2_bad);
ptrWriteDesc = registerNFn(m_c8vec_0("(pointer).Write"), ptrobjWrite_c1, ptrobjWrite_c2);
ptrCastDesc = registerNFn(m_c8vec_0("(pointer).Cast"), ptrobjCast_c1, c2_bad);
ptrAddDesc = registerNFn(m_c8vec_0("(pointer).Add"), ptrobjAdd_c1, c2_bad);
ptrSubDesc = registerNFn(m_c8vec_0("(pointer).Sub"), ptrobjSub_c1, c2_bad);
ptrFieldDesc = registerNFn(m_c8vec_0("(pointer).Field"), ptrobjField_c1, c2_bad);
TIi(t_ffiType,freeO) = ffiType_freeO;
TIi(t_ffiType,freeF) = ffiType_freeF;
TIi(t_ffiType,visit) = ffiType_visit;

View File

@ -6,25 +6,26 @@
# bad •FFI invocation
# generally weird
!"•FFI: Path must be a list of characters" % 12 •FFI """bqn_init"
"file that doesn't exist" •FFI'e' """abort" %% 'e'
@ •FFI'e' """bqn_this symbol doesn't exist" %% 'e'
!"This function can't be called monadically" % •FFI """bqn_init"
@ •FFI{𝕊: 28•CurrentError@} """bqn_this symbol doesn't exist" %% "FFI: Failed to find symbol: "
!"FFI: Type must be a string" % @•FFI "hello"
!"FFI: Error parsing type: Unexpected character '?'" % @•FFI"""bqn_init""?"
!"FFI: Bad array type" % @•FFI"""bqn_init""["
!"FFI: Bad array type" % @•FFI"""bqn_init""[0"
!"FFI: Error parsing type: Unexpected end of input" % @•FFI"""bqn_init""[1]"
!"FFI: Error parsing type: Unexpected end of input" % @•FFI"""bqn_init""{"
!"FFI: Bad integer width" % @•FFI"""bqn_init""i"
!"FFI: Bad float width" % @•FFI"""bqn_init""f80"
!"FFI: Bad float width" % @•FFI"""bqn_init""f128"
!"FFI: Bad float width" % @•FFI"""bqn_init""f16"
!"FFI: Bad float width" % @•FFI"""bqn_init""f1"
!"FFI: Bad integer width" % @•FFI"""bqn_init""u1"
!"FFI: Bad integer width" % @•FFI"""bqn_init""i0"
!"FFI: Bad integer width" % @•FFI"""bqn_init""i10"
!"FFI: Bad integer width" % @•FFI"""bqn_init""u128"
!"FFI: number literal too large" % @•FFI"""bqn_init""u99999999999999"
!"FFI: Too many arguments" % @•FFI"""bqn_init"70000<"i32"
!"Type parser: Unexpected character '?'" % @•FFI"""bqn_init""?"
!"Type parser: Bad array type" % @•FFI"""bqn_init""["
!"Type parser: Bad array type" % @•FFI"""bqn_init""[0"
!"Type parser: Unexpected end of input" % @•FFI"""bqn_init""[1]"
!"Type parser: Unexpected end of input" % @•FFI"""bqn_init""{"
!"Type parser: Bad integer width" % @•FFI"""bqn_init""i"
!"Type parser: Bad float width" % @•FFI"""bqn_init""f80"
!"Type parser: Bad float width" % @•FFI"""bqn_init""f128"
!"Type parser: Bad float width" % @•FFI"""bqn_init""f16"
!"Type parser: Bad float width" % @•FFI"""bqn_init""f1"
!"Type parser: Bad integer width" % @•FFI"""bqn_init""u1"
!"Type parser: Bad integer width" % @•FFI"""bqn_init""i0"
!"Type parser: Bad integer width" % @•FFI"""bqn_init""i10"
!"Type parser: Bad integer width" % @•FFI"""bqn_init""u128"
!"Type parser: Type was empty" % @•FFI"""bqn_init"""
!"Type parser: number literal too large" % @•FFI"""bqn_init""u99999999999999"
# >/𝕨/𝕩
!"FFI: Multiple occurrences of '>' within one argument" % @•FFI"""bqn_init"">>i32"
@ -37,38 +38,37 @@
!"FFI: Multiple arguments on 𝕨 specified, some with '>'" % @•FFI"""bqn_init"">𝕨i32""𝕨i32"
# arrays
!"FFI: 0-item arrays not supported" % @•FFI"""bqn_init""[0]i32"
!"FFI: number literal too large" % @•FFI"""bqn_init""[999999999999]i32"
!"FFI: Top-level array too large; limit is 65535 elements" % @•FFI"""bqn_init""[65536]i32"
!"FFI: Top-level array too large; limit is 65535 elements" % @•FFI"""bqn_init""[4000000000]i32"
!"FFI: Array too large; limit is 65534 bytes" % @•FFI"""bqn_init""*[20000]i32"
!"Type parser: 0-item arrays not supported" % @•FFI"""bqn_init""[0]i32"
!"Type parser: number literal too large" % @•FFI"""bqn_init""[999999999999]i32"
!"Type parser: Top-level array too large; limit is 65535 elements" % @•FFI"""bqn_init""[65536]i32"
!"Type parser: Top-level array too large; limit is 65535 elements" % @•FFI"""bqn_init""[4000000000]i32"
!"Type parser: Array too large; limit is 65534 bytes" % @•FFI"""bqn_init""*[20000]i32"
!"FFI: Cannot return array" % @•FFI"[4]i32""bqn_init"
# structs
!"FFI: Improper struct separator or end" % @•FFI"""bqn_init""{*i32*i32}"
!"FFI: Improper struct separator or end" % @•FFI"""bqn_init""{*i32"
!"FFI: Error parsing type: Unexpected end of input" % @•FFI"""bqn_init""{*i32,"
!"FFI: Structs currently cannot contain mutable references" % @•FFI"""bqn_init""{&i32}"
!"Type parser: Improper struct separator or end" % @•FFI"""bqn_init""{*i32*i32}"
!"Type parser: Improper struct separator or end" % @•FFI"""bqn_init""{*i32"
!"Type parser: Unexpected end of input" % @•FFI"""bqn_init""{*i32,"
!"Type parser: Structs currently cannot contain mutable references" % @•FFI"""bqn_init""{&i32}"
# !"Type parser: Structs currently cannot contain mutable references" % @•FFI""‿"bqn_init"‿"{[2]&i32}" # TODO
# :
!"FFI: Unexpected character after "":""" % @•FFI"""bqn_init""i32:"
!"FFI: Unexpected character after "":""" % f@•FFI"""bqn_init"">*u64:q8"
!"FFI: Unsupported width in :i0" % @•FFI"""bqn_init""i32:i"
!"FFI: Unsupported width in :i9" % @•FFI"""bqn_init""i32:i9"
!"FFI: Unsupported width in :u16" % @•FFI"""bqn_init""i32:u16"
!"FFI: Unsupported width in :i64" % @•FFI"i32:i64""bqn_init"
!"FFI: Unsupported width in :f32" % @•FFI"""bqn_init""i32:f32"
!"FFI: number literal too large" % @•FFI"""bqn_init""i32:u9999999999999999"
!"FFI: Representation wider than the value for ""i32:f64""" % @•FFI"""bqn_init""i32:f64"
!"FFI: Garbage at end of type" % @•FFI"""bqn_init""i32 hello world"
!"Type parser: Unexpected character after "":""" % @•FFI"""bqn_init""i32:"
!"Type parser: Unexpected character after "":""" % f@•FFI"""bqn_init"">*u64:q8"
!"Type parser: Unsupported width in :i0" % @•FFI"""bqn_init""i32:i"
!"Type parser: Unsupported width in :i9" % @•FFI"""bqn_init""i32:i9"
!"Type parser: Unsupported width in :u16" % @•FFI"""bqn_init""i32:u16"
!"Type parser: Unsupported width in :i64" % @•FFI"i32:i64""bqn_init"
!"Type parser: Unsupported width in :f32" % @•FFI"""bqn_init""i32:f32"
!"Type parser: number literal too large" % @•FFI"""bqn_init""i32:u9999999999999999"
!"Type parser: Representation wider than the value for ""i32:f64""" % @•FFI"""bqn_init""i32:f64"
!"Type parser: Garbage at end of type" % @•FFI"""bqn_init""i32 hello world"
# return value
!"FFI: Function specification must have at least two items" % @•FFI "&"
!"FFI: Return value is specified as ""&"", but there are 0 mutated values" % @•FFI"&""bqn_init"">*i32"
!"FFI: Return value is specified as ""&"", but there are 2 mutated values" % @•FFI"&""bqn_init""&i32""&i32"
!"FFI: Cannot return array" % @•FFI"[4]i32""bqn_init"
!"FFI: Unimplemented result type" % @•FFI"*i32""bqn_init"
!"FFI: Unimplemented result type" % @•FFI"&i32""bqn_init"
# correct elements
@ -87,7 +87,7 @@
# bad array elements
!"FFI: Structs currently cannot contain mutable references" % %USE defs f@•FFI"&""memcpy""&{&i16}""*i8"size_t G{F𝕩,0,0} G ¨¯3276832767
!"Type parser: Structs currently cannot contain mutable references" % size_t "u64" size_tw 64 f@•FFI"&""memcpy""&{&i16}""*i8"size_t G{F𝕩,0,0} G ¨¯3276832767
!"FFI: Array provided for &u8 contained 299" % f@•FFI"&""bqn_init"">&u8" F 300
!"FFI: Array provided for &u8 contained ¯99" % f@•FFI"&""bqn_init"">&u8" F -100
@ -166,11 +166,93 @@
# unimplemented stuff
!"FFI: ""*"" unimplemented" % f@•FFI"""bqn_init"">*" F 1
!"FFI: ""*i64"" argument type not yet implemented" % f@•FFI"""bqn_init"">&i64" F 10
!"FFI: ""*i64"" argument type not yet implemented" % f@•FFI"""bqn_init"">*i64" F 10
!"FFI: ""*u64"" argument type not yet implemented" % f@•FFI"""bqn_init"">&u64" F 10
!"FFI: Pointer element type not implemented" % f@•FFI"""bqn_init"">**u64" F 2
# pointer objects
%DEF mm %USE defs Alloc {1𝕩 {𝕏𝕨} @•FFI"*"𝕨, "calloc", size_t, size_t} free @•FFI"""free"">*"
%DEF WallocE Cpy +´(28×8)×256|(@•FFI"*:i8""memcpy""*""*""u64"){𝔽} {%USE mm allocs Clean{Free¨allocs𝕩} Clean 𝕏{𝕊: !•CurrentError Clean @} {{r allocs r 𝕨 Alloc 𝕩}}}
%DEF Walloc Cpy +´(28×8)×256|(@•FFI"*:i8""memcpy""*""*""u64"){𝔽} {%USE mm allocs Clean{Free¨allocs𝕩} Clean 𝕏 {{r allocs r 𝕨 Alloc 𝕩}}}
# .Field
%USE Walloc {{𝕊al: 1("{i32,i32,i32}" Al 99).Field0¨ ¯2+8}} %% 00111000
%USE Walloc {{𝕊al: 1("[5]u8" Al 99).Field0¨ ¯2+9}} %% 001111100
%USE Walloc {{𝕊al: 1("[5]u8" Al 99).Field0¨ ,¯∞,0÷0,@,"foo",{}}} %% 60
# .Write, .Read
%USE Walloc {{𝕊al: p"{i16,i16,i16,i64}" Al 99 p.Write ¯410000¯300007e12 s-{Cpy 0˜˜𝕩}´ p.Field 2, p Cpy p.Add 1, p, s p.Read¨ 2}} %% ¯410000¯300007e12, ¯41000000
%USE Walloc {{𝕊al: p"{i32,{i64,i64}}" Al 999 {𝕩 p.Write 𝕩×10,𝕩×2030}¨ 4 ((p.Field 1).Field 0).Read¨ 4 }} %% 20×4
!"Cannot get a field of a pointer to a scalar" % %USE WallocE {{𝕊al: ("i32" Al 99).Field 0}}
# garbage arguments
!"Expected integer, got character" % %USE WallocE {{𝕊al: p"i8" Al 1 p.Add '!'}}
!"(pointer).Sub: Unexpected argument type: character" % %USE WallocE {{𝕊al: p"i8" Al 1 p.Sub '!'}}
!"Expected non-negative integer, got character" % %USE WallocE {{𝕊al: p"i8" Al 1 p.Field '!'}}
!"Type parser: Pointer type must be a string" % %USE WallocE {{𝕊al: p"i8" Al 1 p.Cast '!'}}
!"Expected integer, got character" % %USE WallocE {{𝕊al: p"i8" Al 1 p.Read '!'}}
!"FFI: improper value for i8" % %USE WallocE {{𝕊al: p"i8" Al 1 p.Write '!'}}
# untyped pointers
!"Cannot get a field of a pointer to a scalar" % %USE WallocE {{𝕊al: ("" Al 99).Field 0}}
# %USE Walloc {{𝕊al: p←"i32" Al 99 ⋄ p.Write 123 ⋄ (((p.Cast "").Add 0).Cast"i32").Read 0}} %% 123
!"Cannot offset an untyped pointer" % %USE WallocE {{𝕊al: ("" Al 99).Add 0}}
!"Cannot offset an untyped pointer" % %USE WallocE {{𝕊al: ("" Al 99).Add 1}}
# .Add, .Sub, .Cast
%USE Walloc {{𝕊al: p"{i32,*}" Al 1 p.Sub p.Cast "{i32,*i32}"}} %% 0
%USE Walloc {{𝕊al: p"{i32,*}" Al 1 p.Sub p.Add 123}} %% ¯123
%USE Walloc {{𝕊al: p"{i32,*}" Al 1 p.Sub p.Sub 123}} %% 123
# ptr1.Sub ptr2
!"(pointer).Sub ptr: Both pointers must be typed" % %USE WallocE {{𝕊al: p"" Al 1 p.Sub p}}
!"(pointer).Sub ptr: Both pointers must be typed" % %USE WallocE {{𝕊al: p"i8" Al 1 (p.Cast "").Sub p.Cast "{{i8,i16},i32}"}}
!"(pointer).Sub ptr: Both pointers must be typed" % %USE WallocE {{𝕊al: p"i8" Al 1 (p.Cast "{{i8,i16},i32}").Sub p.Cast ""}}
!"(pointer).Sub ptr: Arguments must have the same stride" % %USE WallocE {{𝕊al: p"i8" Al 1 ((p.Cast "{{i8,i16},i32}").Field 0).Sub p.Cast "{i8,i16}"}}
!"(pointer).Sub ptr: Arguments must have compatible types" % %USE WallocE {{𝕊al: p"i8" Al 1 p.Sub p.Cast "u8"}}
!"(pointer).Sub ptr: Distance between pointers isn't an exact multiple of stride" % %USE WallocE {{𝕊al: p"i8" Al 1 (p.Cast "i32").Sub (p.Add 6).Cast "i32"}}
!"Type parser: Structs currently cannot contain mutable references" % @•FFI """bqn_init""{&i8}"
# !"" % %USE Walloc {{𝕊al: p←"i8" Al 1 ⋄ (p.Cast "{{i8,i16},i32}").Sub p.Cast "{{u8,i16},i32}"}}
# !"" % %USE Walloc {{𝕊al: p←"i8" Al 1 ⋄ (p.Cast "{{i8,*i16},i32}").Sub p.Cast "{{u8,*u16},i32}"}}
# type comparison
%USE Walloc {{𝕊al: p"i8" Al 1 t"{{i8,i16,**i8,[12]i8,*:i32,i64:f64,*,*f64},i32,i64:c8}" (p.Cast t).Sub p.Cast t}} %% 0 # ⎊-less test of most passing things to run under heapverify
%DEF TyEq {exp𝕊ab: %USE Walloc {{𝕊al: p"i8" Al 1 ! (exp990) ((p.Cast a).Sub)99 p.Cast b}}}
1 %USE TyEq "{*i16}", "{*i16}"
0 %USE TyEq "{*i16}", "{*u16}"
1 %USE TyEq "{*i16}", "{*}"
1 %USE TyEq "{*}", "{*i16}"
1 %USE TyEq "{*:i8}", "{*i16}"
1 %USE TyEq "{*:i8}", "{*i16:i8}"
1 %USE TyEq "{*:i8}", "{*i16:i32}"
1 %USE TyEq "{{i8,i16},i32}", "{{i8,i16},i32}"
0 %USE TyEq "{{i8,i16},i32}", "{{u8,i16},i32}"
1 %USE TyEq "{[3]i32,[5]i32}", "{[3]i32,[5]i32}"
1 %USE TyEq "{[3]i32,[5]i32}", "{[3]i32,[5]i32:i8}"
0 %USE TyEq "{[3]i32,[5]i32}", "{[3]i32,[5]u32}"
0 %USE TyEq "{[3]i32,[5]i32}", "{[5]i32,[3]i32}"
0 %USE TyEq "[4]i32", "[3]i32"
0 %USE TyEq "*[4]i32", "*[3]i32"
0 %USE TyEq "{i32}", "i32"
0 %USE TyEq "i16", "i32"
0 %USE TyEq "i16", "u16"
1 %USE TyEq "{i32:i8,i64}", "{i32:i8,i64}"
0 %USE TyEq "{i32:i8,i64}", "{u32:i8,i64}"
0 %USE TyEq "{i32:i8,i64}", "{i16:i8,i64}"
0 %USE TyEq "*{i32:i8}", "*{i16:i8}"
1 %USE TyEq "{i32:i8,i64}", "{i32:i16,i64}"
1 %USE TyEq "{i32:i32,i64}","{i32:u1,i64}"
1 %USE TyEq "{i32:i8,i64}", "{i32,i64}"
1 %USE TyEq "*i32:i8", "*"
1 %USE TyEq "*i32:i8", "*:c32"
# !"FFI: Unimplemented result type" % @•FFI"*i32"‿"bqn_init"
# !"FFI: Unimplemented result type" % @•FFI"&i32"‿"bqn_init"
# unchecked stuff
# !"FFI: Bad array corresponding to ""i64:c8"": expected 8 elements, got 1" % f←@•FFI""‿"bqn_init"‿">i64:c8" ⋄ F 8⥊2
# !"FFI: Bad array corresponding to ""i64:c8"": expected 8 elements, got 1" % f←@•FFI""‿"bqn_init"‿">i64:c8" ⋄ F 8⥊2

View File

@ -24,4 +24,4 @@
!"•Import: cyclic import of "".../cyclic_B.bqn""" % %USE file •ImportCleanPath File "cyclic_B.bqn"
!"•Import: cyclic import of "".../cyclic_A.bqn""" % %USE file •ImportCleanPath File "cyclic_B.bqn"
!"•Import: cyclic import of "".../cyclic_B.bqn""" % %USE file •ImportCleanPath File "cyclic_A.bqn"
%USE file 3 •Import File "notCyclic.bqn" %% 30
%USE file 3 •Import File "notCyclic.bqn" %% 30

View File

@ -78,4 +78,4 @@ X←1‿2{a⇐𝕘-𝕗}3‿4 ⋄ ⟨a⟩←x ⋄ +´a %% 4
{𝕊: a"ab" (b"c")"d", a, b}¨ 4 %% 4<"c""d", "ab", "c"
{𝕊: a"ab" (a"c")"d", a}¨ 4 %% 4<"ab","c""d", "ab","c"
{𝕊: a"ab" (a"c")"d", a}¨ 4 %% 4<"c""d", "c"
{𝕊: a"ab" (b"c")"d", a, b}¨ 4 %% 4<"c""d", "ab", "c"
{𝕊: a"ab" (b"c")"d", a, b}¨ 4 %% 4<"c""d", "ab", "c"