use isStr more, include builtin name in bad path messages
This commit is contained in:
parent
31e4b29115
commit
789064140b
@ -270,20 +270,13 @@ B show_c1(B t, B x) {
|
||||
return x;
|
||||
}
|
||||
|
||||
NOINLINE bool isStr(B x) {
|
||||
if (isAtm(x) || RNK(x)!=1) return false;
|
||||
if (elChr(TI(x,elType))) return true;
|
||||
usz ia = IA(x); SGetU(x)
|
||||
for (usz i = 0; i < ia; i++) if (!isC32(GetU(x,i))) return false;
|
||||
return true;
|
||||
}
|
||||
NOINLINE B vfyStr(B x, char* name, char* arg) {
|
||||
if (!isStr(x)) thrF("%U: %U must be a string", name, arg);
|
||||
return x;
|
||||
}
|
||||
|
||||
B cdPath;
|
||||
static B args_path(B* fullpath, B w, char* name) { // consumes w, returns args, writes to fullpath
|
||||
static NOINLINE B args_path(B* fullpath, B w, char* name) { // consumes w, returns args, writes to fullpath
|
||||
if (!isArr(w) || RNK(w)!=1 || IA(w)>3) thrF("%U: 𝕨 must be a vector with at most 3 items, but had shape %H", name, w);
|
||||
usz ia = IA(w);
|
||||
SGet(w)
|
||||
@ -721,28 +714,28 @@ B currentError_c1(B t, B x) { thrM("•CurrentError: No errors as error catching
|
||||
|
||||
static NFnDesc* fileAtDesc;
|
||||
B fileAt_c1(B d, B x) {
|
||||
return path_rel(nfn_objU(d), x);
|
||||
return path_rel(nfn_objU(d), x, "•file.At");
|
||||
}
|
||||
B fileAt_c2(B d, B w, B x) {
|
||||
vfyStr(w,"(file).At","𝕨");
|
||||
B r = path_rel(w, x);
|
||||
vfyStr(w,"•file.At","𝕨");
|
||||
B r = path_rel(w, x, "•file.At");
|
||||
dec(w);
|
||||
return r;
|
||||
}
|
||||
static NFnDesc* fCharsDesc;
|
||||
B fchars_c1(B d, B x) {
|
||||
return path_chars(path_rel(nfn_objU(d), x));
|
||||
return path_chars(path_rel(nfn_objU(d), x, "•file.Chars"));
|
||||
}
|
||||
B fchars_c2(B d, B w, B x) {
|
||||
if (isAtm(x) || RNK(x)!=1) thrM("•file.Chars: 𝕩 must be a list of characters");
|
||||
B p = path_rel(nfn_objU(d), w);
|
||||
B p = path_rel(nfn_objU(d), w, "•file.Chars");
|
||||
path_wChars(incG(p), x);
|
||||
dec(x);
|
||||
return p;
|
||||
}
|
||||
static NFnDesc* fBytesDesc;
|
||||
B fbytes_c1(B d, B x) {
|
||||
I8Arr* tf = path_bytes(path_rel(nfn_objU(d), x));
|
||||
I8Arr* tf = path_bytes(path_rel(nfn_objU(d), x, "•file.Bytes"));
|
||||
usz ia = PIA(tf);
|
||||
u8* rp; B r = m_c8arrv(&rp, ia);
|
||||
COPY_TO(rp, el_i8, 0, taga(tf), 0, ia);
|
||||
@ -751,14 +744,14 @@ B fbytes_c1(B d, B x) {
|
||||
}
|
||||
B fbytes_c2(B d, B w, B x) {
|
||||
if (isAtm(x) || RNK(x)!=1) thrM("•file.Bytes: 𝕩 must be a list");
|
||||
B p = path_rel(nfn_objU(d), w);
|
||||
B p = path_rel(nfn_objU(d), w, "•file.Bytes");
|
||||
path_wBytes(incG(p), x);
|
||||
dec(x);
|
||||
return p;
|
||||
}
|
||||
static NFnDesc* fLinesDesc;
|
||||
B flines_c1(B d, B x) {
|
||||
return path_lines(path_rel(nfn_objU(d), x));
|
||||
return path_lines(path_rel(nfn_objU(d), x, "•file.Lines"));
|
||||
}
|
||||
B flines_c2(B d, B w, B x) {
|
||||
if (isAtm(x) || RNK(x)!=1) thrM("•file.Lines: 𝕩 must be a list");
|
||||
@ -773,7 +766,7 @@ B flines_c2(B d, B w, B x) {
|
||||
s = vec_addN(s, m_c32('\n'));
|
||||
}
|
||||
dec(x);
|
||||
B p = path_rel(nfn_objU(d), w);
|
||||
B p = path_rel(nfn_objU(d), w, "•file.Lines");
|
||||
path_wChars(incG(p), s);
|
||||
decG(s);
|
||||
return p;
|
||||
@ -783,7 +776,7 @@ static NFnDesc* importDesc;
|
||||
|
||||
|
||||
B import_c2(B d, B w, B x) {
|
||||
return bqn_execFile(path_rel(nfn_objU(d), x), w);
|
||||
return bqn_execFile(path_rel(nfn_objU(d), x, "•Import"), w);
|
||||
}
|
||||
|
||||
// defined in fns.c
|
||||
@ -798,7 +791,7 @@ B import_c1(B d, B x) {
|
||||
importKeyList = emptyHVec();
|
||||
importValList = emptyHVec();
|
||||
}
|
||||
B path = path_abs(path_rel(nfn_objU(d), x));
|
||||
B path = path_abs(path_rel(nfn_objU(d), x, "•Import"));
|
||||
|
||||
i32 prevIdx = getPrevImport(path);
|
||||
if (prevIdx>=0) {
|
||||
@ -847,56 +840,56 @@ static NFnDesc* renameDesc;
|
||||
static NFnDesc* removeDesc;
|
||||
|
||||
B list_c1(B d, B x) {
|
||||
return path_list(path_rel(nfn_objU(d), x));
|
||||
return path_list(path_rel(nfn_objU(d), x, "•file.List"));
|
||||
}
|
||||
B createdir_c1(B d, B x) {
|
||||
B p = path_rel(nfn_objU(d), x);
|
||||
B p = path_rel(nfn_objU(d), x, "•file.CreateDir");
|
||||
if (dir_create(p)) return p;
|
||||
thrM("(file).CreateDir: Failed to create directory");
|
||||
thrM("•file.CreateDir: Failed to create directory");
|
||||
}
|
||||
B realpath_c1(B d, B x) {
|
||||
return path_abs(path_rel(nfn_objU(d), x));
|
||||
return path_abs(path_rel(nfn_objU(d), x, "•file.RealPath"));
|
||||
}
|
||||
|
||||
B rename_c2(B d, B w, B x) {
|
||||
d = nfn_objU(d);
|
||||
B p = path_rel(d, w);
|
||||
if (path_rename(path_rel(d, x), p)) return p;
|
||||
thrM("(file).Rename: Failed to rename file");
|
||||
B p = path_rel(d, w, "•file.Rename");
|
||||
if (path_rename(path_rel(d, x, "•file.Rename"), p)) return p;
|
||||
thrM("•file.Rename: Failed to rename file");
|
||||
}
|
||||
|
||||
B remove_c1(B d, B x) {
|
||||
if (path_remove(path_rel(nfn_objU(d), x))) return m_i32(1);
|
||||
thrM("(file).Remove: Failed to remove file");
|
||||
if (path_remove(path_rel(nfn_objU(d), x, "•file.Remove"))) return m_i32(1);
|
||||
thrM("•file.Remove: Failed to remove file");
|
||||
}
|
||||
|
||||
B ftype_c1(B d, B x) {
|
||||
char ty = path_type(path_rel(nfn_objU(d), x));
|
||||
char ty = path_type(path_rel(nfn_objU(d), x, "•file.Type"));
|
||||
if (ty==0) thrM("•file.Type: Error while accessing file");
|
||||
return m_c32(ty);
|
||||
}
|
||||
|
||||
B fcreated_c1 (B d, B x) { return path_info(path_rel(nfn_objU(d), x), 0); }
|
||||
B faccessed_c1(B d, B x) { return path_info(path_rel(nfn_objU(d), x), 1); }
|
||||
B fmodified_c1(B d, B x) { return path_info(path_rel(nfn_objU(d), x), 2); }
|
||||
B fsize_c1 (B d, B x) { return path_info(path_rel(nfn_objU(d), x), 3); }
|
||||
B fcreated_c1 (B d, B x) { return path_info(path_rel(nfn_objU(d), x, "•file.Created"), 0); }
|
||||
B faccessed_c1(B d, B x) { return path_info(path_rel(nfn_objU(d), x, "•file.Accessed"), 1); }
|
||||
B fmodified_c1(B d, B x) { return path_info(path_rel(nfn_objU(d), x, "•file.Modified"), 2); }
|
||||
B fsize_c1 (B d, B x) { return path_info(path_rel(nfn_objU(d), x, "•file.Size"), 3); }
|
||||
|
||||
B fexists_c1(B d, B x) {
|
||||
char ty = path_type(path_rel(nfn_objU(d), x));
|
||||
char ty = path_type(path_rel(nfn_objU(d), x, "•file.Exists"));
|
||||
return m_f64(ty!=0);
|
||||
}
|
||||
|
||||
B fName_c1(B t, B x) {
|
||||
if (isAtm(x) || RNK(x)!=1) thrM("•file.Name: Argument must be a string");
|
||||
if (!isStr(x)) thrM("•file.Name: Argument must be a string");
|
||||
return path_name(x);
|
||||
}
|
||||
B fParent_c1(B t, B x) {
|
||||
if (isAtm(x) || RNK(x)!=1) thrM("•file.Parent: Argument must be a string");
|
||||
if (!isStr(x)) thrM("•file.Parent: Argument must be a string");
|
||||
return path_parent(x);
|
||||
}
|
||||
|
||||
B mapBytes_c1(B d, B x) {
|
||||
return mmap_file(path_rel(nfn_objU(d), x));
|
||||
return mmap_file(path_rel(nfn_objU(d), x, "•file.MapBytes"));
|
||||
}
|
||||
|
||||
B unixTime_c1(B t, B x) {
|
||||
|
||||
@ -228,6 +228,7 @@ char* pm1_repr(u8 u);
|
||||
char* pm2_repr(u8 u);
|
||||
char* eltype_repr(u8 u);
|
||||
bool isPureFn(B x); // doesn't consume
|
||||
bool isStr(B x); // doesn't consume; returns if x is a rank 1 array of characters (includes any empty array)
|
||||
B bqn_merge(B x, u32 type); // consumes
|
||||
|
||||
B any_squeeze(B x); // consumes; accepts any array, returns one with the smallest type (doesn't recurse!)
|
||||
|
||||
@ -1011,7 +1011,7 @@ B ffiload_c2(B t, B w, B x) {
|
||||
|
||||
char* ws = NULL;
|
||||
if (w.u != m_c32(0).u) {
|
||||
w = path_rel(nfn_objU(t), w);
|
||||
w = path_rel(nfn_objU(t), w, "•FFI");
|
||||
ws = toCStr(w);
|
||||
}
|
||||
void* dl = dlopen(ws, RTLD_NOW);
|
||||
|
||||
@ -235,7 +235,6 @@ B bqn_exec(B str, B path, B args) { // consumes all
|
||||
return evalFunBlockConsume(bqn_comp(str, path, args));
|
||||
}
|
||||
|
||||
bool isStr(B x);
|
||||
B str_all, str_none;
|
||||
void init_comp(B* new_re, B* prev_re, B prim, B sys) {
|
||||
if (q_N(prim)) {
|
||||
|
||||
@ -491,7 +491,7 @@ static NOINLINE i64 readInt(char** p) {
|
||||
}
|
||||
|
||||
static NOINLINE B path_rel_dec(B base, B rel) {
|
||||
B res = path_rel(base, rel);
|
||||
B res = path_rel(base, rel, "main.c");
|
||||
dec(base);
|
||||
return res;
|
||||
}
|
||||
|
||||
@ -115,23 +115,14 @@ B path_lines(B path) { // consumes; TODO rewrite this, it's horrible
|
||||
}
|
||||
|
||||
|
||||
static NOINLINE void guaranteeStr(B x) { // assumes x is an array
|
||||
if (elChr(TI(x,elType))) return;
|
||||
usz xia = IA(x);
|
||||
SGetU(x)
|
||||
for (usz i = 0; i < xia; i++) if (!isC32(GetU(x, i))) thrM("Path wasn't a list of characters");
|
||||
}
|
||||
|
||||
|
||||
B path_rel(B base, B rel) { // consumes rel; assumes base is a char vector or bi_N
|
||||
B path_rel(B base, B rel, char* name) {
|
||||
assert(isArr(base) || q_N(base));
|
||||
if (!isArr(rel) || RNK(rel)!=1) thrM("Path wasn't a list of characters");
|
||||
SGetU(rel)
|
||||
if (!isStr(rel)) thrF("%U: Path must be a list of characters", name);
|
||||
usz ria = IA(rel);
|
||||
if (RNK(rel)!=1) thrM("Path wasn't a list of characters");
|
||||
guaranteeStr(rel);
|
||||
if (ria>0 && isAbsolutePath(rel)) return rel;
|
||||
if (q_N(base)) thrM("Using relative path with no absolute base path known");
|
||||
if (q_N(base)) thrM("%U: Using relative path with no absolute base path known");
|
||||
if (ria==0) { dec(rel); return incG(base); }
|
||||
usz bia = IA(base);
|
||||
if (bia==0) return rel;
|
||||
@ -141,17 +132,17 @@ B path_rel(B base, B rel) { // consumes rel; assumes base is a char vector or bi
|
||||
usz ri = 0;
|
||||
for (usz i = 0; i < bia-(has?1:0); i++) rp[ri++] = o2cG(GetU(base, i));
|
||||
rp[ri++] = PREFERRED_SEP;
|
||||
SGetU(rel)
|
||||
for (usz i = 0; i < ria; i++) rp[ri++] = o2cG(GetU(rel, i));
|
||||
dec(rel);
|
||||
return r;
|
||||
}
|
||||
|
||||
B path_parent(B path) {
|
||||
assert(isArr(path));
|
||||
SGetU(path)
|
||||
assert(isStr(path));
|
||||
usz pia = IA(path);
|
||||
if (pia==0) thrM("Empty file path");
|
||||
guaranteeStr(path);
|
||||
if (pia==0) thrM("•file.Parent: Path must be non-empty");
|
||||
SGetU(path)
|
||||
for (i64 i = (i64)pia-2; i >= 0; i--) {
|
||||
if (isPathSep(o2cG(GetU(path, i)))) return taga(arr_shVec(TI(path,slice)(path, 0, i+1)));
|
||||
}
|
||||
@ -162,10 +153,9 @@ B path_parent(B path) {
|
||||
}
|
||||
B path_name(B path) {
|
||||
assert(isArr(path));
|
||||
SGetU(path)
|
||||
usz pia = IA(path);
|
||||
if (pia==0) thrM("Empty file path");
|
||||
guaranteeStr(path);
|
||||
if (pia==0) thrM("•file.Name: Path must be non-empty");
|
||||
SGetU(path)
|
||||
for (i64 i = (i64)pia-1; i >= 0; i--) {
|
||||
if (isPathSep(o2cG(GetU(path, i)))) {
|
||||
if (i == pia-1) thrF("File path ended with a slash: \"%R\"", path);
|
||||
@ -176,6 +166,7 @@ B path_name(B path) {
|
||||
}
|
||||
|
||||
B path_abs(B path) {
|
||||
assert(isStr(path));
|
||||
#if WASM
|
||||
return path; // lazy
|
||||
#else
|
||||
@ -334,9 +325,7 @@ void mmap_init(void) {
|
||||
// use default canStore
|
||||
}
|
||||
#else
|
||||
B mmap_file(B path) {
|
||||
thrM("CBQN was compiled without •file.MapBytes support");
|
||||
}
|
||||
B mmap_file(B path) { thrM("CBQN was compiled without •file.MapBytes support"); }
|
||||
void mmap_init() { }
|
||||
#endif
|
||||
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
#pragma once
|
||||
#include "utf.h"
|
||||
// unless otherwise specified, these assume that paths are strings
|
||||
|
||||
B path_rel(B base, B rel); // consumes rel; assumes base is a char vector or bi_N
|
||||
B path_rel(B base, B rel, char* name); // consumes rel; assumes base is a string or bi_N, throws if !isStr(rel)
|
||||
B path_parent(B path); // consumes; returns the containing directory, with trailing slash
|
||||
B path_name(B path); // consumes; returns filename from a path
|
||||
B path_abs(B path); // consumes; returns absolute version of the path; propagates bi_N
|
||||
|
||||
20
src/vm.c
20
src/vm.c
@ -1667,18 +1667,16 @@ void unwindCompiler() {
|
||||
#endif
|
||||
}
|
||||
|
||||
NOINLINE bool isStr(B x) {
|
||||
if (isAtm(x) || RNK(x)!=1) return false;
|
||||
if (elChr(TI(x,elType))) return true;
|
||||
usz ia = IA(x); SGetU(x)
|
||||
for (usz i = 0; i < ia; i++) if (!isC32(GetU(x,i))) return false;
|
||||
return true;
|
||||
}
|
||||
NOINLINE void printErrMsg(B msg) {
|
||||
if (isArr(msg)) {
|
||||
if (!elChr(TI(msg,elType))) {
|
||||
SGetU(msg)
|
||||
usz msgLen = IA(msg);
|
||||
for (usz i = 0; i < msgLen; i++) if (!isC32(GetU(msg,i))) goto base;
|
||||
}
|
||||
fprintsB(stderr, msg);
|
||||
return;
|
||||
}
|
||||
base:
|
||||
fprintI(stderr, msg);
|
||||
if (isStr(msg)) fprintsB(stderr, msg);
|
||||
else fprintI(stderr, msg);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
|
||||
# bad •FFI invocation
|
||||
# generally weird
|
||||
!"•FFI: Path must be a list of characters" % 1‿2 •FFI ""‿"bqn_init"
|
||||
"file that doesn't exist" •FFI⎊'e' ""‿"abort" %% 'e'
|
||||
@ •FFI⎊'e' ""‿"bqn_this symbol doesn't exist" %% 'e'
|
||||
!"FFI: Type must be a string" % @•FFI "hello"
|
||||
|
||||
@ -86,6 +86,8 @@ f←•ReBQN{primitives⇐⋈'÷'‿- ⋄ system⇐⟨"primitives", "foo"‿⋈,
|
||||
•file.Exists "/dev/file that doesn't exist" %% 0
|
||||
!"Couldn't read file ""/dev/file that doesn't exist""" % •FChars "/dev/file that doesn't exist"
|
||||
! (•path∾"/") ≡ •file.Parent •file.At "hi"
|
||||
!"•file.Parent: Path must be non-empty" % •file.Parent ""
|
||||
!"•file.Name: Path must be non-empty" % •file.Name ""
|
||||
|
||||
!"•file.Lines: 𝕩 must be a list" % "testfile.bqn" •FLines 2‿2⥊<"abcd"
|
||||
!"•file.Lines: 𝕩 must be a list" % "testfile.bqn" •file.Lines <"abcd"
|
||||
@ -96,8 +98,6 @@ f←•ReBQN{primitives⇐⋈'÷'‿- ⋄ system⇐⟨"primitives", "foo"‿⋈,
|
||||
!"•file.Chars: 𝕩 must be a list of characters" % "testfile.bqn" •FChars <'a'
|
||||
!"Expected character" % "testfile.bqn" •FBytes ⋈"abcd"
|
||||
!"•file.Bytes: 𝕩 must be a list" % "testfile.bqn" •file.Bytes <'a'
|
||||
!"Path wasn't a list of characters" % 'a' •FChars "hi"
|
||||
!"Path wasn't a list of characters" % (4‿2⥊"testfile.bqn") •file.Chars "hi"
|
||||
!"Expected character" % "badwrite"•FChars ""‿""
|
||||
!"Expected character" % "badwrite"•FLines ⟨⟨"foo"⟩⟩
|
||||
!"•file.Chars: 𝕩 must be a list of characters" % "badwrite"•FChars @
|
||||
@ -116,11 +116,11 @@ f←•ReBQN{primitives⇐⋈'÷'‿- ⋄ system⇐⟨"primitives", "foo"‿⋈,
|
||||
•FLines "testfile.bqn" %% "abc"‿"def𝕩"
|
||||
|
||||
"testfile3B.bqn" •file.Rename "testfile3.bqn"
|
||||
!"(file).Rename: Failed to rename file" % "testfile3B.bqn" •file.Rename "testfile.bqn"
|
||||
!"•file.Rename: Failed to rename file" % "testfile3B.bqn" •file.Rename "testfile.bqn"
|
||||
•file.Exists¨ "testfile.bqn"‿"nontestfile.bqn"‿"testfile3.bqn"‿"testfile3B.bqn" %% 1‿0‿0‿1
|
||||
¯22↑•file.Createdir "testdirNested" %% "/testDir/testdirNested"
|
||||
¯8↑•file.RealPath "testdirNested/.././" %% "/testDir"
|
||||
!"(file).CreateDir: Failed to create directory" % •file.Createdir "testdirNested"
|
||||
!"•file.CreateDir: Failed to create directory" % •file.Createdir "testdirNested"
|
||||
"testfile.bqn"‿"testfile2.bqn"‿"testfile3.bqn"‿"testfile3B.bqn"‿"testdirNested" ∊ •file.List "." %% 1‿1‿0‿1‿1
|
||||
"testfile.bqn"‿"testfile2.bqn"‿"testfile3.bqn"‿"testfile3B.bqn"‿"testdirNested" ∊ •file.List •file.path %% 1‿1‿0‿1‿1
|
||||
|
||||
@ -132,6 +132,36 @@ f←•ReBQN{primitives⇐⋈'÷'‿- ⋄ system⇐⟨"primitives", "foo"‿⋈,
|
||||
!"Failed to resolve ""/dev/file that doesn't exist"": No such file or directory" % •file.RealPath "/dev/file that doesn't exist"
|
||||
!"Failed to resolve ""/dev/file that doesn't exist"": No such file or directory" % •Import "/dev/file that doesn't exist"
|
||||
|
||||
# bad paths
|
||||
!"•file.At: Path must be a list of characters" % •file.At 1‿2
|
||||
!"•file.At: Path must be a list of characters" % •file.At 1‿2
|
||||
!"•file.At: Path must be a list of characters" % "foo" •file.At 1‿2
|
||||
!"•file.At: 𝕨 must be a string" % 1‿2 •file.At "foo"
|
||||
!"•file.List: Path must be a list of characters" % •file.List 1‿2
|
||||
!"•file.Bytes: Path must be a list of characters" % •file.Bytes 1‿2
|
||||
!"•file.Chars: Path must be a list of characters" % •file.Chars 1‿2
|
||||
!"•file.Lines: Path must be a list of characters" % •file.Lines 1‿2
|
||||
!"•file.Bytes: Path must be a list of characters" % 1‿2 •FBytes "abc"
|
||||
!"•file.Chars: Path must be a list of characters" % 1‿2 •FChars "abc"
|
||||
!"•file.Lines: Path must be a list of characters" % 1‿2 •FLines "abc"‿"def"
|
||||
!"•file.Type: Path must be a list of characters" % •file.Type 1‿2
|
||||
!"•file.Exists: Path must be a list of characters" % •file.Exists 1‿2
|
||||
!"•file.Name: Argument must be a string" % •file.Name 1‿2
|
||||
!"•file.Parent: Argument must be a string" % •file.Parent 1‿2
|
||||
!"•file.MapBytes: Path must be a list of characters" % •file.MapBytes 1‿2
|
||||
!"•file.CreateDir: Path must be a list of characters" % •file.CreateDir 1‿2
|
||||
!"•file.RealPath: Path must be a list of characters" % •file.RealPath 1‿2
|
||||
!"•file.Rename: Path must be a list of characters" % "foo" •file.Rename 1‿2
|
||||
!"•file.Rename: Path must be a list of characters" % 1‿2 •file.Rename "foo"
|
||||
!"•file.Remove: Path must be a list of characters" % •file.Remove 1‿2
|
||||
!"•file.Created: Path must be a list of characters" % •file.Created 1‿2
|
||||
!"•file.Modified: Path must be a list of characters" % •file.Modified 1‿2
|
||||
!"•file.Accessed: Path must be a list of characters" % •file.Accessed 1‿2
|
||||
!"•file.Size: Path must be a list of characters" % •file.Size 1‿2
|
||||
!"•file.Size: Path must be a list of characters" % •file.Size 2‿2⥊"abcd"
|
||||
|
||||
|
||||
|
||||
# some of •term
|
||||
!"Expected character" % •term.OutRaw 2⥊{⇐}
|
||||
!"•term.ErrRaw: 𝕩 must be a list" % •term.ErrRaw 2‿2⥊0
|
||||
|
||||
Loading…
Reference in New Issue
Block a user