/* Claro v1.17.26 - Networking Reliability Release A compact, buildable Claro interpreter focused on the beginner-friendly teaching core: readable commands, simple expressions, functions, collections, files, JSON, imports, TRY/CATCH, stronger beginner checking, and small standard-library built-ins. Build: gcc -std=c99 src/claro.c -O0 -o claro -lm */ #include #include #include #ifndef _WIN32 #include #define strnicmp2 strncasecmp #else #define strnicmp2 _strnicmp #endif #include #include #include #include #include #include #ifndef _WIN32 FILE *popen(const char *command, const char *mode); int pclose(FILE *stream); #endif #ifndef _WIN32 #include #include #else #include #include #ifdef __MINGW32__ #include #endif #define access _access #define F_OK 0 #endif #define CLARO_VERSION "Claro v1.17.26" static void *xmalloc(size_t n){ void *p=malloc(n?n:1); if(!p){ fprintf(stderr,"Out of memory\n"); exit(1);} return p; } static void *xrealloc(void *p,size_t n){ void *q=realloc(p,n?n:1); if(!q){ fprintf(stderr,"Out of memory\n"); exit(1);} return q; } static char *xstrdup(const char *s){ size_t n; char *p; if(!s) s=""; n=strlen(s); p=(char*)xmalloc(n+1); memcpy(p,s,n+1); return p; } static int ci_cmp(const char *a,const char *b){ while(*a&&*b){ int ca=toupper((unsigned char)*a), cb=toupper((unsigned char)*b); if(ca!=cb) return ca-cb; a++; b++; } return (unsigned char)*a-(unsigned char)*b; } static int ci_eq(const char *a,const char *b){ return ci_cmp(a,b)==0; } static int starts_ci(const char *s,const char *p){ while(*p){ if(!*s) return 0; if(toupper((unsigned char)*s)!=toupper((unsigned char)*p)) return 0; s++; p++; } return 1; } static char *trim_inplace(char *s){ char *e; while(*s&&isspace((unsigned char)*s)) s++; e=s+strlen(s); while(e>s&&isspace((unsigned char)e[-1])) e--; *e=0; return s; } static void first_word(const char *s,char *out,size_t n){ size_t i=0; while(*s&&isspace((unsigned char)*s)) s++; while(*s&&!isspace((unsigned char)*s)&&i+1s=NULL; b->len=0; b->cap=0; } static void str_need(Str *b,size_t extra){ size_t need=b->len+extra+1; if(need>b->cap){ size_t nc=b->cap?b->cap*2:64; while(ncs=(char*)xrealloc(b->s,nc); b->cap=nc; } } static void str_addn(Str *b,const char *s,size_t n){ str_need(b,n); memcpy(b->s+b->len,s,n); b->len+=n; b->s[b->len]=0; } static void str_add(Str *b,const char *s){ if(s) str_addn(b,s,strlen(s)); } static void str_ch(Str *b,char c){ str_need(b,1); b->s[b->len++]=c; b->s[b->len]=0; } static char *str_take(Str *b){ char *s=b->s?b->s:xstrdup(""); b->s=NULL; b->len=b->cap=0; return s; } /* ---------- values ---------- */ typedef struct Value Value; typedef struct { Value *items; int count, cap; } List; typedef struct { char **keys; Value *vals; int count, cap; } Map; struct Value { int type; double num; int boolean; char *str; List *list; Map *map; }; enum { V_NONE, V_NUM, V_STR, V_BOOL, V_LIST, V_MAP }; static Value v_none(void){ Value v; memset(&v,0,sizeof(v)); v.type=V_NONE; return v; } static Value v_num(double n){ Value v=v_none(); v.type=V_NUM; v.num=n; return v; } static Value v_bool(int b){ Value v=v_none(); v.type=V_BOOL; v.boolean=b?1:0; return v; } static Value v_str(const char *s){ Value v=v_none(); v.type=V_STR; v.str=xstrdup(s?s:""); return v; } static List *list_new(void){ List *l=(List*)xmalloc(sizeof(List)); l->items=NULL; l->count=l->cap=0; return l; } static Map *map_new(void){ Map *m=(Map*)xmalloc(sizeof(Map)); m->keys=NULL; m->vals=NULL; m->count=m->cap=0; return m; } static Value v_list(void){ Value v=v_none(); v.type=V_LIST; v.list=list_new(); return v; } static Value v_map(void){ Value v=v_none(); v.type=V_MAP; v.map=map_new(); return v; } static Value v_copy(Value v); static void list_add(List *l,Value v){ if(l->count>=l->cap){ l->cap=l->cap?l->cap*2:8; l->items=(Value*)xrealloc(l->items,sizeof(Value)*l->cap);} l->items[l->count++]=v_copy(v); } static int map_index(Map *m,const char *key){ int i; for(i=0;icount;i++) if(strcmp(m->keys[i],key)==0) return i; return -1; } static void map_put(Map *m,const char *key,Value v){ int i=map_index(m,key); if(i>=0){ m->vals[i]=v_copy(v); return;} if(m->count>=m->cap){ m->cap=m->cap?m->cap*2:8; m->keys=(char**)xrealloc(m->keys,sizeof(char*)*m->cap); m->vals=(Value*)xrealloc(m->vals,sizeof(Value)*m->cap);} m->keys[m->count]=xstrdup(key); m->vals[m->count++]=v_copy(v); } static Value map_get(Map *m,const char *key){ int i=map_index(m,key); if(i>=0) return v_copy(m->vals[i]); return v_none(); } static Value v_copy(Value v){ Value o=v_none(); int i; o.type=v.type; o.num=v.num; o.boolean=v.boolean; if(v.type==V_STR) o.str=xstrdup(v.str); else if(v.type==V_LIST){ o=v_list(); for(i=0;icount;i++) list_add(o.list,v.list->items[i]); } else if(v.type==V_MAP){ o=v_map(); for(i=0;icount;i++) map_put(o.map,v.map->keys[i],v.map->vals[i]); } return o; } static int v_truth(Value v){ if(v.type==V_BOOL) return v.boolean; if(v.type==V_NUM) return fabs(v.num)>0.0000001; if(v.type==V_STR) return v.str&&v.str[0]; if(v.type==V_LIST) return v.list&&v.list->count>0; if(v.type==V_MAP) return v.map&&v.map->count>0; return 0; } static double v_number(Value v){ if(v.type==V_NUM) return v.num; if(v.type==V_BOOL) return v.boolean; if(v.type==V_STR) return atof(v.str?v.str:""); return 0; } static char *v_to_string(Value v); static char *num_to_string(double d){ char buf[80]; if(fabs(d-(long long)d)<0.0000001) snprintf(buf,sizeof(buf),"%lld",(long long)d); else snprintf(buf,sizeof(buf),"%.15g",d); return xstrdup(buf); } static char *v_to_string(Value v){ if(v.type==V_NONE) return xstrdup(""); if(v.type==V_NUM) return num_to_string(v.num); if(v.type==V_BOOL) return xstrdup(v.boolean?"YES":"NO"); if(v.type==V_STR) return xstrdup(v.str?v.str:""); if(v.type==V_LIST) return xstrdup("[list]"); if(v.type==V_MAP) return xstrdup("[map]"); return xstrdup(""); } static int parse_number_strict(const char *s,double *out){ char *end; double d; if(!s) return 0; while(*s&&isspace((unsigned char)*s)) s++; if(!*s) return 0; d=strtod(s,&end); if(end==s) return 0; while(end&&*end&&isspace((unsigned char)*end)) end++; if(end&&*end) return 0; if(out) *out=d; return 1; } static int value_compare(Value a,Value b){ char *as,*bs; int r; double x,y; if((a.type==V_NUM||a.type==V_BOOL)&&(b.type==V_NUM||b.type==V_BOOL)){ x=v_number(a); y=v_number(b); if(xy) return 1; return 0; } if(a.type==V_STR && (b.type==V_NUM||b.type==V_BOOL) && parse_number_strict(a.str,&x)){ y=v_number(b); if(xy) return 1; return 0; } if((a.type==V_NUM||a.type==V_BOOL) && b.type==V_STR && parse_number_strict(b.str,&y)){ x=v_number(a); if(xy) return 1; return 0; } if(a.type==V_STR && b.type==V_STR && parse_number_strict(a.str,&x) && parse_number_strict(b.str,&y)){ if(xy) return 1; return 0; } as=v_to_string(a); bs=v_to_string(b); r=strcmp(as,bs); free(as); free(bs); return r; } /* ---------- env/program/runtime ---------- */ typedef struct Var { char *name; Value val; struct Var *next; } Var; typedef struct Program { char *path; char **lines; int count, cap; } Program; typedef struct Function { char *name; char **params; int pcnt; Program *prog; int start, end; struct Function *next; } Function; typedef struct Module { char *ns; char *kind; struct Module *next; } Module; typedef struct FieldDef { char *name; char *type; struct FieldDef *next; } FieldDef; typedef struct ClassDef { char *name; FieldDef *fields; struct ClassDef *next; } ClassDef; typedef struct Runtime { Var *globals; Var *locals; Function *funcs; Module *modules; ClassDef *classes; Str captured; int capture; int trace; int error; char *err_msg; char *err_file; int err_line; int returning; Value ret; Program **programs; int pcount, pcap; char **import_stack; int import_depth; int script_argc; char **script_argv; } Runtime; static void rt_init(Runtime *rt){ memset(rt,0,sizeof(*rt)); str_init(&rt->captured); rt->ret=v_none(); srand(1); } static Var *env_find(Var *v,const char *name){ while(v){ if(strcmp(v->name,name)==0) return v; v=v->next;} return NULL; } static Value rt_get(Runtime *rt,const char *name){ Var *v=env_find(rt->locals,name); if(v) return v_copy(v->val); v=env_find(rt->globals,name); if(v) return v_copy(v->val); return v_none(); } static int rt_has(Runtime *rt,const char *name){ return env_find(rt->locals,name)||env_find(rt->globals,name); } static void rt_set(Runtime *rt,const char *name,Value val){ Var **head=rt->locals?&rt->locals:&rt->globals; Var *v=env_find(*head,name); if(!v && rt->locals) v=env_find(rt->globals,name); if(v){ v->val=v_copy(val); return;} v=(Var*)xmalloc(sizeof(Var)); v->name=xstrdup(name); v->val=v_copy(val); v->next=*head; *head=v; } static void rt_set_global(Runtime *rt,const char *name,Value val){ Var *v=env_find(rt->globals,name); if(v){ v->val=v_copy(val); return;} v=(Var*)xmalloc(sizeof(Var)); v->name=xstrdup(name); v->val=v_copy(val); v->next=rt->globals; rt->globals=v; } static void rt_error(Runtime *rt,const char *file,int line,const char *fmt,...); static int claro_is_type_word(const char *s){ return s&&(ci_eq(s,"NUMBER")||ci_eq(s,"TEXT")||ci_eq(s,"YESNO")||ci_eq(s,"BOOL")||ci_eq(s,"BOOLEAN")||ci_eq(s,"LIST")||ci_eq(s,"MAP")||ci_eq(s,"OBJECT")||ci_eq(s,"ANY")); } static const char *claro_value_type(Value v){ if(v.type==V_NUM) return "NUMBER"; if(v.type==V_STR) return "TEXT"; if(v.type==V_BOOL) return "YESNO"; if(v.type==V_LIST) return "LIST"; if(v.type==V_MAP) return "MAP"; return "NONE"; } static Value claro_default_for_type(const char *type){ if(ci_eq(type,"NUMBER")) return v_num(0); if(ci_eq(type,"TEXT")) return v_str(""); if(ci_eq(type,"YESNO")||ci_eq(type,"BOOL")||ci_eq(type,"BOOLEAN")) return v_bool(0); if(ci_eq(type,"LIST")) return v_list(); if(ci_eq(type,"MAP")||ci_eq(type,"OBJECT")) return v_map(); return v_none(); } static int claro_type_matches(const char *type,Value v){ if(!type||!*type||ci_eq(type,"ANY")) return 1; if(ci_eq(type,"NUMBER")) return v.type==V_NUM || (v.type==V_STR && parse_number_strict(v.str,NULL)); if(ci_eq(type,"TEXT")) return v.type==V_STR; if(ci_eq(type,"YESNO")||ci_eq(type,"BOOL")||ci_eq(type,"BOOLEAN")) return v.type==V_BOOL; if(ci_eq(type,"LIST")) return v.type==V_LIST; if(ci_eq(type,"MAP")||ci_eq(type,"OBJECT")) return v.type==V_MAP; return 1; } static int claro_value_from_text(const char *type,const char *text,Value *out){ double d; if(!type||!*type||ci_eq(type,"TEXT")||ci_eq(type,"ANY")){ if(out) *out=v_str(text?text:""); return 1; } if(ci_eq(type,"NUMBER")){ if(parse_number_strict(text,&d)){ if(out) *out=v_num(d); return 1; } return 0; } if(ci_eq(type,"YESNO")||ci_eq(type,"BOOL")||ci_eq(type,"BOOLEAN")){ if(ci_eq(text,"YES")||ci_eq(text,"TRUE")||strcmp(text,"1")==0){ if(out) *out=v_bool(1); return 1; } if(ci_eq(text,"NO")||ci_eq(text,"FALSE")||strcmp(text,"0")==0){ if(out) *out=v_bool(0); return 1; } return 0; } if(ci_eq(type,"LIST")){ if(out) *out=v_list(); return 1; } if(ci_eq(type,"MAP")||ci_eq(type,"OBJECT")){ if(out) *out=v_map(); return 1; } if(out) *out=v_str(text?text:""); return 1; } static const char *runtime_type_for(Runtime *rt,const char *name,char *buf,size_t n); static const char *declared_type_for_expr(Runtime *rt,const char *expr,char *buf,size_t n){ char tmp[256]; const char *t=expr; size_t i=0; while(t&&*t&&isspace((unsigned char)*t)) t++; if(!t||!*t) return NULL; while(t[i]&&(isalnum((unsigned char)t[i])||t[i]=='_'||t[i]=='.')&&i+1error) return; va_start(ap,fmt); vsnprintf(buf,sizeof(buf),fmt,ap); va_end(ap); rt->error=1; rt->err_msg=xstrdup(buf); rt->err_file=xstrdup(file?file:""); rt->err_line=line; rt_set(rt,"LASTERROR",v_str(buf)); rt_set(rt,"LASTERRORFILE",v_str(file?file:"")); rt_set(rt,"LASTERRORLINE",v_num(line)); } static void rt_clear_error(Runtime *rt){ rt->error=0; } static void out_line(Runtime *rt,Value v){ char *s=v_to_string(v); if(rt->capture){ str_add(&rt->captured,s); str_ch(&rt->captured,'\n'); } else { printf("%s\n",s); } free(s); } static void add_function(Runtime *rt,const char *name,char **params,int pcnt,Program *p,int start,int end){ Function *f=(Function*)xmalloc(sizeof(Function)); int i; f->name=xstrdup(name); f->pcnt=pcnt; f->params=(char**)xmalloc(sizeof(char*)*(pcnt?pcnt:1)); for(i=0;iparams[i]=xstrdup(params[i]); f->prog=p; f->start=start; f->end=end; f->next=rt->funcs; rt->funcs=f; } static Function *find_function(Runtime *rt,const char *name){ Function *f=rt->funcs; while(f){ if(strcmp(f->name,name)==0) return f; f=f->next;} return NULL; } static void add_module(Runtime *rt,const char *ns,const char *kind){ Module *m=(Module*)xmalloc(sizeof(Module)); m->ns=xstrdup(ns); m->kind=xstrdup(kind); m->next=rt->modules; rt->modules=m; } static const char *module_kind(Runtime *rt,const char *ns){ Module *m=rt->modules; while(m){ if(strcmp(m->ns,ns)==0) return m->kind; m=m->next;} return NULL; } static ClassDef *find_class(Runtime *rt,const char *name){ ClassDef *c=rt->classes; while(c){ if(strcmp(c->name,name)==0) return c; c=c->next; } return NULL; } static ClassDef *add_class(Runtime *rt,const char *name){ ClassDef *c=find_class(rt,name); if(c) return c; c=(ClassDef*)xmalloc(sizeof(ClassDef)); c->name=xstrdup(name); c->fields=NULL; c->next=rt->classes; rt->classes=c; return c; } static FieldDef *find_field(ClassDef *c,const char *name){ FieldDef *f=c?c->fields:NULL; while(f){ if(strcmp(f->name,name)==0) return f; f=f->next; } return NULL; } static void class_add_field(ClassDef *c,const char *name,const char *type){ FieldDef *f; if(!c||!name||!*name) return; f=find_field(c,name); if(f){ free(f->type); f->type=xstrdup(type&&*type?type:"ANY"); return; } f=(FieldDef*)xmalloc(sizeof(FieldDef)); f->name=xstrdup(name); f->type=xstrdup(type&&*type?type:"ANY"); f->next=c->fields; c->fields=f; } static const char *object_class_name(Value obj){ Value cv; if(obj.type!=V_MAP||!obj.map) return NULL; cv=map_get(obj.map,"class"); if(cv.type==V_STR && cv.str && cv.str[0]) return cv.str; return NULL; } /* ---------- tokenization / expressions ---------- */ typedef struct { char **t; int n, cap, pos; } Tokens; static void toks_add(Tokens *ts,const char *s,size_t n){ if(ts->n>=ts->cap){ ts->cap=ts->cap?ts->cap*2:16; ts->t=(char**)xrealloc(ts->t,sizeof(char*)*ts->cap);} ts->t[ts->n]=(char*)xmalloc(n+1); memcpy(ts->t[ts->n],s,n); ts->t[ts->n][n]=0; ts->n++; } static Tokens tokenize(const char *s){ Tokens ts; size_t i=0; memset(&ts,0,sizeof(ts)); while(s[i]){ if(isspace((unsigned char)s[i])){ i++; continue;} if(s[i]=='"'){ Str b; Str q; str_init(&b); str_init(&q); i++; while(s[i]&&s[i]!='"'){ if(s[i]=='\\'){ i++; if(s[i]=='n') str_ch(&b,'\n'); else if(s[i]=='t') str_ch(&b,'\t'); else if(s[i]=='r') str_ch(&b,'\r'); else if(s[i]=='"') str_ch(&b,'"'); else if(s[i]=='\\') str_ch(&b,'\\'); else if(s[i]) str_ch(&b,s[i]); if(s[i]) i++; } else str_ch(&b,s[i++]); } if(s[i]=='"') i++; str_ch(&q,'\001'); str_add(&q,b.s?b.s:""); toks_add(&ts,q.s?q.s:"",strlen(q.s?q.s:"")); free(b.s); free(q.s); continue; } if(strchr("(),",s[i])){ toks_add(&ts,s+i,1); i++; continue; } if((s[i]=='!'||s[i]=='<'||s[i]=='>')&&s[i+1]=='='){ toks_add(&ts,s+i,2); i+=2; continue; } if(strchr("+-*/=<>",s[i])){ toks_add(&ts,s+i,1); i++; continue; } { size_t st=i; while(s[i]&&!isspace((unsigned char)s[i])&&!strchr("(),+-*/=<>!",s[i])) i++; toks_add(&ts,s+st,i-st); } } return ts; } static int tok_end(Tokens *ts){ return ts->pos>=ts->n; } static char *tok_peek(Tokens *ts){ return tok_end(ts)?NULL:ts->t[ts->pos]; } static char *tok_next(Tokens *ts){ return tok_end(ts)?NULL:ts->t[ts->pos++]; } static int tok_match(Tokens *ts,const char *s){ if(!tok_end(ts)&&ci_eq(ts->t[ts->pos],s)){ ts->pos++; return 1;} return 0; } static Value parse_expr(Runtime *rt,Tokens *ts); static Value parse_primary(Runtime *rt,Tokens *ts){ char *t=tok_next(ts); Value v; if(!t) return v_none(); if(strcmp(t,"(")==0){ v=parse_expr(rt,ts); tok_match(ts,")"); return v; } if(t[0]=='\001') return v_str(t+1); if(ci_eq(t,"YES")) return v_bool(1); if(ci_eq(t,"NO")) return v_bool(0); if(ci_eq(t,"NONE")) return v_none(); if(ci_eq(t,"LIST")) return v_list(); if(ci_eq(t,"MAP")) return v_map(); if((isdigit((unsigned char)t[0])||((t[0]=='-'||t[0]=='+')&&isdigit((unsigned char)t[1])))){ char *end=NULL; double d=strtod(t,&end); if(end&&*end==0) return v_num(d); } /* Because tokenizer stores string literals as raw token text, quoted and bare identifiers are ambiguous. Bare identifiers resolve to variables; unresolved become strings only when they came from quotes. We mark quoted literals by detecting tokens that contain spaces/newlines or are followed by original quotes is not possible here. To keep Claro beginner-friendly, unresolved words become variable lookup if known, otherwise plain text. */ if(rt_has(rt,t)) return rt_get(rt,t); if(isalpha((unsigned char)t[0]) || strchr(t,'.')){ rt_error(rt,"",0,"I do not know the variable \"%s\" yet.",t); return v_none(); } return v_str(t); } static Value parse_unary(Runtime *rt,Tokens *ts){ if(tok_match(ts,"NOT")){ Value v=parse_unary(rt,ts); return v_bool(!v_truth(v)); } if(tok_match(ts,"-")){ Value v=parse_unary(rt,ts); return v_num(-v_number(v)); } return parse_primary(rt,ts); } static Value parse_mul(Runtime *rt,Tokens *ts){ Value v=parse_unary(rt,ts); while(!tok_end(ts)){ char *op=tok_peek(ts); if(strcmp(op,"*")&&strcmp(op,"/")) break; tok_next(ts); Value r=parse_unary(rt,ts); if(strcmp(op,"*")==0) v=v_num(v_number(v)*v_number(r)); else v=v_num(v_number(r)==0?0:v_number(v)/v_number(r)); } return v; } static Value parse_add(Runtime *rt,Tokens *ts){ Value v=parse_mul(rt,ts); while(!tok_end(ts)){ char *op=tok_peek(ts); if(strcmp(op,"+")&&strcmp(op,"-")) break; tok_next(ts); Value r=parse_mul(rt,ts); if(strcmp(op,"+")==0){ double x,y; int vn=0,rn=0; if(v.type==V_NUM||v.type==V_BOOL){ x=v_number(v); vn=1; } else if(v.type==V_STR && parse_number_strict(v.str,&x)) vn=1; if(r.type==V_NUM||r.type==V_BOOL){ y=v_number(r); rn=1; } else if(r.type==V_STR && parse_number_strict(r.str,&y)) rn=1; if(vn&&rn) v=v_num(x+y); else if(v.type==V_STR||r.type==V_STR){ char *a=v_to_string(v), *b=v_to_string(r); Str s; str_init(&s); str_add(&s,a); str_add(&s,b); v=v_str(s.s?s.s:""); free(a); free(b); free(s.s); } else v=v_num(v_number(v)+v_number(r)); } else v=v_num(v_number(v)-v_number(r)); } return v; } static Value parse_cmp(Runtime *rt,Tokens *ts){ Value v=parse_add(rt,ts); while(!tok_end(ts)){ char *op=tok_peek(ts); int c; if(!(ci_eq(op,"IS")||!strcmp(op,"=")||!strcmp(op,"!=")||!strcmp(op,"<")||!strcmp(op,"<=")||!strcmp(op,">")||!strcmp(op,">="))) break; tok_next(ts); Value r=parse_add(rt,ts); c=value_compare(v,r); if(ci_eq(op,"IS")||!strcmp(op,"=")) v=v_bool(c==0); else if(!strcmp(op,"!=")) v=v_bool(c!=0); else if(!strcmp(op,"<")) v=v_bool(c<0); else if(!strcmp(op,"<=")) v=v_bool(c<=0); else if(!strcmp(op,">")) v=v_bool(c>0); else v=v_bool(c>=0); } return v; } static Value parse_and(Runtime *rt,Tokens *ts){ Value v=parse_cmp(rt,ts); while(tok_match(ts,"AND")){ Value r=parse_cmp(rt,ts); v=v_bool(v_truth(v)&&v_truth(r)); } return v; } static Value parse_expr(Runtime *rt,Tokens *ts){ Value v=parse_and(rt,ts); while(tok_match(ts,"OR")){ Value r=parse_and(rt,ts); v=v_bool(v_truth(v)||v_truth(r)); } return v; } static Value eval_expr(Runtime *rt,const char *expr){ Tokens ts=tokenize(expr); Value v=parse_expr(rt,&ts); return v; } /* Better string token parsing helpers for commands. */ static char *unquote_token(const char **ps){ const char *s=*ps; Str b; str_init(&b); while(*s&&isspace((unsigned char)*s)) s++; if(*s=='"'){ s++; while(*s&&*s!='"'){ if(*s=='\\'){ s++; if(*s=='n') str_ch(&b,'\n'); else if(*s=='t') str_ch(&b,'\t'); else if(*s=='r') str_ch(&b,'\r'); else if(*s=='"') str_ch(&b,'"'); else if(*s=='\\') str_ch(&b,'\\'); else if(*s) str_ch(&b,*s); if(*s) s++; } else str_ch(&b,*s++); } if(*s=='"') s++; } else { while(*s&&!isspace((unsigned char)*s)) str_ch(&b,*s++); } *ps=s; return str_take(&b); } static char *expr_after_word(const char *line,const char *word){ const char *p=line; while(*p&&isspace((unsigned char)*p)) p++; while(*p&&!isspace((unsigned char)*p)) p++; while(*p&&isspace((unsigned char)*p)) p++; (void)word; return xstrdup(p); } static char *substr(const char *a,const char *b){ if(b=cap){ cap=cap?cap*2:4; arr=(char**)xrealloc(arr,sizeof(char*)*cap);} arr[count++]=xstrdup(t); free(part); if(c==0) break; st=p+1; } } p++; } *out=arr; return count; } static int is_word_token(const char *s){ if(!s||!*s) return 0; if(!(isalpha((unsigned char)s[0])||s[0]=='_')) return 0; for(s++;*s;s++) if(!(isalnum((unsigned char)*s)||*s=='_')) return 0; return 1; } static char *text_upper_copy(const char *s){ char *o=xstrdup(s?s:""); int i; for(i=0;o[i];i++) o[i]=(char)toupper((unsigned char)o[i]); return o; } static char *text_lower_copy(const char *s){ char *o=xstrdup(s?s:""); int i; for(i=0;o[i];i++) o[i]=(char)tolower((unsigned char)o[i]); return o; } static char *text_trim_copy(const char *s){ char *o=xstrdup(s?s:""); char *t=trim_inplace(o); char *r=xstrdup(t); free(o); return r; } static int str_starts_with(const char *s,const char *prefix){ size_t n=strlen(prefix?prefix:""); return strncmp(s?s:"",prefix?prefix:"",n)==0; } static int str_ends_with(const char *s,const char *suffix){ size_t a=strlen(s?s:""), b=strlen(suffix?suffix:""); return b<=a && strcmp((s?s:"")+a-b,suffix?suffix:"")==0; } static char *capture_command_output(const char *cmd,int *exit_code){ Str out; char buf[512]; FILE *fp; str_init(&out); #ifdef _WIN32 fp=_popen(cmd,"r"); #else fp=popen(cmd,"r"); #endif if(!fp){ if(exit_code) *exit_code=-1; return xstrdup(""); } while(fgets(buf,sizeof(buf),fp)) str_add(&out,buf); #ifdef _WIN32 if(exit_code) *exit_code=_pclose(fp); #else if(exit_code) *exit_code=pclose(fp); #endif return str_take(&out); } static int claro_url_is_safe(const char *url){ int i; if(!url||!*url) return 0; if(!(starts_ci(url,"http://")||starts_ci(url,"https://")||starts_ci(url,"claro://"))) return 0; for(i=0;url[i];i++){ unsigned char c=(unsigned char)url[i]; if(c<32||c>126) return 0; if(url[i]=='"'||url[i]=='\''||url[i]=='`'||url[i]=='$'||url[i]=='\\'||url[i]=='|'||url[i]=='<'||url[i]=='>') return 0; } return 1; } static char *claro_mock_http(const char *url,int *status){ if(starts_ci(url,"claro://hello")){ if(status) *status=200; return xstrdup("Hello from Claro networking."); } if(starts_ci(url,"claro://json")){ if(status) *status=200; return xstrdup("{\"message\":\"Hello from Claro\",\"ok\":true}"); } if(starts_ci(url,"claro://empty")){ if(status) *status=204; return xstrdup(""); } if(status) *status=404; return xstrdup("Claro mock URL was not found."); } static char *claro_http_get_text(const char *url,int *http_status,int *exit_code){ char cmd[2300]; char *raw,*mark,*body; const char *marker="\n__CLARO_HTTP_STATUS__"; if(http_status) *http_status=0; if(exit_code) *exit_code=0; if(starts_ci(url,"claro://")) return claro_mock_http(url,http_status); snprintf(cmd,sizeof(cmd),"curl -L -s -m 15 -w \"\\n__CLARO_HTTP_STATUS__%%{http_code}\" \"%s\"",url); raw=capture_command_output(cmd,exit_code); mark=strstr(raw,marker); if(mark){ *mark=0; if(http_status) *http_status=atoi(mark+strlen(marker)); body=xstrdup(raw); free(raw); return body; } if(http_status) *http_status=0; return raw; } static int write_text_bytes(const char *path,const char *text){ FILE *f=fopen(path,"wb"); if(!f) return 0; fwrite(text?text:"",1,strlen(text?text:""),f); fclose(f); return 1; } static int copy_file_bytes(const char *src,const char *dst){ FILE *a=fopen(src,"rb"), *b; char buf[8192]; size_t n; if(!a) return 0; b=fopen(dst,"wb"); if(!b){ fclose(a); return 0; } while((n=fread(buf,1,sizeof(buf),a))>0) fwrite(buf,1,n,b); fclose(a); fclose(b); return 1; } static int make_folder(const char *path){ #ifdef _WIN32 return _mkdir(path)==0 || errno==EEXIST; #else return mkdir(path,0777)==0 || errno==EEXIST; #endif } static int remove_folder(const char *path){ #ifdef _WIN32 return _rmdir(path)==0; #else return rmdir(path)==0; #endif } static Value list_folder_value(const char *path){ Value arr=v_list(); #if defined(_WIN32) && !defined(__MINGW32__) (void)path; #else DIR *d=opendir(path); struct dirent *de; if(!d) return arr; while((de=readdir(d))){ if(strcmp(de->d_name,".")&&strcmp(de->d_name,"..")) list_add(arr.list,v_str(de->d_name)); } closedir(d); #endif return arr; } static void value_list_sort(Value v){ int i,j; if(v.type!=V_LIST||!v.list) return; for(i=0;icount;i++) for(j=i+1;jcount;j++){ char *a=v_to_string(v.list->items[i]); char *b=v_to_string(v.list->items[j]); if(strcmp(a,b)>0){ Value tmp=v.list->items[i]; v.list->items[i]=v.list->items[j]; v.list->items[j]=tmp; } free(a); free(b); } } static void value_list_reverse(Value v){ int i; if(v.type!=V_LIST||!v.list) return; for(i=0;icount/2;i++){ Value tmp=v.list->items[i]; v.list->items[i]=v.list->items[v.list->count-1-i]; v.list->items[v.list->count-1-i]=tmp; } } /* ---------- program loading / blocks ---------- */ static Program *load_program(Runtime *rt,const char *path){ FILE *f; char buf[4096]; Program *p; int i; for(i=0;ipcount;i++) if(strcmp(rt->programs[i]->path,path)==0) return rt->programs[i]; f=fopen(path,"rb"); if(!f) return NULL; p=(Program*)xmalloc(sizeof(Program)); p->path=xstrdup(path); p->lines=NULL; p->count=p->cap=0; while(fgets(buf,sizeof(buf),f)){ size_t n=strlen(buf); while(n&& (buf[n-1]=='\n'||buf[n-1]=='\r')) buf[--n]=0; if(p->count>=p->cap){ p->cap=p->cap?p->cap*2:32; p->lines=(char**)xrealloc(p->lines,sizeof(char*)*p->cap);} p->lines[p->count++]=xstrdup(buf); } fclose(f); if(rt->pcount>=rt->pcap){ rt->pcap=rt->pcap?rt->pcap*2:16; rt->programs=(Program**)xrealloc(rt->programs,sizeof(Program*)*rt->pcap);} rt->programs[rt->pcount++]=p; return p; } static void scan_functions(Runtime *rt,Program *p,const char *prefix); static void line_word(Program *p,int i,char *up,size_t n){ char tmp[512],w[128]; strncpy(tmp,p->lines[i],sizeof(tmp)-1); tmp[sizeof(tmp)-1]=0; first_word(trim_inplace(tmp),w,sizeof(w)); upper_copy(up,w,n); } static int line_is_block_opener(Program *p,int i,const char *up){ char *t; char tmp[512]; if(ci_eq(up,"IF")||ci_eq(up,"FOR")||ci_eq(up,"REPEAT")||ci_eq(up,"TEACH")||ci_eq(up,"TRY")||ci_eq(up,"COMMENT")||ci_eq(up,"CLASS")) return 1; if(ci_eq(up,"DO")){ strncpy(tmp,p->lines[i],sizeof(tmp)-1); tmp[sizeof(tmp)-1]=0; t=trim_inplace(tmp); return find_word_ci(t,"TIMES")!=NULL; } if(ci_eq(up,"START")){ strncpy(tmp,p->lines[i],sizeof(tmp)-1); tmp[sizeof(tmp)-1]=0; t=trim_inplace(tmp); return starts_ci(t+5," TASK"); } return 0; } static int line_is_block_closer(const char *up){ return ci_eq(up,"END")||ci_eq(up,"ENDIF")||ci_eq(up,"DONE")||ci_eq(up,"UNTIL")||ci_eq(up,"LEARNED")||ci_eq(up,"ENDTRY")||ci_eq(up,"ENDCOMMENT")||ci_eq(up,"ENDCLASS")||ci_eq(up,"ENDTASK"); } static int match_block(Program *p,int start,const char *open,const char *close,const char *mid,int *midpos){ int depth=0,i; char up[64]; if(midpos) *midpos=-1; for(i=start;icount;i++){ line_word(p,i,up,sizeof(up)); if(line_is_block_opener(p,i,up)){ depth++; continue; } if(mid&&ci_eq(up,mid)&&depth==1){ if(midpos&&*midpos<0) *midpos=i; continue; } if(depth>1 && line_is_block_closer(up)){ depth--; continue; } if(depth==1 && (ci_eq(up,close)||ci_eq(up,"END"))) { depth--; if(depth==0) return i; } } return -1; } static int find_done(Program *p,int start){ int depth=0,i; char up[64]; for(i=start;icount;i++){ line_word(p,i,up,sizeof(up)); if(line_is_block_opener(p,i,up)){ depth++; continue; } if(line_is_block_closer(up)){ depth--; if(depth==0) return i; } } return -1; } /* ---------- JSON ---------- */ typedef struct { const char *s; } JsonP; static void jp_ws(JsonP *j){ while(*j->s&&isspace((unsigned char)*j->s)) j->s++; } static char *jp_string(JsonP *j){ Str b; str_init(&b); jp_ws(j); if(*j->s!='"') return xstrdup(""); j->s++; while(*j->s&&*j->s!='"'){ if(*j->s=='\\'){ j->s++; if(*j->s=='n') str_ch(&b,'\n'); else if(*j->s=='t') str_ch(&b,'\t'); else if(*j->s=='r') str_ch(&b,'\r'); else if(*j->s) str_ch(&b,*j->s); if(*j->s) j->s++; } else str_ch(&b,*j->s++); } if(*j->s=='"') j->s++; return str_take(&b); } static Value jp_value(JsonP *j); static Value jp_array(JsonP *j){ Value arr=v_list(); jp_ws(j); if(*j->s=='[') j->s++; jp_ws(j); if(*j->s==']'){ j->s++; return arr; } while(*j->s){ Value v=jp_value(j); list_add(arr.list,v); jp_ws(j); if(*j->s==','){ j->s++; continue;} if(*j->s==']'){ j->s++; break;} } return arr; } static Value jp_object(JsonP *j){ Value obj=v_map(); jp_ws(j); if(*j->s=='{') j->s++; jp_ws(j); if(*j->s=='}'){ j->s++; return obj; } while(*j->s){ char *k=jp_string(j); jp_ws(j); if(*j->s==':') j->s++; Value v=jp_value(j); map_put(obj.map,k,v); free(k); jp_ws(j); if(*j->s==','){ j->s++; continue;} if(*j->s=='}'){ j->s++; break;} } return obj; } static Value jp_value(JsonP *j){ char *end; jp_ws(j); if(*j->s=='"'){ char *s=jp_string(j); Value v=v_str(s); free(s); return v;} if(*j->s=='{') return jp_object(j); if(*j->s=='[') return jp_array(j); if(!strncmp(j->s,"true",4)){ j->s+=4; return v_bool(1);} if(!strncmp(j->s,"false",5)){ j->s+=5; return v_bool(0);} if(!strncmp(j->s,"null",4)){ j->s+=4; return v_none();} end=NULL; { double d=strtod(j->s,&end); if(end&&end!=j->s){ j->s=end; return v_num(d);} } return v_none(); } static Value parse_json_text(const char *s){ JsonP j; j.s=s; return jp_value(&j); } static void json_escape(Str *b,const char *s){ str_ch(b,'"'); while(s&&*s){ if(*s=='"'){ str_add(b,"\\\""); } else if(*s=='\\'){ str_add(b,"\\\\"); } else if(*s=='\n'){ str_add(b,"\\n"); } else str_ch(b,*s); s++; } str_ch(b,'"'); } static void json_write(Str *b,Value v,int pretty,int indent); static void indent_add(Str *b,int n){ int i; for(i=0;icount) str_ch(b,'\n'); for(i=0;icount;i++){ if(pretty) indent_add(b,indent+2); json_write(b,v.list->items[i],pretty,indent+2); if(i+1count) str_ch(b,','); if(pretty) str_ch(b,'\n'); } if(pretty&&v.list->count) indent_add(b,indent); str_ch(b,']'); } else if(v.type==V_MAP){ str_ch(b,'{'); if(pretty&&v.map->count) str_ch(b,'\n'); for(i=0;icount;i++){ if(pretty) indent_add(b,indent+2); json_escape(b,v.map->keys[i]); str_ch(b,':'); if(pretty) str_ch(b,' '); json_write(b,v.map->vals[i],pretty,indent+2); if(i+1count) str_ch(b,','); if(pretty) str_ch(b,'\n'); } if(pretty&&v.map->count) indent_add(b,indent); str_ch(b,'}'); } } static char *make_json(Value v,int pretty){ Str b; str_init(&b); json_write(&b,v,pretty,0); return str_take(&b); } /* ---------- stdlib built-ins ---------- */ static Value builtin_call(Runtime *rt,const char *name,Value *args,int argc,int *handled){ char ns[128], fn[128]; const char *dot=strrchr(name,'.'); const char *kind=NULL; *handled=0; if(!dot) return v_none(); snprintf(ns,sizeof(ns),"%.*s",(int)(dot-name),name); snprintf(fn,sizeof(fn),"%s",dot+1); kind=module_kind(rt,ns); if(!kind) return v_none(); *handled=1; if(strcmp(kind,"text")==0){ char *a=argc>0?v_to_string(args[0]):xstrdup(""); char *b=argc>1?v_to_string(args[1]):xstrdup(""); int i; if(strcmp(fn,"upper")==0){ for(i=0;a[i];i++) a[i]=(char)toupper((unsigned char)a[i]); Value r=v_str(a); free(a); free(b); return r; } if(strcmp(fn,"lower")==0){ for(i=0;a[i];i++) a[i]=(char)tolower((unsigned char)a[i]); Value r=v_str(a); free(a); free(b); return r; } if(strcmp(fn,"contains")==0){ Value r=v_bool(strstr(a,b)!=NULL); free(a); free(b); return r; } if(strcmp(fn,"endswith")==0){ size_t la=strlen(a), lb=strlen(b); Value r=v_bool(lb<=la && strcmp(a+la-lb,b)==0); free(a); free(b); return r; } if(strcmp(fn,"split2")==0){ char *pos=strstr(a,b); Value arr=v_list(); if(pos){ char *left=substr(a,pos); list_add(arr.list,v_str(left)); list_add(arr.list,v_str(pos+strlen(b))); free(left); } else list_add(arr.list,v_str(a)); free(a); free(b); return arr; } free(a); free(b); } if(strcmp(kind,"math")==0){ if(strcmp(fn,"abs")==0) return v_num(fabs(v_number(args[0]))); if(strcmp(fn,"clamp")==0){ double x=v_number(args[0]), lo=v_number(args[1]), hi=v_number(args[2]); if(xhi)x=hi; return v_num(x); } } 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(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;icount;i++){ Value row=args[0].list->items[i]; if(row.type==V_LIST){ for(j=0;jcount;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:""); } } return v_none(); } /* ---------- execution ---------- */ static void exec_range(Runtime *rt,Program *p,int start,int end); static int parse_params(char *s,char ***out){ char **arr=NULL; int cnt=0,cap=0; char *tok=strtok(s,","); while(tok){ char *t=trim_inplace(tok); if(*t){ if(cnt>=cap){cap=cap?cap*2:4; arr=(char**)xrealloc(arr,sizeof(char*)*cap);} arr[cnt++]=xstrdup(t);} tok=strtok(NULL,","); } *out=arr; return cnt; } static void parse_teach_parts(char *t,char **name_out,char ***params_out,int *pcnt_out){ char *name,*takes; char **params=NULL; int pcnt=0; t+=5; while(*t&&isspace((unsigned char)*t)) t++; name=t; while(*t&&!isspace((unsigned char)*t)) t++; if(*t) *t++=0; takes=strstr(t,"TAKES"); if(takes){ takes+=5; pcnt=parse_params(takes,¶ms); } else { char *simple=trim_inplace(t); if(*simple) pcnt=parse_params(simple,¶ms); } *name_out=name; *params_out=params; *pcnt_out=pcnt; } static void scan_functions(Runtime *rt,Program *p,const char *prefix){ int i; for(i=0;icount;i++){ char tmp[1024],up[64],w[128]; 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,"CLASS")==0){ const char *pcur=t+5; char *cls=unquote_token(&pcur); char *cn=trim_inplace(cls); int end=match_block(p,i,"CLASS","ENDCLASS",NULL,NULL); ClassDef *c=NULL; int j; if(*cn) c=add_class(rt,cn); for(j=i+1;j<(end<0?p->count:end);j++){ char itmp[1024],iup[64],iw[128]; char *it; strncpy(itmp,p->lines[j],sizeof(itmp)-1); itmp[sizeof(itmp)-1]=0; it=trim_inplace(itmp); first_word(it,iw,sizeof(iw)); upper_copy(iup,iw,sizeof(iup)); if(strcmp(iup,"HAS")==0){ const char *fp=it+3; char *fn=unquote_token(&fp); char *ft=unquote_token(&fp); char *ftt=trim_inplace(ft); if(c) class_add_field(c,trim_inplace(fn),*ftt?ftt:"ANY"); free(fn); free(ft); } else if(strcmp(iup,"TEACH")==0){ char *mname; char **params=NULL; int pcnt=0; char fname[256]; int mend=match_block(p,j,"TEACH","LEARNED",NULL,NULL); parse_teach_parts(it,&mname,¶ms,&pcnt); if(c&&*mname){ snprintf(fname,sizeof(fname),"%s.%s",c->name,mname); if(!find_function(rt,fname)) add_function(rt,fname,params,pcnt,p,j+1,mend<0?(end<0?p->count:end):mend); } if(mend>=0) j=mend; } } free(cls); if(end>=0) i=end; continue; } if(strcmp(up,"TEACH")==0){ char *name; char **params=NULL; int pcnt=0; char fname[256]; int end=match_block(p,i,"TEACH","LEARNED",NULL,NULL); parse_teach_parts(t,&name,¶ms,&pcnt); snprintf(fname,sizeof(fname),"%s%s",prefix?prefix:"",name); if(!find_function(rt,fname)) add_function(rt,fname,params,pcnt,p,i+1,end<0?p->count:end); if(end>=0) i=end; } } } static int call_object_method(Runtime *rt,const char *name,Value *args,int argc){ const char *dot=strchr(name,'.'); char objname[128],method[128],fname[256],fieldname[256]; Value obj,cv; ClassDef *c; Function *f; Var *old; FieldDef *fd; int i; if(!dot) return 0; snprintf(objname,sizeof(objname),"%.*s",(int)(dot-name),name); snprintf(method,sizeof(method),"%s",dot+1); obj=rt_get(rt,objname); if(obj.type!=V_MAP || !obj.map) return 0; cv=map_get(obj.map,"class"); if(cv.type!=V_STR || !cv.str || !cv.str[0]) return 0; snprintf(fname,sizeof(fname),"%s.%s",cv.str,method); f=find_function(rt,fname); if(!f){ rt_error(rt,"",0,"%s does not know how to do %s yet.",cv.str,method); return 1; } c=find_class(rt,cv.str); old=rt->locals; rt->locals=NULL; rt_set(rt,"self",obj); if(c){ for(fd=c->fields;fd;fd=fd->next){ Value fv=map_get(obj.map,fd->name); rt_set(rt,fd->name,fv); snprintf(fieldname,sizeof(fieldname),"__type_%s",fd->name); rt_set(rt,fieldname,v_str(fd->type)); } } for(i=0;ipcnt;i++) rt_set(rt,f->params[i], ireturning=0; exec_range(rt,f->prog,f->start,f->end); if(c){ for(fd=c->fields;fd;fd=fd->next){ Value fv=rt_get(rt,fd->name); map_put(obj.map,fd->name,fv); } } if(rt->returning){ rt_set(rt,"RESULT",rt->ret); rt->returning=0; } rt->locals=old; rt_set(rt,objname,obj); if(c){ for(fd=c->fields;fd;fd=fd->next){ Value fv=map_get(obj.map,fd->name); snprintf(fieldname,sizeof(fieldname),"%s.%s",objname,fd->name); rt_set_checked(rt,"",0,fieldname,fd->type,fv); } } return 1; } 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;ipcnt;i++) rt_set(rt,f->params[i], ireturning=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;iimport_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;iimport_stack=(char**)xrealloc(rt->import_stack,sizeof(char*)*(rt->import_depth+1)); rt->import_stack[rt->import_depth++]=xstrdup(path); for(i=0;icount;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); } rt->import_depth--; free(prefix); } static void create_object_value(Runtime *rt,const char *file,int line,const char *cls,const char *var){ Value obj=v_map(); ClassDef *c=find_class(rt,cls); FieldDef *fd; char fullname[256]; if(!cls||!*cls||!var||!*var){ rt_error(rt,file,line,"NEW needs a class and a name. Try: NEW Player player"); return; } map_put(obj.map,"class",v_str(cls)); rt_set_checked(rt,file,line,var,"OBJECT",obj); if(c){ for(fd=c->fields;fd;fd=fd->next){ Value def=claro_default_for_type(fd->type); snprintf(fullname,sizeof(fullname),"%s.%s",var,fd->name); rt_set_checked(rt,file,line,fullname,fd->type,def); } } } static void exec_line(Runtime *rt,Program *p,int *pcp,const char *raw){ char buf[4096],up[64],w[128]; char *t; int pc=*pcp; strncpy(buf,raw,sizeof(buf)-1); buf[sizeof(buf)-1]=0; t=trim_inplace(buf); if(is_blank_or_comment(t)) return; first_word(t,w,sizeof(w)); upper_copy(up,w,sizeof(up)); if(strcmp(up,"COMMENT")==0){ int end=match_block(p,pc,"COMMENT","ENDCOMMENT",NULL,NULL); *pcp=end<0?pc:end; return; } if(strcmp(up,"SAY")==0){ char *e=expr_after_word(t,"SAY"); Value v=eval_expr(rt,e); if(!rt->error) out_line(rt,v); free(e); return; } if(strcmp(up,"CLASS")==0){ int end=match_block(p,pc,"CLASS","ENDCLASS",NULL,NULL); *pcp=end<0?pc:end; return; } if(strcmp(up,"NEW")==0){ const char *as=find_word_ci(t,"AS"); if(as){ char *cls=substr(t+3,as); char *var=xstrdup(trim_inplace((char*)as+2)); create_object_value(rt,p->path,pc+1,trim_inplace(cls),trim_inplace(var)); free(cls); free(var); } else { const char *pcur=t+3; char *cls=unquote_token(&pcur); char *var=xstrdup(trim_inplace((char*)pcur)); create_object_value(rt,p->path,pc+1,trim_inplace(cls),trim_inplace(var)); free(cls); free(var); } return; } if(strcmp(up,"START")==0){ if(starts_ci(t+5," TASK")){ const char *pcur=t+10; char *name=unquote_token(&pcur); int end=match_block(p,pc,"START","ENDTASK",NULL,NULL); exec_range(rt,p,pc+1,end); if(name&&*name){ char done[256]; snprintf(done,sizeof(done),"TASK_%s_DONE",trim_inplace(name)); rt_set(rt,done,v_bool(1)); } *pcp=end<0?pc:end; free(name); return; } } if(strcmp(up,"WAIT")==0){ return; } if(strcmp(up,"OPEN")==0||strcmp(up,"CLEAR")==0||strcmp(up,"DRAW")==0||strcmp(up,"UPDATE")==0||strcmp(up,"CLOSE")==0){ graphics_note(rt,up); return; } if(strcmp(up,"HTTP")==0){ const char *as=find_word_ci(t,"AS"); if(as&&starts_ci(t+4," GET")){ char *ue=substr(t+8,as); char *after=xstrdup(trim_inplace((char*)as+2)); const char *status_word=find_word_ci(after,"STATUS"); char *var=status_word?substr(after,status_word):xstrdup(after); char *status_var=status_word?xstrdup(trim_inplace((char*)status_word+6)):NULL; Value uv=eval_expr(rt,ue); char *url=v_to_string(uv); int code=0,http=0; char *out; if(!claro_url_is_safe(url)){ rt_error(rt,p->path,pc+1,"HTTP GET needs a safe http://, https://, or claro:// URL."); free(ue); free(after); free(var); free(status_var); free(url); return; } out=claro_http_get_text(url,&http,&code); rt_set(rt,trim_inplace(var),v_str(out)); rt_set(rt,"LASTEXIT",v_num(code)); rt_set(rt,"LASTHTTP",v_num(http)); if(status_var&&*status_var) rt_set(rt,status_var,v_num(http)); free(out); free(url); free(ue); free(after); free(var); free(status_var); return; } if(as&&starts_ci(t+4," STATUS")){ char *ue=substr(t+11,as); char *var=xstrdup(trim_inplace((char*)as+2)); Value uv=eval_expr(rt,ue); char *url=v_to_string(uv); int code=0,http=0; char *out; if(!claro_url_is_safe(url)){ rt_error(rt,p->path,pc+1,"HTTP STATUS needs a safe http://, https://, or claro:// URL."); free(ue); free(var); free(url); return; } out=claro_http_get_text(url,&http,&code); rt_set(rt,trim_inplace(var),v_num(http)); rt_set(rt,"LASTEXIT",v_num(code)); rt_set(rt,"LASTHTTP",v_num(http)); free(out); free(url); free(ue); free(var); return; } if(starts_ci(t+4," SAVE")){ const char *to=find_word_ci(t,"TO"); const char *as2=find_word_ci(t,"AS"); if(to){ char *ue=substr(t+9,to); char *pe=as2?substr(to+2,as2):xstrdup(to+2); char *status_var=as2?xstrdup(trim_inplace((char*)as2+2)):NULL; Value uv=eval_expr(rt,ue), pv=eval_expr(rt,pe); char *url=v_to_string(uv), *path=v_to_string(pv); int code=0,http=0; char *out; if(!claro_url_is_safe(url)){ rt_error(rt,p->path,pc+1,"HTTP SAVE needs a safe http://, https://, or claro:// URL."); free(ue); free(pe); free(status_var); free(url); free(path); return; } out=claro_http_get_text(url,&http,&code); if(!write_text_bytes(path,out)) rt_error(rt,p->path,pc+1,"Could not save HTTP response to file."); if(status_var&&*status_var) rt_set(rt,status_var,v_num(http)); rt_set(rt,"LASTEXIT",v_num(code)); rt_set(rt,"LASTHTTP",v_num(http)); free(out); free(url); free(path); free(ue); free(pe); free(status_var); return; } } if(as&&starts_ci(t+4," CHECK")){ char *ue=substr(t+10,as); char *var=xstrdup(trim_inplace((char*)as+2)); Value uv=eval_expr(rt,ue); char *url=v_to_string(uv); rt_set(rt,var,v_bool(claro_url_is_safe(url))); free(url); free(ue); free(var); return; } rt_error(rt,p->path,pc+1,"HTTP needs GET, STATUS, SAVE, or CHECK. Try: HTTP GET \"claro://hello\" AS page"); return; } if(strcmp(up,"OBJECT")==0){ const char *as=find_word_ci(t,"AS"); if(as && starts_ci(t+6," CLASS")){ char *oe=substr(t+12,as); char *var=xstrdup(trim_inplace((char*)as+2)); Value ov=eval_expr(rt,oe); const char *cn=object_class_name(ov); rt_set_checked(rt,p->path,pc+1,var,"TEXT",v_str(cn?cn:"")); free(oe); free(var); } else if(as && starts_ci(t+6," FIELDS")){ char *oe=substr(t+13,as); char *var=xstrdup(trim_inplace((char*)as+2)); Value ov=eval_expr(rt,oe); Value arr=v_list(); int i; if(ov.type==V_MAP&&ov.map){ for(i=0;icount;i++){ if(strcmp(ov.map->keys[i],"class")!=0 && !starts_ci(ov.map->keys[i],"__type_")) list_add(arr.list,v_str(ov.map->keys[i])); } } rt_set_checked(rt,p->path,pc+1,var,"LIST",arr); free(oe); free(var); } else rt_error(rt,p->path,pc+1,"OBJECT needs CLASS or FIELDS. Try: OBJECT CLASS player AS kind"); return; } if(strcmp(up,"TYPE")==0 && starts_ci(t,"TYPE OF")){ const char *as=find_word_ci(t,"AS"); if(as){ char *expr=substr(t+7,as); char *var=xstrdup(trim_inplace((char*)as+2)); char dbuf[320]; const char *decl=declared_type_for_expr(rt,expr,dbuf,sizeof(dbuf)); Value v=eval_expr(rt,expr); rt_set_checked(rt,p->path,pc+1,var,"TEXT",v_str(decl?decl:claro_value_type(v))); free(expr); free(var); } else rt_error(rt,p->path,pc+1,"TYPE OF needs AS. Try: TYPE OF score AS kind"); return; } if(strcmp(up,"CHECK")==0 && starts_ci(t,"CHECK TYPE")){ const char *is=find_word_ci(t,"IS"); if(is){ char *expr=substr(t+10,is); char *ty=xstrdup(trim_inplace((char*)is+2)); char dbuf[320]; const char *decl=declared_type_for_expr(rt,expr,dbuf,sizeof(dbuf)); Value v=eval_expr(rt,expr); if(decl && !ci_eq(decl,ty) && !ci_eq(ty,"ANY")){ rt_error(rt,p->path,pc+1,"Type check failed: expected %s, but %s is %s.",ty,trim_inplace(expr),decl); } else if(!decl && !claro_type_matches(ty,v)){ rt_error(rt,p->path,pc+1,"Type check failed: expected %s, but got %s.",ty,claro_value_type(v)); } free(expr); free(ty); } else rt_error(rt,p->path,pc+1,"CHECK TYPE needs IS. Try: CHECK TYPE score IS NUMBER"); return; } if(strcmp(up,"SET")==0){ char *rest=t+3; const char *to=find_word_ci(rest,"TO"); const char *as=find_word_ci(rest,"AS"); if(as&&to&&aspath,pc+1,n,ty,v); free(name); free(type); } else if(to){ char *name=substr(rest,to); char *n=trim_inplace(name); char *space=strchr(n,' '); const char *type=NULL; if(space){ *space=0; if(claro_is_type_word(trim_inplace(space+1))) type=trim_inplace(space+1); else { *space=' '; } } { Value v=eval_expr(rt,to+2); rt_set_checked(rt,p->path,pc+1,n,type,v); } free(name); } else { const char *pcur=rest; char *name=unquote_token(&pcur); char *n=trim_inplace(name); const char *save=pcur; char *maybe=unquote_token(&pcur); char *ty=trim_inplace(maybe); if(claro_is_type_word(ty)){ char *after=trim_inplace((char*)pcur); Value v=*after?eval_expr(rt,after):claro_default_for_type(ty); if(*n) rt_set_checked(rt,p->path,pc+1,n,ty,v); } else { Value v=eval_expr(rt,save); if(*n) rt_set_checked(rt,p->path,pc+1,n,NULL,v); } free(maybe); free(name); } return; } if(strcmp(up,"ASK")==0){ const char *as=find_word_ci(t,"AS"); char input[1024]; char *prompt_text=NULL; char *var=NULL; char *type=NULL; if(as){ char *prompt_expr=substr(t+3,as); const char *pcur=as+2; var=unquote_token(&pcur); { char *rest=xstrdup(trim_inplace((char*)pcur)); if(claro_is_type_word(rest)) type=rest; else free(rest); } { Value prompt=eval_expr(rt,prompt_expr); prompt_text=v_to_string(prompt); } free(prompt_expr); } else { const char *pcur=t+3; prompt_text=unquote_token(&pcur); var=unquote_token(&pcur); { char *rest=xstrdup(trim_inplace((char*)pcur)); if(claro_is_type_word(rest)) type=rest; else free(rest); } } if(prompt_text&&prompt_text[0]){ if(rt->capture){ str_add(&rt->captured,prompt_text); str_ch(&rt->captured,'\n'); } else { printf("%s\n",prompt_text); fflush(stdout); } } if(var&&*trim_inplace(var)){ Value val=v_str(""); if(fgets(input,sizeof(input),stdin)){ size_t n=strlen(input); while(n&&(input[n-1]=='\n'||input[n-1]=='\r')) input[--n]=0; } else input[0]=0; if(type&&*type){ if(!claro_value_from_text(type,input,&val)){ rt_error(rt,p->path,pc+1,"%s needs %s input. Try a value like %s.",trim_inplace(var),type,ci_eq(type,"NUMBER")?"5":(ci_eq(type,"YESNO")?"YES":"text")); } else rt_set_checked(rt,p->path,pc+1,trim_inplace(var),type,val); } else rt_set_checked(rt,p->path,pc+1,trim_inplace(var),NULL,v_str(input)); } else rt_error(rt,p->path,pc+1,"ASK needs a variable name. Try: ASK \"What is your name?\" name"); free(prompt_text); free(var); free(type); return; } if(strcmp(up,"IF")==0){ int elsepos=-1; int end=match_block(p,pc,"IF","ENDIF","ELSE",&elsepos); char *e=expr_after_word(t,"IF"); Value cond=eval_expr(rt,e); free(e); if(v_truth(cond)) exec_range(rt,p,pc+1,elsepos>=0?elsepos:end); else if(elsepos>=0) exec_range(rt,p,elsepos+1,end); *pcp=end<0?pc:end; return; } if(strcmp(up,"DO")==0){ int end=find_done(p,pc); const char *times=find_word_ci(t,"TIMES"); if(!times){ const char *pcur=t+2; char *name=unquote_token(&pcur); char **parts=NULL; int ac=0,i; Value *args=NULL; char *n=trim_inplace(name); if(*trim_inplace((char*)pcur)) ac=split_args(pcur,&parts); args=(Value*)xmalloc(sizeof(Value)*(ac?ac:1)); for(i=0;ierror && !rt->returning;i++) exec_range(rt,p,pc+1,end); *pcp=end<0?pc:end; return; } } if(strcmp(up,"FOR")==0){ int end=find_done(p,pc); if(starts_ci(t,"FOR EACH")){ char *r=t+8; const char *in=find_word_ci(r,"IN"); char *vars=substr(r,in?in:r); char *expr=xstrdup(in?in+2:""); Value col=eval_expr(rt,expr); char *comma=strchr(vars,','); if(comma&&col.type==V_MAP){ *comma=0; char *k=trim_inplace(vars), *v=trim_inplace(comma+1); int i; for(i=0;icount&&!rt->error&&!rt->returning;i++){ rt_set(rt,k,v_str(col.map->keys[i])); rt_set(rt,v,col.map->vals[i]); exec_range(rt,p,pc+1,end); } } else if(col.type==V_LIST){ char *v=trim_inplace(vars); int i; for(i=0;icount&&!rt->error&&!rt->returning;i++){ rt_set(rt,v,col.list->items[i]); exec_range(rt,p,pc+1,end); } } free(vars); free(expr); } else { char var[128]; const char *from=find_word_ci(t,"FROM"), *to=find_word_ci(t,"TO"), *step=find_word_ci(t,"STEP"); double a,b,s=1; if(from&&to){ snprintf(var,sizeof(var),"%.*s",(int)(from-(t+3)),t+3); { char *vt=trim_inplace(var); memmove(var,vt,strlen(vt)+1); } { char *ea=substr(from+4,to); char *eb=step?substr(to+2,step):xstrdup(to+2); a=v_number(eval_expr(rt,ea)); b=v_number(eval_expr(rt,eb)); if(step) s=v_number(eval_expr(rt,step+4)); free(ea); free(eb); } if(s==0) s=1; if(s>0){ for(;a<=b&&!rt->error&&!rt->returning;a+=s){ rt_set(rt,var,v_num(a)); exec_range(rt,p,pc+1,end);} } else { for(;a>=b&&!rt->error&&!rt->returning;a+=s){ rt_set(rt,var,v_num(a)); exec_range(rt,p,pc+1,end);} } } } *pcp=end<0?pc:end; return; } if(strcmp(up,"REPEAT")==0){ int end=match_block(p,pc,"REPEAT","UNTIL",NULL,NULL); char *cond=p->lines[end]+5; do{ exec_range(rt,p,pc+1,end); if(rt->error||rt->returning) break; }while(!v_truth(eval_expr(rt,cond))); *pcp=end<0?pc:end; return; } if(strcmp(up,"TEACH")==0){ int end=match_block(p,pc,"TEACH","LEARNED",NULL,NULL); *pcp=end<0?pc:end; return; } if(strcmp(up,"RETURN")==0){ char *e=expr_after_word(t,"RETURN"); rt->ret=eval_expr(rt,e); rt->returning=1; free(e); return; } if(strcmp(up,"CALL")==0){ char *rest=t+4; const char *with=find_word_ci(rest,"WITH"); char *name=with?substr(rest,with):xstrdup(rest); char **parts=NULL; int ac=0,i; Value *args=NULL; char *n=trim_inplace(name); if(with) ac=split_args(with+4,&parts); args=(Value*)xmalloc(sizeof(Value)*(ac?ac:1)); for(i=0;ipath,pc+1); free(path); free(ns); return; } if(strcmp(up,"ADD")==0){ const char *to=find_word_ci(t,"TO"); if(to){ char *ex=substr(t+3,to); char *var=xstrdup(trim_inplace((char*)to+2)); Value v=eval_expr(rt,ex); Value l=rt_get(rt,var); if(l.type==V_LIST){ list_add(l.list,v); rt_set(rt,var,l); } free(ex); free(var);} return; } if(strcmp(up,"COUNT")==0){ const char *as=find_word_ci(t,"AS"); if(as){ if(starts_ci(t+5," ARGUMENTS")){ char *var=xstrdup(trim_inplace((char*)as+2)); rt_set(rt,var,v_num(rt->script_argc)); free(var); } else { char *ex=substr(t+5,as); char *var=xstrdup(trim_inplace((char*)as+2)); Value v=eval_expr(rt,ex); rt_set(rt,var,v_num(v.type==V_LIST?v.list->count:(v.type==V_MAP?v.map->count:0))); free(ex); free(var); } } return; } if(strcmp(up,"GET")==0){ const char *at=find_word_ci(t,"AT"), *key=find_word_ci(t,"KEY"), *as=find_word_ci(t,"AS"); if(as&&starts_ci(t+3," ALL ARGUMENTS")){ char *var=xstrdup(trim_inplace((char*)as+2)); Value arr=v_list(); int i; for(i=0;iscript_argc;i++) list_add(arr.list,v_str(rt->script_argv[i])); rt_set(rt,var,arr); free(var); } else if(as&&starts_ci(t+3," ARGUMENT")){ char *ie=substr(t+12,as); char *var=xstrdup(trim_inplace((char*)as+2)); int idx=(int)v_number(eval_expr(rt,ie)); if(idx>=1&&idx<=rt->script_argc) rt_set(rt,var,v_str(rt->script_argv[idx-1])); else rt_set(rt,var,v_str("")); free(ie); free(var); } else if(as&&starts_ci(t+3," ENV")){ const char *pcur=t+7; char *name=substr(pcur,as); char *var=xstrdup(trim_inplace((char*)as+2)); Value nv=eval_expr(rt,name); char *ns=v_to_string(nv); const char *ev=getenv(ns); rt_set(rt,var,v_str(ev?ev:"")); free(ns); free(name); free(var); } else if(as&&at){ char *ce=substr(t+3,at); char *ie=substr(at+2,as); char *var=xstrdup(trim_inplace((char*)as+2)); Value c=eval_expr(rt,ce); int idx=(int)v_number(eval_expr(rt,ie)); if(c.type==V_LIST && idx>=1 && idx<=c.list->count) rt_set(rt,var,c.list->items[idx-1]); else rt_set(rt,var,v_str("")); free(ce); free(ie); free(var); } else if(as&&key){ char *ce=substr(t+3,key); char *ke=substr(key+3,as); char *var=xstrdup(trim_inplace((char*)as+2)); Value c=eval_expr(rt,ce); Value kv=eval_expr(rt,ke); char *ks=v_to_string(kv); if(c.type==V_MAP) rt_set(rt,var,map_get(c.map,ks)); else rt_set(rt,var,v_str("")); free(ks); free(ce); free(ke); free(var); } return; } if(strcmp(up,"PUT")==0){ const char *key=find_word_ci(t,"KEY"), *val=find_word_ci(t,"VALUE"), *as=find_word_ci(t,"AS"), *into=find_word_ci(t,"INTO"); if(key&&val){ char *me=substr(t+3,key); char *ke=substr(key+3,val); char *ve=xstrdup(val+5); Value m=eval_expr(rt,me); Value k=eval_expr(rt,ke); Value v=eval_expr(rt,ve); char *ks=v_to_string(k); if(m.type==V_MAP){ char *mn=trim_inplace(me); map_put(m.map,ks,v); rt_set(rt,mn,m); } free(ks); free(me); free(ke); free(ve); } else if(as&&into){ char *ke=substr(t+3,as); char *ve=substr(as+2,into); char *mn=xstrdup(trim_inplace((char*)into+4)); Value m=rt_get(rt,mn); Value k=eval_expr(rt,ke); Value v=eval_expr(rt,ve); char *ks=v_to_string(k); if(m.type==V_MAP){ map_put(m.map,ks,v); rt_set(rt,mn,m); } free(ks); free(ke); free(ve); free(mn); } return; } if(strcmp(up,"WRITE")==0||strcmp(up,"APPEND")==0){ if(starts_ci(t+strlen(w)," FILE")){ const char *pcur=t+strlen(w)+5; const char *with=find_word_ci(pcur,"WITH"); if(with){ char *pe=substr(pcur,with); char *ve=xstrdup(with+4); Value pv=eval_expr(rt,pe); Value vv=eval_expr(rt,ve); char *path=v_to_string(pv), *text=v_to_string(vv); FILE *f=fopen(path,strcmp(up,"APPEND")==0?"ab":"wb"); if(!f) rt_error(rt,p->path,pc+1,"Could not write file: %s",strerror(errno)); else { fwrite(text,1,strlen(text),f); fclose(f);} free(path); free(text); free(pe); free(ve); } } return; } if(strcmp(up,"READ")==0){ const char *pcur=t+4; const char *as=find_word_ci(pcur,"AS"); if(as&&starts_ci(pcur," FILE")){ char *pe=substr(pcur+5,as); char *var=xstrdup(trim_inplace((char*)as+2)); Value pv=eval_expr(rt,pe); char *path=v_to_string(pv); FILE *f=fopen(path,"rb"); if(!f) rt_error(rt,p->path,pc+1,"Could not read file: %s",strerror(errno)); else { Str b; int c; str_init(&b); while((c=fgetc(f))!=EOF) str_ch(&b,(char)c); fclose(f); rt_set(rt,var,v_str(b.s?b.s:"")); free(b.s);} free(path); free(pe); free(var); } return; } if(strcmp(up,"EXISTS")==0){ const char *pcur=t+6; const char *as=find_word_ci(pcur,"AS"); if(as&&starts_ci(pcur," FILE")){ char *pe=substr(pcur+5,as); char *var=xstrdup(trim_inplace((char*)as+2)); Value pv=eval_expr(rt,pe); char *path=v_to_string(pv); rt_set(rt,var,v_bool(access(path,F_OK)==0)); free(path); free(pe); free(var);} return; } if(strcmp(up,"CREATE")==0){ const char *pcur=t+6; if(starts_ci(pcur," FOLDER")){ char *pe=xstrdup(pcur+7); Value pv=eval_expr(rt,pe); char *path=v_to_string(pv); if(!make_folder(path)) rt_error(rt,p->path,pc+1,"Could not create folder: %s",strerror(errno)); free(path); free(pe); } return; } if(strcmp(up,"COPY")==0){ const char *to=find_word_ci(t,"TO"); if(starts_ci(t+4," FILE")&&to){ char *se=substr(t+9,to); char *de=xstrdup(to+2); Value sv=eval_expr(rt,se), dv=eval_expr(rt,de); char *src=v_to_string(sv), *dst=v_to_string(dv); if(!copy_file_bytes(src,dst)) rt_error(rt,p->path,pc+1,"Could not copy file"); free(src); free(dst); free(se); free(de); } return; } if(strcmp(up,"MOVE")==0){ const char *to=find_word_ci(t,"TO"); if(starts_ci(t+4," FILE")&&to){ char *se=substr(t+9,to); char *de=xstrdup(to+2); Value sv=eval_expr(rt,se), dv=eval_expr(rt,de); char *src=v_to_string(sv), *dst=v_to_string(dv); if(rename(src,dst)!=0) rt_error(rt,p->path,pc+1,"Could not move file: %s",strerror(errno)); free(src); free(dst); free(se); free(de); } return; } if(strcmp(up,"LIST")==0){ const char *as=find_word_ci(t,"AS"); if(starts_ci(t+4," FOLDER")&&as){ char *pe=substr(t+11,as); char *var=xstrdup(trim_inplace((char*)as+2)); Value pv=eval_expr(rt,pe); char *path=v_to_string(pv); rt_set(rt,var,list_folder_value(path)); free(path); free(pe); free(var); } return; } if(strcmp(up,"DELETE")==0){ const char *pcur=t+6; if(starts_ci(pcur," FILE")){ char *pe=xstrdup(pcur+5); Value pv=eval_expr(rt,pe); char *path=v_to_string(pv); remove(path); free(path); free(pe);} else if(starts_ci(pcur," FOLDER")){ char *pe=xstrdup(pcur+7); Value pv=eval_expr(rt,pe); char *path=v_to_string(pv); remove_folder(path); free(path); free(pe);} return; } if(strcmp(up,"PARSE")==0){ const char *as=find_word_ci(t,"AS"); if(as){ char *ex=substr(t+10,as); char *var=xstrdup(trim_inplace((char*)as+2)); Value s=eval_expr(rt,ex); char *txt=v_to_string(s); rt_set(rt,var,parse_json_text(txt)); free(txt); free(ex); free(var);} return; } if(strcmp(up,"MAKE")==0){ int pretty=strstr(t,"JSON PRETTY")!=NULL; const char *as=find_word_ci(t,"AS"); if(as){ const char *st=strstr(t,pretty?"JSON PRETTY":"JSON"); char *ex=substr(st+(pretty?11:4),as); char *var=xstrdup(trim_inplace((char*)as+2)); Value v=eval_expr(rt,ex); char *js=make_json(v,pretty); rt_set(rt,var,v_str(js)); free(js); free(ex); free(var);} return; } if(strcmp(up,"TEXT")==0){ char restup[64],op[64]; const char *as=find_word_ci(t,"AS"); const char *pcur=t+4; first_word(pcur,op,sizeof(op)); upper_copy(restup,op,sizeof(restup)); while(*pcur&&isspace((unsigned char)*pcur)) pcur++; while(*pcur&&!isspace((unsigned char)*pcur)) pcur++; if(as){ char *var=xstrdup(trim_inplace((char*)as+2)); if(!strcmp(restup,"STARTS")||!strcmp(restup,"ENDS")){ const char *with=find_word_ci(pcur,"WITH"); if(with){ char *left=substr(pcur,with); char *right=substr(with+4,as); Value lv=eval_expr(rt,left), rv=eval_expr(rt,right); char *ls=v_to_string(lv), *rs=v_to_string(rv); size_t ll=strlen(ls), rl=strlen(rs); int ok=!strcmp(restup,"STARTS")?(rl<=ll&&strncmp(ls,rs,rl)==0):(rl<=ll&&strcmp(ls+ll-rl,rs)==0); rt_set(rt,var,v_bool(ok)); free(left); free(right); free(ls); free(rs); } } else if(!strcmp(restup,"REPLACE")){ const char *from=find_word_ci(pcur,"FROM"); const char *to=from?find_word_ci(from+4,"TO"):NULL; if(from&&to){ char *src=substr(pcur,from); char *old=substr(from+4,to); char *neu=substr(to+2,as); Value sv=eval_expr(rt,src), ov=eval_expr(rt,old), nv=eval_expr(rt,neu); char *ss=v_to_string(sv), *os=v_to_string(ov), *ns=v_to_string(nv); Str b; char *pos; str_init(&b); if(os[0]){ char *cur=ss; while((pos=strstr(cur,os))){ str_addn(&b,cur,(size_t)(pos-cur)); str_add(&b,ns); cur=pos+strlen(os); } str_add(&b,cur); rt_set(rt,var,v_str(b.s?b.s:"")); } else rt_set(rt,var,v_str(ss)); free(b.s); free(src); free(old); free(neu); free(ss); free(os); free(ns); } } else { char *ex=substr(pcur,as); Value v=eval_expr(rt,ex); char *txt=v_to_string(v); if(!strcmp(restup,"UPPER")){ char *r=text_upper_copy(txt); rt_set(rt,var,v_str(r)); free(r); } else if(!strcmp(restup,"LOWER")){ char *r=text_lower_copy(txt); rt_set(rt,var,v_str(r)); free(r); } else if(!strcmp(restup,"TRIM")){ char *r=text_trim_copy(txt); rt_set(rt,var,v_str(r)); free(r); } else if(!strcmp(restup,"LENGTH")){ rt_set(rt,var,v_num((double)strlen(txt))); } else if(!strcmp(restup,"CONTAINS")){ char *left=NULL,*right=NULL; const char *needle=find_word_ci(pcur,"NEEDLE"); if(!needle) needle=find_word_ci(pcur,"TEXT"); if(needle){ left=substr(pcur,needle); right=substr(needle+6,as); } else { char **args=NULL; int n=split_args(pcur,&args); if(n>=2){ left=xstrdup(args[0]); right=substr(args[1],as); } } if(left&&right){ Value lv=eval_expr(rt,left), rv=eval_expr(rt,right); char *ls=v_to_string(lv), *rs=v_to_string(rv); rt_set(rt,var,v_bool(strstr(ls,rs)!=NULL)); free(ls); free(rs); } free(left); free(right); } free(txt); free(ex); } free(var); } return; } if(strcmp(up,"RANDOM")==0){ const char *as=find_word_ci(t,"AS"); if(as&&starts_ci(t+6," NUMBER")){ char *range=substr(t+13,as); char *var=xstrdup(trim_inplace((char*)as+2)); char **args=NULL; int n=split_args(range,&args); double a=1,b=100; if(n>=2){ a=v_number(eval_expr(rt,args[0])); b=v_number(eval_expr(rt,args[1])); } else { char *tmp=range; char *one=unquote_token((const char**)&tmp); char *two=unquote_token((const char**)&tmp); if(*one) a=v_number(eval_expr(rt,one)); if(*two) b=v_number(eval_expr(rt,two)); free(one); free(two); } if(bcount;i++) if(value_compare(nv,lv.list->items[i])==0){ found=i+1; break; } } rt_set(rt,var,v_num(found)); free(needle); free(listname); free(var); } return; } if(strcmp(up,"REMOVE")==0){ const char *from=find_word_ci(t,"FROM"); if(from){ char *needle=substr(t+6,from); char *listname=xstrdup(trim_inplace((char*)from+4)); Value nv=eval_expr(rt,needle), lv=rt_get(rt,listname); int i,j; if(lv.type==V_LIST){ for(i=0;icount;i++) if(value_compare(nv,lv.list->items[i])==0){ for(j=i;jcount-1;j++) lv.list->items[j]=lv.list->items[j+1]; lv.list->count--; break; } rt_set(rt,listname,lv); } free(needle); free(listname); } return; } if(strcmp(up,"TRY")==0){ int catchpos=-1,end=match_block(p,pc,"TRY","ENDTRY","CATCH",&catchpos); exec_range(rt,p,pc+1,catchpos>=0?catchpos:end); if(rt->error){ rt_clear_error(rt); if(catchpos>=0) exec_range(rt,p,catchpos+1,end); } *pcp=end<0?pc:end; return; } if(strcmp(up,"RAISE")==0){ char *e=expr_after_word(t,"RAISE"); Value v=eval_expr(rt,e); char *s=v_to_string(v); rt_error(rt,p->path,pc+1,"%s",s); free(s); free(e); return; } if(strcmp(up,"CREATE")==0||strcmp(up,"MOVE")==0||strcmp(up,"SHOW")==0||strcmp(up,"HIDE")==0||strcmp(up,"EXPORT")==0||strcmp(up,"LEARNED")==0||strcmp(up,"ENDIF")==0||strcmp(up,"ELSE")==0||strcmp(up,"DONE")==0||strcmp(up,"ENDTRY")==0||strcmp(up,"CATCH")==0||strcmp(up,"ENDCOMMENT")==0||strcmp(up,"END")==0||strcmp(up,"WAIT")==0){ return; } rt_error(rt,p->path,pc+1,"Unknown command: %s",w); } static void exec_range(Runtime *rt,Program *p,int start,int end){ int pc; if(end<0||end>p->count) end=p->count; for(pc=start; pcerror && !rt->returning; pc++) exec_line(rt,p,&pc,p->lines[pc]); } static int run_file(Runtime *rt,const char *path){ Program *p=load_program(rt,path); if(!p){ fprintf(stderr,"Could not open %s: %s\n",path,strerror(errno)); return 1;} scan_functions(rt,p,""); exec_range(rt,p,0,p->count); if(rt->error){ if(!rt->capture) fprintf(stderr,"%s:%d: %s\n",rt->err_file?rt->err_file:path,rt->err_line,rt->err_msg?rt->err_msg:"error"); return 1;} return 0; } /* ---------- formatter/check/test runner ---------- */ static int fmt_file(const char *path){ FILE *f=fopen(path,"rb"); char line[4096]; int indent=0; if(!f){ fprintf(stderr,"Could not open %s\n",path); return 1;} while(fgets(line,sizeof(line),f)){ char *t=trim_inplace(line),w[64],up[64]; int opens=0; first_word(t,w,sizeof(w)); upper_copy(up,w,sizeof(up)); if(!strcmp(up,"ENDIF")||!strcmp(up,"LEARNED")||!strcmp(up,"DONE")||!strcmp(up,"ENDTRY")||!strcmp(up,"ELSE")||!strcmp(up,"CATCH")||!strcmp(up,"ENDCOMMENT")||!strcmp(up,"ENDCLASS")||!strcmp(up,"ENDTASK")||!strcmp(up,"END")) indent-=4; if(indent<0) indent=0; for(int i=0;iname=xstrdup(t); v->next=*vars; *vars=v; } static char *check_var_after_as(const char *line){ const char *as=find_word_ci(line,"AS"); if(!as) return NULL; return xstrdup(trim_inplace((char*)as+2)); } static char *check_between_words(const char *line,const char *a,const char *b){ const char *pa=find_word_ci(line,a); const char *pb; if(!pa) return NULL; pa+=strlen(a); pb=find_word_ci(pa,b); if(!pb) return NULL; return substr(pa,pb); } typedef struct CheckBlock { const char *expect; const char *open; int line; } CheckBlock; static int check_file(const char *path){ FILE *f=fopen(path,"rb"); char line[4096]; int line_no=0,errs=0,sp=0; CheckBlock stack[256]; Var *vars=NULL; check_add_var(&vars,"RESULT"); check_add_var(&vars,"LASTERROR"); check_add_var(&vars,"LASTERRORFILE"); check_add_var(&vars,"LASTERRORLINE"); check_add_var(&vars,"LASTEXIT"); check_add_var(&vars,"LASTHTTP"); if(!f){ fprintf(stderr,"Could not open %s\n",path); return 1;} while(fgets(line,sizeof(line),f)){ char tmp[4096],up[64],w[128]; char *t; line_no++; strncpy(tmp,line,sizeof(tmp)-1); tmp[sizeof(tmp)-1]=0; t=trim_inplace(tmp); if(is_blank_or_comment(t)) continue; first_word(t,w,sizeof(w)); upper_copy(up,w,sizeof(up)); if(!known_command(up)){ const char *sug=suggest_command_name(up); printf("%s:%d: I do not know the command \"%s\".",path,line_no,w); if(sug) printf(" Did you mean \"%s\"?",sug); printf("\n"); errs++; continue; } if(!strcmp(up,"IF")){ if(!*trim_inplace(t+2)){ printf("%s:%d: IF needs a condition. Try: IF score = 10\n",path,line_no); errs++; } stack[sp++]=(CheckBlock){"ENDIF","IF",line_no}; } else if(!strcmp(up,"CLASS")){ stack[sp++]=(CheckBlock){"ENDCLASS","CLASS",line_no}; } else if(!strcmp(up,"START")){ if(starts_ci(t+5," TASK")) stack[sp++]=(CheckBlock){"ENDTASK","START",line_no}; } else if(!strcmp(up,"NEW")){ char *name=check_var_after_as(t); if(name){ check_add_var(&vars,name); free(name); } else { const char *pcur=t+3; char *cls=unquote_token(&pcur); name=unquote_token(&pcur); if(name&&*trim_inplace(name)) check_add_var(&vars,name); else { printf("%s:%d: NEW needs a class and a name. Try: NEW Player player\n",path,line_no); errs++; } free(cls); free(name); } } else if(!strcmp(up,"TEACH")){ if(!*trim_inplace(t+5)){ printf("%s:%d: TEACH needs a function name. Try: TEACH greet name\n",path,line_no); errs++; } else { const char *takes=find_word_ci(t,"TAKES"); const char *usingw=find_word_ci(t,"USING"); const char *params=takes?takes+5:(usingw?usingw+5:NULL); if(!params){ char *after=t+5; while(*after&&isspace((unsigned char)*after)) after++; while(*after&&!isspace((unsigned char)*after)) after++; params=after; } if(params){ char *pc=xstrdup(params); char *pp=pc; while(pp&&*pp){ char *comma=strchr(pp,','); if(comma){ *comma=0; check_add_var(&vars,pp); pp=comma+1; } else { check_add_var(&vars,pp); break; } } free(pc); } } stack[sp++]=(CheckBlock){"LEARNED","TEACH",line_no}; } else if(!strcmp(up,"DO")){ if(find_word_ci(t,"TIMES")){ stack[sp++]=(CheckBlock){"DONE","DO",line_no}; } } else if(!strcmp(up,"FOR")){ char *name=NULL; if(find_word_ci(t,"EACH")){ const char *each=find_word_ci(t,"EACH"); const char *in=find_word_ci(t,"IN"); if(!in){ printf("%s:%d: FOR EACH needs IN. Try: FOR EACH item IN list\n",path,line_no); errs++; } else { name=substr(each+4,in); char *comma=strchr(name,','); if(comma){ *comma=0; check_add_var(&vars,name); check_add_var(&vars,comma+1); } else check_add_var(&vars,name); free(name); } } else { const char *from=find_word_ci(t,"FROM"); if(!from){ printf("%s:%d: FOR needs FROM. Try: FOR i FROM 1 TO 5\n",path,line_no); errs++; } else { name=substr(t+3,from); check_add_var(&vars,name); free(name); if(!find_word_ci(t,"TO")){ printf("%s:%d: FOR needs TO. Try: FOR i FROM 1 TO 5\n",path,line_no); errs++; } } } stack[sp++]=(CheckBlock){"DONE","FOR",line_no}; } else if(!strcmp(up,"TRY")){ stack[sp++]=(CheckBlock){"ENDTRY","TRY",line_no}; } else if(!strcmp(up,"COMMENT")){ stack[sp++]=(CheckBlock){"ENDCOMMENT","COMMENT",line_no}; } else if(!strcmp(up,"REPEAT")){ stack[sp++]=(CheckBlock){"UNTIL","REPEAT",line_no}; } else if(!strcmp(up,"ELSE")){ if(sp==0 || strcmp(stack[sp-1].expect,"ENDIF")!=0){ printf("%s:%d: ELSE must be inside an IF block.\n",path,line_no); errs++; } } else if(!strcmp(up,"CATCH")){ if(sp==0 || strcmp(stack[sp-1].expect,"ENDTRY")!=0){ printf("%s:%d: CATCH must be inside a TRY block.\n",path,line_no); errs++; } } else if(!strcmp(up,"ENDIF")||!strcmp(up,"LEARNED")||!strcmp(up,"DONE")||!strcmp(up,"ENDTRY")||!strcmp(up,"ENDCOMMENT")||!strcmp(up,"ENDCLASS")||!strcmp(up,"ENDTASK")||!strcmp(up,"UNTIL")||!strcmp(up,"END")){ if(!strcmp(up,"END")){ if(sp==0){ printf("%s:%d: unexpected END\n",path,line_no); errs++; } else sp--; } else if(sp==0 || strcmp(stack[sp-1].expect,up)!=0){ printf("%s:%d: unexpected %s\n",path,line_no,up); errs++; } else sp--; } if(!strcmp(up,"HAS")){ const char *pcur=t+3; char *name=unquote_token(&pcur); if(name&&*trim_inplace(name)) check_add_var(&vars,name); free(name); } if(!strcmp(up,"SET")){ const char *to=find_word_ci(t,"TO"); if(!to){ const char *pcur=t+3; char *name=unquote_token(&pcur); if(!*trim_inplace(name)||!*trim_inplace((char*)pcur)){ printf("%s:%d: SET needs a name and a value. Try: SET name TO \"Alex\" or SET name \"Alex\"\n",path,line_no); errs++; } else check_add_var(&vars,name); free(name); } else { char *name=substr(t+3,to); if(!*trim_inplace(name)){ printf("%s:%d: SET needs a variable name. Try: SET score TO 0\n",path,line_no); errs++; } else check_add_var(&vars,name); free(name); } } if(!strcmp(up,"ASK")){ char *name=NULL; const char *as=find_word_ci(t,"AS"); if(as){ const char *pcur=as+2; name=unquote_token(&pcur); } else { const char *pcur=t+3; char *prompt=unquote_token(&pcur); free(prompt); name=unquote_token(&pcur); } if(!name||!*trim_inplace(name)){ printf("%s:%d: ASK needs a variable name. Try: ASK \"What is your name?\" name\n",path,line_no); errs++; } else check_add_var(&vars,name); free(name); } if(!strcmp(up,"COUNT")||!strcmp(up,"READ")||!strcmp(up,"EXISTS")||!strcmp(up,"PARSE")||!strcmp(up,"MAKE")){ char *name=check_var_after_as(t); if(name){ check_add_var(&vars,name); free(name); } else if(!strcmp(up,"COUNT")){ printf("%s:%d: COUNT needs AS. Try: COUNT items AS total\n",path,line_no); errs++; } } if(!strcmp(up,"GET")){ char *name=check_var_after_as(t); if(name){ check_add_var(&vars,name); free(name); } else { printf("%s:%d: GET needs AS. Try: GET items AT 1 AS item\n",path,line_no); errs++; } } if(!strcmp(up,"HTTP")){ const char *as=find_word_ci(t,"AS"); if(as){ char *name=xstrdup(trim_inplace((char*)as+2)); const char *status=find_word_ci(name,"STATUS"); if(status){ char *primary=substr(name,status); check_add_var(&vars,primary); check_add_var(&vars,trim_inplace((char*)status+6)); free(primary); } else check_add_var(&vars,name); free(name); } } if(!strcmp(up,"CALL")){ char *name=check_var_after_as(t); if(name){ check_add_var(&vars,name); free(name); } } if(!strcmp(up,"TEXT")||!strcmp(up,"TYPE")||!strcmp(up,"OBJECT")||!strcmp(up,"RANDOM")||!strcmp(up,"RUN")||!strcmp(up,"LIST")||!strcmp(up,"FIND")||(!strcmp(up,"GET")&&find_word_ci(t,"AS"))){ char *name=check_var_after_as(t); if(name){ check_add_var(&vars,name); free(name); } } if(!strcmp(up,"SAY")||!strcmp(up,"IF")||!strcmp(up,"RETURN")){ const char *expr=t+strlen(w); Tokens ts=tokenize(expr); int i; for(i=0;iname); if(dname;} v=v->next; } if(best&&bd<=2) printf("%s:%d: I do not know the variable \"%s\" yet. Did you mean \"%s\"?\n",path,line_no,tok,best); else printf("%s:%d: I do not know the variable \"%s\" yet.\n",path,line_no,tok); errs++; } } } } fclose(f); while(sp>0){ CheckBlock b=stack[--sp]; printf("%s:%d: %s block opened here is missing %s\n",path,b.line,b.open,b.expect); errs++; } if(errs==0) printf("OK\n"); return errs?1:0; } static const char *type_env_get(Var *types,const char *name){ Var *v=env_find(types,name); if(v&&v->val.type==V_STR) return v->val.str; return NULL; } static void type_env_set(Var **types,const char *name,const char *type){ Var *v; char tmp[256]; char *t; if(!name||!type||!*type) return; strncpy(tmp,name,sizeof(tmp)-1); tmp[sizeof(tmp)-1]=0; t=trim_inplace(tmp); if(!*t) return; v=env_find(*types,t); if(v){ v->val=v_str(type); return; } v=(Var*)xmalloc(sizeof(Var)); memset(v,0,sizeof(Var)); v->name=xstrdup(t); v->val=v_str(type); v->next=*types; *types=v; } static const char *simple_expr_type(Var *types,const char *expr){ char tmp[512]; char *t; double d; size_t i=0; if(!expr) return NULL; strncpy(tmp,expr,sizeof(tmp)-1); tmp[sizeof(tmp)-1]=0; t=trim_inplace(tmp); if(!*t) return NULL; if(*t=='"') return "TEXT"; if(parse_number_strict(t,&d)) return "NUMBER"; if(ci_eq(t,"YES")||ci_eq(t,"NO")||ci_eq(t,"TRUE")||ci_eq(t,"FALSE")) return "YESNO"; if(ci_eq(t,"LIST")) return "LIST"; if(ci_eq(t,"MAP")) return "MAP"; if(starts_ci(t,"NEW ")) return "OBJECT"; while(t[i]&&(isalnum((unsigned char)t[i])||t[i]=='_'||t[i]=='.')) i++; if(i>0 && t[i]==0){ return type_env_get(types,t); } return NULL; } static int type_words_match(const char *need,const char *got){ if(!need||!*need||ci_eq(need,"ANY")) return 1; if(!got||!*got) return 1; if(ci_eq(need,got)) return 1; if((ci_eq(need,"YESNO")||ci_eq(need,"BOOL")||ci_eq(need,"BOOLEAN")) && (ci_eq(got,"YESNO")||ci_eq(got,"BOOL")||ci_eq(got,"BOOLEAN"))) return 1; if(ci_eq(need,"OBJECT") && ci_eq(got,"MAP")) return 1; return 0; } static void parse_set_for_typecheck2(const char *t,char **name,char **type,char **expr){ const char *rest=t+3; const char *to=find_word_ci(rest,"TO"); const char *as=find_word_ci(rest,"AS"); *name=NULL; *type=NULL; *expr=NULL; if(as&&to&&asd_name,".claro")){ char path[512]; snprintf(path,sizeof(path),"tests/%s",de->d_name); fails+=run_one_test(path); } } closedir(d); #else const char *basic[]={"tests/01_hello.claro","tests/02_math.claro","tests/03_function.claro",NULL}; int i; for(i=0;basic[i];i++) fails+=run_one_test(basic[i]); #endif printf("%s: %d failure(s)\n",fails?"FAIL":"PASS",fails); return fails?1:0; } static int write_text_file_simple(const char *path,const char *text){ FILE *f=fopen(path,"wb"); if(!f) return 0; fputs(text,f); fclose(f); return 1; } static int create_new_project(const char *name){ char path[512]; char text[1024]; if(!name||!*name){ fprintf(stderr,"Project needs a name. Try: claro new MyProject\n"); return 1; } if(!make_folder(name)){ fprintf(stderr,"Could not create project folder: %s\n",name); return 1; } snprintf(path,sizeof(path),"%s/main.claro",name); write_text_file_simple(path,"SAY \"Welcome to Claro!\"\n\nSET name TO \"Learner\"\nSAY \"Hello \" + name\n\nTEACH greet person\n SAY \"Nice to meet you, \" + person\nEND\n\nDO greet \"Friend\"\n"); snprintf(path,sizeof(path),"%s/packages",name); make_folder(path); snprintf(path,sizeof(path),"%s/claro.project",name); snprintf(text,sizeof(text),"name: %s\nmain: main.claro\nversion: v1.17.26\npackages:\n",name); write_text_file_simple(path,text); snprintf(path,sizeof(path),"%s/claro.lock",name); write_text_file_simple(path,"# Claro package lock\nversion: v1.17.26\n"); snprintf(path,sizeof(path),"%s/README.md",name); write_text_file_simple(path,"# My Claro Project\n\nBuild Claro, then run this project with:\n\n```bash\nclaro run\n```\n\nUse packages with:\n\n```bash\nclaro package add text\nclaro package list\nclaro package doctor\n```\n"); printf("Created Claro project: %s\n",name); printf("Next steps:\n cd %s\n claro run\n",name); return 0; } static int run_repl(void){ Runtime rt; char line[4096]; Program p; rt_init(&rt); memset(&p,0,sizeof(p)); p.path=xstrdup(""); p.count=1; p.cap=1; p.lines=(char**)xmalloc(sizeof(char*)); printf("%s\n",CLARO_VERSION); printf("Type HELP for help, or EXIT to leave.\n"); while(1){ int pc=0; printf("> "); fflush(stdout); if(!fgets(line,sizeof(line),stdin)) break; { size_t n=strlen(line); while(n&&(line[n-1]=='\n'||line[n-1]=='\r')) line[--n]=0; } { char *t=trim_inplace(line); if(ci_eq(t,"EXIT")||ci_eq(t,"QUIT")) break; if(ci_eq(t,"HELP")){ printf("Try: SAY \"Hello\"\n"); printf("Try: SET name TO \"Jon\"\n"); printf("Try: SAY name\n"); continue; } if(!*t) continue; p.lines[0]=t; exec_line(&rt,&p,&pc,t); if(rt.error){ fprintf(stderr,"%s:%d: %s\n",rt.err_file?rt.err_file:"",rt.err_line,rt.err_msg?rt.err_msg:"error"); rt_clear_error(&rt); } } } return 0; } static int file_exists_simple(const char *path); static int path_exists_simple(const char *path){ struct stat st; return path&&*path&&stat(path,&st)==0; } static int package_name_safe(const char *name){ int i; if(!name||!*name) return 0; if(name[0]=='.'||name[0]=='-'||name[0]=='_') return 0; for(i=0;name[i];i++){ unsigned char c=(unsigned char)name[i]; if(!(isalnum(c)||c=='_'||c=='-')) return 0; } return 1; } static void ensure_project_file(void){ if(!file_exists_simple("claro.project")) write_text_file_simple("claro.project","name: ClaroProject\nmain: main.claro\nversion: v1.17.26\npackages:\n"); } static char *project_value(const char *key){ char *txt=read_file_text("claro.project"); size_t kn=strlen(key); char *p,*out=NULL; if(!txt) return NULL; p=txt; while(*p){ char *line=p; char save; while(*p&&*p!='\n'&&*p!='\r') p++; save=*p; *p=0; { char *t=trim_inplace(line); if(strnicmp2(t,key,kn)==0 && t[kn]==':'){ out=xstrdup(trim_inplace(t+kn+1)); break; } } if(save) p++; while(*p=='\n'||*p=='\r') p++; } free(txt); return out; } static int package_has_name(const char *name){ char *txt=read_file_text("claro.project"); char *p; int found=0; if(!txt) return 0; p=txt; while(*p){ char *line=p; char save; while(*p&&*p!='\n'&&*p!='\r') p++; save=*p; *p=0; { char *t=trim_inplace(line); if(strnicmp2(t,"package:",8)==0){ char *pkg=trim_inplace(t+8); if(strcmp(pkg,name)==0){ found=1; break; } } } if(save) p++; while(*p=='\n'||*p=='\r') p++; } free(txt); return found; } static int write_package_lock(void){ char *txt=read_file_text("claro.project"); FILE *f; char *p; if(!txt) return 0; f=fopen("claro.lock","wb"); if(!f){ free(txt); return 0; } fputs("# Claro package lock\nversion: v1.17.26\n",f); p=txt; while(*p){ char *line=p; char save; while(*p&&*p!='\n'&&*p!='\r') p++; save=*p; *p=0; { char *t=trim_inplace(line); if(strnicmp2(t,"package:",8)==0){ char *pkg=trim_inplace(t+8); if(*pkg) fprintf(f,"package: %s\n",pkg); } } if(save) p++; while(*p=='\n'||*p=='\r') p++; } fclose(f); free(txt); return 1; } static int append_package_to_project(const char *name){ FILE *f; if(!package_name_safe(name)){ fprintf(stderr,"Package names may use only letters, numbers, dash, and underscore.\n"); return 0; } ensure_project_file(); if(package_has_name(name)){ printf("Package already listed: %s\n",name); return 1; } f=fopen("claro.project","ab"); if(!f) return 0; fprintf(f,"package: %s\n",name); fclose(f); write_package_lock(); return 1; } static int remove_package_from_project(const char *name){ char *txt=read_file_text("claro.project"); FILE *f; char *p; int removed=0; if(!txt) return 0; f=fopen("claro.project","wb"); if(!f){ free(txt); return 0; } p=txt; while(*p){ char *line=p; char save; int skip=0; while(*p&&*p!='\n'&&*p!='\r') p++; save=*p; *p=0; { char *copy=xstrdup(line); char *t=trim_inplace(copy); if(strnicmp2(t,"package:",8)==0){ char *pkg=trim_inplace(t+8); if(strcmp(pkg,name)==0){ skip=1; removed=1; } } free(copy); } if(!skip) fprintf(f,"%s\n",line); if(save) p++; while(*p=='\n'||*p=='\r') p++; } fclose(f); free(txt); write_package_lock(); return removed; } static void create_package_folder(const char *name){ char path[512], meta[512], readme[512]; make_folder("packages"); snprintf(path,sizeof(path),"packages/%s",name); make_folder(path); snprintf(meta,sizeof(meta),"%s/claro.package",path); if(!file_exists_simple(meta)){ char text[512]; snprintf(text,sizeof(text),"name: %s\nversion: 1\n",name); write_text_file_simple(meta,text); } snprintf(readme,sizeof(readme),"%s/README.md",path); if(!file_exists_simple(readme)){ char text[512]; snprintf(text,sizeof(text),"# %s\n\nThis is a local Claro package folder.\n",name); write_text_file_simple(readme,text); } } static int list_project_packages(void){ char *txt=read_file_text("claro.project"); char *p; int count=0; printf("Packages in claro.project:\n"); if(!txt){ printf(" (no claro.project yet)\n"); return 0; } p=txt; while(*p){ char *line=p; char save; while(*p&&*p!='\n'&&*p!='\r') p++; save=*p; *p=0; { char *t=trim_inplace(line); if(strnicmp2(t,"package:",8)==0){ char *pkg=trim_inplace(t+8); if(*pkg){ printf(" %s\n",pkg); count++; } } } if(save) p++; while(*p=='\n'||*p=='\r') p++; } if(!count) printf(" (none)\n"); free(txt); return 0; } static int package_doctor(void){ int ok=1; char *mainfile; printf("%s\n",CLARO_VERSION); printf("Package/project doctor:\n"); printf(" %s claro.project\n",file_exists_simple("claro.project")?"OK":"MISSING"); if(!file_exists_simple("claro.project")) ok=0; mainfile=project_value("main"); if(mainfile&&*mainfile){ printf(" %s main script: %s\n",file_exists_simple(mainfile)?"OK":"MISSING",mainfile); if(!file_exists_simple(mainfile)) ok=0; } else { printf(" MISSING main setting\n"); ok=0; } printf(" %s packages folder\n",path_exists_simple("packages")?"OK":"MISSING"); if(!path_exists_simple("packages")) ok=0; printf(" %s claro.lock\n",file_exists_simple("claro.lock")?"OK":"MISSING"); if(!file_exists_simple("claro.lock")) ok=0; free(mainfile); printf("%s\n",ok?"Package/project files look ready.":"Package/project files need attention."); return ok?0:1; } static int run_package_cmd(int argc,char **argv,int arg){ if(arg>=argc){ printf("Package commands:\n claro package init\n claro package add NAME\n claro package remove NAME\n claro package list\n claro package doctor\n claro package lock\n"); return 0; } if(strcmp(argv[arg],"init")==0){ ensure_project_file(); make_folder("packages"); write_package_lock(); printf("Package project ready: claro.project, claro.lock, packages/\n"); return 0; } if(strcmp(argv[arg],"add")==0&&arg+1=argc) return 0; if(strcmp(argv[arg],"help")==0||strcmp(argv[arg],"--help")==0){ print_help(); return 0; } if(strcmp(argv[arg],"test")==0) return run_tests(); if(strcmp(argv[arg],"repl")==0) return run_repl(); if(strcmp(argv[arg],"examples")==0) return show_examples(); if(strcmp(argv[arg],"doctor")==0) return run_doctor(); if(strcmp(argv[arg],"validate")==0) return run_validate(); if(strcmp(argv[arg],"package")==0) return run_package_cmd(argc,argv,arg+1); if(strcmp(argv[arg],"ide")==0) return print_ide_info(); if(strcmp(argv[arg],"new")==0&&arg+1=argc){ Runtime rt; int rc; char *mainfile=project_value("main"); if(!mainfile||!*mainfile){ if(mainfile) free(mainfile); mainfile=xstrdup("main.claro"); } rt_init(&rt); rt.trace=trace; rt.script_argc=0; rt.script_argv=NULL; rc=run_file(&rt,mainfile); free(mainfile); return rc; } } { Runtime rt; int rc; rt_init(&rt); rt.trace=trace; rt.script_argc=argc-arg-1; rt.script_argv=argv+arg+1; rc=run_file(&rt,argv[arg]); return rc; } }