Files
Claro/src/claro.c
T
Hermes Agent ed23524817 fix: harden Claro diagnostics and runtime guards
Preserve source locations, validate malformed JSON, and add opt-in resource limits. Include executable error-guide coverage, learner pilot materials, native Windows acceptance helpers, and updated CI/documentation.
2026-10-25 02:42:53 +00:00

8085 lines
290 KiB
C

/*
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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifndef _WIN32
#include <strings.h>
#define strnicmp2 strncasecmp
#else
#define strnicmp2 _strnicmp
#endif
#include <ctype.h>
#include <stdarg.h>
#include <errno.h>
#include <math.h>
#include <time.h>
#include <limits.h>
#include <float.h>
#include <sys/stat.h>
#ifndef _WIN32
FILE *popen(const char *command, const char *mode);
int pclose(FILE *stream);
#endif
#ifndef _WIN32
#include <dirent.h>
#include <unistd.h>
#include <sys/wait.h>
#else
#include <direct.h>
#include <io.h>
#ifdef __MINGW32__
#include <dirent.h>
#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, "<configuration>", 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, "<configuration>", 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, &params);
} else {
char *simple = trim_inplace(t);
if (*simple)
pcnt = strchr(simple, ',') ? parse_params(simple, &params)
: parse_simple_params(simple, &params);
}
*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, &params, &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, &params, &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, &parameters, &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, &params, &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, &params, &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, &params, &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, &params, &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, &params, &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, &param);
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("<repl>");
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 : "<repl>", 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;
}
}