|
|
@@ -296,6 +296,27 @@ static Value builtin_call(Runtime *rt,const char *name,Value *args,int argc,int
|
|
|
|
if(strcmp(kind,"random")==0){ if(strcmp(fn,"seed")==0){ srand((unsigned)v_number(args[0])); return v_none(); } if(strcmp(fn,"int")==0){ int a=(int)v_number(args[0]), b=(int)v_number(args[1]); int r=a + (rand() % (b-a+1)); return v_num(r); } }
|
|
|
|
if(strcmp(kind,"random")==0){ if(strcmp(fn,"seed")==0){ srand((unsigned)v_number(args[0])); return v_none(); } if(strcmp(fn,"int")==0){ int a=(int)v_number(args[0]), b=(int)v_number(args[1]); int r=a + (rand() % (b-a+1)); return v_num(r); } }
|
|
|
|
if(strcmp(kind,"csv")==0){ if(strcmp(fn,"read")==0){ char *txt=argc>0?v_to_string(args[0]):xstrdup(""); char *del=argc>1?v_to_string(args[1]):xstrdup(","); char d=del[0]?del[0]:','; Value rows=v_list(); const char *p=txt; while(*p){ Value row=v_list(); Str field; str_init(&field); int inq=0; while(*p&&*p!='\n'&&*p!='\r'){ if(*p=='"'){ if(inq&&p[1]=='"'){ str_ch(&field,'"'); p+=2; continue;} inq=!inq; p++; continue;} if(!inq&&*p==d){ list_add(row.list,v_str(field.s?field.s:"")); field.len=0; if(field.s) field.s[0]=0; p++; continue;} str_ch(&field,*p++); } list_add(row.list,v_str(field.s?field.s:"")); free(field.s); list_add(rows.list,row); while(*p=='\r'||*p=='\n') p++; } free(txt); free(del); return rows; }
|
|
|
|
if(strcmp(kind,"csv")==0){ if(strcmp(fn,"read")==0){ char *txt=argc>0?v_to_string(args[0]):xstrdup(""); char *del=argc>1?v_to_string(args[1]):xstrdup(","); char d=del[0]?del[0]:','; Value rows=v_list(); const char *p=txt; while(*p){ Value row=v_list(); Str field; str_init(&field); int inq=0; while(*p&&*p!='\n'&&*p!='\r'){ if(*p=='"'){ if(inq&&p[1]=='"'){ str_ch(&field,'"'); p+=2; continue;} inq=!inq; p++; continue;} if(!inq&&*p==d){ list_add(row.list,v_str(field.s?field.s:"")); field.len=0; if(field.s) field.s[0]=0; p++; continue;} str_ch(&field,*p++); } list_add(row.list,v_str(field.s?field.s:"")); free(field.s); list_add(rows.list,row); while(*p=='\r'||*p=='\n') p++; } free(txt); free(del); return rows; }
|
|
|
|
if(strcmp(fn,"write")==0){ char *del=argc>1?v_to_string(args[1]):xstrdup(","); char d=del[0]?del[0]:','; Str out; str_init(&out); int i,j; if(argc>0&&args[0].type==V_LIST){ for(i=0;i<args[0].list->count;i++){ Value row=args[0].list->items[i]; if(row.type==V_LIST){ for(j=0;j<row.list->count;j++){ char *f=v_to_string(row.list->items[j]); int quote=strchr(f,d)||strchr(f,'\n')||strchr(f,'"'); if(j) str_ch(&out,d); if(quote){ const char *q=f; str_ch(&out,'"'); while(*q){ if(*q=='"') str_add(&out,"\"\""); else str_ch(&out,*q); q++; } str_ch(&out,'"'); } else str_add(&out,f); free(f); } } str_ch(&out,'\n'); } } free(del); return v_str(out.s?out.s:""); } }
|
|
|
|
if(strcmp(fn,"write")==0){ char *del=argc>1?v_to_string(args[1]):xstrdup(","); char d=del[0]?del[0]:','; Str out; str_init(&out); int i,j; if(argc>0&&args[0].type==V_LIST){ for(i=0;i<args[0].list->count;i++){ Value row=args[0].list->items[i]; if(row.type==V_LIST){ for(j=0;j<row.list->count;j++){ char *f=v_to_string(row.list->items[j]); int quote=strchr(f,d)||strchr(f,'\n')||strchr(f,'"'); if(j) str_ch(&out,d); if(quote){ const char *q=f; str_ch(&out,'"'); while(*q){ if(*q=='"') str_add(&out,"\"\""); else str_ch(&out,*q); q++; } str_ch(&out,'"'); } else str_add(&out,f); free(f); } } str_ch(&out,'\n'); } } free(del); return v_str(out.s?out.s:""); } }
|
|
|
|
|
|
|
|
if(strcmp(kind,"path")==0){
|
|
|
|
|
|
|
|
char *a=argc>0?v_to_string(args[0]):xstrdup("");
|
|
|
|
|
|
|
|
if(strcmp(fn,"join")==0){ char *b=argc>1?v_to_string(args[1]):xstrdup(""); Str out; size_t n=strlen(a); str_init(&out); str_add(&out,a); if(a[0]&&b[0]&&a[n-1]!='/'&&a[n-1]!='\\') str_ch(&out,'/'); while(*b=='/'||*b=='\\') b++; str_add(&out,b); free(a); return v_str(out.s?out.s:""); }
|
|
|
|
|
|
|
|
if(strcmp(fn,"basename")==0){ char *slash=strrchr(a,'/'), *back=strrchr(a,'\\'), *cut=slash; if(back&&(!cut||back>cut)) cut=back; Value r=v_str(cut?cut+1:a); free(a); return r; }
|
|
|
|
|
|
|
|
if(strcmp(fn,"dirname")==0){ char *slash=strrchr(a,'/'), *back=strrchr(a,'\\'), *cut=slash; if(back&&(!cut||back>cut)) cut=back; if(!cut){ free(a); return v_str(""); } if(cut==a){ free(a); return v_str("/"); } *cut=0; { Value r=v_str(a); free(a); return r; } }
|
|
|
|
|
|
|
|
if(strcmp(fn,"ext")==0||strcmp(fn,"stem")==0){ char *slash=strrchr(a,'/'), *back=strrchr(a,'\\'), *base=slash; char *dot; if(back&&(!base||back>base)) base=back; base=base?base+1:a; dot=strrchr(base,'.'); if(!dot||dot==base){ Value r=v_str(strcmp(fn,"stem")==0?base:""); free(a); return r; } if(strcmp(fn,"ext")==0){ Value r=v_str(dot+1); free(a); return r; } *dot=0; { Value r=v_str(base); free(a); return r; } }
|
|
|
|
|
|
|
|
if(strcmp(fn,"absolute")==0){ int yes=(a[0]=='/'||a[0]=='\\'||((isalpha((unsigned char)a[0]))&&a[1]==':')); free(a); return v_bool(yes); }
|
|
|
|
|
|
|
|
free(a);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if(strcmp(kind,"collections")==0){
|
|
|
|
|
|
|
|
Value source=argc>0?args[0]:v_none();
|
|
|
|
|
|
|
|
if(strcmp(fn,"length")==0){ if(source.type==V_LIST&&source.list) return v_num(source.list->count); if(source.type==V_MAP&&source.map) return v_num(source.map->count); return v_num(0); }
|
|
|
|
|
|
|
|
if(strcmp(fn,"contains")==0&&source.type==V_LIST&&source.list){ int i; Value needle=argc>1?args[1]:v_none(); for(i=0;i<source.list->count;i++) if(value_compare(source.list->items[i],needle)==0) return v_bool(1); return v_bool(0); }
|
|
|
|
|
|
|
|
if(strcmp(fn,"index")==0&&source.type==V_LIST&&source.list){ int i; Value needle=argc>1?args[1]:v_none(); for(i=0;i<source.list->count;i++) if(value_compare(source.list->items[i],needle)==0) return v_num(i+1); return v_num(0); }
|
|
|
|
|
|
|
|
if(strcmp(fn,"join")==0&&source.type==V_LIST&&source.list){ char *separator=argc>1?v_to_string(args[1]):xstrdup(""); Str out; int i; str_init(&out); for(i=0;i<source.list->count;i++){ char *part=v_to_string(source.list->items[i]); if(i) str_add(&out,separator); str_add(&out,part); free(part); } free(separator); return v_str(out.s?out.s:""); }
|
|
|
|
|
|
|
|
if(strcmp(fn,"reversed")==0&&source.type==V_LIST&&source.list){ Value out=v_list(); int i; for(i=source.list->count-1;i>=0;i--) list_add(out.list,source.list->items[i]); return out; }
|
|
|
|
|
|
|
|
if(strcmp(fn,"has_key")==0&&source.type==V_MAP&&source.map){ char *key=argc>1?v_to_string(args[1]):xstrdup(""); int found=map_index(source.map,key)>=0; free(key); return v_bool(found); }
|
|
|
|
|
|
|
|
if(strcmp(fn,"get")==0&&source.type==V_MAP&&source.map){ char *key=argc>1?v_to_string(args[1]):xstrdup(""); int index=map_index(source.map,key); Value fallback=argc>2?args[2]:v_none(); Value r=index>=0?v_copy(source.map->vals[index]):v_copy(fallback); free(key); return r; }
|
|
|
|
|
|
|
|
if(strcmp(fn,"keys")==0&&source.type==V_MAP&&source.map){ Value out=v_list(); int i; for(i=0;i<source.map->count;i++) list_add(out.list,v_str(source.map->keys[i])); return out; }
|
|
|
|
|
|
|
|
if(strcmp(fn,"values")==0&&source.type==V_MAP&&source.map){ Value out=v_list(); int i; for(i=0;i<source.map->count;i++) list_add(out.list,source.map->vals[i]); return out; }
|
|
|
|
|
|
|
|
}
|
|
|
|
return v_none(); }
|
|
|
|
return v_none(); }
|
|
|
|
|
|
|
|
|
|
|
|
/* ---------- execution ---------- */
|
|
|
|
/* ---------- execution ---------- */
|
|
|
@@ -313,7 +334,7 @@ static int call_object_method(Runtime *rt,const char *name,Value *args,int argc)
|
|
|
|
static void call_function(Runtime *rt,const char *name,Value *args,int argc){ int handled=0,i; Value br=builtin_call(rt,name,args,argc,&handled); if(handled){ rt_set(rt,"RESULT",br); return; } if(call_object_method(rt,name,args,argc)) return; Function *f=find_function(rt,name); if(!f){ rt_error(rt,"",0,"Unknown function: %s",name); return;} Var *old=rt->locals; rt->locals=NULL; for(i=0;i<f->pcnt;i++) rt_set(rt,f->params[i], i<argc?args[i]:v_none()); rt->returning=0; exec_range(rt,f->prog,f->start,f->end); if(rt->returning){ rt_set(rt,"RESULT",rt->ret); rt->returning=0; } rt->locals=old; }
|
|
|
|
static void call_function(Runtime *rt,const char *name,Value *args,int argc){ int handled=0,i; Value br=builtin_call(rt,name,args,argc,&handled); if(handled){ rt_set(rt,"RESULT",br); return; } if(call_object_method(rt,name,args,argc)) return; Function *f=find_function(rt,name); if(!f){ rt_error(rt,"",0,"Unknown function: %s",name); return;} Var *old=rt->locals; rt->locals=NULL; for(i=0;i<f->pcnt;i++) rt_set(rt,f->params[i], i<argc?args[i]:v_none()); rt->returning=0; exec_range(rt,f->prog,f->start,f->end); if(rt->returning){ rt_set(rt,"RESULT",rt->ret); rt->returning=0; } rt->locals=old; }
|
|
|
|
static int stack_has(Runtime *rt,const char *path){ int i; for(i=0;i<rt->import_depth;i++) if(strcmp(rt->import_stack[i],path)==0) return 1; return 0; }
|
|
|
|
static int stack_has(Runtime *rt,const char *path){ int i; for(i=0;i<rt->import_depth;i++) if(strcmp(rt->import_stack[i],path)==0) return 1; return 0; }
|
|
|
|
static int export_allows(char **exports,int ec,const char *name){ int i; if(ec==0) return 1; for(i=0;i<ec;i++) if(strcmp(exports[i],name)==0) return 1; return 0; }
|
|
|
|
static int export_allows(char **exports,int ec,const char *name){ int i; if(ec==0) return 1; for(i=0;i<ec;i++) if(strcmp(exports[i],name)==0) return 1; return 0; }
|
|
|
|
static void do_import(Runtime *rt,const char *path,const char *ns,const char *curfile,int line){ Program *p; char *prefix=NULL; char **exports=NULL; int ec=0,i; if(stack_has(rt,path)){ rt_error(rt,curfile,line,"Import cycle detected at: %s",path); return;} if(strstr(path,"lib/text.claro")&&ns){ add_module(rt,ns,"text"); return;} if(strstr(path,"lib/math.claro")&&ns){ add_module(rt,ns,"math"); return;} if(strstr(path,"lib/random.claro")&&ns){ add_module(rt,ns,"random"); return;} if(strstr(path,"lib/csv.claro")&&ns){ add_module(rt,ns,"csv"); return;} if(strstr(path,"lib/path.claro")&&ns){ add_module(rt,ns,"path"); return;} if(strstr(path,"lib/time.claro")&&ns){ add_module(rt,ns,"time"); return;}
|
|
|
|
static void do_import(Runtime *rt,const char *path,const char *ns,const char *curfile,int line){ Program *p; char *prefix=NULL; char **exports=NULL; int ec=0,i; if(stack_has(rt,path)){ rt_error(rt,curfile,line,"Import cycle detected at: %s",path); return;} if(strstr(path,"lib/text.claro")&&ns){ add_module(rt,ns,"text"); return;} if(strstr(path,"lib/math.claro")&&ns){ add_module(rt,ns,"math"); return;} if(strstr(path,"lib/random.claro")&&ns){ add_module(rt,ns,"random"); return;} if(strstr(path,"lib/csv.claro")&&ns){ add_module(rt,ns,"csv"); return;} if(strstr(path,"lib/path.claro")&&ns){ add_module(rt,ns,"path"); return;} if(strstr(path,"lib/collections.claro")&&ns){ add_module(rt,ns,"collections"); return;} if(strstr(path,"lib/time.claro")&&ns){ add_module(rt,ns,"time"); return;}
|
|
|
|
p=load_program(rt,path); if(!p){ rt_error(rt,curfile,line,"Could not import file: %s",path); return;} rt->import_stack=(char**)xrealloc(rt->import_stack,sizeof(char*)*(rt->import_depth+1)); rt->import_stack[rt->import_depth++]=xstrdup(path);
|
|
|
|
p=load_program(rt,path); if(!p){ rt_error(rt,curfile,line,"Could not import file: %s",path); return;} rt->import_stack=(char**)xrealloc(rt->import_stack,sizeof(char*)*(rt->import_depth+1)); rt->import_stack[rt->import_depth++]=xstrdup(path);
|
|
|
|
for(i=0;i<p->count;i++){ char tmp[1024],up[64],w[64]; char *t; strncpy(tmp,p->lines[i],sizeof(tmp)-1); tmp[sizeof(tmp)-1]=0; t=trim_inplace(tmp); first_word(t,w,sizeof(w)); upper_copy(up,w,sizeof(up)); if(strcmp(up,"EXPORT")==0){ t+=6; t=trim_inplace(t); if(ci_eq(t,"ALL")){ ec=0; break;} ec=parse_params(t,&exports); break; } }
|
|
|
|
for(i=0;i<p->count;i++){ char tmp[1024],up[64],w[64]; char *t; strncpy(tmp,p->lines[i],sizeof(tmp)-1); tmp[sizeof(tmp)-1]=0; t=trim_inplace(tmp); first_word(t,w,sizeof(w)); upper_copy(up,w,sizeof(up)); if(strcmp(up,"EXPORT")==0){ t+=6; t=trim_inplace(t); if(ci_eq(t,"ALL")){ ec=0; break;} ec=parse_params(t,&exports); break; } }
|
|
|
|
if(ns){ Str pr; str_init(&pr); str_add(&pr,ns); str_ch(&pr,'.'); prefix=str_take(&pr); scan_functions(rt,p,prefix); exec_range(rt,p,0,p->count); /* copy exported vars */ for(Var *v=rt->globals; v; v=v->next){ if(strncmp(v->name,prefix,strlen(prefix))!=0 && export_allows(exports,ec,v->name)){ char nn[256]; snprintf(nn,sizeof(nn),"%s%s",prefix,v->name); if(!rt_has(rt,nn)) rt_set_global(rt,nn,v->val); } } } else { scan_functions(rt,p,""); exec_range(rt,p,0,p->count); }
|
|
|
|
if(ns){ Str pr; str_init(&pr); str_add(&pr,ns); str_ch(&pr,'.'); prefix=str_take(&pr); scan_functions(rt,p,prefix); exec_range(rt,p,0,p->count); /* copy exported vars */ for(Var *v=rt->globals; v; v=v->next){ if(strncmp(v->name,prefix,strlen(prefix))!=0 && export_allows(exports,ec,v->name)){ char nn[256]; snprintf(nn,sizeof(nn),"%s%s",prefix,v->name); if(!rt_has(rt,nn)) rt_set_global(rt,nn,v->val); } } } else { scan_functions(rt,p,""); exec_range(rt,p,0,p->count); }
|
|
|
@@ -533,7 +554,9 @@ static int typecheck_file(const char *path){ FILE *f=fopen(path,"rb"); char line
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fclose(f); if(errs==0) printf("Type check OK\n"); return errs?1:0; }
|
|
|
|
fclose(f); if(errs==0) printf("Type check OK\n"); return errs?1:0; }
|
|
|
|
static char *read_file_text(const char *path){ FILE *f=fopen(path,"rb"); Str b; int c; if(!f) return NULL; str_init(&b); while((c=fgetc(f))!=EOF) str_ch(&b,(char)c); fclose(f); return str_take(&b); }
|
|
|
|
static char *read_file_text(const char *path){ FILE *f=fopen(path,"rb"); Str b; int c; if(!f) return NULL; str_init(&b); while((c=fgetc(f))!=EOF) str_ch(&b,(char)c); fclose(f); return str_take(&b); }
|
|
|
|
static int run_one_test(const char *path){ char outpath[512]; char *dot; char *expect,*got; int rc,ok; Runtime rt; snprintf(outpath,sizeof(outpath),"%s",path); dot=strrchr(outpath,'.'); if(dot) strcpy(dot,".out"); expect=read_file_text(outpath); if(!expect) return 0; rt_init(&rt); rt.capture=1; rc=run_file(&rt,path); got=rt.captured.s?xstrdup(rt.captured.s):xstrdup(""); ok=(rc==0 && strcmp(got,expect)==0); printf("%s %s\n",ok?"PASS":"FAIL",path); if(!ok) printf("Expected:\n%s\nGot:\n%s\n",expect,got); free(expect); free(got); return ok?0:1; }
|
|
|
|
/* Golden output files may be checked out as CRLF on Windows while Claro captures output as LF. Normalize only test expectations so test results represent behavior, not Git line-ending settings. */
|
|
|
|
|
|
|
|
static char *read_test_expectation(const char *path){ char *raw=read_file_text(path); Str normalized; size_t i; if(!raw) return NULL; str_init(&normalized); for(i=0;raw[i];i++){ if(raw[i]=='\015'){ if(raw[i+1]=='\012') continue; str_ch(&normalized,'\012'); } else str_ch(&normalized,raw[i]); } free(raw); return str_take(&normalized); }
|
|
|
|
|
|
|
|
static int run_one_test(const char *path){ char outpath[512]; char *dot; char *expect,*got; int rc,ok; Runtime rt; snprintf(outpath,sizeof(outpath),"%s",path); dot=strrchr(outpath,'.'); if(dot) strcpy(dot,".out"); expect=read_test_expectation(outpath); if(!expect) return 0; rt_init(&rt); rt.capture=1; rc=run_file(&rt,path); got=rt.captured.s?xstrdup(rt.captured.s):xstrdup(""); ok=(rc==0 && strcmp(got,expect)==0); printf("%s %s\n",ok?"PASS":"FAIL",path); if(!ok) printf("Expected:\n%s\nGot:\n%s\n",expect,got); free(expect); free(got); return ok?0:1; }
|
|
|
|
static int run_tests(void){ int fails=0;
|
|
|
|
static int run_tests(void){ int fails=0;
|
|
|
|
#ifndef _WIN32
|
|
|
|
#ifndef _WIN32
|
|
|
|
DIR *d=opendir("tests"); struct dirent *de; if(!d){ fprintf(stderr,"No tests directory\n"); return 1;} while((de=readdir(d))){ if(strstr(de->d_name,".claro")){ char path[512]; snprintf(path,sizeof(path),"tests/%s",de->d_name); fails+=run_one_test(path); } } closedir(d);
|
|
|
|
DIR *d=opendir("tests"); struct dirent *de; if(!d){ fprintf(stderr,"No tests directory\n"); return 1;} while((de=readdir(d))){ if(strstr(de->d_name,".claro")){ char path[512]; snprintf(path,sizeof(path),"tests/%s",de->d_name); fails+=run_one_test(path); } } closedir(d);
|
|
|
|