🚀 crashed coroutine will not make main thread crash.
This commit is contained in:
parent
ae85791f01
commit
405175061a
1
makefile
1
makefile
|
@ -31,7 +31,6 @@ test:nasal
|
||||||
@ ./nasal -o -e test/ascii-art.nas
|
@ ./nasal -o -e test/ascii-art.nas
|
||||||
@ ./nasal -o -c test/auto_crash.nas
|
@ ./nasal -o -c test/auto_crash.nas
|
||||||
@ ./nasal -o -a -c test/bf.nas
|
@ ./nasal -o -a -c test/bf.nas
|
||||||
@ ./nasal -o -a -c test/bfcolored.nas
|
|
||||||
@ ./nasal -o -a -c test/bfconvertor.nas
|
@ ./nasal -o -a -c test/bfconvertor.nas
|
||||||
@ ./nasal -o -e -d test/bfs.nas
|
@ ./nasal -o -e -d test/bfs.nas
|
||||||
@ ./nasal -o -t test/bigloop.nas
|
@ ./nasal -o -t test/bigloop.nas
|
||||||
|
|
|
@ -1047,21 +1047,21 @@ nas_ref builtin_cocreate(nas_ref* local,nasal_gc& gc)
|
||||||
if(gc.cort)
|
if(gc.cort)
|
||||||
return nas_err("coroutine::create","cannot create another coroutine in a coroutine");
|
return nas_err("coroutine::create","cannot create another coroutine in a coroutine");
|
||||||
nas_ref co=gc.alloc(vm_co);
|
nas_ref co=gc.alloc(vm_co);
|
||||||
nas_co& coroutine=co.co();
|
nas_co& cort=co.co();
|
||||||
coroutine.pc=func.func().entry-1;
|
cort.pc=func.func().entry-1;
|
||||||
|
|
||||||
coroutine.top[0]=nil;
|
cort.top[0]=nil;
|
||||||
coroutine.localr=coroutine.top+1;
|
cort.localr=cort.top+1;
|
||||||
coroutine.top=coroutine.localr+func.func().lsize;
|
cort.top=cort.localr+func.func().lsize;
|
||||||
coroutine.localr[0]=func.func().local[0];
|
cort.localr[0]=func.func().local[0];
|
||||||
coroutine.top[0]=nil; // old upvalr
|
cort.top[0]=nil; // old upvalr
|
||||||
coroutine.top++;
|
cort.top++;
|
||||||
coroutine.top[0]={vm_addr,(nas_ref*)nullptr}; // old localr
|
cort.top[0]={vm_addr,(nas_ref*)nullptr}; // old localr
|
||||||
coroutine.top++;
|
cort.top++;
|
||||||
coroutine.top[0]={vm_ret,(u32)0}; // old pc, set to zero to make op_ret recognizing this as coroutine function
|
cort.top[0]={vm_ret,(u32)0}; // old pc, set to zero to make op_ret recognizing this as coroutine function
|
||||||
|
|
||||||
coroutine.funcr=func; // make sure the coroutine function can use correct upvalues
|
cort.funcr=func; // make sure the coroutine function can use correct upvalues
|
||||||
coroutine.status=nas_co::suspended;
|
cort.status=nas_co::suspended;
|
||||||
|
|
||||||
return co;
|
return co;
|
||||||
}
|
}
|
||||||
|
|
12
nasal_gc.h
12
nasal_gc.h
|
@ -32,20 +32,20 @@ const u32 ini[gc_tsize]=
|
||||||
128, // vm_str
|
128, // vm_str
|
||||||
128, // vm_vec
|
128, // vm_vec
|
||||||
32, // vm_hash
|
32, // vm_hash
|
||||||
512, // vm_func
|
128, // vm_func
|
||||||
512, // vm_upval
|
0, // vm_upval
|
||||||
0, // vm_obj
|
0, // vm_obj
|
||||||
0 // vm_co
|
0 // vm_co
|
||||||
};
|
};
|
||||||
const u32 incr[gc_tsize]=
|
const u32 incr[gc_tsize]=
|
||||||
{
|
{
|
||||||
256, // vm_str
|
256, // vm_str
|
||||||
512, // vm_vec
|
256, // vm_vec
|
||||||
512, // vm_hash
|
256, // vm_hash
|
||||||
512, // vm_func
|
128, // vm_func
|
||||||
128, // vm_upval
|
128, // vm_upval
|
||||||
128, // vm_obj
|
128, // vm_obj
|
||||||
16 // vm_co
|
32 // vm_co
|
||||||
};
|
};
|
||||||
|
|
||||||
struct nas_vec; // vector
|
struct nas_vec; // vector
|
||||||
|
|
89
nasal_vm.h
89
nasal_vm.h
|
@ -45,7 +45,8 @@ protected:
|
||||||
void lstate();
|
void lstate();
|
||||||
void ustate();
|
void ustate();
|
||||||
void detail();
|
void detail();
|
||||||
[[noreturn]] void die(const string&);
|
void die(const string&);
|
||||||
|
#define vm_error(info) {die(info);return;}
|
||||||
/* vm calculation functions*/
|
/* vm calculation functions*/
|
||||||
bool condition(nas_ref);
|
bool condition(nas_ref);
|
||||||
/* vm operands */
|
/* vm operands */
|
||||||
|
@ -306,8 +307,6 @@ void nasal_vm::detail()
|
||||||
lstate();
|
lstate();
|
||||||
ustate();
|
ustate();
|
||||||
}
|
}
|
||||||
|
|
||||||
[[noreturn]]
|
|
||||||
void nasal_vm::die(const string& str)
|
void nasal_vm::die(const string& str)
|
||||||
{
|
{
|
||||||
std::cout<<"[vm] error: "<<str<<"\n";
|
std::cout<<"[vm] error: "<<str<<"\n";
|
||||||
|
@ -315,7 +314,13 @@ void nasal_vm::die(const string& str)
|
||||||
stackinfo();
|
stackinfo();
|
||||||
if(detail_info)
|
if(detail_info)
|
||||||
detail();
|
detail();
|
||||||
|
if(gc.stack==stack){
|
||||||
std::exit(1);
|
std::exit(1);
|
||||||
|
}else{
|
||||||
|
pc=0; // mark coroutine 'dead'
|
||||||
|
gc.ctxreserve();
|
||||||
|
top[0]=nil;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
inline bool nasal_vm::condition(nas_ref val)
|
inline bool nasal_vm::condition(nas_ref val)
|
||||||
{
|
{
|
||||||
|
@ -436,7 +441,7 @@ inline void nasal_vm::o_unot()
|
||||||
else
|
else
|
||||||
top[0]=num?zero:one;
|
top[0]=num?zero:one;
|
||||||
}break;
|
}break;
|
||||||
default:die("incorrect value type");break;
|
default:vm_error("incorrect value type");break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
inline void nasal_vm::o_usub()
|
inline void nasal_vm::o_usub()
|
||||||
|
@ -581,7 +586,7 @@ inline void nasal_vm::o_jf()
|
||||||
inline void nasal_vm::o_cnt()
|
inline void nasal_vm::o_cnt()
|
||||||
{
|
{
|
||||||
if(top[0].type!=vm_vec)
|
if(top[0].type!=vm_vec)
|
||||||
die("must use vector in forindex/foreach");
|
vm_error("must use vector in forindex/foreach");
|
||||||
(++top)[0]={vm_cnt,(i64)-1};
|
(++top)[0]={vm_cnt,(i64)-1};
|
||||||
}
|
}
|
||||||
inline void nasal_vm::o_findex()
|
inline void nasal_vm::o_findex()
|
||||||
|
@ -626,15 +631,15 @@ inline void nasal_vm::o_callv()
|
||||||
{
|
{
|
||||||
top[0]=vec.vec().get_val(val.tonum());
|
top[0]=vec.vec().get_val(val.tonum());
|
||||||
if(top[0].type==vm_none)
|
if(top[0].type==vm_none)
|
||||||
die("index out of range:"+std::to_string(val.tonum()));
|
vm_error("index out of range:"+std::to_string(val.tonum()));
|
||||||
}
|
}
|
||||||
else if(vec.type==vm_hash)
|
else if(vec.type==vm_hash)
|
||||||
{
|
{
|
||||||
if(val.type!=vm_str)
|
if(val.type!=vm_str)
|
||||||
die("must use string as the key");
|
vm_error("must use string as the key");
|
||||||
top[0]=vec.hash().get_val(val.str());
|
top[0]=vec.hash().get_val(val.str());
|
||||||
if(top[0].type==vm_none)
|
if(top[0].type==vm_none)
|
||||||
die("cannot find member \""+val.str()+"\"");
|
vm_error("cannot find member \""+val.str()+"\"");
|
||||||
if(top[0].type==vm_func)
|
if(top[0].type==vm_func)
|
||||||
top[0].func().local[0]=val;// 'me'
|
top[0].func().local[0]=val;// 'me'
|
||||||
}
|
}
|
||||||
|
@ -644,33 +649,30 @@ inline void nasal_vm::o_callv()
|
||||||
i32 num=val.tonum();
|
i32 num=val.tonum();
|
||||||
i32 len=str.length();
|
i32 len=str.length();
|
||||||
if(num<-len || num>=len)
|
if(num<-len || num>=len)
|
||||||
die("index out of range:"+std::to_string(val.tonum()));
|
vm_error("index out of range:"+std::to_string(val.tonum()));
|
||||||
top[0]={vm_num,f64((u8)str[num>=0? num:num+len])};
|
top[0]={vm_num,f64((u8)str[num>=0? num:num+len])};
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
die("must call a vector/hash/string");
|
vm_error("must call a vector/hash/string");
|
||||||
}
|
}
|
||||||
inline void nasal_vm::o_callvi()
|
inline void nasal_vm::o_callvi()
|
||||||
{
|
{
|
||||||
nas_ref val=top[0];
|
nas_ref val=top[0];
|
||||||
if(val.type!=vm_vec)
|
if(val.type!=vm_vec)
|
||||||
die("must use a vector");
|
vm_error("must use a vector");
|
||||||
|
|
||||||
// cannot use operator[],because this may cause overflow
|
// cannot use operator[],because this may cause overflow
|
||||||
(++top)[0]=val.vec().get_val(imm[pc]);
|
(++top)[0]=val.vec().get_val(imm[pc]);
|
||||||
if(top[0].type==vm_none)
|
if(top[0].type==vm_none)
|
||||||
die("index out of range:"+std::to_string(imm[pc]));
|
vm_error("index out of range:"+std::to_string(imm[pc]));
|
||||||
}
|
}
|
||||||
inline void nasal_vm::o_callh()
|
inline void nasal_vm::o_callh()
|
||||||
{
|
{
|
||||||
nas_ref val=top[0];
|
nas_ref val=top[0];
|
||||||
if(val.type!=vm_hash)
|
if(val.type!=vm_hash)
|
||||||
die("must call a hash");
|
vm_error("must call a hash");
|
||||||
|
|
||||||
top[0]=val.hash().get_val(str_table[imm[pc]]);
|
top[0]=val.hash().get_val(str_table[imm[pc]]);
|
||||||
if(top[0].type==vm_none)
|
if(top[0].type==vm_none)
|
||||||
die("member \""+str_table[imm[pc]]+"\" does not exist");
|
vm_error("member \""+str_table[imm[pc]]+"\" does not exist");
|
||||||
|
|
||||||
if(top[0].type==vm_func)
|
if(top[0].type==vm_func)
|
||||||
top[0].func().local[0]=val;// 'me'
|
top[0].func().local[0]=val;// 'me'
|
||||||
}
|
}
|
||||||
|
@ -679,19 +681,18 @@ inline void nasal_vm::o_callfv()
|
||||||
u32 argc=imm[pc]; // arguments counter
|
u32 argc=imm[pc]; // arguments counter
|
||||||
nas_ref* local=top-argc+1; // arguments begin address
|
nas_ref* local=top-argc+1; // arguments begin address
|
||||||
if(local[-1].type!=vm_func)
|
if(local[-1].type!=vm_func)
|
||||||
die("must call a function");
|
vm_error("must call a function");
|
||||||
|
|
||||||
auto& func=local[-1].func();
|
auto& func=local[-1].func();
|
||||||
nas_ref tmp=local[-1];
|
nas_ref tmp=local[-1];
|
||||||
local[-1]=funcr;
|
local[-1]=funcr;
|
||||||
funcr=tmp;
|
funcr=tmp;
|
||||||
// top-argc+lsize(local) +1(old pc) +1(old localr) +1(old upvalr)
|
// top-argc+lsize(local) +1(old pc) +1(old localr) +1(old upvalr)
|
||||||
if(top-argc+func.lsize+3>=canary)
|
if(top-argc+func.lsize+3>=canary)
|
||||||
die("stack overflow");
|
vm_error("stack overflow");
|
||||||
// parameter size is func->psize-1, 1 is reserved for "me"
|
// parameter size is func->psize-1, 1 is reserved for "me"
|
||||||
u32 psize=func.psize-1;
|
u32 psize=func.psize-1;
|
||||||
if(argc<psize && func.local[argc+1].type==vm_none)
|
if(argc<psize && func.local[argc+1].type==vm_none)
|
||||||
die("lack argument(s)");
|
vm_error("lack argument(s)");
|
||||||
|
|
||||||
nas_ref dynamic=nil;
|
nas_ref dynamic=nil;
|
||||||
top=local+func.lsize;
|
top=local+func.lsize;
|
||||||
|
@ -726,17 +727,16 @@ inline void nasal_vm::o_callfh()
|
||||||
{
|
{
|
||||||
auto& hash=top[0].hash().elems;
|
auto& hash=top[0].hash().elems;
|
||||||
if(top[-1].type!=vm_func)
|
if(top[-1].type!=vm_func)
|
||||||
die("must call a function");
|
vm_error("must call a function");
|
||||||
|
|
||||||
auto& func=top[-1].func();
|
auto& func=top[-1].func();
|
||||||
nas_ref tmp=top[-1];
|
nas_ref tmp=top[-1];
|
||||||
top[-1]=funcr;
|
top[-1]=funcr;
|
||||||
funcr=tmp;
|
funcr=tmp;
|
||||||
// top -1(hash) +lsize(local) +1(old pc) +1(old localr) +1(old upvalr)
|
// top -1(hash) +lsize(local) +1(old pc) +1(old localr) +1(old upvalr)
|
||||||
if(top+func.lsize+2>=canary)
|
if(top+func.lsize+2>=canary)
|
||||||
die("stack overflow");
|
vm_error("stack overflow");
|
||||||
if(func.dpara>=0)
|
if(func.dpara>=0)
|
||||||
die("special call cannot use dynamic argument");
|
vm_error("special call cannot use dynamic argument");
|
||||||
|
|
||||||
nas_ref* local=top;
|
nas_ref* local=top;
|
||||||
top+=func.lsize;
|
top+=func.lsize;
|
||||||
|
@ -749,7 +749,7 @@ inline void nasal_vm::o_callfh()
|
||||||
if(hash.count(key))
|
if(hash.count(key))
|
||||||
local[i.second]=hash[key];
|
local[i.second]=hash[key];
|
||||||
else if(local[i.second].type==vm_none)
|
else if(local[i.second].type==vm_none)
|
||||||
die("lack argument(s): \""+key+"\"");
|
vm_error("lack argument(s): \""+key+"\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
top[0]=upvalr;
|
top[0]=upvalr;
|
||||||
|
@ -769,7 +769,7 @@ inline void nasal_vm::o_callb()
|
||||||
// (top) will be set to another context.top, instead of main_context.top
|
// (top) will be set to another context.top, instead of main_context.top
|
||||||
top[0]=(*builtin[imm[pc]].func)(localr,gc);
|
top[0]=(*builtin[imm[pc]].func)(localr,gc);
|
||||||
if(top[0].type==vm_none)
|
if(top[0].type==vm_none)
|
||||||
die("native function error");
|
vm_error("native function error");
|
||||||
}
|
}
|
||||||
inline void nasal_vm::o_slcbeg()
|
inline void nasal_vm::o_slcbeg()
|
||||||
{
|
{
|
||||||
|
@ -780,7 +780,7 @@ inline void nasal_vm::o_slcbeg()
|
||||||
// +--------------+
|
// +--------------+
|
||||||
(++top)[0]=gc.alloc(vm_vec);
|
(++top)[0]=gc.alloc(vm_vec);
|
||||||
if(top[-1].type!=vm_vec)
|
if(top[-1].type!=vm_vec)
|
||||||
die("must slice a vector");
|
vm_error("must slice a vector");
|
||||||
}
|
}
|
||||||
inline void nasal_vm::o_slcend()
|
inline void nasal_vm::o_slcend()
|
||||||
{
|
{
|
||||||
|
@ -792,7 +792,7 @@ inline void nasal_vm::o_slc()
|
||||||
nas_ref val=(top--)[0];
|
nas_ref val=(top--)[0];
|
||||||
nas_ref res=top[-1].vec().get_val(val.tonum());
|
nas_ref res=top[-1].vec().get_val(val.tonum());
|
||||||
if(res.type==vm_none)
|
if(res.type==vm_none)
|
||||||
die("index out of range:"+std::to_string(val.tonum()));
|
vm_error("index out of range:"+std::to_string(val.tonum()));
|
||||||
top[0].vec().elems.push_back(res);
|
top[0].vec().elems.push_back(res);
|
||||||
}
|
}
|
||||||
inline void nasal_vm::o_slc2()
|
inline void nasal_vm::o_slc2()
|
||||||
|
@ -816,15 +816,12 @@ inline void nasal_vm::o_slc2()
|
||||||
else if(type1!=vm_nil && type2==vm_nil)
|
else if(type1!=vm_nil && type2==vm_nil)
|
||||||
num2=num1<0? -1:size-1;
|
num2=num1<0? -1:size-1;
|
||||||
|
|
||||||
if(num1>num2)
|
if(num1<-size || num1>=size || num2<-size || num2>=size){
|
||||||
die("begin index must be less than or equal to end index");
|
vm_error("index "+std::to_string(num1)+":"+std::to_string(num2)+" out of range.");
|
||||||
else if(num1<-size || num1>=size)
|
}else if(num1<=num2){
|
||||||
die("begin index out of range: "+std::to_string(num1));
|
|
||||||
else if(num2<-size || num2>=size)
|
|
||||||
die("end index out of range: "+std::to_string(num2));
|
|
||||||
else
|
|
||||||
for(i32 i=num1;i<=num2;++i)
|
for(i32 i=num1;i<=num2;++i)
|
||||||
aim.push_back(i>=0?ref[i]:ref[i+size]);
|
aim.push_back(i>=0?ref[i]:ref[i+size]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
inline void nasal_vm::o_mcallg()
|
inline void nasal_vm::o_mcallg()
|
||||||
{
|
{
|
||||||
|
@ -855,12 +852,11 @@ inline void nasal_vm::o_mcallv()
|
||||||
{
|
{
|
||||||
memr=vec.vec().get_mem(val.tonum());
|
memr=vec.vec().get_mem(val.tonum());
|
||||||
if(!memr)
|
if(!memr)
|
||||||
die("index out of range:"+std::to_string(val.tonum()));
|
vm_error("index out of range:"+std::to_string(val.tonum()));
|
||||||
}
|
}else if(vec.type==vm_hash) // do mcallh but use the mcallv way
|
||||||
else if(vec.type==vm_hash) // do mcallh but use the mcallv way
|
|
||||||
{
|
{
|
||||||
if(val.type!=vm_str)
|
if(val.type!=vm_str)
|
||||||
die("must use string as the key");
|
vm_error("must use string as the key");
|
||||||
nas_hash& ref=vec.hash();
|
nas_hash& ref=vec.hash();
|
||||||
string& str=val.str();
|
string& str=val.str();
|
||||||
memr=ref.get_mem(str);
|
memr=ref.get_mem(str);
|
||||||
|
@ -869,15 +865,14 @@ inline void nasal_vm::o_mcallv()
|
||||||
ref.elems[str]=nil;
|
ref.elems[str]=nil;
|
||||||
memr=ref.get_mem(str);
|
memr=ref.get_mem(str);
|
||||||
}
|
}
|
||||||
}
|
}else
|
||||||
else
|
vm_error("cannot get memory space in this type");
|
||||||
die("cannot get memory space in this type");
|
|
||||||
}
|
}
|
||||||
inline void nasal_vm::o_mcallh()
|
inline void nasal_vm::o_mcallh()
|
||||||
{
|
{
|
||||||
nas_ref hash=top[0]; // mcall hash, reserved on stack to avoid gc
|
nas_ref hash=top[0]; // mcall hash, reserved on stack to avoid gc
|
||||||
if(hash.type!=vm_hash)
|
if(hash.type!=vm_hash)
|
||||||
die("must call a hash");
|
vm_error("must call a hash");
|
||||||
nas_hash& ref=hash.hash();
|
nas_hash& ref=hash.hash();
|
||||||
const string& str=str_table[imm[pc]];
|
const string& str=str_table[imm[pc]];
|
||||||
memr=ref.get_mem(str);
|
memr=ref.get_mem(str);
|
||||||
|
@ -1021,14 +1016,12 @@ void nasal_vm::run(
|
||||||
while(code[pc]){
|
while(code[pc]){
|
||||||
(this->*code[pc])();
|
(this->*code[pc])();
|
||||||
if(top>=canary)
|
if(top>=canary)
|
||||||
break;
|
die("stack overflow");
|
||||||
++pc;
|
++pc;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
vmexit:
|
vmexit:
|
||||||
if(top>=canary)
|
|
||||||
die("stack overflow");
|
|
||||||
if(detail)
|
if(detail)
|
||||||
gc.info();
|
gc.info();
|
||||||
gc.clear();
|
gc.clear();
|
||||||
|
@ -1041,7 +1034,7 @@ vmexit:
|
||||||
op();\
|
op();\
|
||||||
if(top<canary)\
|
if(top<canary)\
|
||||||
goto *code[++pc];\
|
goto *code[++pc];\
|
||||||
goto vmexit;\
|
die("stack overflow");\
|
||||||
}
|
}
|
||||||
// do not cause stackoverflow
|
// do not cause stackoverflow
|
||||||
#define exec_nodie(op) {op();goto *code[++pc];}
|
#define exec_nodie(op) {op();goto *code[++pc];}
|
||||||
|
|
|
@ -44,8 +44,10 @@ var total=1000; # ms
|
||||||
var co=coroutine.create(productor);
|
var co=coroutine.create(productor);
|
||||||
var tm=maketimestamp();
|
var tm=maketimestamp();
|
||||||
|
|
||||||
if(os.platform()=="windows")
|
if(os.platform()=="windows"){
|
||||||
system("chcp 65001");
|
system("chcp 65001");
|
||||||
|
system("color");
|
||||||
|
}
|
||||||
var counter=0;
|
var counter=0;
|
||||||
var bar=process_bar.high_resolution_bar(40);
|
var bar=process_bar.high_resolution_bar(40);
|
||||||
var consumer=func(){
|
var consumer=func(){
|
||||||
|
@ -59,4 +61,18 @@ var consumer=func(){
|
||||||
tm.stamp();
|
tm.stamp();
|
||||||
while(tm.elapsedMSec()<total)
|
while(tm.elapsedMSec()<total)
|
||||||
consumer();
|
consumer();
|
||||||
println("\nexecute ",counter," tasks during ",total," ms, avg ",counter/total," tasks/ms.")
|
println("\nexecute ",counter," tasks during ",total," ms, avg ",counter/total," tasks/ms.");
|
||||||
|
|
||||||
|
var co=coroutine.create(func{
|
||||||
|
var b=func(){b()}
|
||||||
|
coroutine.yield(b);
|
||||||
|
b();
|
||||||
|
coroutine.yield(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
println("coroutine yield: ",coroutine.resume(co));
|
||||||
|
println("coroutine state:\e[32m ",coroutine.status(co),"\e[0m");
|
||||||
|
println("coroutine error: ",coroutine.resume(co));
|
||||||
|
println("coroutine state:\e[91m ",coroutine.status(co),"\e[0m");
|
||||||
|
println("coroutine yield: ",coroutine.resume(co));
|
||||||
|
println("coroutine state:\e[91m ",coroutine.status(co),"\e[0m");
|
|
@ -119,6 +119,6 @@ func(diff){
|
||||||
print("\n");
|
print("\n");
|
||||||
diff(
|
diff(
|
||||||
io.fin("test/bf.nas"),
|
io.fin("test/bf.nas"),
|
||||||
io.fin("test/bfcolored.nas")
|
io.fin("test/bfconvertor.nas")
|
||||||
);
|
);
|
||||||
}(myers);
|
}(myers);
|
Loading…
Reference in New Issue