/* Claro v2026.10.25.014405 - 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=c89 -pedantic-errors -Werror=implicit-function-declaration 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 #include #include #ifndef _WIN32 FILE *popen(const char *command, const char *mode); int pclose(FILE *stream); #endif #ifndef _WIN32 #include #include #include #else #include #include #ifdef __MINGW32__ #include #endif #define access _access #define F_OK 0 #endif #define CLARO_VERSION "Claro v2026.10.25.014405" #define CLARO_MAX_CALL_DEPTH 256 #define CLARO_MAX_EXPR_DEPTH 128 #define CLARO_MAX_BLOCK_DEPTH 128 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 + 1 < n) { out[i++] = *s++; } out[i] = 0; } static void upper_copy(char *d, const char *s, size_t n) { size_t i = 0; if (!n) return; while (s && s[i] && i + 1 < n) { d[i] = (char)toupper((unsigned char)s[i]); i++; } d[i] = 0; } static int is_blank_or_comment(const char *s) { while (*s && isspace((unsigned char)*s)) s++; return *s == 0 || *s == '#'; } /* C89 bounded formatter for Claro's internal formats only. Not a public printf replacement: supported conversions are %s, %.*s, %d, %08lx, %.15g, %.0f, and %%. Strings are copied directly with bounds; sprintf is used only for fixed numeric formats into a buffer sized for the largest double. */ static void claro_format_copy(char *out, size_t size, size_t *total, const char *text, size_t length) { size_t copy = 0; if (size && *total < size - 1) { copy = size - 1 - *total; if (copy > length) copy = length; if (copy) memcpy(out + *total, text, copy); } if (length > (size_t)INT_MAX - *total) { fprintf(stderr, "Claro format output is too large.\n"); exit(1); } *total += length; } static int claro_vsnprintf(char *out, size_t size, const char *format, va_list args) { size_t total = 0; while (*format) { const char *text; size_t length; char number[DBL_MAX_10_EXP + 64]; if (*format != '%') { text = format; while (*format && *format != '%') format++; length = (size_t)(format - text); } else { format++; if (*format == '%') { text = "%"; length = 1; format++; } else if (*format == 's') { text = va_arg(args, const char *); if (!text) text = "(null)"; length = strlen(text); format++; } else if (strncmp(format, ".*s", 3) == 0) { int limit = va_arg(args, int); text = va_arg(args, const char *); if (!text) text = "(null)"; length = 0; while (text[length] && (limit < 0 || length < (size_t)limit)) length++; format += 3; } else if (*format == 'd') { sprintf(number, "%d", va_arg(args, int)); text = number; length = strlen(number); format++; } else if (strncmp(format, "08lx", 4) == 0) { sprintf(number, "%08lx", va_arg(args, unsigned long)); text = number; length = strlen(number); format += 4; } else if (strncmp(format, ".15g", 4) == 0) { sprintf(number, "%.15g", va_arg(args, double)); text = number; length = strlen(number); format += 4; } else if (strncmp(format, ".0f", 3) == 0) { sprintf(number, "%.0f", va_arg(args, double)); text = number; length = strlen(number); format += 3; } else { fprintf(stderr, "Unsupported internal Claro format.\n"); exit(1); } } claro_format_copy(out, size, &total, text, length); } if (size) out[total < size ? total : size - 1] = 0; return (int)total; } static int claro_snprintf(char *out, size_t size, const char *format, ...) { va_list args; int result; va_start(args, format); result = claro_vsnprintf(out, size, format, args); va_end(args); return result; } /* ---------- growable string ---------- */ typedef struct { char *s; size_t len, cap; } Str; static void str_init(Str *b) { b->s = 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 (nc < need) nc *= 2; b->s = (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 value_free(Value v) { int i; if (v.type == V_STR) { free(v.str); } else if (v.type == V_LIST && v.list) { for (i = 0; i < v.list->count; i++) value_free(v.list->items[i]); free(v.list->items); free(v.list); } else if (v.type == V_MAP && v.map) { for (i = 0; i < v.map->count; i++) { free(v.map->keys[i]); value_free(v.map->vals[i]); } free(v.map->keys); free(v.map->vals); free(v.map); } } 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; i < m->count; 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) { value_free(m->vals[i]); 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; i < v.list->count; i++) list_add(o.list, v.list->items[i]); } else if (v.type == V_MAP) { o = v_map(); for (i = 0; i < v.map->count; 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]; double integer; if (fabs(d) < 9223372036854775808.0 && fabs(modf(d, &integer)) < 0.0000001) claro_snprintf(buf, sizeof(buf), "%.0f", integer == 0.0 ? 0.0 : integer); else claro_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 (x < y) return -1; if (x > y) 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 (x < y) return -1; if (x > y) 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 (x < y) return -1; if (x > y) 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 (x < y) return -1; if (x > y) 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; const char *active_file; /* borrowed while a source line executes */ int active_line; unsigned long steps, step_limit, read_file_limit; int returning; Value ret; int call_depth; int range_depth; Program **programs; int pcount, pcap; char **import_stack; int import_depth; int script_argc; char **script_argv; } Runtime; static void rt_error(Runtime *rt, const char *file, int line, const char *fmt, ...); static unsigned long rt_env_limit(Runtime *rt, const char *name) { const char *text = getenv(name); unsigned long value = 0; if (!text || !*text) return 0; while (*text) { unsigned long digit; if (*text < '0' || *text > '9') { rt_error(rt, "", 1, "%s needs a non-negative whole number.", name); return 0; } digit = (unsigned long)(*text++ - '0'); if (value > (ULONG_MAX - digit) / 10) { rt_error(rt, "", 1, "%s is too large for this build.", name); return 0; } value = value * 10 + digit; } return value; } static int rt_tick(Runtime *rt) { if (rt->error) return 0; if (!rt->step_limit) return 1; if (rt->steps >= rt->step_limit) { rt_error(rt, "", 0, "Execution reached its step limit. Check the loop or raise CLARO_STEP_LIMIT."); return 0; } rt->steps++; return 1; } static void rt_init(Runtime *rt) { memset(rt, 0, sizeof(*rt)); str_init(&rt->captured); rt->ret = v_none(); rt->step_limit = rt_env_limit(rt, "CLARO_STEP_LIMIT"); rt->read_file_limit = rt_env_limit(rt, "CLARO_READ_FILE_LIMIT"); 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) { value_free(v->val); 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) { value_free(v->val); 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 char *normalize_path_copy(const char *path) { char *out = xstrdup(path ? path : ""); char *p; for (p = out; *p; p++) if (*p == '\\') *p = '/'; return out; } 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 + 1 < sizeof(tmp)) { tmp[i] = t[i]; i++; } tmp[i] = 0; if (!tmp[0]) return NULL; return runtime_type_for(rt, tmp, buf, n); } static void type_key_for(const char *name, char *out, size_t n) { claro_snprintf(out, n, "__type_%s", name ? name : ""); } static const char *runtime_type_for(Runtime *rt, const char *name, char *buf, size_t n) { Var *entry; type_key_for(name, buf, n); entry = env_find(rt->locals, buf); if (!entry) entry = env_find(rt->globals, buf); if (entry && entry->val.type == V_STR && entry->val.str && entry->val.str[0]) return entry->val.str; return NULL; } static int rt_set_checked(Runtime *rt, const char *file, int line, const char *name, const char *type, Value val) { char key[320], oldbuf[320]; const char *oldtype = NULL; char *dot; if (!name || !*name) return 0; if (type && *type) { Value metadata; type_key_for(name, key, sizeof(key)); metadata = v_str(type); rt_set(rt, key, metadata); value_free(metadata); oldtype = type; } else oldtype = runtime_type_for(rt, name, oldbuf, sizeof(oldbuf)); if (oldtype && !claro_type_matches(oldtype, val)) { rt_error(rt, file, line, "%s must be %s, but got %s.", name, oldtype, claro_value_type(val)); return 0; } dot = strchr(name, '.'); if (dot) { char objname[256]; char field[256]; Value obj; claro_snprintf(objname, sizeof(objname), "%.*s", (int)(dot - name), name); claro_snprintf(field, sizeof(field), "%s", dot + 1); obj = rt_get(rt, objname); if (obj.type != V_MAP) { value_free(obj); obj = v_map(); } map_put(obj.map, field, val); rt_set(rt, objname, obj); value_free(obj); } rt_set(rt, name, val); return 1; } static void graphics_note(Runtime *rt, const char *what) { (void)what; rt_set(rt, "GRAPHICS_READY", v_bool(1)); } static void rt_error(Runtime *rt, const char *file, int line, const char *fmt, ...) { va_list ap; char buf[1024]; char *nfile; Value message, file_value; if (rt->error) return; va_start(ap, fmt); claro_vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); if (!file || !*file) file = rt->active_file; if (line <= 0) line = rt->active_line; nfile = normalize_path_copy(file ? file : ""); rt->error = 1; rt->err_msg = xstrdup(buf); rt->err_file = xstrdup(nfile); rt->err_line = line; message = v_str(buf); rt_set(rt, "LASTERROR", message); value_free(message); file_value = v_str(nfile); rt_set(rt, "LASTERRORFILE", file_value); value_free(file_value); rt_set(rt, "LASTERRORLINE", v_num(line)); free(nfile); } static void rt_clear_error(Runtime *rt) { rt->error = 0; free(rt->err_msg); free(rt->err_file); rt->err_msg = NULL; rt->err_file = NULL; } 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; i < pcnt; i++) f->params[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) { int index; if (obj.type != V_MAP || !obj.map) return NULL; index = map_index(obj.map, "class"); if (index >= 0 && obj.map->vals[index].type == V_STR && obj.map->vals[index].str && obj.map->vals[index].str[0]) return obj.map->vals[index].str; return NULL; } /* ---------- tokenization / expressions ---------- */ typedef struct { char **t; int n, cap, pos; int depth; const char *error; } 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++; else if (!ts.error) ts.error = "Text is missing a closing quote. Add a double quote at the end."; 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])) { if (s[i] == '!' && !ts.error) ts.error = "Use NOT instead of ! to reverse a condition."; 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 void tokens_free(Tokens *ts) { int i; if (!ts) return; for (i = 0; i < ts->n; i++) free(ts->t[i]); free(ts->t); ts->t = NULL; ts->n = ts->cap = ts->pos = 0; } 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) { if (ts->n) rt_error(rt, "", 0, "This expression needs a value after the operator."); return v_none(); } if (strcmp(t, "(") == 0) { v = parse_expr(rt, ts); if (!tok_match(ts, ")")) rt_error(rt, "", 0, "This expression needs a closing parenthesis: )."); return v; } if (strcmp(t, ")") == 0) { rt_error(rt, "", 0, "This expression needs a value before the closing parenthesis."); return v_none(); } 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) { Value v, out; if (ts->depth >= CLARO_MAX_EXPR_DEPTH) { rt_error(rt, "", 0, "Expression nesting exceeds the safe limit of 128. Simplify the expression."); return v_none(); } ts->depth++; if (tok_match(ts, "NOT")) { v = parse_unary(rt, ts); out = v_bool(!v_truth(v)); value_free(v); } else if (tok_match(ts, "-")) { v = parse_unary(rt, ts); out = v_num(-v_number(v)); value_free(v); } else out = parse_primary(rt, ts); ts->depth--; return out; } static Value parse_mul(Runtime *rt, Tokens *ts) { Value v = parse_unary(rt, ts); while (!tok_end(ts)) { char *op = tok_peek(ts); Value out; if (strcmp(op, "*") && strcmp(op, "/")) break; tok_next(ts); { Value r = parse_unary(rt, ts); if (strcmp(op, "*") == 0) out = v_num(v_number(v) * v_number(r)); else out = v_num(v_number(r) == 0 ? 0 : v_number(v) / v_number(r)); value_free(v); value_free(r); v = out; } } 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); Value out; 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) out = 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); out = v_str(s.s ? s.s : ""); free(a); free(b); free(s.s); } else out = v_num(v_number(v) + v_number(r)); } else out = v_num(v_number(v) - v_number(r)); value_free(v); value_free(r); v = out; } } 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; Value out; 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, "=")) out = v_bool(c == 0); else if (!strcmp(op, "!=")) out = v_bool(c != 0); else if (!strcmp(op, "<")) out = v_bool(c < 0); else if (!strcmp(op, "<=")) out = v_bool(c <= 0); else if (!strcmp(op, ">")) out = v_bool(c > 0); else out = v_bool(c >= 0); value_free(v); value_free(r); v = out; } } 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); Value out = v_bool(v_truth(v) && v_truth(r)); value_free(v); value_free(r); v = out; } 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); Value out = v_bool(v_truth(v) || v_truth(r)); value_free(v); value_free(r); v = out; } return v; } static Value eval_expr(Runtime *rt, const char *expr) { Tokens ts = tokenize(expr); Value v = v_none(); if (ts.error) rt_error(rt, "", 0, "%s", ts.error); else { v = parse_expr(rt, &ts); if (!rt->error && !tok_end(&ts)) rt_error(rt, "", 0, "Unexpected text after this expression: %s.", tok_peek(&ts)); } tokens_free(&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) { char *s; if (b < a) b = a; s = (char *)xmalloc((size_t)(b - a) + 1); memcpy(s, a, (size_t)(b - a)); s[b - a] = 0; return s; } static const char *find_word_ci(const char *s, const char *word) { size_t n = strlen(word); int inq = 0, depth = 0; const char *p = s; while (*p) { if (*p == '"' && (p == s || p[-1] != '\\')) inq = !inq; if (!inq) { if (*p == '(') depth++; else if (*p == ')') depth--; if (depth == 0 && (p == s || isspace((unsigned char)p[-1])) && strnicmp2(p, word, n) == 0 && (p[n] == 0 || isspace((unsigned char)p[n]))) return p; } p++; } return NULL; } static int split_args(const char *s, char ***out) { int count = 0, cap = 0; char **arr = NULL; int inq = 0, depth = 0; const char *st = s, *p = s; while (1) { char c = *p; if (c == '"' && (p == s || p[-1] != '\\')) inq = !inq; if (!inq) { if (c == '(') depth++; else if (c == ')') depth--; if ((c == ',' && depth == 0) || c == 0) { char *part = substr(st, p); char *t = trim_inplace(part); if (count >= 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 split_simple_args(const char *s, char ***out) { int count = 0, cap = 0; char **arr = NULL; const char *p = s; if (strchr(s, ',')) return split_args(s, out); while (*p) { const char *st; while (*p && isspace((unsigned char)*p)) p++; if (!*p) break; st = p; if (*p == '"') { p++; while (*p && (*p != '"' || p[-1] == '\\')) p++; if (*p) p++; } else while (*p && !isspace((unsigned char)*p)) p++; if (count >= cap) { cap = cap ? cap * 2 : 4; arr = (char **)xrealloc(arr, sizeof(char *) * cap); } arr[count++] = substr(st, p); } *out = arr; return count; } static void split_args_free(char **parts, int count) { int i; for (i = 0; i < count; i++) free(parts[i]); free(parts); } static void call_args_free(Value *args, int count) { int i; for (i = 0; i < count; i++) value_free(args[i]); free(args); } 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, size_t max_bytes) { Str out; char buf[512]; FILE *fp; int capped = 0; 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)) { size_t n = strlen(buf); if (max_bytes && out.len + n > max_bytes) { if (out.len < max_bytes) { size_t keep = max_bytes - out.len; char *part = substr(buf, buf + keep); str_add(&out, part); free(part); } capped = 1; continue; } if (!capped) str_add(&out, buf); } #ifdef _WIN32 if (exit_code && !capped) *exit_code = _pclose(fp); else _pclose(fp); #else { int status = pclose(fp); if (exit_code && !capped) { if (WIFEXITED(status)) *exit_code = WEXITSTATUS(status); else if (WIFSIGNALED(status)) *exit_code = 128 + WTERMSIG(status); else *exit_code = -1; } } #endif if (capped && exit_code) *exit_code = -2; 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__"; size_t max_bytes = 1024 * 1024; if (http_status) *http_status = 0; if (exit_code) *exit_code = 0; if (starts_ci(url, "claro://")) return claro_mock_http(url, http_status); claro_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, max_bytes); if (exit_code && *exit_code == -2) { free(raw); return xstrdup(""); } mark = NULL; { char *scan = raw; while ((scan = strstr(scan, marker)) != NULL) { mark = scan; scan++; } } 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; i < v.list->count; i++) for (j = i + 1; j < v.list->count; 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; i < v.list->count / 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; i < rt->pcount; 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 program_free(Program *p) { int i; if (!p) return; for (i = 0; i < p->count; i++) free(p->lines[i]); free(p->lines); free(p->path); free(p); } static void runtime_programs_free(Runtime *rt) { int i; if (!rt) return; for (i = 0; i < rt->pcount; i++) program_free(rt->programs[i]); free(rt->programs); rt->programs = NULL; rt->pcount = rt->pcap = 0; } static void runtime_vars_free(Var **head) { Var *v, *next; if (!head) return; for (v = *head; v; v = next) { next = v->next; free(v->name); value_free(v->val); free(v); } *head = NULL; } static void runtime_free(Runtime *rt) { Function *fn, *fn_next; Module *m, *m_next; ClassDef *c, *c_next; FieldDef *field, *field_next; int i; if (!rt) return; runtime_programs_free(rt); runtime_vars_free(&rt->locals); runtime_vars_free(&rt->globals); for (fn = rt->funcs; fn; fn = fn_next) { fn_next = fn->next; free(fn->name); for (i = 0; i < fn->pcnt; i++) free(fn->params[i]); free(fn->params); free(fn); } rt->funcs = NULL; for (m = rt->modules; m; m = m_next) { m_next = m->next; free(m->ns); free(m->kind); free(m); } rt->modules = NULL; for (c = rt->classes; c; c = c_next) { c_next = c->next; for (field = c->fields; field; field = field_next) { field_next = field->next; free(field->name); free(field->type); free(field); } free(c->name); free(c); } rt->classes = NULL; for (i = 0; i < rt->import_depth; i++) free(rt->import_stack[i]); free(rt->import_stack); rt->import_stack = NULL; rt->import_depth = 0; free(rt->captured.s); rt->captured.s = NULL; value_free(rt->ret); rt->ret = v_none(); free(rt->err_msg); free(rt->err_file); rt->err_msg = NULL; rt->err_file = NULL; } 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; i < p->count; 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; i < p->count; 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; int depth; const char *error; } JsonP; #define CLARO_MAX_JSON_DEPTH 128 static void jp_ws(JsonP *j) { while (*j->s && isspace((unsigned char)*j->s)) j->s++; } static unsigned long jp_hex4(JsonP *j) { unsigned long value = 0; int i, digit; for (i = 0; i < 4; i++) { char c = *j->s; if (c >= '0' && c <= '9') digit = c - '0'; else if (c >= 'a' && c <= 'f') digit = c - 'a' + 10; else if (c >= 'A' && c <= 'F') digit = c - 'A' + 10; else { j->error = "JSON Unicode escapes need four hexadecimal digits."; return 0; } value = value * 16 + (unsigned long)digit; j->s++; } return value; } static void jp_utf8(JsonP *j, Str *b, unsigned long code) { unsigned long low; if (code >= 0xd800UL && code <= 0xdbffUL) { if (j->s[0] != '\\' || j->s[1] != 'u') { j->error = "JSON Unicode needs a matching low surrogate."; return; } j->s += 2; low = jp_hex4(j); if (j->error) return; if (low < 0xdc00UL || low > 0xdfffUL) { j->error = "JSON Unicode needs a matching low surrogate."; return; } code = 0x10000UL + (code - 0xd800UL) * 1024 + low - 0xdc00UL; } else if (code >= 0xdc00UL && code <= 0xdfffUL) { j->error = "JSON Unicode has an unmatched low surrogate."; return; } if (!code) { j->error = "JSON text cannot contain a zero character."; return; } if (code < 0x80UL) str_ch(b, (char)code); else if (code < 0x800UL) { str_ch(b, (char)(0xc0UL | (code >> 6))); str_ch(b, (char)(0x80UL | (code & 63))); } else if (code < 0x10000UL) { str_ch(b, (char)(0xe0UL | (code >> 12))); str_ch(b, (char)(0x80UL | ((code >> 6) & 63))); str_ch(b, (char)(0x80UL | (code & 63))); } else { str_ch(b, (char)(0xf0UL | (code >> 18))); str_ch(b, (char)(0x80UL | ((code >> 12) & 63))); str_ch(b, (char)(0x80UL | ((code >> 6) & 63))); str_ch(b, (char)(0x80UL | (code & 63))); } } static char *jp_string(JsonP *j) { Str b; char escaped; str_init(&b); jp_ws(j); if (*j->s != '"') { j->error = "JSON object keys need double quotes."; return xstrdup(""); } j->s++; while (*j->s && *j->s != '"' && !j->error) { if (*j->s == '\\') { j->s++; if (!*j->s) { j->error = "JSON text is missing a closing quote."; break; } escaped = *j->s++; if (escaped == 'n') str_ch(&b, '\n'); else if (escaped == 't') str_ch(&b, '\t'); else if (escaped == 'r') str_ch(&b, '\r'); else if (escaped == 'b') str_ch(&b, '\b'); else if (escaped == 'f') str_ch(&b, '\f'); else if (escaped == 'u') { unsigned long code = jp_hex4(j); if (!j->error) jp_utf8(j, &b, code); } else if (escaped == '"' || escaped == '\\' || escaped == '/') str_ch(&b, escaped); else j->error = "JSON has an invalid text escape."; } else if ((unsigned char)*j->s < 32) j->error = "JSON text contains an unescaped control character."; else str_ch(&b, *j->s++); } if (*j->s == '"') j->s++; else if (!j->error) j->error = "JSON text is missing a closing quote."; return str_take(&b); } static Value jp_value(JsonP *j); static Value jp_array(JsonP *j) { Value arr = v_list(); j->s++; jp_ws(j); if (*j->s == ']') { j->s++; return arr; } while (!j->error) { Value v = jp_value(j); if (!j->error) list_add(arr.list, v); value_free(v); if (j->error) break; jp_ws(j); if (*j->s == ']') { j->s++; break; } if (*j->s != ',') { j->error = "JSON list needs a comma or closing bracket."; break; } j->s++; } return arr; } static Value jp_object(JsonP *j) { Value obj = v_map(); j->s++; jp_ws(j); if (*j->s == '}') { j->s++; return obj; } while (!j->error) { char *key = jp_string(j); Value v = v_none(); jp_ws(j); if (!j->error && *j->s != ':') j->error = "JSON object needs a colon after its key."; if (!j->error) { j->s++; v = jp_value(j); } if (!j->error) map_put(obj.map, key, v); value_free(v); free(key); if (j->error) break; jp_ws(j); if (*j->s == '}') { j->s++; break; } if (*j->s != ',') { j->error = "JSON object needs a comma or closing brace."; break; } j->s++; } return obj; } static Value jp_value(JsonP *j) { const char *scan; char *end; double number; Value result; jp_ws(j); if (*j->s == '"') { char *text = jp_string(j); result = v_str(text); free(text); return result; } if (*j->s == '[' || *j->s == '{') { if (j->depth >= CLARO_MAX_JSON_DEPTH) { j->error = "JSON nesting exceeds the safe limit of 128. Simplify the data."; return v_none(); } j->depth++; result = *j->s == '[' ? jp_array(j) : jp_object(j); j->depth--; return result; } 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(); } scan = j->s; if (*scan == '-') scan++; if (*scan == '0') scan++; else if (*scan >= '1' && *scan <= '9') while (isdigit((unsigned char)*scan)) scan++; else { j->error = "JSON needs a value here."; return v_none(); } if (*scan == '.') { scan++; if (!isdigit((unsigned char)*scan)) { j->error = "JSON number needs digits after its decimal point."; return v_none(); } while (isdigit((unsigned char)*scan)) scan++; } if (*scan == 'e' || *scan == 'E') { scan++; if (*scan == '+' || *scan == '-') scan++; if (!isdigit((unsigned char)*scan)) { j->error = "JSON number needs exponent digits."; return v_none(); } while (isdigit((unsigned char)*scan)) scan++; } number = strtod(j->s, &end); if (end != scan || number > DBL_MAX || number < -DBL_MAX) { j->error = "JSON number is outside the supported range."; return v_none(); } j->s = scan; return v_num(number); } static Value parse_json_text(Runtime *rt, const char *text) { JsonP j; Value result; memset(&j, 0, sizeof(j)); j.s = text; result = jp_value(&j); jp_ws(&j); if (!j.error && *j.s) j.error = "JSON has unexpected text after its value."; if (j.error) { value_free(result); rt_error(rt, "", 0, "%s", j.error); return v_none(); } return result; } 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 if ((unsigned char)*s < 32) { static const char hex[] = "0123456789abcdef"; unsigned char c = (unsigned char)*s; str_add(b, "\\u00"); str_ch(b, hex[c >> 4]); str_ch(b, hex[c & 15]); } 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; i < n; i++) str_ch(b, ' '); } static void json_write(Str *b, Value v, int pretty, int indent) { int i; char *s; if (v.type == V_STR) { json_escape(b, v.str); } else if (v.type == V_NUM) { s = num_to_string(v.num); str_add(b, s); free(s); } else if (v.type == V_BOOL) { str_add(b, v.boolean ? "true" : "false"); } else if (v.type == V_NONE) { str_add(b, "null"); } else if (v.type == V_LIST) { str_ch(b, '['); if (pretty && v.list->count) str_ch(b, '\n'); for (i = 0; i < v.list->count; i++) { if (pretty) indent_add(b, indent + 2); json_write(b, v.list->items[i], pretty, indent + 2); if (i + 1 < v.list->count) 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; i < v.map->count; 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 + 1 < v.map->count) 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 int builtin_required_args(const char *kind, const char *fn, const char **shape) { if (strcmp(kind, "text") == 0) { if (strcmp(fn, "upper") == 0 || strcmp(fn, "lower") == 0) { if (shape) *shape = "value"; return 1; } if (strcmp(fn, "contains") == 0 || strcmp(fn, "endswith") == 0 || strcmp(fn, "split2") == 0) { if (shape) *shape = "text, part"; return 2; } } if (strcmp(kind, "math") == 0) { if (strcmp(fn, "abs") == 0) { if (shape) *shape = "value"; return 1; } if (strcmp(fn, "clamp") == 0) { if (shape) *shape = "value, lower, upper"; return 3; } } if (strcmp(kind, "random") == 0) { if (strcmp(fn, "seed") == 0) { if (shape) *shape = "value"; return 1; } if (strcmp(fn, "int") == 0) { if (shape) *shape = "lower, upper"; return 2; } } if (strcmp(kind, "csv") == 0) { if (strcmp(fn, "read") == 0) { if (shape) *shape = "text"; return 1; } if (strcmp(fn, "write") == 0) { if (shape) *shape = "rows"; return 1; } } if (strcmp(kind, "path") == 0) { if (strcmp(fn, "join") == 0) { if (shape) *shape = "folder, name"; return 2; } if (strcmp(fn, "basename") == 0 || strcmp(fn, "dirname") == 0 || strcmp(fn, "ext") == 0 || strcmp(fn, "stem") == 0 || strcmp(fn, "absolute") == 0) { if (shape) *shape = "path"; return 1; } } if (strcmp(kind, "collections") == 0) { if (strcmp(fn, "length") == 0 || strcmp(fn, "reversed") == 0 || strcmp(fn, "keys") == 0 || strcmp(fn, "values") == 0) { if (shape) *shape = "collection"; return 1; } if (strcmp(fn, "contains") == 0 || strcmp(fn, "index") == 0 || strcmp(fn, "has_key") == 0) { if (shape) *shape = "collection, value"; return 2; } if (strcmp(fn, "join") == 0) { if (shape) *shape = "collection, separator"; return 2; } if (strcmp(fn, "get") == 0) { if (shape) *shape = "map, key"; return 2; } } return 0; } 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; const char *shape = NULL; int required; *handled = 0; if (!dot) return v_none(); claro_snprintf(ns, sizeof(ns), "%.*s", (int)(dot - name), name); claro_snprintf(fn, sizeof(fn), "%s", dot + 1); kind = module_kind(rt, ns); if (!kind) return v_none(); *handled = 1; required = builtin_required_args(kind, fn, &shape); if (required > argc) { rt_error(rt, "", 0, "%s.%s needs %d argument%s, but got %d. Try: CALL %s.%s WITH %s.", ns, fn, required, required == 1 ? "" : "s", argc, ns, fn, shape ? shape : "the required values"); return v_none(); } 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) { Value r; for (i = 0; a[i]; i++) a[i] = (char)toupper((unsigned char)a[i]); r = v_str(a); free(a); free(b); return r; } if (strcmp(fn, "lower") == 0) { Value r; for (i = 0; a[i]; i++) a[i] = (char)tolower((unsigned char)a[i]); 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 (x < lo) x = lo; if (x > hi) 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]); double span, r; if (a > b) { rt_error(rt, "", 0, "random.int needs the lower bound to be less than or equal to the upper " "bound."); return v_none(); } span = (double)b - (double)a + 1.0; r = (double)a + fmod((double)rand(), span); return v_num((double)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; int inq = 0; str_init(&field); 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; int i, j; str_init(&out); 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; Value r; if (back && (!cut || back > cut)) cut = back; 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(); } /* ---------- 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 int parse_simple_params(char *s, char ***out) { char **arr = NULL; int cnt = 0, cap = 0; char *tok = strtok(s, " \t\r\n"); while (tok) { if (cnt >= cap) { cap = cap ? cap * 2 : 4; arr = (char **)xrealloc(arr, sizeof(char *) * cap); } arr[cnt++] = xstrdup(tok); tok = strtok(NULL, " \t\r\n"); } *out = arr; return cnt; } static void parse_teach_parts(char *t, char **name_out, char ***params_out, int *pcnt_out) { char *name, *takes, *returns; 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; returns = (char *)find_word_ci(t, "RETURNS"); if (returns) *returns = 0; takes = (char *)find_word_ci(t, "TAKES"); if (takes) { takes += 5; pcnt = parse_params(takes, ¶ms); } else { char *simple = trim_inplace(t); if (*simple) pcnt = strchr(simple, ',') ? parse_params(simple, ¶ms) : parse_simple_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; i < p->count; 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) { claro_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); } split_args_free(params, pcnt); 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); claro_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); split_args_free(params, pcnt); 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; claro_snprintf(objname, sizeof(objname), "%.*s", (int)(dot - name), name); claro_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; claro_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); claro_snprintf(fieldname, sizeof(fieldname), "__type_%s", fd->name); rt_set(rt, fieldname, v_str(fd->type)); } } 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 (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); claro_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 (rt->call_depth >= CLARO_MAX_CALL_DEPTH) { rt_error(rt, "", 0, "Claro function call depth exceeded the safe limit of %d. Simplify the recursion " "or add a stopping condition.", CLARO_MAX_CALL_DEPTH); return; } rt->call_depth++; if (call_object_method(rt, name, args, argc)) { rt->call_depth--; return; } { Function *f = find_function(rt, name); if (!f) { rt_error(rt, "", 0, "Unknown function: %s", name); rt->call_depth--; 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; } } rt->call_depth--; } 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 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); 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; Var *v; 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 (v = rt->globals; v; v = v->next) { if (strncmp(v->name, prefix, strlen(prefix)) != 0 && export_allows(exports, ec, v->name)) { char nn[256]; claro_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(rt->import_stack[rt->import_depth]); rt->import_stack[rt->import_depth] = NULL; split_args_free(exports, ec); free(prefix); } static void create_object_value(Runtime *rt, const char *file, int line, const char *cls, const char *var) { Value obj, class_name; ClassDef *c; 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; } obj = v_map(); c = find_class(rt, cls); class_name = v_str(cls); map_put(obj.map, "class", class_name); value_free(class_name); rt_set_checked(rt, file, line, var, "OBJECT", obj); value_free(obj); if (c) { for (fd = c->fields; fd; fd = fd->next) { Value def = claro_default_for_type(fd->type); claro_snprintf(fullname, sizeof(fullname), "%s.%s", var, fd->name); rt_set_checked(rt, file, line, fullname, fd->type, def); value_free(def); } } } static void exec_line_body(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); value_free(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]; claro_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, "YIELD") == 0) { return; } if (strcmp(up, "WAIT") == 0) { return; } if (strcmp(up, "TASK") == 0) { if (starts_ci(t + 4, " STATUS")) { const char *as = find_word_ci(t, "AS"); if (as) { char *name = substr(t + 11, as); char *var = xstrdup(trim_inplace((char *)as + 2)); char key[256]; Value done; claro_snprintf(key, sizeof(key), "TASK_%s_DONE", trim_inplace(name)); done = rt_get(rt, key); rt_set(rt, var, v_str(v_truth(done) ? "DONE" : "PENDING")); free(name); free(var); } else rt_error(rt, p->path, pc + 1, "TASK STATUS needs AS. Try: TASK STATUS helloTask AS status"); 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); if (code == -2) { rt_error(rt, p->path, pc + 1, "HTTP response exceeds the safe size limit of 1048576 bytes."); free(out); free(url); free(ue); free(after); free(var); free(status_var); return; } 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); if (code == -2) { rt_error(rt, p->path, pc + 1, "HTTP response exceeds the safe size limit of 1048576 bytes."); free(out); free(url); free(ue); free(var); return; } 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 (code == -2) { rt_error(rt, p->path, pc + 1, "HTTP response exceeds the safe size limit of 1048576 bytes."); free(out); free(url); free(path); free(ue); free(pe); free(status_var); return; } 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); Value class_name = v_str(cn ? cn : ""); rt_set_checked(rt, p->path, pc + 1, var, "TEXT", class_name); value_free(class_name); value_free(ov); 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; i < ov.map->count; i++) { if (strcmp(ov.map->keys[i], "class") != 0 && !starts_ci(ov.map->keys[i], "__type_")) { Value field_name = v_str(ov.map->keys[i]); list_add(arr.list, field_name); value_free(field_name); } } } rt_set_checked(rt, p->path, pc + 1, var, "LIST", arr); value_free(ov); value_free(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); const char *pcur = as + 2; char *var = unquote_token(&pcur); char *extra = xstrdup(trim_inplace((char *)pcur)); if (extra && *extra) rt_error(rt, p->path, pc + 1, "TYPE OF %s has extra text after result name %s. Keep only the " "expression, AS, and one result name.", trim_inplace(expr), trim_inplace(var)); else { 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); free(extra); } 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 && as < to) { char *name = substr(rest, as); char *type = substr(as + 2, to); char *n = trim_inplace(name); char *ty = trim_inplace(type); Value v = eval_expr(rt, to + 2); rt_set_checked(rt, p->path, pc + 1, n, ty, v); value_free(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); value_free(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); value_free(v); } else { Value v = eval_expr(rt, save); if (*n) rt_set_checked(rt, p->path, pc + 1, n, NULL, v); value_free(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); value_free(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_none(); 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 { val = v_str(input); rt_set_checked(rt, p->path, pc + 1, trim_inplace(var), NULL, val); } value_free(val); } 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); value_free(cond); *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_simple_args(pcur, &parts); args = (Value *)xmalloc(sizeof(Value) * (ac ? ac : 1)); for (i = 0; i < ac; i++) args[i] = eval_expr(rt, parts[i]); call_function(rt, n, args, ac); split_args_free(parts, ac); call_args_free(args, ac); free(name); return; } else { char *e = substr(t + 2, times); Value times_value = eval_expr(rt, e); int n = (int)v_number(times_value); int i; value_free(times_value); free(e); for (i = 0; i < n && !rt->error && !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) { char *k, *v; int i; *comma = 0; k = trim_inplace(vars); v = trim_inplace(comma + 1); for (i = 0; i < col.map->count && !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; i < col.list->count && !rt->error && !rt->returning; i++) { rt_set(rt, v, col.list->items[i]); exec_range(rt, p, pc + 1, end); } } value_free(col); 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) { claro_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; int again = 0; if (end < 0) { rt_error(rt, p->path, pc + 1, "REPEAT needs UNTIL and a stopping condition."); return; } cond = p->lines[end] + 5; do { exec_range(rt, p, pc + 1, end); if (rt->error || rt->returning) break; rt->active_file = p->path; rt->active_line = end + 1; { Value condition = eval_expr(rt, cond); again = !v_truth(condition); value_free(condition); } } while (again && !rt->error); *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; i < ac; i++) args[i] = eval_expr(rt, parts[i]); call_function(rt, n, args, ac); split_args_free(parts, ac); call_args_free(args, ac); free(name); return; } if (strcmp(up, "IMPORT") == 0) { const char *pcur = t + 6; char *path = unquote_token(&pcur); char *ns = NULL; while (*pcur && isspace((unsigned char)*pcur)) pcur++; if (starts_ci(pcur, "AS")) { pcur += 2; ns = xstrdup(trim_inplace((char *)pcur)); } do_import(rt, path, ns, p->path, 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); value_free(v); value_free(l); } 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))); value_free(v); 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; i < rt->script_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)); Value index_value = eval_expr(rt, ie); int idx = (int)v_number(index_value); Value argument_value; value_free(index_value); argument_value = v_str(idx >= 1 && idx <= rt->script_argc ? rt->script_argv[idx - 1] : ""); rt_set(rt, var, argument_value); value_free(argument_value); 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); Value result = v_str(ev ? ev : ""); rt_set(rt, var, result); value_free(result); free(ns); value_free(nv); 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), iv = eval_expr(rt, ie); int idx = (int)v_number(iv); value_free(iv); if (c.type == V_LIST && idx >= 1 && idx <= c.list->count) rt_set(rt, var, c.list->items[idx - 1]); else { Value empty = v_str(""); rt_set(rt, var, empty); value_free(empty); } value_free(c); 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) { Value got = map_get(c.map, ks); rt_set(rt, var, got); value_free(got); } else rt_set(rt, var, v_str("")); free(ks); value_free(c); value_free(kv); 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); value_free(m); value_free(k); value_free(v); } 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); value_free(pv); value_free(vv); 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; Value contents; unsigned long bytes = 0; str_init(&b); while ((c = fgetc(f)) != EOF) { if (rt->read_file_limit && bytes >= rt->read_file_limit) { rt_error(rt, p->path, pc + 1, "READ FILE exceeded the configured file limit. Raise CLARO_READ_FILE_LIMIT if needed."); break; } str_ch(&b, (char)c); if (rt->read_file_limit) bytes++; } if (ferror(f)) rt_error(rt, p->path, pc + 1, "Could not finish reading the file."); fclose(f); contents = v_str(b.s ? b.s : ""); if (!rt->error) rt_set(rt, var, contents); value_free(contents); free(b.s); } free(path); value_free(pv); 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); value_free(pv); 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); value_free(pv); 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); value_free(sv); value_free(dv); } 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); value_free(sv); value_free(dv); } 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); Value entries = list_folder_value(path); rt_set(rt, var, entries); value_free(entries); value_free(pv); 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); value_free(pv); 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); value_free(pv); 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); Value parsed = parse_json_text(rt, txt); if (!rt->error) rt_set(rt, var, parsed); value_free(parsed); value_free(s); 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); Value encoded = v_str(js); rt_set(rt, var, encoded); value_free(encoded); value_free(v); 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); Value result = v_str(r); rt_set(rt, var, result); value_free(result); free(r); } else if (!strcmp(restup, "LOWER")) { char *r = text_lower_copy(txt); Value result = v_str(r); rt_set(rt, var, result); value_free(result); free(r); } else if (!strcmp(restup, "TRIM")) { char *r = text_trim_copy(txt); Value result = v_str(r); rt_set(rt, var, result); value_free(result); 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); } split_args_free(args, n); } 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); } value_free(v); 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); } split_args_free(args, n); if (b < a) { double c = a; a = b; b = c; } rt_set(rt, var, v_num((int)a + (rand() % ((int)(b - a + 1))))); free(range); free(var); } return; } if (strcmp(up, "RUN") == 0) { const char *as = find_word_ci(t, "AS"); if (as && starts_ci(t + 3, " COMMAND")) { char *ex = substr(t + 11, as); char *var = xstrdup(trim_inplace((char *)as + 2)); Value cv = eval_expr(rt, ex); char *cmd = v_to_string(cv); int code = 0; char *out = capture_command_output(cmd, &code, 0); Value output = v_str(out); rt_set(rt, var, output); value_free(output); rt_set(rt, "LASTEXIT", v_num(code)); value_free(cv); free(out); free(cmd); free(ex); free(var); } return; } if (strcmp(up, "SORT") == 0) { char *name = xstrdup(trim_inplace(t + 4)); Value v = rt_get(rt, name); value_list_sort(v); rt_set(rt, name, v); value_free(v); free(name); return; } if (strcmp(up, "REVERSE") == 0) { char *name = xstrdup(trim_inplace(t + 7)); Value v = rt_get(rt, name); value_list_reverse(v); rt_set(rt, name, v); free(name); return; } if (strcmp(up, "FIND") == 0) { const char *in = find_word_ci(t, "IN"), *as = find_word_ci(t, "AS"); if (in && as) { char *needle = substr(t + 4, in); char *listname = substr(in + 2, as); char *var = xstrdup(trim_inplace((char *)as + 2)); Value nv = eval_expr(rt, needle), lv = eval_expr(rt, listname); int found = 0, i; if (lv.type == V_LIST) { for (i = 0; i < lv.list->count; 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); value_free(nv); value_free(lv); } 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; i < lv.list->count; i++) if (value_compare(nv, lv.list->items[i]) == 0) { value_free(lv.list->items[i]); for (j = i; j < lv.list->count - 1; j++) lv.list->items[j] = lv.list->items[j + 1]; lv.list->count--; break; } rt_set(rt, listname, lv); } free(needle); free(listname); value_free(nv); value_free(lv); } 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_line(Runtime *rt, Program *p, int *pcp, const char *raw) { const char *saved_file = rt->active_file; int saved_line = rt->active_line; rt->active_file = p->path; rt->active_line = *pcp + 1; if (rt_tick(rt)) exec_line_body(rt, p, pcp, raw); rt->active_file = saved_file; rt->active_line = saved_line; } static void exec_range(Runtime *rt, Program *p, int start, int end) { int pc; /* Function calls have their own guard; count additional control-block frames. */ if (rt->range_depth - rt->call_depth > CLARO_MAX_BLOCK_DEPTH) { rt_error(rt, p->path, start + 1, "Block nesting exceeds the safe limit of 128. Simplify the nested blocks."); return; } if (!rt_tick(rt)) return; rt->range_depth++; if (end < 0 || end > p->count) end = p->count; for (pc = start; pc < end && !rt->error && !rt->returning; pc++) exec_line(rt, p, &pc, p->lines[pc]); rt->range_depth--; } static int run_file(Runtime *rt, const char *path) { Program *p = load_program(rt, path); int rc; 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); rc = rt->error ? 1 : 0; if (rt->error && !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"); runtime_programs_free(rt); return rc; } /* ---------- 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, i; 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 (i = 0; i < indent; i++) putchar(' '); puts(t); opens = !strcmp(up, "TEACH") || !strcmp(up, "IF") || !strcmp(up, "FOR") || !strcmp(up, "TRY") || !strcmp(up, "CLASS") || !strcmp(up, "START") || !strcmp(up, "COMMENT") || !strcmp(up, "REPEAT") || !strcmp(up, "ELSE") || !strcmp(up, "CATCH") || (!strcmp(up, "DO") && find_word_ci(t, "TIMES")); if (opens) indent += 4; } fclose(f); return 0; } static const char *check_commands[] = { "SAY", "SET", "ASK", "IF", "ELSE", "ENDIF", "DO", "DONE", "FOR", "REPEAT", "UNTIL", "TEACH", "LEARNED", "RETURN", "CALL", "IMPORT", "ADD", "COUNT", "GET", "PUT", "WRITE", "APPEND", "READ", "EXISTS", "DELETE", "PARSE", "MAKE", "TRY", "CATCH", "ENDTRY", "RAISE", "CREATE", "COPY", "MOVE", "LIST", "TEXT", "RANDOM", "RUN", "SORT", "REVERSE", "FIND", "REMOVE", "SHOW", "HIDE", "EXPORT", "COMMENT", "ENDCOMMENT", "END", "WAIT", "YIELD", "TASK", "CLASS", "HAS", "ENDCLASS", "NEW", "START", "ENDTASK", "OPEN", "CLEAR", "DRAW", "UPDATE", "CLOSE", "HTTP", "OBJECT", "TYPE", "CHECK", NULL}; static int known_command(const char *up) { int i; for (i = 0; check_commands[i]; i++) if (strcmp(up, check_commands[i]) == 0) return 1; return 0; } static int levenshtein(const char *a, const char *b) { int la = (int)strlen(a), lb = (int)strlen(b), i, j; int *dp = (int *)xmalloc(sizeof(int) * (la + 1) * (lb + 1)); for (i = 0; i <= la; i++) dp[i * (lb + 1)] = i; for (j = 0; j <= lb; j++) dp[j] = j; for (i = 1; i <= la; i++) for (j = 1; j <= lb; j++) { int c = tolower((unsigned char)a[i - 1]) == tolower((unsigned char)b[j - 1]) ? 0 : 1; int x = dp[(i - 1) * (lb + 1) + j] + 1, y = dp[i * (lb + 1) + j - 1] + 1, z = dp[(i - 1) * (lb + 1) + j - 1] + c; int m = x < y ? x : y; if (z < m) m = z; dp[i * (lb + 1) + j] = m; } i = dp[la * (lb + 1) + lb]; free(dp); return i; } static const char *suggest_command_name(const char *up) { const char *best = NULL; int bd = 999, i; if (strcmp(up, "PRINT") == 0 || strcmp(up, "PRNT") == 0 || strcmp(up, "ECHO") == 0 || strcmp(up, "SAI") == 0) return "SAY"; for (i = 0; check_commands[i]; i++) { int d = levenshtein(up, check_commands[i]); if (d < bd) { bd = d; best = check_commands[i]; } } return (best && bd <= 2) ? best : NULL; } static void check_add_var(Var **vars, const char *name) { char tmp[256]; char *t; Var *v; if (!name) return; strncpy(tmp, name, sizeof(tmp) - 1); tmp[sizeof(tmp) - 1] = 0; t = trim_inplace(tmp); if (!*t) return; if (env_find(*vars, t)) return; v = (Var *)xmalloc(sizeof(Var)); memset(v, 0, sizeof(Var)); v->name = 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 CheckBlock check_block(const char *expect, const char *open, int line) { CheckBlock block; block.expect = expect; block.open = open; block.line = line; return block; } static void check_vars_free(Var **vars) { Var *v, *next; if (!vars) return; for (v = *vars; v; v = next) { next = v->next; free(v->name); value_free(v->val); free(v); } *vars = NULL; } 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 (sp >= CLARO_MAX_BLOCK_DEPTH && (!strcmp(up, "IF") || !strcmp(up, "TEACH") || !strcmp(up, "CLASS") || !strcmp(up, "FOR") || !strcmp(up, "TRY") || !strcmp(up, "COMMENT") || !strcmp(up, "REPEAT") || (!strcmp(up, "DO") && find_word_ci(t, "TIMES")) || (!strcmp(up, "START") && starts_ci(t + 5, " TASK")))) { printf("%s:%d: Block nesting exceeds the safe limit of 128. Simplify the nested blocks.\n", path, line_no); errs++; sp = 0; break; } 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++] = check_block("ENDIF", "IF", line_no); } else if (!strcmp(up, "CLASS")) { stack[sp++] = check_block("ENDCLASS", "CLASS", line_no); } else if (!strcmp(up, "START")) { if (starts_ci(t + 5, " TASK")) stack[sp++] = check_block("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 { char *function_name = NULL; char **parameters = NULL; int count = 0, index; parse_teach_parts(t, &function_name, ¶meters, &count); for (index = 0; index < count; index++) check_add_var(&vars, parameters[index]); split_args_free(parameters, count); /* function_name points into the borrowed line buffer. */ } stack[sp++] = check_block("LEARNED", "TEACH", line_no); } else if (!strcmp(up, "DO")) { if (find_word_ci(t, "TIMES")) { stack[sp++] = check_block("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 { char *comma; name = substr(each + 4, in); 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++] = check_block("DONE", "FOR", line_no); } else if (!strcmp(up, "TRY")) { stack[sp++] = check_block("ENDTRY", "TRY", line_no); } else if (!strcmp(up, "COMMENT")) { stack[sp++] = check_block("ENDCOMMENT", "COMMENT", line_no); } else if (!strcmp(up, "REPEAT")) { stack[sp++] = check_block("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 \"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 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, "TASK")) { char *name = check_var_after_as(t); if (name) { 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; if (ts.error) { printf("%s:%d: %s\n", path, line_no, ts.error); errs++; } for (i = 0; i < ts.n; i++) { char *tok = ts.t[i]; if (isalpha((unsigned char)tok[0]) && !ci_eq(tok, "YES") && !ci_eq(tok, "NO") && !ci_eq(tok, "AND") && !ci_eq(tok, "OR") && !ci_eq(tok, "NOT") && !ci_eq(tok, "IS") && !ci_eq(tok, "LIST") && !ci_eq(tok, "MAP") && !env_find(vars, tok)) { Var *v = vars; char *best = NULL; int bd = 999; while (v) { int d = levenshtein(tok, v->name); if (d < bd) { bd = d; best = v->name; } 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++; } } tokens_free(&ts); } } fclose(f); while (sp > 0) { CheckBlock b = stack[--sp]; const char *ending = b.expect; if (!strcmp(b.open, "IF") || !strcmp(b.open, "TEACH") || !strcmp(b.open, "DO") || !strcmp(b.open, "FOR")) ending = "END"; printf("%s:%d: %s block opened here needs %s\n", path, b.line, b.open, ending); errs++; } if (errs == 0) printf("OK\n"); check_vars_free(&vars); 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_free(Var **types) { Var *v, *next; if (!types) return; for (v = *types; v; v = next) { next = v->next; free(v->name); value_free(v->val); free(v); } *types = 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) { value_free(v->val); 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; int in_string = 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"; for (i = 0; t[i]; i++) { if (t[i] == '"') in_string = !in_string; if (!in_string && strchr("+-*/", t[i])) { char *left = substr(t, t + i), *right = xstrdup(t + i + 1); const char *lt, *rt; lt = simple_expr_type(types, left); rt = simple_expr_type(types, right); free(left); free(right); if ((lt && ci_eq(lt, "TEXT")) || (rt && ci_eq(rt, "TEXT"))) return "TEXT"; if (lt && rt && ci_eq(lt, "NUMBER") && ci_eq(rt, "NUMBER")) return "NUMBER"; return NULL; } } i = 0; 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 return_value_extra_tokens(const char *expr, char *value, size_t value_size) { char tmp[512], *t, *p; if (!expr || !value || value_size == 0) return 0; strncpy(tmp, expr, sizeof(tmp) - 1); tmp[sizeof(tmp) - 1] = 0; t = trim_inplace(tmp); if (!*t || strpbrk(t, "+-*/\"") != NULL) return 0; p = t; while (*p && !isspace((unsigned char)*p)) p++; if (!*p) return 0; *p++ = 0; while (*p && isspace((unsigned char)*p)) p++; if (!*p) return 0; claro_snprintf(value, value_size, "%s", t); return 1; } static const char *text_operand_for_operator(Var *types, const char *expr, char wanted) { static char operand[256]; char tmp[512]; char *t; size_t i; int in_string = 0; if (!expr) return NULL; strncpy(tmp, expr, sizeof(tmp) - 1); tmp[sizeof(tmp) - 1] = 0; t = trim_inplace(tmp); for (i = 0; t[i]; i++) { char *left, *right; const char *lt, *rt; if (t[i] == '"') in_string = !in_string; if (!in_string && t[i] == wanted) { left = substr(t, t + i); right = xstrdup(t + i + 1); lt = simple_expr_type(types, left); rt = simple_expr_type(types, right); if (lt && ci_eq(lt, "TEXT")) { claro_snprintf(operand, sizeof(operand), "%s", trim_inplace(left)); free(left); free(right); return operand; } if (rt && ci_eq(rt, "TEXT")) { claro_snprintf(operand, sizeof(operand), "%s", trim_inplace(right)); free(left); free(right); return operand; } free(left); free(right); } } return NULL; } static char text_operator_for_number_return(Var *types, const char *expr) { const char ops[] = "+-*/"; int i; for (i = 0; ops[i]; i++) if (text_operand_for_operator(types, expr, ops[i])) return ops[i]; return 0; } static int type_words_match(const char *need, const char *got) { if (!need || !*need || ci_eq(need, "ANY") || ci_eq(got, "ANY")) return 1; if (!got || !*got) return 1; if (ci_eq(need, got)) return 1; if ((ci_eq(need, "LIST") || ci_eq(need, "MAP")) && starts_ci(got, need)) return 1; if (starts_ci(need, "LIST OF") && ci_eq(got, "LIST")) return 1; if (starts_ci(need, "MAP OF") && ci_eq(got, "MAP")) 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") || starts_ci(got, "OBJECT:"))) return 1; return 0; } static const char *container_member_type(const char *type, const char *kind) { const char *p; static char buf[128]; if (!type || !kind) return NULL; if (!starts_ci(type, kind)) return NULL; p = type + strlen(kind); while (*p && isspace((unsigned char)*p)) p++; if (!starts_ci(p, "OF")) return NULL; p += 2; while (*p && isspace((unsigned char)*p)) p++; if (!*p) return NULL; claro_snprintf(buf, sizeof(buf), "%s", p); return trim_inplace(buf); } 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 && as < to) { *name = substr(rest, as); *type = substr(as + 2, to); *expr = xstrdup(to + 2); return; } if (to) { char *before = substr(rest, to); const char *pcur = before; char *var = unquote_token(&pcur); char *maybe = unquote_token(&pcur); char *mt = trim_inplace(maybe); *name = xstrdup(trim_inplace(var)); if (claro_is_type_word(mt)) *type = xstrdup(mt); *expr = xstrdup(to + 2); free(before); free(var); free(maybe); return; } { const char *pcur = rest; char *var = unquote_token(&pcur); const char *after_var = pcur; char *maybe = unquote_token(&pcur); char *mt = trim_inplace(maybe); char *remaining = trim_inplace((char *)pcur); int uppercase_candidate = 1; char *q; for (q = mt; *q; q++) if (isalpha((unsigned char)*q) && !isupper((unsigned char)*q)) uppercase_candidate = 0; *name = xstrdup(trim_inplace(var)); if (!*remaining && (ci_eq(mt, "LIST") || ci_eq(mt, "MAP"))) { /* A bare container word constructs a value, not an empty annotation. */ *expr = xstrdup(mt); } else if (claro_is_type_word(mt)) { *type = xstrdup(mt); *expr = xstrdup(remaining); } else if (strchr(trim_inplace(var), '.') && *remaining && strchr(mt, ' ') == NULL && uppercase_candidate) { *type = xstrdup(mt); *expr = xstrdup(remaining); } else { *expr = xstrdup(trim_inplace((char *)after_var)); } free(var); free(maybe); } } typedef struct TypeParamCheck { char *func; char *param; char *type; int pos; struct TypeParamCheck *next; } TypeParamCheck; typedef struct TypeFieldCheck { char *cls; char *field; char *type; struct TypeFieldCheck *next; } TypeFieldCheck; typedef struct TypeMethodCheck { char *cls; char *method; struct TypeMethodCheck *next; } TypeMethodCheck; typedef struct TypeArityCheck { char *func; int arity; struct TypeArityCheck *next; } TypeArityCheck; typedef struct TypeReturnCheck { char *func; char *type; struct TypeReturnCheck *next; } TypeReturnCheck; static void add_type_param_check(TypeParamCheck **checks, const char *func, const char *param, const char *type, int pos) { TypeParamCheck *c; if (!func || !*func || !param || !*param || !type || !*type) return; c = (TypeParamCheck *)xmalloc(sizeof(TypeParamCheck)); c->func = xstrdup(func); c->param = xstrdup(param); c->type = xstrdup(type); c->pos = pos; c->next = *checks; *checks = c; } static const char *type_param_check_for(TypeParamCheck *checks, const char *func, int pos, const char **param) { TypeParamCheck *c = checks; while (c) { if (c->pos == pos && ci_eq(c->func, func)) { if (param) *param = c->param; return c->type; } c = c->next; } return NULL; } static void add_type_arity_check(TypeArityCheck **checks, const char *func, int arity) { TypeArityCheck *c; if (!func || !*func) return; c = (TypeArityCheck *)xmalloc(sizeof(TypeArityCheck)); c->func = xstrdup(func); c->arity = arity; c->next = *checks; *checks = c; } static int type_arity_check_for(TypeArityCheck *checks, const char *func) { TypeArityCheck *c = checks; while (c) { if (ci_eq(c->func, func)) return c->arity; c = c->next; } return -1; } static void add_type_return_check(TypeReturnCheck **checks, const char *func, const char *type) { TypeReturnCheck *c; if (!func || !*func || !type || !*type) return; c = (TypeReturnCheck *)xmalloc(sizeof(TypeReturnCheck)); c->func = xstrdup(func); c->type = xstrdup(type); c->next = *checks; *checks = c; } static const char *type_return_check_for(TypeReturnCheck *checks, const char *func) { TypeReturnCheck *c = checks; while (c) { if (ci_eq(c->func, func)) return c->type; c = c->next; } return NULL; } static void add_type_field_check(TypeFieldCheck **checks, const char *cls, const char *field, const char *type) { TypeFieldCheck *c; if (!cls || !*cls || !field || !*field || !type || !*type) return; c = (TypeFieldCheck *)xmalloc(sizeof(TypeFieldCheck)); c->cls = xstrdup(cls); c->field = xstrdup(field); c->type = xstrdup(type); c->next = *checks; *checks = c; } static const char *type_field_check_for(TypeFieldCheck *checks, const char *cls, const char *field) { TypeFieldCheck *c = checks; while (c) { if (ci_eq(c->cls, cls) && ci_eq(c->field, field)) return c->type; c = c->next; } return NULL; } static void add_type_method_check(TypeMethodCheck **checks, const char *cls, const char *method) { TypeMethodCheck *c; if (!cls || !*cls || !method || !*method) return; c = (TypeMethodCheck *)xmalloc(sizeof(TypeMethodCheck)); c->cls = xstrdup(cls); c->method = xstrdup(method); c->next = *checks; *checks = c; } static int type_method_check_for(TypeMethodCheck *checks, const char *cls, const char *method) { TypeMethodCheck *c = checks; while (c) { if (ci_eq(c->cls, cls) && ci_eq(c->method, method)) return 1; c = c->next; } return 0; } static int type_method_name_exists(TypeMethodCheck *checks, const char *method) { TypeMethodCheck *c = checks; while (c) { if (ci_eq(c->method, method)) return 1; c = c->next; } return 0; } static int param_index_named(char **params, int pcnt, const char *name) { int i; char tmp[256]; char *p; for (i = 0; i < pcnt; i++) { strncpy(tmp, params[i], sizeof(tmp) - 1); tmp[sizeof(tmp) - 1] = 0; p = trim_inplace(tmp); if (ci_eq(p, name)) return i; } return -1; } static const char *duplicate_param_name(char **params, int pcnt) { int i, j; char left[256], right[256]; for (i = 0; i < pcnt; i++) { claro_snprintf(left, sizeof(left), "%s", trim_inplace(params[i])); for (j = 0; j < i; j++) { claro_snprintf(right, sizeof(right), "%s", trim_inplace(params[j])); if (ci_eq(left, right)) return params[i]; } } return NULL; } static void collect_teach_param_checks_from_body(TypeParamCheck **checks, FILE *f, const char *func_name, char **params, int pcnt) { char line[4096]; while (fgets(line, sizeof(line), f)) { char inner[4096], iup[64], iw[128]; char *it; strncpy(inner, line, sizeof(inner) - 1); inner[sizeof(inner) - 1] = 0; it = trim_inplace(inner); if (is_blank_or_comment(it)) continue; first_word(it, iw, sizeof(iw)); upper_copy(iup, iw, sizeof(iup)); if (!strcmp(iup, "END") || !strcmp(iup, "LEARNED")) break; if (!strcmp(iup, "CHECK") && starts_ci(it, "CHECK TYPE")) { const char *is = find_word_ci(it, "IS"); if (is) { char *expr = substr(it + 10, is); char *need = xstrdup(trim_inplace((char *)is + 2)); char *en = trim_inplace(expr); int idx = param_index_named(params, pcnt, en); if (idx >= 0) add_type_param_check(checks, func_name, en, need, idx); free(expr); free(need); } } } } static TypeParamCheck *collect_function_param_type_checks(FILE *f) { TypeParamCheck *checks = NULL; char line[4096]; char current_class[128] = ""; while (fgets(line, sizeof(line), f)) { char tmp[4096], up[64], w[128]; char *t; 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 (!strcmp(up, "CLASS")) { const char *pcur = t + 5; char *cls = unquote_token(&pcur); claro_snprintf(current_class, sizeof(current_class), "%s", trim_inplace(cls)); free(cls); continue; } if ((!strcmp(up, "END") || !strcmp(up, "ENDCLASS")) && current_class[0]) { current_class[0] = 0; continue; } if (!strcmp(up, "TEACH")) { char teachline[4096]; char *fname = NULL; char **params = NULL; int pcnt = 0; char full_name[256]; strncpy(teachline, t, sizeof(teachline) - 1); teachline[sizeof(teachline) - 1] = 0; parse_teach_parts(teachline, &fname, ¶ms, &pcnt); if (current_class[0]) claro_snprintf(full_name, sizeof(full_name), "%s.%s", current_class, fname); else claro_snprintf(full_name, sizeof(full_name), "%s", fname); collect_teach_param_checks_from_body(&checks, f, full_name, params, pcnt); split_args_free(params, pcnt); } } rewind(f); return checks; } static TypeArityCheck *collect_function_arity_checks(FILE *f) { TypeArityCheck *checks = NULL; char line[4096]; char current_class[128] = ""; while (fgets(line, sizeof(line), f)) { char tmp[4096], up[64], w[128]; char *t; 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 (!strcmp(up, "CLASS")) { const char *pcur = t + 5; char *cls = unquote_token(&pcur); claro_snprintf(current_class, sizeof(current_class), "%s", trim_inplace(cls)); free(cls); continue; } if ((!strcmp(up, "END") || !strcmp(up, "ENDCLASS")) && current_class[0]) { current_class[0] = 0; continue; } if (!strcmp(up, "TEACH")) { char teachline[4096]; char *fname = NULL; char **params = NULL; int pcnt = 0; char full_name[256]; strncpy(teachline, t, sizeof(teachline) - 1); teachline[sizeof(teachline) - 1] = 0; parse_teach_parts(teachline, &fname, ¶ms, &pcnt); if (current_class[0]) claro_snprintf(full_name, sizeof(full_name), "%s.%s", current_class, fname); else claro_snprintf(full_name, sizeof(full_name), "%s", fname); add_type_arity_check(&checks, full_name, pcnt); split_args_free(params, pcnt); while (fgets(line, sizeof(line), f)) { char inner[4096], iup[64], iw[128]; char *it; strncpy(inner, line, sizeof(inner) - 1); inner[sizeof(inner) - 1] = 0; it = trim_inplace(inner); if (is_blank_or_comment(it)) continue; first_word(it, iw, sizeof(iw)); upper_copy(iup, iw, sizeof(iup)); if (!strcmp(iup, "END") || !strcmp(iup, "LEARNED")) break; } } } rewind(f); return checks; } static TypeReturnCheck *collect_function_return_type_checks(FILE *f) { TypeReturnCheck *checks = NULL; char line[4096]; char current_class[128] = ""; while (fgets(line, sizeof(line), f)) { char tmp[4096], up[64], w[128]; char *t; 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 (!strcmp(up, "CLASS")) { const char *pcur = t + 5; char *cls = unquote_token(&pcur); claro_snprintf(current_class, sizeof(current_class), "%s", trim_inplace(cls)); free(cls); continue; } if ((!strcmp(up, "END") || !strcmp(up, "ENDCLASS")) && current_class[0]) { current_class[0] = 0; continue; } if (!strcmp(up, "TEACH")) { char teachline[4096], full_name[256], *fname = NULL, **params = NULL; int pcnt = 0; const char *returns; strncpy(teachline, t, sizeof(teachline) - 1); teachline[sizeof(teachline) - 1] = 0; returns = find_word_ci(teachline, "RETURNS"); parse_teach_parts(teachline, &fname, ¶ms, &pcnt); if (returns) { char ret[128], first_ret[128]; const char *p = returns + 7; while (*p && isspace((unsigned char)*p)) p++; claro_snprintf(ret, sizeof(ret), "%s", p); if (sscanf(ret, "%127s", first_ret) == 1) { if (current_class[0]) claro_snprintf(full_name, sizeof(full_name), "%s.%s", current_class, fname); else claro_snprintf(full_name, sizeof(full_name), "%s", fname); add_type_return_check(&checks, full_name, first_ret); } } split_args_free(params, pcnt); while (fgets(line, sizeof(line), f)) { char inner[4096], iup[64], iw[128]; char *it; strncpy(inner, line, sizeof(inner) - 1); inner[sizeof(inner) - 1] = 0; it = trim_inplace(inner); if (is_blank_or_comment(it)) continue; first_word(it, iw, sizeof(iw)); upper_copy(iup, iw, sizeof(iup)); if (!strcmp(iup, "END") || !strcmp(iup, "LEARNED")) break; } } } rewind(f); return checks; } static TypeFieldCheck *collect_class_field_type_checks(FILE *f) { TypeFieldCheck *checks = NULL; char line[4096]; char current_class[128] = ""; while (fgets(line, sizeof(line), f)) { char tmp[4096], up[64], w[128]; char *t; 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 (!strcmp(up, "CLASS")) { const char *pcur = t + 5; char *cls = unquote_token(&pcur); claro_snprintf(current_class, sizeof(current_class), "%s", trim_inplace(cls)); free(cls); continue; } if (current_class[0] && !strcmp(up, "TEACH")) { while (fgets(line, sizeof(line), f)) { char inner[4096], iup[64], iw[128]; char *it; strncpy(inner, line, sizeof(inner) - 1); inner[sizeof(inner) - 1] = 0; it = trim_inplace(inner); if (is_blank_or_comment(it)) continue; first_word(it, iw, sizeof(iw)); upper_copy(iup, iw, sizeof(iup)); if (!strcmp(iup, "END") || !strcmp(iup, "LEARNED")) break; } continue; } if ((!strcmp(up, "END") || !strcmp(up, "ENDCLASS")) && current_class[0]) { current_class[0] = 0; continue; } if (current_class[0] && !strcmp(up, "HAS")) { const char *pcur = t + 3; char *field = unquote_token(&pcur); char *type = unquote_token(&pcur); add_type_field_check(&checks, current_class, trim_inplace(field), trim_inplace(type)); free(field); free(type); } } rewind(f); return checks; } static TypeMethodCheck *collect_class_method_type_checks(FILE *f) { TypeMethodCheck *checks = NULL; char line[4096]; char current_class[128] = ""; while (fgets(line, sizeof(line), f)) { char tmp[4096], up[64], w[128]; char *t; 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 (!strcmp(up, "CLASS")) { const char *pcur = t + 5; char *cls = unquote_token(&pcur); claro_snprintf(current_class, sizeof(current_class), "%s", trim_inplace(cls)); free(cls); continue; } if ((!strcmp(up, "END") || !strcmp(up, "ENDCLASS")) && current_class[0]) { current_class[0] = 0; continue; } if (current_class[0] && !strcmp(up, "TEACH")) { char teachline[4096]; char *fname = NULL; char **params = NULL; int pcnt = 0; strncpy(teachline, t, sizeof(teachline) - 1); teachline[sizeof(teachline) - 1] = 0; parse_teach_parts(teachline, &fname, ¶ms, &pcnt); add_type_method_check(&checks, current_class, trim_inplace(fname)); split_args_free(params, pcnt); while (fgets(line, sizeof(line), f)) { char inner[4096], iup[64], iw[128]; char *it; strncpy(inner, line, sizeof(inner) - 1); inner[sizeof(inner) - 1] = 0; it = trim_inplace(inner); if (is_blank_or_comment(it)) continue; first_word(it, iw, sizeof(iw)); upper_copy(iup, iw, sizeof(iup)); if (!strcmp(iup, "END") || !strcmp(iup, "LEARNED")) break; } } } rewind(f); return checks; } static void type_checks_free(TypeParamCheck *params, TypeArityCheck *arities, TypeReturnCheck *returns, TypeFieldCheck *fields, TypeMethodCheck *methods) { while (params) { TypeParamCheck *next = params->next; free(params->func); free(params->param); free(params->type); free(params); params = next; } while (arities) { TypeArityCheck *next = arities->next; free(arities->func); free(arities); arities = next; } while (returns) { TypeReturnCheck *next = returns->next; free(returns->func); free(returns->type); free(returns); returns = next; } while (fields) { TypeFieldCheck *next = fields->next; free(fields->cls); free(fields->field); free(fields->type); free(fields); fields = next; } while (methods) { TypeMethodCheck *next = methods->next; free(methods->cls); free(methods->method); free(methods); methods = next; } } static int typecheck_file(const char *path) { FILE *f = fopen(path, "rb"); char line[4096]; int line_no = 0, errs = 0; Var *types = NULL; TypeParamCheck *param_checks = NULL; TypeFieldCheck *field_checks = NULL; TypeMethodCheck *method_checks = NULL; TypeArityCheck *arity_checks = NULL; TypeReturnCheck *return_checks = NULL; char current_func[256] = ""; char current_method[256] = ""; char current_class[128] = ""; char declared_classes[128][256]; char declared_functions[128][256]; char declared_methods[128][256]; char declared_fields[128][256]; char declared_objects[128][256]; int declared_class_count = 0, declared_function_count = 0, declared_method_count = 0, declared_field_count = 0, declared_object_count = 0; int in_teach = 0, return_seen = 0, top_level_return_seen = 0, nested_blocks = 0, conditional_paths_complete = 1; int branch_returned[64], branch_has_else[64]; if (!f) { fprintf(stderr, "Could not open %s\n", path); return 1; } param_checks = collect_function_param_type_checks(f); arity_checks = collect_function_arity_checks(f); return_checks = collect_function_return_type_checks(f); field_checks = collect_class_field_type_checks(f); method_checks = collect_class_method_type_checks(f); type_env_set(&types, "RESULT", "ANY"); type_env_set(&types, "LASTERROR", "TEXT"); type_env_set(&types, "LASTERRORFILE", "TEXT"); type_env_set(&types, "LASTERRORLINE", "NUMBER"); type_env_set(&types, "LASTEXIT", "NUMBER"); 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 (!strcmp(up, "CLASS")) { const char *pcur = t + 5; char *cls = unquote_token(&pcur); char *name = trim_inplace(cls); char *extra = trim_inplace((char *)pcur); int duplicate = 0, ci; for (ci = 0; ci < declared_class_count; ci++) if (ci_eq(declared_classes[ci], name)) { duplicate = 1; break; } if (!*name) { printf("%s:%d: CLASS needs a class name. Add a name after CLASS, such as CLASS " "Player.\n", path, line_no); errs++; current_class[0] = 0; } else if (*extra) { printf("%s:%d: Class %s has extra text after its name. Keep only the class name " "after CLASS.\n", path, line_no, name); errs++; } else if (duplicate) { printf("%s:%d: Class %s is declared more than once. Give each class a different " "name.\n", path, line_no, name); errs++; } else if (declared_class_count < 128) claro_snprintf(declared_classes[declared_class_count++], sizeof(declared_classes[0]), "%s", name); claro_snprintf(current_class, sizeof(current_class), "%s", name); declared_field_count = 0; free(cls); } else if (current_class[0] && !strcmp(up, "HAS")) { const char *pcur = t + 3; char *field = unquote_token(&pcur); char *field_type = unquote_token(&pcur); char *name = trim_inplace(field); char *ft = trim_inplace(field_type); char *extra = trim_inplace((char *)pcur); int duplicate = 0, fi; for (fi = 0; fi < declared_field_count; fi++) if (ci_eq(declared_fields[fi], name)) { duplicate = 1; break; } if (!*name) { printf("%s:%d: Class %s needs a field name. Add a name and type after HAS, such as " "HAS score NUMBER.\n", path, line_no, current_class); errs++; } else if (duplicate) { printf("%s:%d: Class %s declares field %s more than once. Give each field a " "different name.\n", path, line_no, current_class, name); errs++; } else if (!*ft) { printf("%s:%d: Class %s field %s is missing a type. Add a type such as NUMBER, " "TEXT, YESNO, LIST, or MAP after the field name.\n", path, line_no, current_class, name); errs++; } else if (!claro_is_type_word(ft)) { printf("%s:%d: Class %s field %s uses an unknown type %s. Use a Claro type such as " "NUMBER, TEXT, YESNO, LIST, or MAP.\n", path, line_no, current_class, name, ft); errs++; } else if (*extra) { printf("%s:%d: Class %s field %s has extra text after type %s. Keep only the field " "name and one type.\n", path, line_no, current_class, name, ft); errs++; } else if (declared_field_count < 128) claro_snprintf(declared_fields[declared_field_count++], sizeof(declared_fields[0]), "%s", name); free(field); free(field_type); } else if (!strcmp(up, "TEACH")) { char teachline[4096], full_name[256], *fname = NULL, **params = NULL; int pcnt = 0; int duplicate_method = 0; strncpy(teachline, t, sizeof(teachline) - 1); teachline[sizeof(teachline) - 1] = 0; parse_teach_parts(teachline, &fname, ¶ms, &pcnt); if (current_class[0]) claro_snprintf(full_name, sizeof(full_name), "%s.%s", current_class, fname); else claro_snprintf(full_name, sizeof(full_name), "%s", fname); { const char *returns = find_word_ci(t, "RETURNS"); if (returns) { const char *rp = returns + 7; char ret[128], first_ret[128], *extra; while (*rp && isspace((unsigned char)*rp)) rp++; claro_snprintf(ret, sizeof(ret), "%s", rp); if (sscanf(ret, "%127s", first_ret) == 1) { extra = trim_inplace((char *)rp + strlen(first_ret)); if (*extra) { printf("%s:%d: %s %s has extra text after return type %s. Keep only " "the function name, parameters, and one return type.\n", path, line_no, current_class[0] ? "Method" : "Function", full_name, first_ret); errs++; } } else { printf("%s:%d: %s %s needs a return type after RETURNS. Add a type such as " "NUMBER.\n", path, line_no, current_class[0] ? "Method" : "Function", full_name); errs++; } } } if (!fname || !*trim_inplace(fname)) { if (current_class[0]) printf("%s:%d: Method %s needs a method name. Add a name after TEACH, such as " "TEACH show.\n", path, line_no, current_class); else printf("%s:%d: TEACH needs a function name. Add a name after TEACH, such as " "TEACH greet.\n", path, line_no); errs++; } if (!fname) fname = xstrdup(""); if (current_class[0]) claro_snprintf(full_name, sizeof(full_name), "%s.%s", current_class, fname); else claro_snprintf(full_name, sizeof(full_name), "%s", fname); if (current_class[0]) { int mi; for (mi = 0; mi < declared_method_count; mi++) if (ci_eq(declared_methods[mi], full_name)) { duplicate_method = 1; break; } if (duplicate_method) { printf("%s:%d: Method %s is declared more than once. Give each method a " "different name.\n", path, line_no, full_name); errs++; } else if (declared_method_count < 128) claro_snprintf(declared_methods[declared_method_count++], sizeof(declared_methods[0]), "%s", full_name); } else { int fi, duplicate_function = 0; for (fi = 0; fi < declared_function_count; fi++) if (ci_eq(declared_functions[fi], full_name)) { duplicate_function = 1; break; } if (duplicate_function) { printf("%s:%d: Function %s is declared more than once. Give each function a " "different name.\n", path, line_no, full_name); errs++; } else if (declared_function_count < 128) claro_snprintf(declared_functions[declared_function_count++], sizeof(declared_functions[0]), "%s", full_name); } { const char *duplicate = duplicate_param_name(params, pcnt); if (duplicate) { printf("%s:%d: %s %s declares parameter %s more than once. Give each parameter " "a different name.\n", path, line_no, current_class[0] ? "Method" : "Function", full_name, trim_inplace((char *)duplicate)); errs++; } } { int pi; for (pi = 0; pi < pcnt; pi++) type_env_set(&types, params[pi], "ANY"); } current_func[0] = 0; current_method[0] = 0; return_seen = 0; top_level_return_seen = 0; nested_blocks = 0; conditional_paths_complete = 1; in_teach = 1; if (current_class[0]) claro_snprintf(current_method, sizeof(current_method), "%s", full_name); if (current_class[0]) { TypeFieldCheck *fc; for (fc = field_checks; fc; fc = fc->next) if (ci_eq(fc->cls, current_class)) type_env_set(&types, fc->field, fc->type); } { const char *declared_return = type_return_check_for(return_checks, full_name); if (declared_return && !claro_is_type_word(declared_return)) { printf("%s:%d: %s %s declares an unknown return type %s. Use a Claro type such " "as NUMBER, TEXT, YESNO, LIST, or MAP.\n", path, line_no, strchr(full_name, '.') ? "Method" : "Function", full_name, declared_return); errs++; } else if (declared_return) claro_snprintf(current_func, sizeof(current_func), "%s", full_name); } split_args_free(params, pcnt); } else if (!strcmp(up, "IF")) { if (in_teach) { if (nested_blocks < 64) { branch_returned[nested_blocks] = 0; branch_has_else[nested_blocks] = 0; } nested_blocks++; } } else if (!strcmp(up, "ELSE")) { if (in_teach && nested_blocks > 0 && nested_blocks <= 64) { if (!branch_returned[nested_blocks - 1]) conditional_paths_complete = 0; branch_has_else[nested_blocks - 1] = 1; branch_returned[nested_blocks - 1] = 0; } } else if (!strcmp(up, "END") || !strcmp(up, "LEARNED") || !strcmp(up, "ENDIF")) { if (in_teach && nested_blocks > 0) { int nested_complete = (nested_blocks <= 64 && branch_has_else[nested_blocks - 1] && branch_returned[nested_blocks - 1]); if (nested_blocks <= 64 && (!branch_has_else[nested_blocks - 1] || !branch_returned[nested_blocks - 1])) conditional_paths_complete = 0; nested_blocks--; if (nested_complete && nested_blocks > 0 && nested_blocks <= 64) branch_returned[nested_blocks - 1] = 1; } else if (in_teach) { if (current_func[0] && !return_seen) { const char *need = type_return_check_for(return_checks, current_func); printf("%s:%d: %s %s declares RETURNS %s but has no RETURN statement. Add " "RETURN with a %s value.\n", path, line_no, strchr(current_func, '.') ? "Method" : "Function", current_func, need, need); errs++; } else if (current_func[0] && (!top_level_return_seen && !conditional_paths_complete)) { const char *need = type_return_check_for(return_checks, current_func); printf("%s:%d: %s %s declares RETURNS %s but does not return a %s value on " "every path. Add RETURN to each branch or after the conditional.\n", path, line_no, strchr(current_func, '.') ? "Method" : "Function", current_func, need, need); errs++; } in_teach = 0; current_func[0] = 0; current_method[0] = 0; } else current_class[0] = 0; } else if (!strcmp(up, "RETURN") && current_func[0]) { const char *need = type_return_check_for(return_checks, current_func); char *return_expr = trim_inplace(t + 6); const char *got = simple_expr_type(types, return_expr); return_seen = 1; if (nested_blocks == 0) top_level_return_seen = 1; else if (nested_blocks <= 64) branch_returned[nested_blocks - 1] = 1; if (need && !*return_expr) { printf("%s:%d: %s %s needs a %s value after RETURN. Add a %s expression.\n", path, line_no, strchr(current_func, '.') ? "Method" : "Function", current_func, need, need); errs++; } else if (need && !got) { char first_value[128]; if (return_value_extra_tokens(return_expr, first_value, sizeof(first_value))) { printf("%s:%d: %s %s has extra text after return value %s. Keep only one " "expression after RETURN.\n", path, line_no, strchr(current_func, '.') ? "Method" : "Function", current_func, first_value); } else { printf("%s:%d: %s %s's return value could not be understood yet. Use a %s " "expression after RETURN.\n", path, line_no, strchr(current_func, '.') ? "Method" : "Function", current_func, need); } errs++; } else if (need && got && !type_words_match(need, got)) { char text_operator = (ci_eq(need, "NUMBER") && ci_eq(got, "TEXT")) ? text_operator_for_number_return(types, return_expr) : 0; const char *text_operand = text_operator ? text_operand_for_operator(types, return_expr, text_operator) : NULL; if (text_operand) { printf("%s:%d: Type mismatch for return from %s: %s needs NUMBER values, but " "%s looks like TEXT.\n", path, line_no, current_func, text_operator == '+' ? "addition" : (text_operator == '-' ? "subtraction" : (text_operator == '*' ? "multiplication" : "division")), text_operand); } else { printf("%s:%d: Type mismatch for return from %s: expected %s, but this value " "looks like %s.\n", path, line_no, current_func, need, got); } errs++; } } else if (!strcmp(up, "SET")) { char *name = NULL, *type = NULL, *expr = NULL; const char *got, *old; const char *as = find_word_ci(t + 3, "AS"), *to = find_word_ci(t + 3, "TO"); if (as && !to) { const char *pcur = as + 2; char *typed_name = substr(t + 3, as); char *typed_type = unquote_token(&pcur); char *nt = trim_inplace(typed_name); char *tt = trim_inplace(typed_type); if (*nt && *tt) { printf("%s:%d: SET %s needs TO after type %s. Try: SET %s AS %s TO 10.\n", path, line_no, nt, tt, nt, tt); errs++; } free(typed_name); free(typed_type); continue; } parse_set_for_typecheck2(t, &name, &type, &expr); if (name && *trim_inplace(name)) { char *nt = trim_inplace(name); char *tt = type ? trim_inplace(type) : NULL; if (!tt && (!expr || !*trim_inplace(expr)) && strchr(nt, '.')) { printf( "%s:%d: SET %s needs a value. Add one expression after the field name.\n", path, line_no, nt); errs++; free(name); free(type); free(expr); continue; } if (as && to && as < to) { char value[128]; if (!*trim_inplace((char *)to + 2)) { printf("%s:%d: SET %s needs a value after type %s and TO. Add one " "expression.\n", path, line_no, nt, tt ? tt : "the declared type"); errs++; free(name); free(type); free(expr); continue; } if (return_value_extra_tokens(to + 2, value, sizeof(value))) { printf("%s:%d: SET %s has extra text after value %s. Keep only the field " "name, type, TO, and one expression.\n", path, line_no, nt, value); errs++; free(name); free(type); free(expr); continue; } } else if (tt && *tt) { char value[128]; if (!expr || !*trim_inplace(expr)) { printf("%s:%d: SET %s needs a value after type %s. Add one expression.\n", path, line_no, nt, tt); errs++; free(name); free(type); free(expr); continue; } if (return_value_extra_tokens(expr, value, sizeof(value))) { printf("%s:%d: SET %s has extra text after value %s. Keep only the field " "name, type, and one expression.\n", path, line_no, nt, value); errs++; free(name); free(type); free(expr); continue; } } got = simple_expr_type(types, expr); if (current_method[0] && current_class[0] && !strchr(nt, '.') && !type_env_get(types, nt) && !type_field_check_for(field_checks, current_class, nt)) { if (got) { printf("%s:%d: Object %s has no field %s. Check the field name or add HAS " "%s %s to the class.\n", path, line_no, current_class, nt, nt, got); } else { printf("%s:%d: Object %s has no field %s. Check the field name or add the " "field to the class with the right type.\n", path, line_no, current_class, nt); } errs++; free(name); free(type); free(expr); continue; } old = type_env_get(types, nt); if (tt && *tt) { if (current_method[0] && current_class[0] && !strchr(nt, '.')) { const char *declared = type_field_check_for(field_checks, current_class, nt); if (declared && tt && !claro_is_type_word(tt)) { printf("%s:%d: Field %s in %s needs a known type such as NUMBER, TEXT, " "or YESNO, but %s is not a Claro type. Use %s for %s.\n", path, line_no, nt, current_method, tt, declared, nt); errs++; got = NULL; } else if (declared && tt && !type_words_match(declared, tt)) { printf("%s:%d: Type mismatch for field %s in %s: class declares %s, " "but this assignment says %s. Use %s for %s.\n", path, line_no, nt, current_method, declared, tt, declared, nt); errs++; got = NULL; } else if (declared && got && !type_words_match(declared, got)) { printf("%s:%d: Type mismatch for field %s in %s: expected %s, but this " "value looks like %s.\n", path, line_no, nt, current_method, declared, got); errs++; } } if (strchr(nt, '.')) { char objname[128], fieldname[128]; char *dot = strchr(nt, '.'); const char *objtype; claro_snprintf(objname, sizeof(objname), "%.*s", (int)(dot - nt), nt); claro_snprintf(fieldname, sizeof(fieldname), "%s", dot + 1); objtype = type_env_get(types, objname); if (objtype && starts_ci(objtype, "OBJECT:")) { const char *declared = type_field_check_for(field_checks, objtype + 7, fieldname); if (declared && tt && !claro_is_type_word(tt)) { printf("%s:%d: Field %s needs a known type such as NUMBER, TEXT, " "or YESNO, but %s is not a Claro type. Use %s for %s.\n", path, line_no, nt, tt, declared, fieldname); errs++; } else if (declared && tt && !type_words_match(declared, tt)) { printf("%s:%d: Type mismatch for field %s: class declares %s, but " "this assignment says %s. Use %s for %s.\n", path, line_no, nt, declared, tt, declared, fieldname); errs++; } else if (declared && got && !type_words_match(declared, got)) { char text_operator = (ci_eq(declared, "NUMBER") && ci_eq(got, "TEXT")) ? (text_operand_for_operator(types, expr, '+') ? '+' : (text_operand_for_operator(types, expr, '-') ? '-' : (text_operand_for_operator(types, expr, '*') ? '*' : (text_operand_for_operator(types, expr, '/') ? '/' : 0)))) : 0; const char *text_operand = text_operator ? text_operand_for_operator(types, expr, text_operator) : NULL; if (text_operand) { printf("%s:%d: Type mismatch for field %s: %s needs NUMBER " "values, but %s looks like TEXT.\n", path, line_no, nt, text_operator == '+' ? "addition" : (text_operator == '-' ? "subtraction" : (text_operator == '*' ? "multiplication" : "division")), text_operand); } else { printf("%s:%d: Type mismatch for field %s: expected %s, but " "this value looks like %s.\n", path, line_no, nt, declared, got); } errs++; } else if (!declared) { printf("%s:%d: Object %s has no field %s. Check the field name or " "add HAS %s %s to the class.\n", path, line_no, objtype + 7, fieldname, fieldname, tt); errs++; } else if (got && !type_words_match(tt, got)) { printf("%s:%d: Type mismatch for %s: expected %s, but this value " "looks like %s.\n", path, line_no, nt, tt, got); errs++; } } else if (got && !type_words_match(tt, got)) { printf("%s:%d: Type mismatch for %s: expected %s, but this value looks " "like %s.\n", path, line_no, nt, tt, got); errs++; } } else if (got && !type_words_match(tt, got)) { printf("%s:%d: Type mismatch for %s: expected %s, but this value looks " "like %s.\n", path, line_no, nt, tt, got); errs++; } type_env_set(&types, nt, tt); } else if (old) { if (got && !type_words_match(old, got)) { if (strchr(nt, '.')) { char text_operator = (ci_eq(old, "NUMBER") && ci_eq(got, "TEXT")) ? (text_operand_for_operator(types, expr, '+') ? '+' : (text_operand_for_operator(types, expr, '-') ? '-' : (text_operand_for_operator(types, expr, '*') ? '*' : (text_operand_for_operator(types, expr, '/') ? '/' : 0)))) : 0; const char *text_operand = text_operator ? text_operand_for_operator(types, expr, text_operator) : NULL; if (text_operand) printf("%s:%d: Type mismatch for field %s: %s needs NUMBER values, " "but %s looks like TEXT.\n", path, line_no, nt, text_operator == '+' ? "addition" : (text_operator == '-' ? "subtraction" : (text_operator == '*' ? "multiplication" : "division")), text_operand); else printf("%s:%d: Type mismatch for field %s: expected %s, but this " "value looks like %s.\n", path, line_no, nt, old, got); } else if (in_teach && current_class[0] && type_field_check_for(field_checks, current_class, nt)) { const char *declared = type_field_check_for(field_checks, current_class, nt); char text_operator = (ci_eq(declared, "NUMBER") && ci_eq(got, "TEXT")) ? (text_operand_for_operator(types, expr, '+') ? '+' : (text_operand_for_operator(types, expr, '-') ? '-' : (text_operand_for_operator(types, expr, '*') ? '*' : (text_operand_for_operator(types, expr, '/') ? '/' : 0)))) : 0; const char *text_operand = text_operator ? text_operand_for_operator(types, expr, text_operator) : NULL; if (text_operand) printf("%s:%d: Type mismatch for field %s in %s: %s needs NUMBER " "values, but %s looks like TEXT.\n", path, line_no, nt, current_method, text_operator == '+' ? "addition" : (text_operator == '-' ? "subtraction" : (text_operator == '*' ? "multiplication" : "division")), text_operand); else printf("%s:%d: Type mismatch for field %s in %s: expected %s, but " "this value looks like %s.\n", path, line_no, nt, current_method, declared, got); errs++; } else printf("%s:%d: Type mismatch for %s: it was first set as %s, but this " "value looks like %s.\n", path, line_no, nt, old, got); errs++; } } else if (strchr(nt, '.')) { char objname[128], fieldname[128]; char *dot = strchr(nt, '.'); const char *objtype; claro_snprintf(objname, sizeof(objname), "%.*s", (int)(dot - nt), nt); claro_snprintf(fieldname, sizeof(fieldname), "%s", dot + 1); objtype = type_env_get(types, objname); if (objtype && starts_ci(objtype, "OBJECT:")) { const char *cls = objtype + 7; if (!type_field_check_for(field_checks, cls, fieldname)) { if (got && !strchr(expr, '+') && !strchr(expr, '-') && !strchr(expr, '*') && !strchr(expr, '/')) printf("%s:%d: Object %s has no field %s. Check the field name or " "add HAS %s %s to the class.\n", path, line_no, cls, fieldname, fieldname, got); else printf("%s:%d: Object %s has no field %s. Check the field name or " "add the field to the class with the right type.\n", path, line_no, cls, fieldname); errs++; } else { const char *fieldtype = type_field_check_for(field_checks, cls, fieldname); if (got && !type_words_match(fieldtype, got)) { printf("%s:%d: Type mismatch for field %s.%s: expected %s, but " "this value looks like %s.\n", path, line_no, objname, fieldname, fieldtype, got); errs++; } } } else { printf("%s:%d: Object %s is not known yet. Create it with NEW ClassName %s " "before setting %s.\n", path, line_no, objname, objname, nt); errs++; } } else if (got) type_env_set(&types, nt, got); else type_env_set(&types, nt, "ANY"); } free(name); free(type); free(expr); } else if (!strcmp(up, "ADD")) { const char *to = find_word_ci(t, "TO"); if (to) { char *expr = substr(t + 3, to); char *name = xstrdup(trim_inplace((char *)to + 2)); const char *listtype = type_env_get(types, name); const char *need = container_member_type(listtype, "LIST"); const char *got = simple_expr_type(types, expr); if (need && got && !type_words_match(need, got)) { printf("%s:%d: Type mismatch for list %s: expected %s item, but this value " "looks like %s.\n", path, line_no, name, need, got); errs++; } free(expr); free(name); } } else if (!strcmp(up, "PUT")) { const char *key = find_word_ci(t, "KEY"), *val = find_word_ci(t, "VALUE"); if (key && val) { char *name = substr(t + 3, key); char *expr = xstrdup(trim_inplace((char *)val + 5)); char *nt = trim_inplace(name); const char *maptype = type_env_get(types, nt); const char *need = container_member_type(maptype, "MAP"); const char *got = simple_expr_type(types, expr); if (need && got && !type_words_match(need, got)) { printf("%s:%d: Type mismatch for map %s: expected %s value, but this value " "looks like %s.\n", path, line_no, nt, need, got); errs++; } free(name); free(expr); } } else if (!strcmp(up, "ASK")) { const char *as = find_word_ci(t, "AS"); char *name = NULL, *type = NULL; if (as) { const char *pcur = as + 2; name = unquote_token(&pcur); { char *rest = xstrdup(trim_inplace((char *)pcur)); if (claro_is_type_word(rest)) type = rest; else free(rest); } } else { const char *pcur = t + 3; char *prompt = unquote_token(&pcur); free(prompt); name = unquote_token(&pcur); { char *rest = xstrdup(trim_inplace((char *)pcur)); if (claro_is_type_word(rest)) type = rest; else free(rest); } } if (name && *trim_inplace(name)) type_env_set(&types, trim_inplace(name), type ? type : "TEXT"); free(name); free(type); } else if (!strcmp(up, "IMPORT")) { const char *as = find_word_ci(t, "AS"); if (as) { const char *pcur = as + 2; char *alias = unquote_token(&pcur); if (alias && *trim_inplace(alias)) type_env_set(&types, trim_inplace(alias), "MODULE"); free(alias); } } else if (!strcmp(up, "TYPE") && starts_ci(t, "TYPE OF")) { const char *as = find_word_ci(t, "AS"); if (!as) { if (!*trim_inplace(t + 7)) printf("%s:%d: TYPE OF needs an expression, AS, and a result name. Try: TYPE " "OF score AS kind.\n", path, line_no); else printf("%s:%d: TYPE OF needs AS. Try: TYPE OF score AS kind.\n", path, line_no); errs++; } else { char *expr = substr(t + 7, as); const char *pcur = as + 2; char *name = unquote_token(&pcur); char *extra = xstrdup(trim_inplace((char *)pcur)); if (!*trim_inplace(expr)) { printf("%s:%d: TYPE OF needs an expression before AS. Try: TYPE OF score AS " "kind.\n", path, line_no); errs++; } else if (!name || !*trim_inplace(name)) { printf("%s:%d: TYPE OF needs a result name after AS. Try: TYPE OF score AS " "kind.\n", path, line_no); errs++; } else if (extra && *extra) { printf("%s:%d: TYPE OF %s has extra text after result name %s. Keep only the " "expression, AS, and one result name.\n", path, line_no, trim_inplace(expr), trim_inplace(name)); errs++; } else if (name && *trim_inplace(name)) type_env_set(&types, name, "TEXT"); free(expr); free(name); free(extra); } } else if (!strcmp(up, "NEW")) { const char *as = find_word_ci(t, "AS"); char *name = NULL; char *cls = NULL; char *extra = NULL; char objtype[256]; if (as) { const char *pcur = as + 2; cls = substr(t + 3, as); name = unquote_token(&pcur); extra = xstrdup(trim_inplace((char *)pcur)); } else { const char *pcur = t + 3; cls = unquote_token(&pcur); name = unquote_token(&pcur); extra = xstrdup(trim_inplace((char *)pcur)); } claro_snprintf(objtype, sizeof(objtype), "OBJECT:%s", trim_inplace(cls)); if (name && *trim_inplace(name)) { TypeFieldCheck *fc; char fullname[256]; char *nt = trim_inplace(name); char *ct = trim_inplace(cls); int duplicate = 0, class_known = 0, oi, ci; if (extra && *extra) { printf("%s:%d: NEW %s has extra text after object name %s. Keep only the class " "and object names.\n", path, line_no, ct, nt); errs++; } for (ci = 0; ci < declared_class_count; ci++) if (ci_eq(declared_classes[ci], ct)) { class_known = 1; break; } if (declared_class_count > 0 && !class_known) { printf("%s:%d: Class %s is not known yet. Check the class name or declare " "CLASS %s before creating %s.\n", path, line_no, ct, ct, nt); errs++; } for (oi = 0; oi < declared_object_count; oi++) if (ci_eq(declared_objects[oi], nt)) { duplicate = 1; break; } if (duplicate) { printf("%s:%d: Object %s is created more than once. Give each object a " "different name.\n", path, line_no, nt); errs++; } else if (declared_object_count < 128) claro_snprintf(declared_objects[declared_object_count++], sizeof(declared_objects[0]), "%s", nt); type_env_set(&types, nt, objtype); for (fc = field_checks; fc; fc = fc->next) { if (ci_eq(fc->cls, ct)) { claro_snprintf(fullname, sizeof(fullname), "%s.%s", nt, fc->field); type_env_set(&types, fullname, fc->type); } } } else if (cls && *trim_inplace(cls)) { printf("%s:%d: NEW %s needs an object name. Add a name after the class, such as " "NEW %s player.\n", path, line_no, trim_inplace(cls), trim_inplace(cls)); errs++; } else { printf("%s:%d: NEW needs a class name. Add a class and object name, such as NEW " "Player player.\n", path, line_no); errs++; } free(cls); free(name); free(extra); } else if ((!strcmp(up, "DO") && !find_word_ci(t, "TIMES")) || !strcmp(up, "CALL")) { char *fname = NULL; char **parts = NULL; int ac = 0, i, is_method = 0; char lookup[256], display[256]; if (!strcmp(up, "DO")) { const char *pcur = t + 2; fname = unquote_token(&pcur); if (*trim_inplace((char *)pcur)) ac = split_simple_args(pcur, &parts); } else { char *rest = t + 4; const char *with = find_word_ci(rest, "WITH"); fname = with ? substr(rest, with) : xstrdup(rest); if (with && *trim_inplace((char *)with + 4)) ac = split_args(with + 4, &parts); } claro_snprintf(lookup, sizeof(lookup), "%s", trim_inplace(fname)); claro_snprintf(display, sizeof(display), "%s", lookup); { char *dot = strchr(lookup, '.'); if (dot) { char objname[128]; const char *objtype; claro_snprintf(objname, sizeof(objname), "%.*s", (int)(dot - lookup), lookup); objtype = type_env_get(types, objname); if (objtype && starts_ci(objtype, "OBJECT:")) { char method[128]; const char *cls = objtype + 7; claro_snprintf(method, sizeof(method), "%s", dot + 1); if (!type_method_check_for(method_checks, cls, method)) { printf("%s:%d: Object %s has no method %s. Check the method name or " "add TEACH %s inside CLASS %s.\n", path, line_no, cls, method, method, cls); errs++; } claro_snprintf(lookup, sizeof(lookup), "%s.%s", cls, method); claro_snprintf(display, sizeof(display), "%s", lookup); is_method = 1; } else if (!objtype || (!starts_ci(objtype, "MODULE") && (!strcmp(up, "DO") || type_method_name_exists(method_checks, dot + 1)))) { printf("%s:%d: Object %s is not known yet. Create it with NEW ClassName %s " "before calling %s.\n", path, line_no, objname, objname, lookup); errs++; } } } for (i = 0; i < ac; i++) { const char *param = NULL; const char *need = type_param_check_for(param_checks, lookup, i, ¶m); const char *got = simple_expr_type(types, parts[i]); if (need && got && !type_words_match(need, got)) { printf("%s:%d: Type mismatch for %s %s: parameter %s needs %s, but this " "argument looks like %s.\n", path, line_no, is_method ? "method" : "function", display, param ? param : "argument", need, got); errs++; } } { TypeParamCheck *pc = param_checks; while (pc) { if (ci_eq(pc->func, lookup) && pc->pos >= ac) { printf("%s:%d: %s %s needs argument %s as %s, but this call does not " "provide it.\n", path, line_no, is_method ? "Method" : "Function", display, pc->param, pc->type); errs++; } pc = pc->next; } } { int expected = type_arity_check_for(arity_checks, lookup); if (expected >= 0 && ac > expected) { printf("%s:%d: %s %s only accepts %d %s, but this call gives %d. Remove the " "extra %s.\n", path, line_no, is_method ? "Method" : "Function", display, expected, expected == 1 ? "argument" : "arguments", ac, ac - expected == 1 ? "argument" : "arguments"); errs++; } else if (expected >= 0 && ac < expected) { int has_checked_missing = 0; TypeParamCheck *mpc = param_checks; while (mpc) { if (ci_eq(mpc->func, lookup) && mpc->pos >= ac) { has_checked_missing = 1; break; } mpc = mpc->next; } if (!has_checked_missing) { printf("%s:%d: %s %s needs %d %s, but this call gives %d. Add the missing " "%s.\n", path, line_no, is_method ? "Method" : "Function", display, expected, expected == 1 ? "argument" : "arguments", ac, expected - ac == 1 ? "argument" : "arguments"); errs++; } } else if ((!strcmp(up, "DO") || !strcmp(up, "CALL")) && !is_method && !strchr(lookup, '.') && expected < 0) { printf("%s:%d: Function %s is not known yet. Check the function name or add " "TEACH %s before calling it.\n", path, line_no, lookup, lookup); errs++; } } split_args_free(parts, ac); free(fname); } else if (!strcmp(up, "CHECK") && starts_ci(t, "CHECK TYPE")) { const char *is = find_word_ci(t, "IS"); if (is) { char *expr = substr(t + 10, is); char *need = xstrdup(trim_inplace((char *)is + 2)); char *et = trim_inplace(expr); const char *got; char first_type[64]; int first_type_len = 0; if (current_func[0] && !strchr(et, '.')) { TypeParamCheck *pc; for (pc = param_checks; pc; pc = pc->next) { if (ci_eq(pc->func, current_func) && ci_eq(pc->param, et)) { type_env_set(&types, et, pc->type); break; } } } got = simple_expr_type(types, et); if (!*et) { printf("%s:%d: CHECK TYPE needs an expression before IS. Try: CHECK TYPE score " "IS NUMBER.\n", path, line_no); errs++; } else if (!*trim_inplace(need)) { printf("%s:%d: CHECK TYPE %s needs a type after IS. Try: CHECK TYPE %s IS " "NUMBER.\n", path, line_no, et, et); errs++; } else if (sscanf(need, "%63s%n", first_type, &first_type_len) == 1 && claro_is_type_word(first_type) && *trim_inplace(need + first_type_len)) { printf("%s:%d: CHECK TYPE %s has extra text after type %s. Keep only the " "expression, IS, and one type.\n", path, line_no, et, first_type); errs++; } else if (!claro_is_type_word(need)) { printf("%s:%d: CHECK TYPE needs a known type such as NUMBER, TEXT, YESNO, " "LIST, or MAP, but %s is not a Claro type.\n", path, line_no, need); errs++; } else if (got && !type_words_match(need, got)) { const char *method_field = (current_method[0] && current_class[0] && !strchr(et, '.')) ? type_field_check_for(field_checks, current_class, et) : NULL; if (method_field) { printf( "%s:%d: Type check failed in %s: expected %s, but %s looks like %s.\n", path, line_no, current_method, need, et, got); } else { printf("%s:%d: Type check failed: expected %s, but %s looks like %s.\n", path, line_no, need, et, got); } errs++; } else if (!got && current_method[0] && current_class[0] && !strchr(et, '.') && !type_env_get(types, et) && !type_field_check_for(field_checks, current_class, et)) { printf("%s:%d: Object %s has no field %s. Check the field name or add HAS %s " "%s to the class.\n", path, line_no, current_class, et, et, need); errs++; } else if (!got && strchr(et, '.')) { char objname[128], fieldname[128]; char *dot = strchr(et, '.'); const char *objtype; claro_snprintf(objname, sizeof(objname), "%.*s", (int)(dot - et), et); claro_snprintf(fieldname, sizeof(fieldname), "%s", dot + 1); objtype = type_env_get(types, objname); if (objtype && starts_ci(objtype, "OBJECT:")) { const char *declared = type_field_check_for(field_checks, objtype + 7, fieldname); if (declared && !type_words_match(need, declared)) { printf("%s:%d: Type check failed: expected %s, but %s looks like %s.\n", path, line_no, need, et, declared); errs++; } else if (!declared) { printf("%s:%d: Object %s has no field %s. Check the field name or add " "HAS %s %s to the class.\n", path, line_no, objtype + 7, fieldname, fieldname, need); errs++; } } else if (!objtype) { printf("%s:%d: Object %s is not known yet. Create it with NEW ClassName %s " "before checking %s.\n", path, line_no, objname, objname, et); errs++; } } free(expr); free(need); } else if (!*trim_inplace(t + 10)) { printf("%s:%d: CHECK TYPE needs an expression, IS, and a type. Try: CHECK TYPE " "score IS NUMBER.\n", path, line_no); errs++; } else { printf("%s:%d: CHECK TYPE needs IS. Try: CHECK TYPE score IS NUMBER.\n", path, line_no); errs++; } } } fclose(f); type_checks_free(param_checks, arity_checks, return_checks, field_checks, method_checks); type_env_free(&types); 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); } /* 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; claro_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; #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]; claro_snprintf(path, sizeof(path), "tests/%s", de->d_name); fails += run_one_test(path); } } closedir(d); #else struct _finddata_t fd; #if defined(_MSC_VER) && _MSC_VER < 1300 /* The VC6 CRT returns long here and does not define intptr_t. */ long h; #else intptr_t h; #endif h = _findfirst("tests/*.claro", &fd); if (h == -1) { fprintf(stderr, "No tests directory\n"); return 1; } do { char path[512]; claro_snprintf(path, sizeof(path), "tests/%s", fd.name); fails += run_one_test(path); } while (_findnext(h, &fd) == 0); _findclose(h); #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 project_name_safe(const char *name) { int i; if (!name || !*name) return 0; if (strlen(name) > 64) 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 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 (strlen(name) > 64) { fprintf(stderr, "Project names must be 64 characters or fewer.\n"); return 1; } if (!project_name_safe(name)) { fprintf(stderr, "Project names may use only letters, numbers, dash, and underscore.\n"); return 1; } if (!make_folder(name)) { fprintf(stderr, "Could not create project folder: %s\n", name); return 1; } claro_snprintf(path, sizeof(path), "%s/main.claro", name); write_text_file_simple( path, "SAY \"Welcome to Claro!\"\n\nSET name \"Learner\"\nSAY \"Hello \" + name\n\nTEACH " "greet person\n SAY \"Nice to meet you, \" + person\nEND\n\nDO greet \"Friend\"\n"); claro_snprintf(path, sizeof(path), "%s/packages", name); make_folder(path); claro_snprintf(path, sizeof(path), "%s/claro.project", name); claro_snprintf( text, sizeof(text), "manifest-version: 1\nname: %s\nmain: main.claro\nversion: v2026.10.25.014405\npackages:\n", name); write_text_file_simple(path, text); claro_snprintf(path, sizeof(path), "%s/claro.lock", name); write_text_file_simple(path, "# Claro package lock\nlock-version: 1\nversion: v2026.10.25.014405\n"); claro_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 \"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); } } } runtime_free(&rt); free(p.path); free(p.lines); 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_too_long(const char *name) { return name && strlen(name) > 64; } static int package_name_safe(const char *name) { int i; if (!name || !*name) return 0; if (package_name_too_long(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 package_checksum(const char *name, char *out, size_t n) { unsigned long h = 5381; const char *p = name ? name : ""; while (*p) { h = ((h << 5) + h) + (unsigned char)(*p++); } p = "|local|1|claro"; while (*p) { h = ((h << 5) + h) + (unsigned char)(*p++); } claro_snprintf(out, n, "%08lx", h); } static void ensure_project_file(void) { if (!file_exists_simple("claro.project")) write_text_file_simple("claro.project", "manifest-version: 1\nname: ClaroProject\nmain: " "main.claro\nversion: v2026.10.25.014405\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 project_package_names_safe(void) { char *txt = read_file_text("claro.project"); char *p; char seen[128][65]; int seen_count = 0; if (!txt) return 1; 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); int i; if (*pkg && !package_name_safe(pkg)) { printf("BAD package name: %s\n", pkg); free(txt); return 0; } if (*pkg) { for (i = 0; i < seen_count; i++) { if (strcmp(seen[i], pkg) == 0) { printf("DUPLICATE package: %s\n", pkg); free(txt); return 0; } } if (seen_count < 128) claro_snprintf(seen[seen_count++], sizeof(seen[0]), "%s", pkg); } } } if (save) { *p = save; p++; } while (*p == '\n' || *p == '\r') p++; } free(txt); return 1; } static int write_package_lock(void) { char *txt = read_file_text("claro.project"); FILE *f; char *p; if (!txt) return 0; if (!project_package_names_safe()) { free(txt); return 0; } f = fopen("claro.lock", "wb"); if (!f) { free(txt); return 0; } fputs("# Claro package lock\nlock-version: 1\nversion: v2026.10.25.014405\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) { char sum[32]; package_checksum(pkg, sum, sizeof(sum)); fprintf(f, "package: %s\nchecksum: %s\n", pkg, sum); } } } 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_too_long(name)) { fprintf(stderr, "Package names must be 64 characters or fewer.\n"); return 0; } 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); if (!write_package_lock()) return -1; return removed; } static void create_package_folder(const char *name) { char path[1024], meta[1280], readme[1280], sum[32]; make_folder("packages"); claro_snprintf(path, sizeof(path), "packages/%s", name); make_folder(path); claro_snprintf(meta, sizeof(meta), "%s/claro.package", path); package_checksum(name, sum, sizeof(sum)); { char text[1024]; claro_snprintf(text, sizeof(text), "manifest-version: 1\nname: %s\nversion: 1\nsource: local\nchecksum: %s\n", name, sum); write_text_file_simple(meta, text); } claro_snprintf(readme, sizeof(readme), "%s/README.md", path); if (!file_exists_simple(readme)) { char text[512]; claro_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; } if (!project_package_names_safe()) { free(txt); return 1; } 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_lock_checksum_ok(const char *name) { char *txt = read_file_text("claro.lock"); char *p; int in_pkg = 0, fields = 0, matches = 0; char expected[32]; if (!txt) return 0; package_checksum(name, expected, sizeof(expected)); 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); in_pkg = (strcmp(pkg, name) == 0); } else if (in_pkg && strnicmp2(t, "checksum:", 9) == 0) { char *sum = trim_inplace(t + 9); fields++; if (strcmp(sum, expected) == 0) matches++; } } if (save) { *p = save; p++; } while (*p == '\n' || *p == '\r') p++; } free(txt); return fields == 1 && matches == 1; } static int lock_packages_match_project(void) { char *txt = read_file_text("claro.lock"); char *p; char seen[128][65]; int seen_count = 0; int ok = 1; if (!txt) return 1; 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); int i; if (*pkg && !package_name_safe(pkg)) { printf(" BAD lock package name: %s\n", pkg); ok = 0; } else if (*pkg) { for (i = 0; i < seen_count; i++) { if (strcmp(seen[i], pkg) == 0) { printf(" DUPLICATE lock package: %s\n", pkg); ok = 0; break; } } if (seen_count < 128) claro_snprintf(seen[seen_count++], sizeof(seen[0]), "%s", pkg); if (!package_has_name(pkg)) { printf(" BAD lock package not in claro.project: %s\n", pkg); ok = 0; } } } } if (save) { *p = save; p++; } while (*p == '\n' || *p == '\r') p++; } free(txt); return ok; } static int package_manifest_version_matches(const char *text) { const char *p = text; int fields = 0; int matches = 0; while (p && *p) { const char *line = p; const char *end; while (*p && *p != '\n' && *p != '\r') p++; end = p; if ((size_t)(end - line) >= 17 && strncmp(line, "manifest-version:", 17) == 0) { const char *value = line + 17; fields++; while (value < end && isspace((unsigned char)*value)) value++; while (end > value && isspace((unsigned char)end[-1])) end--; if ((size_t)(end - value) == 1 && value[0] == '1') matches++; } while (*p == '\n' || *p == '\r') p++; } return fields == 1 && matches == 1; } static int package_project_name_valid(const char *text) { const char *p = text; int fields = 0; int nonempty = 0; while (p && *p) { const char *line = p; const char *end; while (*p && *p != '\n' && *p != '\r') p++; end = p; if ((size_t)(end - line) >= 5 && strncmp(line, "name:", 5) == 0) { const char *value = line + 5; fields++; while (value < end && isspace((unsigned char)*value)) value++; while (end > value && isspace((unsigned char)end[-1])) end--; if (value < end) nonempty++; } while (*p == '\n' || *p == '\r') p++; } return fields == 1 && nonempty == 1; } static int package_manifest_name_matches(const char *text, const char *name) { const char *p = text; size_t n = strlen(name); int fields = 0; int matches = 0; while (p && *p) { const char *line = p; const char *end; while (*p && *p != '\n' && *p != '\r') p++; end = p; if ((size_t)(end - line) >= 5 && strncmp(line, "name:", 5) == 0) { const char *value = line + 5; fields++; while (value < end && isspace((unsigned char)*value)) value++; while (end > value && isspace((unsigned char)end[-1])) end--; if ((size_t)(end - value) == n && strncmp(value, name, n) == 0) matches++; } while (*p == '\n' || *p == '\r') p++; } return fields == 1 && matches == 1; } static int package_manifest_version_value_matches(const char *text) { const char *p = text; int fields = 0; int matches = 0; while (p && *p) { const char *line = p; const char *end; while (*p && *p != '\n' && *p != '\r') p++; end = p; if ((size_t)(end - line) >= 8 && strncmp(line, "version:", 8) == 0) { const char *value = line + 8; fields++; while (value < end && isspace((unsigned char)*value)) value++; while (end > value && isspace((unsigned char)end[-1])) end--; if ((size_t)(end - value) == 1 && value[0] == '1') matches++; } while (*p == '\n' || *p == '\r') p++; } return fields == 1 && matches == 1; } static int package_manifest_source_matches(const char *text) { const char *p = text; int fields = 0; int matches = 0; while (p && *p) { const char *line = p; const char *end; while (*p && *p != '\n' && *p != '\r') p++; end = p; if ((size_t)(end - line) >= 7 && strncmp(line, "source:", 7) == 0) { const char *value = line + 7; fields++; while (value < end && isspace((unsigned char)*value)) value++; while (end > value && isspace((unsigned char)end[-1])) end--; if ((size_t)(end - value) == 5 && strncmp(value, "local", 5) == 0) matches++; } while (*p == '\n' || *p == '\r') p++; } return fields == 1 && matches == 1; } static int package_manifest_checksum_matches(const char *text, const char *expected) { const char *p = text; size_t n = strlen(expected); int fields = 0; int matches = 0; while (p && *p) { const char *line = p; const char *end; while (*p && *p != '\n' && *p != '\r') p++; end = p; if ((size_t)(end - line) >= 9 && strncmp(line, "checksum:", 9) == 0) { const char *value = line + 9; fields++; while (value < end && isspace((unsigned char)*value)) value++; while (end > value && isspace((unsigned char)end[-1])) end--; if ((size_t)(end - value) == n && strncmp(value, expected, n) == 0) matches++; } while (*p == '\n' || *p == '\r') p++; } return fields == 1 && matches == 1; } static int package_lock_version_matches(const char *text) { const char *p = text; int fields = 0, matches = 0; while (p && *p) { const char *line = p, *end; while (*p && *p != '\n' && *p != '\r') p++; end = p; while (line < end && isspace((unsigned char)*line)) line++; if ((size_t)(end - line) >= 13 && strnicmp2(line, "lock-version:", 13) == 0) { const char *value = line + 13; fields++; while (value < end && isspace((unsigned char)*value)) value++; while (end > value && isspace((unsigned char)end[-1])) end--; if ((size_t)(end - value) == 1 && value[0] == '1') matches++; } while (*p == '\n' || *p == '\r') p++; } return fields == 1 && matches == 1; } static int package_lock_release_version_matches(const char *text) { const char *p = text; int fields = 0, matches = 0; while (p && *p) { const char *line = p, *end; while (*p && *p != '\n' && *p != '\r') p++; end = p; while (line < end && isspace((unsigned char)*line)) line++; if ((size_t)(end - line) >= 8 && strncmp(line, "version:", 8) == 0) { const char *value = line + 8; fields++; while (value < end && isspace((unsigned char)*value)) value++; while (end > value && isspace((unsigned char)end[-1])) end--; if ((size_t)(end - value) == strlen(CLARO_VERSION + 6) && strncmp(value, CLARO_VERSION + 6, strlen(CLARO_VERSION + 6)) == 0) matches++; } while (*p == '\n' || *p == '\r') p++; } return fields == 1 && matches == 1; } static int package_doctor(void) { int ok = 1; char *mainfile; char *txt; 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; txt = read_file_text("claro.lock"); if (txt && !package_lock_version_matches(txt)) { printf(" BAD lock version: expected exactly one lock-version: 1 line\n"); ok = 0; } if (txt && !package_lock_release_version_matches(txt)) { printf( " BAD lock release version: expected exactly one version: v2026.10.25.014405 line\n"); ok = 0; } free(txt); txt = read_file_text("claro.project"); if (txt && !project_package_names_safe()) { ok = 0; } if (txt && !package_manifest_version_matches(txt)) { printf(" BAD project manifest version: expected 1\n"); ok = 0; } if (txt && !package_project_name_valid(txt)) { printf(" BAD project manifest name: expected a non-empty name\n"); ok = 0; } if (txt) { char *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) { char meta[512], *mt, sum[32]; if (!package_name_safe(pkg)) { printf(" BAD package name: %s\n", pkg); ok = 0; } else { claro_snprintf(meta, sizeof(meta), "packages/%s/claro.package", pkg); mt = read_file_text(meta); if (mt && !package_manifest_version_value_matches(mt)) { printf(" BAD package version: %s\n", pkg); ok = 0; } else if (mt && !package_manifest_source_matches(mt)) { printf(" BAD package source: %s\n", pkg); ok = 0; } else if (mt && !package_manifest_version_matches(mt)) { printf(" BAD package manifest version: %s. Keep exactly one " "manifest-version: 1 line in packages/%s/claro.package.\n", pkg, pkg); ok = 0; } else if (mt && strstr(mt, "checksum:")) { if (!package_manifest_name_matches(mt, pkg)) { printf(" BAD package manifest name: %s\n", pkg); ok = 0; } else { printf(" OK package manifest: %s\n", pkg); package_checksum(pkg, sum, sizeof(sum)); if (package_manifest_checksum_matches(mt, sum)) printf(" OK package checksum: %s\n", pkg); else { printf(" BAD package checksum: %s\n", pkg); ok = 0; } if (package_lock_checksum_ok(pkg)) printf(" OK lock checksum: %s\n", pkg); else { printf(" BAD lock checksum: %s\n", pkg); ok = 0; } } } else { printf(" MISSING package manifest: %s\n", pkg); ok = 0; } free(mt); } } } } if (save) p++; while (*p == '\n' || *p == '\r') p++; } free(txt); } if (!lock_packages_match_project()) 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(); if (!project_package_names_safe()) return 1; make_folder("packages"); if (!write_package_lock()) return 1; printf("Package project ready: claro.project, claro.lock, packages/\n"); return 0; } if (strcmp(argv[arg], "add") == 0 && arg + 1 < argc) { const char *name = argv[arg + 1]; int already; if (package_name_too_long(name)) { fprintf(stderr, "Package names must be 64 characters or fewer.\n"); return 1; } if (!package_name_safe(name)) { fprintf(stderr, "Package names may use only letters, numbers, dash, and underscore.\n"); return 1; } ensure_project_file(); if (!project_package_names_safe()) return 1; make_folder("packages"); already = package_has_name(name); if (!append_package_to_project(name)) return 1; create_package_folder(name); if (!write_package_lock()) return 1; if (!already) printf("Added package: %s\n", name); return 0; } if (strcmp(argv[arg], "remove") == 0 && arg + 1 < argc) { const char *name = argv[arg + 1]; int removed; removed = remove_package_from_project(name); if (removed < 0) return 1; if (removed) printf("Removed package from project: %s\n", name); else printf("Package was not listed: %s\n", name); return 0; } if (strcmp(argv[arg], "list") == 0) return list_project_packages(); if (strcmp(argv[arg], "doctor") == 0) return package_doctor(); if (strcmp(argv[arg], "lock") == 0 || strcmp(argv[arg], "update") == 0) { ensure_project_file(); make_folder("packages"); if (write_package_lock()) { printf("Updated claro.lock\n"); return 0; } fprintf(stderr, "Could not write claro.lock\n"); return 1; } printf("Unknown package command. Try: claro package init\n"); return 1; } static int print_ide_info(void) { printf("{\n"); printf(" \"language\": \"Claro\",\n"); printf(" \"version\": \"v2026.10.25.014405\",\n"); printf(" \"file_extensions\": [\".claro\"],\n"); printf(" \"keywords\": " "[\"SAY\",\"ASK\",\"SET\",\"IF\",\"ELSE\",\"ENDIF\",\"END\",\"REPEAT\",\"UNTIL\"," "\"TEACH\",\"TAKES\",\"RETURNS\",\"CHECK\",\"TYPE\",\"LEARNED\",\"DO\",\"CALL\"," "\"WITH\",\"RETURN\",\"CLASS\",\"HAS\",\"NEW\",\"IMPORT\",\"TRY\",\"CATCH\",\"ENDTRY\"," "\"START TASK\",\"YIELD\",\"WAIT TASK\",\"TASK STATUS\",\"HTTP CHECK\",\"HTTP " "GET\",\"HTTP SAVE\",\"OPEN WINDOW\"],\n"); printf(" \"types\": [\"NUMBER\",\"TEXT\",\"YESNO\",\"LIST\",\"LIST OF TEXT\",\"LIST OF " "NUMBER\",\"LIST OF YESNO\",\"MAP\",\"MAP OF TEXT\",\"MAP OF NUMBER\",\"MAP OF " "YESNO\",\"OBJECT\",\"ANY\"],\n"); printf(" \"commands\": [\n"); printf(" {\"name\": \"run\", \"usage\": \"claro run\", \"description\": \"Run main.claro or " "the project main script\"},\n"); printf(" {\"name\": \"check\", \"usage\": \"claro check file.claro\", \"description\": " "\"Check beginner syntax and block mistakes\"},\n"); printf(" {\"name\": \"typecheck\", \"usage\": \"claro typecheck file.claro\", " "\"description\": \"Check static types before running\"},\n"); printf(" {\"name\": \"fmt\", \"usage\": \"claro fmt file.claro\", \"description\": \"Print " "a formatted version of a script\"},\n"); printf(" {\"name\": \"test\", \"usage\": \"claro test\", \"description\": \"Run golden " "tests\"}\n"); printf(" ],\n"); printf(" \"snippets\": [\n"); printf(" {\"name\": \"hello\", \"prefix\": \"say\", \"body\": \"SAY \\\"Hello!\\\"\"},\n"); printf(" {\"name\": \"if_else\", \"prefix\": \"if\", \"body\": \"IF condition\\n SAY " "\\\"Yes\\\"\\nELSE\\n SAY \\\"No\\\"\\nEND\"},\n"); printf(" {\"name\": \"function\", \"prefix\": \"teach\", \"body\": \"TEACH name item\\n " "SAY item\\nEND\"},\n"); printf(" {\"name\": \"typed_list\", \"prefix\": \"listof\", \"body\": \"SET names AS LIST " "OF TEXT TO LIST\\nADD \\\"Ada\\\" TO names\"},\n"); printf(" {\"name\": \"task\", \"prefix\": \"task\", \"body\": \"START TASK helloTask\\n " "SAY \\\"Inside task\\\"\\n YIELD\\nENDTASK\\nTASK STATUS helloTask AS status\\nSAY " "status\\nWAIT TASK helloTask\"}\n"); printf(" ],\n"); printf(" \"diagnostics\": [\n"); printf(" {\"source\": \"claro check\", \"format\": \"path:line: message\", \"description\": " "\"Syntax and block-structure diagnostics\"},\n"); printf(" {\"source\": \"claro typecheck\", \"format\": \"path:line: message\", " "\"description\": \"Static type diagnostics for variables and containers\"}\n"); printf(" ]\n"); printf("}\n"); return 0; } static void print_help(void) { printf("%s\n", CLARO_VERSION); printf("Usage:\n"); printf(" claro file.claro Run a Claro script\n"); printf(" claro run Run main.claro in this folder\n"); printf(" claro test Run the built-in tests\n"); printf(" claro check file.claro Check a script for beginner mistakes\n"); printf(" claro typecheck file.claro Check simple static types\n"); printf(" claro fmt file.claro Print a formatted version\n"); printf(" claro repl Try one line at a time\n"); printf(" claro new MyProject Create a starter project\n"); printf(" claro examples List included examples\n"); printf(" claro doctor Check this Claro folder\n"); printf(" claro validate Run stable package validation\n"); printf(" claro package init|add|remove|list|doctor Manage Claro packages\n"); printf(" claro ide Print editor keyword metadata\n"); } static int show_examples(void) { const char *items[] = {"hello.claro", "name_input.claro", "quiz.claro", "calculator.claro", "guessing_game.claro", "shopping_list.claro", "save_and_load_file.claro", "simple_functions.claro", "text_and_lists.claro", "text_polish.claro", "practical_scripting.claro", "type_hardening.claro", "typed_ask.claro", "objects_classes.claro", "networking.claro", NULL}; int i; printf("Included beginner examples:\n"); for (i = 0; items[i]; i++) printf(" examples/%s\n", items[i]); printf("\nRun one with: claro examples/hello.claro\n"); return 0; } static int file_exists_simple(const char *path) { FILE *f = fopen(path, "rb"); if (f) { fclose(f); return 1; } return 0; } static int run_doctor(void) { int ok = 1; const char *files[] = {"src/claro.c", "README.md", "assets/Claro_Logo.jpg", "lessons/01_hello.claro", "examples/hello.claro", "tests/01_hello.claro", NULL}; int i; printf("%s\n", CLARO_VERSION); printf("Doctor check:\n"); for (i = 0; files[i]; i++) { int has = file_exists_simple(files[i]); printf(" %s %s\n", has ? "OK" : "MISSING", files[i]); if (!has) ok = 0; } printf("%s\n", ok ? "Claro folder looks ready." : "Some Claro files are missing."); return ok ? 0 : 1; } static int run_validate(void) { int fails = 0, i; const char *checks[] = {"lessons/01_hello.claro", "lessons/02_ask_name.claro", "lessons/03_variables.claro", "lessons/04_math.claro", "lessons/05_if_else.claro", "lessons/06_loops.claro", "lessons/07_lists.claro", "lessons/08_functions.claro", "lessons/09_files.claro", "lessons/10_final_quiz.claro", "examples/hello.claro", "examples/name_input.claro", "examples/quiz.claro", "examples/calculator.claro", "examples/guessing_game.claro", "examples/shopping_list.claro", "examples/save_and_load_file.claro", "examples/simple_functions.claro", "examples/text_and_lists.claro", "examples/text_polish.claro", "examples/practical_scripting.claro", "examples/type_hardening.claro", "examples/objects_classes.claro", "examples/networking.claro", NULL}; printf("%s\n", CLARO_VERSION); printf("Stable package validation:\n"); fails += run_doctor(); fails += run_tests(); printf("Checking lessons and main examples:\n"); for (i = 0; checks[i]; i++) { printf(" %s\n", checks[i]); fails += check_file(checks[i]); } { const char *typecheck_good[] = { "tests/typecheck_function_branch_complete_good.claro", "tests/typecheck_function_nested_branch_complete_good.claro", "tests/typecheck_function_compat_branch_complete_good.claro", "tests/typecheck_function_return_expression_good.claro", "tests/typecheck_function_return_multiplication_good.claro", "tests/typecheck_method_call_good.claro", "tests/typecheck_method_field_assignment_good.claro", "tests/typecheck_method_text_concat_good.claro", "tests/typecheck_method_compat_text_concat_good.claro", "tests/typecheck_method_text_field_assignment_good.claro", "tests/typecheck_method_compat_text_field_assignment_good.claro", "tests/typecheck_method_compat_yesno_field_assignment_good.claro", "tests/typecheck_method_inline_field_annotation_as_to_good.claro", "tests/typecheck_method_compat_inline_field_annotation_as_to_good.claro", "tests/typecheck_method_field_check_type_good.claro", "tests/typecheck_method_compat_field_check_type_good.claro", "tests/typecheck_method_compat_return_good.claro", "tests/typecheck_method_compat_lowercase_return_good.claro", "tests/typecheck_method_compat_return_multiplication_good.claro", "tests/typecheck_method_compat_return_addition_good.claro", "tests/typecheck_method_nested_branch_complete_good.claro", "tests/typecheck_method_return_expression_good.claro", "tests/typecheck_method_return_addition_good.claro", "tests/typecheck_method_return_good.claro", "tests/typecheck_nested_container_good.claro", "tests/typecheck_object_alias_method_call_good.claro", "tests/typecheck_object_alias_method_do_good.claro", "tests/typecheck_object_field_alias_good.claro", "tests/typecheck_object_field_check_type_chained_alias_text_good.claro", "tests/typecheck_object_field_check_type_yesno_good.claro", "tests/typecheck_object_field_division_good.claro", "tests/typecheck_object_field_multiplication_good.claro", "tests/typecheck_object_field_text_concat_good.claro", "tests/typecheck_object_field_text_concat_reverse_good.claro", "tests/typecheck_object_field_subtraction_good.claro", NULL}; const char *typecheck_bad[] = { "tests/typecheck_function_branch_missing_return_bad.claro", "tests/typecheck_function_call_missing_unchecked_arg_bad.claro", "tests/typecheck_function_call_unknown_bad.claro", "tests/typecheck_function_extra_arg_bad.claro", "tests/typecheck_function_missing_arg_bad.claro", "tests/typecheck_function_missing_return_bad.claro", "tests/typecheck_function_compat_missing_return_bad.claro", "tests/typecheck_function_return_expression_bad.claro", "tests/typecheck_function_return_arithmetic_bad.claro", "tests/typecheck_invalid_expected_type_bad.claro", "tests/typecheck_missing_expression_bad.claro", "tests/typecheck_missing_expression_with_is_bad.claro", "tests/typecheck_invalid_return_type_bad.claro", "tests/typecheck_method_call_bad.claro", "tests/typecheck_method_call_unknown_method_bad.claro", "tests/typecheck_method_call_unknown_object_bad.claro", "tests/typecheck_method_call_unknown_object_unknown_method_bad.claro", "tests/typecheck_method_compat_return_bad.claro", "tests/typecheck_method_compat_return_multiplication_bad.claro", "tests/typecheck_method_duplicate_name_bad.claro", "tests/typecheck_duplicate_field_bad.claro", "tests/typecheck_duplicate_class_bad.claro", "tests/typecheck_method_extra_arg_bad.claro", "tests/typecheck_method_invalid_return_type_bad.claro", "tests/typecheck_method_missing_arg_bad.claro", "tests/typecheck_method_missing_return_bad.claro", "tests/typecheck_method_missing_unchecked_arg_bad.claro", "tests/typecheck_method_return_bad.claro", "tests/typecheck_method_return_addition_bad.claro", "tests/typecheck_method_return_expression_bad.claro", "tests/typecheck_method_second_method_bad.claro", "tests/typecheck_method_second_method_extra_arg_bad.claro", "tests/typecheck_method_unknown_method_bad.claro", "tests/typecheck_method_field_assignment_bad.claro", "tests/typecheck_method_short_field_missing_value_bad.claro", "tests/typecheck_method_compat_short_field_missing_value_bad.claro", "tests/typecheck_method_compat_unknown_field_bad.claro", "tests/typecheck_method_unknown_field_typed_bad.claro", "tests/typecheck_method_compat_unknown_field_typed_bad.claro", "tests/typecheck_method_unknown_field_expression_bad.claro", "tests/typecheck_method_text_field_assignment_bad.claro", "tests/typecheck_method_compat_text_field_assignment_bad.claro", "tests/typecheck_method_yesno_field_assignment_bad.claro", "tests/typecheck_method_compat_yesno_field_assignment_bad.claro", "tests/typecheck_method_field_check_type_bad.claro", "tests/typecheck_method_compat_field_check_type_bad.claro", "tests/typecheck_method_unknown_object_bad.claro", "tests/typecheck_object_alias_method_bad.claro", "tests/typecheck_object_alias_method_call_bad.claro", "tests/typecheck_object_field_addition_bad.claro", "tests/typecheck_object_field_after_method_bad.claro", "tests/typecheck_object_field_alias_bad.claro", "tests/typecheck_object_field_chained_alias_bad.claro", "tests/typecheck_object_field_division_bad.claro", "tests/typecheck_object_field_multiplication_bad.claro", "tests/typecheck_object_field_subtraction_bad.claro", "tests/typecheck_object_field_typed_bad.claro", "tests/typecheck_object_field_missing_value_bad.claro", "tests/typecheck_object_field_unknown_object_bad.claro", "tests/typecheck_object_field_unknown_typed_bad.claro", "tests/typecheck_missing_field_type_bad.claro", "tests/typecheck_extra_class_tokens_bad.claro", "tests/typecheck_extra_new_tokens_bad.claro", "tests/typecheck_extra_field_tokens_bad.claro", NULL}; for (i = 0; typecheck_good[i]; i++) fails += typecheck_file(typecheck_good[i]); for (i = 0; typecheck_bad[i]; i++) if (typecheck_file(typecheck_bad[i]) == 0) fails++; } fails += typecheck_file("tests/typecheck_good.claro"); fails += typecheck_file("tests/typecheck_container_good.claro"); fails += typecheck_file("tests/typecheck_function_good.claro"); fails += typecheck_file("tests/typecheck_function_return_good.claro"); fails += typecheck_file("tests/typecheck_function_compat_return_good.claro"); fails += typecheck_file("tests/typecheck_function_multi_good.claro"); fails += typecheck_file("tests/typecheck_method_good.claro"); fails += typecheck_file("tests/typecheck_object_field_good.claro"); fails += typecheck_file("tests/typecheck_object_field_expression_good.claro"); fails += typecheck_file("tests/typecheck_object_field_compound_good.claro"); fails += typecheck_file("tests/typecheck_object_field_subtraction_good.claro"); fails += typecheck_file("tests/typecheck_object_field_text_good.claro"); fails += typecheck_file("tests/typecheck_object_field_text_concat_fields_good.claro"); fails += typecheck_file("tests/typecheck_object_field_yesno_good.claro"); fails += typecheck_file("tests/typecheck_object_field_check_type_number_good.claro"); fails += typecheck_file("tests/typecheck_object_field_check_type_text_good.claro"); fails += typecheck_file("tests/typecheck_object_field_check_type_text_yesno_good.claro"); fails += typecheck_file("tests/typecheck_object_field_check_type_chained_alias_good.claro"); fails += typecheck_file("tests/37_object_field_types.claro"); if (typecheck_file("tests/typecheck_bad.claro") == 0) fails++; if (typecheck_file("tests/typecheck_container_bad.claro") == 0) fails++; if (typecheck_file("tests/typecheck_function_bad.claro") == 0) fails++; if (typecheck_file("tests/typecheck_function_duplicate_param_bad.claro") == 0) fails++; if (typecheck_file("tests/typecheck_function_return_bad.claro") == 0) fails++; if (typecheck_file("tests/typecheck_function_return_extra_tokens_bad.claro") == 0) fails++; if (typecheck_file("tests/typecheck_function_compat_return_extra_tokens_bad.claro") == 0) fails++; if (typecheck_file("tests/typecheck_function_compat_empty_return_bad.claro") == 0) fails++; if (typecheck_file("tests/typecheck_function_compat_return_bad.claro") == 0) fails++; if (typecheck_file("tests/typecheck_function_empty_return_bad.claro") == 0) fails++; if (typecheck_file("tests/typecheck_function_multi_bad.claro") == 0) fails++; if (typecheck_file("tests/typecheck_function_missing_unchecked_arg_bad.claro") == 0) fails++; if (typecheck_file("tests/typecheck_function_unknown_bad.claro") == 0) fails++; if (typecheck_file("tests/typecheck_method_bad.claro") == 0) fails++; if (typecheck_file("tests/typecheck_method_branch_missing_return_bad.claro") == 0) fails++; if (typecheck_file("tests/typecheck_object_field_bad.claro") == 0) fails++; if (typecheck_file("tests/typecheck_object_field_text_bad.claro") == 0) fails++; if (typecheck_file("tests/typecheck_object_field_yesno_bad.claro") == 0) fails++; if (typecheck_file("tests/typecheck_object_field_unknown_bad.claro") == 0) fails++; if (typecheck_file("tests/typecheck_object_field_unknown_text_bad.claro") == 0) fails++; if (typecheck_file("tests/typecheck_object_field_unknown_yesno_bad.claro") == 0) fails++; if (typecheck_file("tests/typecheck_object_field_unknown_expression_bad.claro") == 0) fails++; if (typecheck_file("tests/typecheck_object_field_expression_bad.claro") == 0) fails++; if (typecheck_file("tests/typecheck_object_field_compound_bad.claro") == 0) fails++; if (typecheck_file("tests/typecheck_object_field_text_expression_bad.claro") == 0) fails++; if (typecheck_file("tests/typecheck_object_field_check_type_text_bad.claro") == 0) fails++; if (typecheck_file("tests/typecheck_object_field_check_type_yesno_bad.claro") == 0) fails++; if (typecheck_file("tests/typecheck_object_field_check_type_number_bad.claro") == 0) fails++; if (typecheck_file("tests/typecheck_object_field_check_type_unknown_number_bad.claro") == 0) fails++; if (typecheck_file("tests/typecheck_object_field_check_type_unknown_text_bad.claro") == 0) fails++; if (typecheck_file("tests/typecheck_object_field_check_type_unknown_yesno_bad.claro") == 0) fails++; if (typecheck_file("tests/typecheck_object_field_check_type_unknown_object_bad.claro") == 0) fails++; if (typecheck_file("tests/typecheck_object_field_check_type_alias_bad.claro") == 0) fails++; if (typecheck_file("tests/typecheck_object_field_check_type_chained_alias_bad.claro") == 0) fails++; printf( "%s\n", fails ? "Validation found problems." : "Validation passed. Claro v2026.10.25.014405 foundation checks are ready for use."); return fails ? 1 : 0; } int main(int argc, char **argv) { int arg = 1, trace = 0; if (argc < 2) { print_help(); return 0; } if (strcmp(argv[arg], "--trace") == 0 || strcmp(argv[arg], "trace") == 0) { trace = 1; arg++; } if (arg >= 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) return create_new_project(argv[arg + 1]); if (strcmp(argv[arg], "fmt") == 0 && arg + 1 < argc) return fmt_file(argv[arg + 1]); if (strcmp(argv[arg], "check") == 0 && arg + 1 < argc) return check_file(argv[arg + 1]); if (strcmp(argv[arg], "typecheck") == 0 && arg + 1 < argc) return typecheck_file(argv[arg + 1]); if (strcmp(argv[arg], "version") == 0 || strcmp(argv[arg], "--version") == 0) { puts(CLARO_VERSION); return 0; } if (strcmp(argv[arg], "run") == 0) { arg++; if (arg >= 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); runtime_free(&rt); 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]); runtime_free(&rt); return rc; } }