From 413e75ee8d241876a7576a889a24811251b2ee98 Mon Sep 17 00:00:00 2001 From: RayPals Date: Tue, 17 Feb 2026 23:14:16 +0100 Subject: [PATCH] Upload files to "src" --- src/claro.c | 1462 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 1461 insertions(+), 1 deletion(-) diff --git a/src/claro.c b/src/claro.c index e2ffb3d..c769f96 100644 --- a/src/claro.c +++ b/src/claro.c @@ -1,2 +1,1462 @@ +/* + * CLARO - Programming for Everyone + * An accessible scripting language for people with learning disabilities + * + * Design principles: + * - CAPITALIZED keywords for clarity + * - Natural English-like syntax + * - Helpful error messages + * - Visual feedback with colors + * - Game development ready + * - Portable ANSI C + */ + #include -int main(){puts("Add your real claro.c interpreter here.");} +#include +#include +#include +#include +#include +#ifdef _WIN32 + #define strcasecmp _stricmp +#else + #include +#endif + +/* ============================================================================ + * CONFIGURATION + * ============================================================================ */ + +#define MAX_LINE_LENGTH 512 +#define MAX_VARIABLES 200 +#define MAX_FUNCTIONS 100 +#define MAX_CODE_LINES 2000 +#define MAX_PARAMS 10 +#define MAX_SPRITES 50 + +/* ANSI color codes */ +#define COLOR_RESET "\x1b[0m" +#define COLOR_GREEN "\x1b[32m" +#define COLOR_RED "\x1b[31m" +#define COLOR_YELLOW "\x1b[33m" +#define COLOR_BLUE "\x1b[34m" +#define COLOR_MAGENTA "\x1b[35m" +#define COLOR_CYAN "\x1b[36m" + +/* High contrast mode */ +#define HC_RESET "\x1b[0m" +#define HC_GREEN "\x1b[97;42m" +#define HC_RED "\x1b[97;41m" +#define HC_YELLOW "\x1b[30;43m" + +/* ============================================================================ + * DATA STRUCTURES + * ============================================================================ */ + +typedef enum { + TYPE_NUMBER, + TYPE_TEXT, + TYPE_YES_NO /* Boolean - friendlier for beginners */ +} VarType; + +typedef struct { + char name[MAX_LINE_LENGTH]; + VarType type; + union { + double number; + char text[MAX_LINE_LENGTH]; + int yes_no; /* 1 = YES, 0 = NO */ + } value; +} Variable; + +typedef struct { + char name[MAX_LINE_LENGTH]; + char params[MAX_PARAMS][MAX_LINE_LENGTH]; + int param_count; + char code[MAX_CODE_LINES][MAX_LINE_LENGTH]; + int code_line_count; +} Function; + +typedef struct { + char name[MAX_LINE_LENGTH]; + int x, y; + int width, height; + int visible; + char image[MAX_LINE_LENGTH]; +} Sprite; + +typedef struct { + Variable vars[MAX_VARIABLES]; + int var_count; + + Function funcs[MAX_FUNCTIONS]; + int func_count; + + Sprite sprites[MAX_SPRITES]; + int sprite_count; + + int high_contrast; + int line_number; + int in_function; + double return_value; + int should_return; + + + VarType return_type; + char return_text[MAX_LINE_LENGTH]; + char current_function[MAX_LINE_LENGTH]; + int call_depth; + /* Debug / step mode */ + int debug_mode; +} Runtime; + +static Runtime runtime; + +/* ============================================================================ + * UTILITY FUNCTIONS + * ============================================================================ */ + +char *trim(char *str) { + char *end; + while (isspace((unsigned char)*str)) str++; + if (*str == 0) return str; + end = str + strlen(str) - 1; + while (end > str && isspace((unsigned char)*end)) end--; + end[1] = '\0'; + return str; +} + +void to_upper(char *str) { + for (; *str; ++str) *str = toupper((unsigned char)*str); +} + +void print_colored(const char *color, const char *msg) { + if (runtime.high_contrast) { + if (strcmp(color, COLOR_RED) == 0) printf("%s%s%s", HC_RED, msg, HC_RESET); + else if (strcmp(color, COLOR_GREEN) == 0) printf("%s%s%s", HC_GREEN, msg, HC_RESET); + else if (strcmp(color, COLOR_YELLOW) == 0) printf("%s%s%s", HC_YELLOW, msg, HC_RESET); + else printf("%s", msg); + } else { + printf("%s%s%s", color, msg, COLOR_RESET); + } +} + +/* ============================================================================ + * ERROR HANDLING + * ============================================================================ */ + +void error_message(const char *message, const char *help) { + print_colored(COLOR_RED, "\n*** OOPS! ***\n"); + printf("Line %d: %s\n", runtime.line_number, message); + if (runtime.call_depth > 0 && strlen(runtime.current_function) > 0) { + print_colored(COLOR_MAGENTA, "In function: "); + printf("%s\n", runtime.current_function); + } + if (help && strlen(help) > 0) { + print_colored(COLOR_YELLOW, "Tip: "); + printf("%s\n\n", help); + } +} + + +/* ============================================================================ + * SPELL-CHECK / "DID YOU MEAN?" HELPER + * ============================================================================ */ + +static int levenshtein(const char *s1, const char *s2) { + int l1 = (int)strlen(s1), l2 = (int)strlen(s2); + int dp[l1+1][l2+1]; + for (int i = 0; i <= l1; i++) dp[i][0] = i; + for (int j = 0; j <= l2; j++) dp[0][j] = j; + for (int i = 1; i <= l1; i++) + for (int j = 1; j <= l2; j++) { + int cost = (toupper(s1[i-1]) == toupper(s2[j-1])) ? 0 : 1; + dp[i][j] = dp[i-1][j]+1; + if (dp[i][j-1]+1 < dp[i][j]) dp[i][j] = dp[i][j-1]+1; + if (dp[i-1][j-1]+cost < dp[i][j]) dp[i][j] = dp[i-1][j-1]+cost; + } + return dp[l1][l2]; +} + +void suggest_command(const char *bad_cmd) { + static const char *commands[] = { + "SAY","SET","ASK","IF","ELSE","ENDIF", + "REPEAT","UNTIL","DO","TIMES","DONE", + "TEACH","LEARNED","CALL","RETURN", + "WAIT","HELP","EXIT","VARS","DEBUG", + "CREATE","MOVE","SHOW","HIDE", + NULL + }; + int best_dist = 999; + const char *best = NULL; + char upper[MAX_LINE_LENGTH]; + strncpy(upper, bad_cmd, MAX_LINE_LENGTH-1); + to_upper(upper); + for (int i = 0; commands[i]; i++) { + int d = levenshtein(upper, commands[i]); + if (d < best_dist && d <= 2) { best_dist = d; best = commands[i]; } + } + if (best) { + print_colored(COLOR_YELLOW, "Did you mean: "); + print_colored(COLOR_GREEN, best); + printf("?\n"); + } +} + +/* ============================================================================ + * VARIABLE STORE + * ============================================================================ */ + +Variable *find_var(const char *name) { + for (int i = 0; i < runtime.var_count; i++) + if (strcasecmp(runtime.vars[i].name, name) == 0) + return &runtime.vars[i]; + return NULL; +} + +Variable *get_or_create_var(const char *name) { + Variable *v = find_var(name); + if (v) return v; + if (runtime.var_count >= MAX_VARIABLES) { + error_message("Too many variables!", "Remove some variables you no longer need."); + return NULL; + } + v = &runtime.vars[runtime.var_count++]; + memset(v, 0, sizeof(Variable)); + strncpy(v->name, name, MAX_LINE_LENGTH-1); + return v; +} + +void set_number(const char *name, double val) { + Variable *v = get_or_create_var(name); + if (!v) return; + v->type = TYPE_NUMBER; + v->value.number = val; +} + +void set_text(const char *name, const char *val) { + Variable *v = get_or_create_var(name); + if (!v) return; + v->type = TYPE_TEXT; + strncpy(v->value.text, val, MAX_LINE_LENGTH-1); +} + +void set_yes_no(const char *name, int val) { + Variable *v = get_or_create_var(name); + if (!v) return; + v->type = TYPE_YES_NO; + v->value.yes_no = val; +} + +/* ============================================================================ + * EXPRESSION EVALUATOR + * ============================================================================ */ + +/* ------------------------------------------------- + * Number expressions + * Supports: + * - +, -, *, / with normal precedence + * - parentheses: ( ... ) + * - variables (NUMBER / YES-NO treated as 0/1) + * - functions: SQRT(x), ABS(x), ROUND(x), FLOOR(x), CEIL(x) + * - RANDOM n (1..n) + * ------------------------------------------------- */ + +typedef struct { + const char *s; + int pos; + int had_error; +} NumParser; + +static void np_skip_ws(NumParser *p) { + while (p->s[p->pos] && isspace((unsigned char)p->s[p->pos])) p->pos++; +} + +static int np_peek(NumParser *p) { + np_skip_ws(p); + return (unsigned char)p->s[p->pos]; +} + +static int np_match(NumParser *p, char c) { + np_skip_ws(p); + if (p->s[p->pos] == c) { p->pos++; return 1; } + return 0; +} + +static void np_set_error(NumParser *p, const char *msg, const char *tip) { + if (!p->had_error) { + error_message(msg, tip); + p->had_error = 1; + } +} + +static double np_parse_expr(NumParser *p); /* forward */ + +static int np_parse_identifier(NumParser *p, char out[MAX_LINE_LENGTH]) { + np_skip_ws(p); + int i = 0; + if (!isalpha((unsigned char)p->s[p->pos]) && p->s[p->pos] != '_') return 0; + while (p->s[p->pos] && (isalnum((unsigned char)p->s[p->pos]) || p->s[p->pos] == '_')) { + if (i < MAX_LINE_LENGTH-1) out[i++] = p->s[p->pos]; + p->pos++; + } + out[i] = '\0'; + return 1; +} + +static double np_parse_factor(NumParser *p) { + np_skip_ws(p); + + /* unary + / - */ + if (np_match(p, '+')) return np_parse_factor(p); + if (np_match(p, '-')) return -np_parse_factor(p); + + /* parenthesis */ + if (np_match(p, '(')) { + double v = np_parse_expr(p); + if (!np_match(p, ')')) { + np_set_error(p, "Missing ) in math", "Check your parentheses, like: (2 + 3) * 4"); + } + return v; + } + + /* number literal */ + { + np_skip_ws(p); + char *endptr = NULL; + double v = strtod(p->s + p->pos, &endptr); + if (endptr && endptr != (p->s + p->pos)) { + p->pos = (int)(endptr - p->s); + return v; + } + } + + /* identifier: variable or function */ + char name[MAX_LINE_LENGTH]; + if (np_parse_identifier(p, name)) { + char upper[MAX_LINE_LENGTH]; + strncpy(upper, name, MAX_LINE_LENGTH-1); + upper[MAX_LINE_LENGTH-1] = '\0'; + to_upper(upper); + + /* RANDOM n (space separated) */ + if (strcmp(upper, "RANDOM") == 0) { + double n = np_parse_factor(p); + if (n <= 0) { np_set_error(p, "RANDOM needs a positive number", "Try: RANDOM 10"); return 0; } + return (double)(rand() % (int)n) + 1; + } + + /* function call NAME(expr) */ + if (np_match(p, '(')) { + double a = np_parse_expr(p); + if (!np_match(p, ')')) { + np_set_error(p, "Missing ) after function", "Example: SQRT(16)"); + } + if (strcmp(upper,"SQRT") == 0) return sqrt(a); + if (strcmp(upper,"ABS") == 0) return fabs(a); + if (strcmp(upper,"ROUND") == 0) return round(a); + if (strcmp(upper,"FLOOR") == 0) return floor(a); + if (strcmp(upper,"CEIL") == 0) return ceil(a); + + np_set_error(p, "Unknown math function", "Try: SQRT(x), ABS(x), ROUND(x), FLOOR(x), CEIL(x)"); + return 0; + } + + /* variable lookup */ + Variable *v = find_var(name); + if (v) { + if (v->type == TYPE_NUMBER) return v->value.number; + if (v->type == TYPE_YES_NO) return v->value.yes_no; + np_set_error(p, "That value is not a number", "Use a NUMBER variable in math, like: SET score TO 10"); + return 0; + } + + np_set_error(p, "Unknown number or variable", "Tip: Use VARS to see your variables"); + return 0; + } + + /* nothing matched */ + np_set_error(p, "I couldn't understand that math", "Example: 2 + 3 * 4 or (2 + 3) * 4"); + return 0; +} + +static double np_parse_term(NumParser *p) { + double v = np_parse_factor(p); + for (;;) { + if (np_match(p, '*')) { + v *= np_parse_factor(p); + } else if (np_match(p, '/')) { + double r = np_parse_factor(p); + if (r == 0) { + np_set_error(p, "Divide by zero", "Make sure the right side is not 0"); + return 0; + } + v /= r; + } else { + break; + } + } + return v; +} + +static double np_parse_expr(NumParser *p) { + double v = np_parse_term(p); + for (;;) { + if (np_match(p, '+')) { + v += np_parse_term(p); + } else if (np_match(p, '-')) { + v -= np_parse_term(p); + } else { + break; + } + } + return v; +} + +double eval_number_expr(const char *expr_in) { + NumParser p = { expr_in, 0, 0 }; + double v = np_parse_expr(&p); + np_skip_ws(&p); + if (!p.had_error && p.s[p.pos] != '\0') { + error_message("Extra text in math", "Tip: check for extra words or symbols"); + return 0; + } + return v; +} + +int eval_yes_no_expr(const char *expr_in) { + char expr[MAX_LINE_LENGTH]; + strncpy(expr, expr_in, MAX_LINE_LENGTH-1); + char *e = trim(expr); + + if (strcasecmp(e,"YES")==0 || strcasecmp(e,"TRUE")==0) return 1; + if (strcasecmp(e,"NO")==0 || strcasecmp(e,"FALSE")==0) return 0; + + Variable *v = find_var(e); + if (v && v->type == TYPE_YES_NO) return v->value.yes_no; + + char left[MAX_LINE_LENGTH], op[MAX_LINE_LENGTH], right[MAX_LINE_LENGTH]; + /* Try "left OP right" */ + if (sscanf(e, "%s %s %[^\n]", left, op, right) == 3) { + to_upper(op); + if (strcmp(op,"IS")==0 || strcmp(op,"EQUALS")==0) { + /* String comparison */ + Variable *lv = find_var(left); + if (lv && lv->type == TYPE_TEXT) { + char rstr[MAX_LINE_LENGTH]; + strncpy(rstr, right, MAX_LINE_LENGTH-1); + if (rstr[0]=='"') { memmove(rstr,rstr+1,strlen(rstr)); } + size_t rl = strlen(rstr); + if (rl>0 && rstr[rl-1]=='"') rstr[rl-1]='\0'; + return strcasecmp(lv->value.text, rstr) == 0; + } + return fabs(eval_number_expr(left) - eval_number_expr(right)) < 0.0001; + } + if (strcmp(op,"BIGGER")==0 || strcmp(op,"GREATER")==0) + return eval_number_expr(left) > eval_number_expr(right); + if (strcmp(op,"SMALLER")==0 || strcmp(op,"LESS")==0) + return eval_number_expr(left) < eval_number_expr(right); + if (strcmp(op,"NOT")==0 && (strcmp(right,"IS")==0 || strcmp(right,"EQUALS")==0)) { + /* "x NOT IS y" -> not equal */ + char r2[MAX_LINE_LENGTH]; + sscanf(e, "%*s %*s %*s %[^\n]", r2); + return fabs(eval_number_expr(left) - eval_number_expr(r2)) >= 0.0001; + } + } + + /* If it's just a variable holding a number, treat nonzero as YES */ + if (v && v->type == TYPE_NUMBER) return v->value.number != 0; + + return 0; +} + +/* ============================================================================ + * COMMAND IMPLEMENTATIONS + * ============================================================================ */ + +void execute_line(char *line); +void execute_code(char code[][MAX_LINE_LENGTH], int line_count); +void execute_block(char code[][MAX_LINE_LENGTH], int line_count, int start, int end); + + +/* SAY */ +void cmd_say(char *args) { + args = trim(args); + /* Strip surrounding quotes */ + if ((args[0]=='"' || args[0]=='\'') ) { + size_t len = strlen(args); + if (len>1 && (args[len-1]=='"' || args[len-1]=='\'')) { + args[len-1] = '\0'; + args++; + } + } + Variable *v = find_var(args); + if (v) { + if (v->type == TYPE_NUMBER) { + if (floor(v->value.number) == v->value.number) + printf("%.0f\n", v->value.number); + else + printf("%g\n", v->value.number); + } else if (v->type == TYPE_TEXT) { + printf("%s\n", v->value.text); + } else { + printf("%s\n", v->value.yes_no ? "YES" : "NO"); + } + } else { + printf("%s\n", args); + } +} + +/* SET */ +void cmd_set(char *args) { + char name[MAX_LINE_LENGTH]; + char value[MAX_LINE_LENGTH]; + + /* Accept "SET name TO value" (case-insensitive TO) */ + char *to_pos = NULL; + char upper_args[MAX_LINE_LENGTH]; + strncpy(upper_args, args, MAX_LINE_LENGTH-1); + to_upper(upper_args); + to_pos = strstr(upper_args, " TO "); + if (!to_pos) { + error_message("Could not understand SET command", + "Try: SET score TO 100"); + return; + } + int name_len = (int)(to_pos - upper_args); + strncpy(name, args, name_len); + name[name_len] = '\0'; + trim(name); + strncpy(value, args + name_len + 4, MAX_LINE_LENGTH-1); + trim(value); + + /* Quoted string? */ + if (value[0]=='"' || value[0]=='\'') { + size_t len = strlen(value); + if (len>1 && (value[len-1]=='"' || value[len-1]=='\'')) { + value[len-1] = '\0'; + set_text(name, value+1); + return; + } + } + /* YES/NO */ + if (strcasecmp(value,"YES")==0 || strcasecmp(value,"TRUE")==0) { set_yes_no(name,1); return; } + if (strcasecmp(value,"NO")==0 || strcasecmp(value,"FALSE")==0) { set_yes_no(name,0); return; } + /* Number / expression */ + set_number(name, eval_number_expr(value)); +} + +/* ASK */ +void cmd_ask(char *args) { + char name[MAX_LINE_LENGTH], prompt[MAX_LINE_LENGTH]; + if (sscanf(args, "%s %[^\n]", name, prompt) >= 1) { + if (strlen(prompt) > 0) + printf("%s ", prompt); + char input[MAX_LINE_LENGTH]; + if (fgets(input, sizeof(input), stdin)) { + input[strcspn(input,"\n")] = 0; + trim(input); + char *endptr; + double num = strtod(input, &endptr); + if (*endptr == '\0') + set_number(name, num); + else + set_text(name, input); + } + } else { + error_message("Could not understand ASK command", + "Try: ASK name What is your name?"); + } +} + +/* WAIT */ +void cmd_wait(char *args) { + double seconds = eval_number_expr(args); + if (seconds > 0) { + clock_t start = clock(); + while ((double)(clock()-start)/CLOCKS_PER_SEC < seconds); + } +} + +/* VARS - display all variables */ +void cmd_vars(void) { + if (runtime.var_count == 0) { + printf("No variables set yet.\n"); + return; + } + print_colored(COLOR_CYAN, "=== Variables ===\n"); + for (int i = 0; i < runtime.var_count; i++) { + Variable *v = &runtime.vars[i]; + printf(" %-20s = ", v->name); + if (v->type == TYPE_NUMBER) { + if (floor(v->value.number)==v->value.number) + printf("%.0f\n", v->value.number); + else + printf("%g\n", v->value.number); + } else if (v->type == TYPE_TEXT) { + printf("\"%s\"\n", v->value.text); + } else { + printf("%s\n", v->value.yes_no ? "YES" : "NO"); + } + } +} + +/* HELP */ +void cmd_help(char *topic) { + topic = trim(topic); + to_upper(topic); + + if (strlen(topic) == 0 || strcmp(topic,"ALL")==0) { + print_colored(COLOR_GREEN, "\n=== CLARO Commands ===\n\n"); + printf("Showing / Asking:\n"); + printf(" SAY \"text\" - Show a message\n"); + printf(" SAY variable - Show a variable's value\n"); + printf(" ASK var What? - Ask the user a question\n\n"); + printf("Variables:\n"); + printf(" SET var TO value - Remember something\n"); + printf(" VARS - Show all variables\n\n"); + printf("Making Decisions:\n"); + printf(" IF condition - Check something\n"); + printf(" ...\n"); + printf(" ELSE - Otherwise\n"); + printf(" ...\n"); + printf(" ENDIF - End the IF block\n\n"); + printf("Repeating:\n"); + printf(" DO 5 TIMES - Repeat 5 times\n"); + printf(" ...\n"); + printf(" DONE\n\n"); + printf(" REPEAT 5 - Old-style (still works)\n"); + printf(" ...\n"); + printf(" UNTIL\n\n"); + printf("Functions (Reusable Blocks):\n"); + printf(" TEACH name TAKES a, b - Teach CLARO a new skill\n"); + printf(" ...\n"); + printf(" RETURN value - Send a value back\n"); + printf(" LEARNED - End the TEACH block\n\n"); + printf(" CALL name WITH a, b - Use a function (stores RESULT)\n\n"); + printf("Sprites (Game Objects):\n"); + printf(" CREATE SPRITE name - Create a game character\n"); + printf(" MOVE SPRITE name TO x y\n"); + printf(" SHOW SPRITE name\n"); + printf(" HIDE SPRITE name\n\n"); + printf("Other:\n"); + printf(" WAIT 2 - Pause for 2 seconds\n"); + printf(" DEBUG ON/OFF - Toggle debug mode\n"); + printf(" HELP SAY - Help on a specific command\n"); + printf(" EXIT / QUIT - Exit CLARO\n\n"); + printf("Comparisons in IF:\n"); + printf(" IS / EQUALS - Equal?\n"); + printf(" BIGGER / GREATER - First one larger?\n"); + printf(" SMALLER / LESS - First one smaller?\n\n"); + } else if (strcmp(topic,"SAY")==0) { + printf("\nSAY - Show a message or variable\n"); + printf(" SAY \"Hello!\"\n"); + printf(" SAY score\n"); + } else if (strcmp(topic,"SET")==0) { + printf("\nSET - Store a value in a variable\n"); + printf(" SET score TO 100\n"); + printf(" SET name TO \"Alice\"\n"); + printf(" SET score TO score + 10\n"); + } else if (strcmp(topic,"ASK")==0) { + printf("\nASK - Get input from the user\n"); + printf(" ASK name What is your name?\n"); + printf(" ASK age How old are you?\n"); + } else if (strcmp(topic,"IF")==0) { + printf("\nIF - Make a decision\n"); + printf(" IF score IS 100\n"); + printf(" SAY \"Perfect!\"\n"); + printf(" ELSE\n"); + printf(" SAY \"Try again!\"\n"); + printf(" ENDIF\n"); + } else if (strcmp(topic,"REPEAT")==0 || strcmp(topic,"DO")==0) { + printf("\nDO / REPEAT - Repeat a block\n"); + printf(" DO 5 TIMES\n"); + printf(" SAY \"Hello!\"\n"); + printf(" DONE\n"); + } else if (strcmp(topic,"TEACH")==0) { + printf("\nTEACH - Define a reusable function\n"); + printf(" TEACH greet TAKES name\n"); + printf(" SAY \"Hello \"\n"); + printf(" SAY name\n"); + printf(" RETURN 1\n"); + printf(" LEARNED\n"); + } else if (strcmp(topic,"CALL")==0) { + printf("\nCALL - Run a function you taught CLARO\n"); + printf(" CALL greet WITH \"Sam\"\n"); + printf(" SAY RESULT\n"); + printf("\nTip: If you have more than one value, separate them with commas.\n"); + } else if (strcmp(topic,"RETURN")==0) { + printf("\nRETURN - Send a value back from a function\n"); + printf(" RETURN 42\n"); + printf(" RETURN \"Done!\"\n"); + printf(" RETURN YES\n"); + } else { + printf("No help found for '%s'. Type HELP for all commands.\n", topic); + } +} + +/* SPRITE commands */ +Sprite *find_sprite(const char *name) { + for (int i = 0; i < runtime.sprite_count; i++) + if (strcasecmp(runtime.sprites[i].name, name) == 0) + return &runtime.sprites[i]; + return NULL; +} + +void cmd_create_sprite(char *args) { + char name[MAX_LINE_LENGTH]; + sscanf(args, "%s", name); + if (find_sprite(name)) { + printf("Sprite '%s' already exists.\n", name); return; + } + if (runtime.sprite_count >= MAX_SPRITES) { + error_message("Too many sprites!", "Remove some sprites first."); return; + } + Sprite *s = &runtime.sprites[runtime.sprite_count++]; + memset(s, 0, sizeof(Sprite)); + strncpy(s->name, name, MAX_LINE_LENGTH-1); + s->visible = 1; s->width = 32; s->height = 32; + printf("Sprite '%s' created.\n", name); +} + +void cmd_move_sprite(char *args) { + /* MOVE SPRITE name TO x y */ + char name[MAX_LINE_LENGTH]; int x, y; + if (sscanf(args, "%s TO %d %d", name, &x, &y) == 3 || + sscanf(args, "%s to %d %d", name, &x, &y) == 3) { + Sprite *s = find_sprite(name); + if (!s) { error_message("Sprite not found", "Use CREATE SPRITE first"); return; } + s->x = x; s->y = y; + printf("Moved '%s' to (%d, %d)\n", name, x, y); + } else { + error_message("Could not understand MOVE SPRITE", + "Try: MOVE SPRITE player TO 100 200"); + } +} + +void cmd_show_sprite(char *args) { + char name[MAX_LINE_LENGTH]; sscanf(args, "%s", name); + Sprite *s = find_sprite(name); + if (!s) { error_message("Sprite not found","Use CREATE SPRITE first"); return; } + s->visible = 1; printf("Sprite '%s' is now visible.\n", name); +} + +void cmd_hide_sprite(char *args) { + char name[MAX_LINE_LENGTH]; sscanf(args, "%s", name); + Sprite *s = find_sprite(name); + if (!s) { error_message("Sprite not found","Use CREATE SPRITE first"); return; } + s->visible = 0; printf("Sprite '%s' is now hidden.\n", name); +} + + +/* ============================================================================ + * USER FUNCTIONS (TEACH / CALL / RETURN) + * ============================================================================ */ + +Function *find_func(const char *name) { + for (int i = 0; i < runtime.func_count; i++) + if (strcasecmp(runtime.funcs[i].name, name) == 0) + return &runtime.funcs[i]; + return NULL; +} + +static void clear_function(Function *f) { + memset(f, 0, sizeof(Function)); +} + +static void upsert_function(const Function *src) { + Function *existing = find_func(src->name); + if (existing) { + *existing = *src; + return; + } + if (runtime.func_count >= MAX_FUNCTIONS) { + error_message("Too many functions!", "Tip: keep your program small, or reuse functions you already have."); + return; + } + runtime.funcs[runtime.func_count++] = *src; +} + +static int is_word(const char *w, const char *kw) { return strcasecmp(w, kw) == 0; } + +/* Parse: TEACH name [TAKES a, b, c] */ +static void parse_teach_header(const char *rest, Function *out) { + clear_function(out); + + char tmp[MAX_LINE_LENGTH]; + strncpy(tmp, rest, MAX_LINE_LENGTH-1); + tmp[MAX_LINE_LENGTH-1] = '\0'; + + char *p = trim(tmp); + + /* name */ + char name[MAX_LINE_LENGTH] = {0}; + sscanf(p, "%s", name); + strncpy(out->name, name, MAX_LINE_LENGTH-1); + + /* move past name */ + p = p + strlen(name); + p = trim(p); + + out->param_count = 0; + + if (*p == 0) return; + + char kw[MAX_LINE_LENGTH] = {0}; + sscanf(p, "%s", kw); + if (!is_word(kw, "TAKES") && !is_word(kw, "WITH")) return; + + p = trim(p + strlen(kw)); + if (*p == 0) return; + + /* split by commas, otherwise treat as space-separated simple names */ + if (strchr(p, ',')) { + for (char *tok = strtok(p, ","); + tok && out->param_count < MAX_PARAMS; + tok = strtok(NULL, ",")) { + tok = trim(tok); + if (*tok == 0) continue; + /* take first word as param name */ + char param[MAX_LINE_LENGTH] = {0}; + sscanf(tok, "%s", param); + strncpy(out->params[out->param_count++], param, MAX_LINE_LENGTH-1); + } + } else { + /* space-separated */ + for (char *tok = strtok(p, " \t"); + tok && out->param_count < MAX_PARAMS; + tok = strtok(NULL, " \t")) { + tok = trim(tok); + if (*tok == 0) continue; + strncpy(out->params[out->param_count++], tok, MAX_LINE_LENGTH-1); + } + } +} + +/* Scan a code buffer and register any TEACH blocks (functions). */ +void parse_functions(char code[][MAX_LINE_LENGTH], int line_count) { + for (int i = 0; i < line_count; i++) { + char line_copy[MAX_LINE_LENGTH]; + strncpy(line_copy, code[i], MAX_LINE_LENGTH-1); + line_copy[MAX_LINE_LENGTH-1] = '\0'; + char *l = trim(line_copy); + + if (strlen(l)==0 || l[0]=='#') continue; + + char cmd[MAX_LINE_LENGTH]; + sscanf(l, "%s", cmd); + to_upper(cmd); + + if (strcmp(cmd, "TEACH") != 0) continue; + + Function f; + char *rest = trim(l + strlen(cmd)); + parse_teach_header(rest, &f); + + if (strlen(f.name) == 0) { + runtime.line_number = i + 1; + error_message("TEACH needs a function name", "Try: TEACH greet TAKES name"); + continue; + } + + /* collect lines until LEARNED */ + int start = i + 1; + int end = start; + while (end < line_count) { + char w[MAX_LINE_LENGTH]; w[0] = '\0'; + sscanf(trim(code[end]), "%s", w); + to_upper(w); + if (strcmp(w, "LEARNED") == 0) break; + end++; + } + if (end >= line_count) { + runtime.line_number = i + 1; + error_message("TEACH without LEARNED", "Add LEARNED at the end of your TEACH block"); + continue; + } + + f.code_line_count = 0; + for (int j = start; j < end && f.code_line_count < MAX_CODE_LINES; j++) { + strncpy(f.code[f.code_line_count], code[j], MAX_LINE_LENGTH-1); + f.code[f.code_line_count][MAX_LINE_LENGTH-1] = '\0'; + f.code_line_count++; + } + + upsert_function(&f); + + /* skip past this function block */ + i = end; + } +} + +static void set_result_from_return(void) { + if (runtime.return_type == TYPE_TEXT) { + set_text("RESULT", runtime.return_text); + } else if (runtime.return_type == TYPE_YES_NO) { + set_yes_no("RESULT", runtime.return_value != 0.0); + } else { + set_number("RESULT", runtime.return_value); + } +} + +static void parse_value(const char *expr, VarType *out_type, double *out_num, char *out_text) { + char tmp[MAX_LINE_LENGTH]; + strncpy(tmp, expr, MAX_LINE_LENGTH-1); + tmp[MAX_LINE_LENGTH-1] = '\0'; + char *t = trim(tmp); + + if (strlen(t) == 0) { *out_type = TYPE_NUMBER; *out_num = 0; out_text[0] = '\0'; return; } + + /* quoted text */ + if ((t[0] == '"' && t[strlen(t)-1] == '"') || (t[0] == '\'' && t[strlen(t)-1] == '\'')) { + *out_type = TYPE_TEXT; + t[strlen(t)-1] = '\0'; + strncpy(out_text, t+1, MAX_LINE_LENGTH-1); + out_text[MAX_LINE_LENGTH-1] = '\0'; + return; + } + + /* YES/NO */ + char up[MAX_LINE_LENGTH]; + strncpy(up, t, MAX_LINE_LENGTH-1); + up[MAX_LINE_LENGTH-1] = '\0'; + to_upper(up); + if (strcmp(up, "YES") == 0 || strcmp(up, "NO") == 0) { + *out_type = TYPE_YES_NO; + *out_num = (strcmp(up, "YES") == 0) ? 1.0 : 0.0; + out_text[0] = '\0'; + return; + } + + /* variable reference (text/bool) */ + Variable *v = find_var(t); + if (v && v->type == TYPE_TEXT) { + *out_type = TYPE_TEXT; + strncpy(out_text, v->value.text, MAX_LINE_LENGTH-1); + out_text[MAX_LINE_LENGTH-1] = '\0'; + return; + } + if (v && v->type == TYPE_YES_NO) { + *out_type = TYPE_YES_NO; + *out_num = v->value.yes_no ? 1.0 : 0.0; + out_text[0] = '\0'; + return; + } + + /* fallback: number expression */ + *out_type = TYPE_NUMBER; + *out_num = eval_number_expr(t); + out_text[0] = '\0'; +} + +/* CALL name [WITH arg1, arg2, ...] */ +void cmd_call(char *args) { + char tmp[MAX_LINE_LENGTH]; + strncpy(tmp, args, MAX_LINE_LENGTH-1); + tmp[MAX_LINE_LENGTH-1] = '\0'; + char *t = trim(tmp); + + if (strlen(t) == 0) { + error_message("CALL needs a function name", "Try: CALL greet WITH \"Sam\""); + return; + } + + char fname[MAX_LINE_LENGTH] = {0}; + sscanf(t, "%s", fname); + + Function *f = find_func(fname); + if (!f) { + char msg[MAX_LINE_LENGTH]; + snprintf(msg, sizeof(msg), "I don't know how to '%s' yet.", fname); + error_message(msg, "Tip: Define it first with TEACH ... LEARNED"); + return; + } + + /* move past name */ + t = trim(t + strlen(fname)); + + /* optional WITH/USING */ + char kw[MAX_LINE_LENGTH] = {0}; + char *arg_str = t; + if (strlen(arg_str) > 0) { + sscanf(arg_str, "%s", kw); + to_upper(kw); + if (strcmp(kw,"WITH")==0 || strcmp(kw,"USING")==0) { + arg_str = trim(arg_str + strlen(kw)); + } + } + + /* parse args */ + char argbuf[MAX_LINE_LENGTH]; + strncpy(argbuf, arg_str, MAX_LINE_LENGTH-1); + argbuf[MAX_LINE_LENGTH-1] = '\0'; + char *ab = trim(argbuf); + + char *argv[MAX_PARAMS]; + int argc = 0; + + if (strlen(ab) == 0) { + argc = 0; + } else if (strchr(ab, ',')) { + for (char *tok = strtok(ab, ","); + tok && argc < MAX_PARAMS; + tok = strtok(NULL, ",")) { + tok = trim(tok); + if (*tok == 0) continue; + argv[argc++] = tok; + } + } else { + /* single argument (may contain spaces / expressions) */ + argv[argc++] = ab; + } + + if (argc != f->param_count) { + char msg[MAX_LINE_LENGTH]; + snprintf(msg, sizeof(msg), "CALL %s needs %d value(s), but you gave %d.", + f->name, f->param_count, argc); + error_message(msg, "Tip: Use commas: CALL name WITH a, b, \"text\""); + return; + } + + /* Save previous param variables so we can restore them after the call */ + Variable saved[MAX_PARAMS]; + int had_saved[MAX_PARAMS]; + memset(saved, 0, sizeof(saved)); + memset(had_saved, 0, sizeof(had_saved)); + + for (int i = 0; i < f->param_count; i++) { + Variable *v = find_var(f->params[i]); + if (v) { had_saved[i] = 1; saved[i] = *v; } + } + + /* Assign params */ + for (int i = 0; i < f->param_count; i++) { + VarType ttype; + double tnum; + char ttext[MAX_LINE_LENGTH]; + parse_value(argv[i], &ttype, &tnum, ttext); + + if (ttype == TYPE_TEXT) set_text(f->params[i], ttext); + else if (ttype == TYPE_YES_NO) set_yes_no(f->params[i], (tnum != 0.0)); + else set_number(f->params[i], tnum); + } + + /* Call */ + int prev_in_func = runtime.in_function; + int prev_should_return = runtime.should_return; + VarType prev_return_type = runtime.return_type; + double prev_return_value = runtime.return_value; + char prev_return_text[MAX_LINE_LENGTH]; + strncpy(prev_return_text, runtime.return_text, MAX_LINE_LENGTH-1); + prev_return_text[MAX_LINE_LENGTH-1] = '\0'; + + runtime.in_function = 1; + runtime.should_return = 0; + runtime.return_type = TYPE_NUMBER; + runtime.return_value = 0; + runtime.return_text[0] = '\0'; + + runtime.call_depth++; + strncpy(runtime.current_function, f->name, MAX_LINE_LENGTH-1); + runtime.current_function[MAX_LINE_LENGTH-1] = '\0'; + + execute_code(f->code, f->code_line_count); + + runtime.call_depth--; + if (runtime.call_depth == 0) runtime.current_function[0] = '\0'; + + /* Restore param variables */ + for (int i = 0; i < f->param_count; i++) { + if (had_saved[i]) { + Variable *v = get_or_create_var(saved[i].name); + if (v) *v = saved[i]; + } + } + + /* publish return as RESULT */ + set_result_from_return(); + + /* restore caller context flags */ + runtime.in_function = prev_in_func; + runtime.should_return = prev_should_return; + + /* keep returned value in runtime, but restore caller's return storage if nested call */ + if (runtime.call_depth > 0) { + runtime.return_type = prev_return_type; + runtime.return_value = prev_return_value; + strncpy(runtime.return_text, prev_return_text, MAX_LINE_LENGTH-1); + runtime.return_text[MAX_LINE_LENGTH-1] = '\0'; + } +} + +void cmd_return(char *expr) { + if (!runtime.in_function) { + error_message("RETURN can only be used inside TEACH ... LEARNED", "Tip: RETURN ends a function early."); + return; + } + + VarType ttype; + double tnum; + char ttext[MAX_LINE_LENGTH]; + parse_value(expr, &ttype, &tnum, ttext); + + runtime.return_type = ttype; + if (ttype == TYPE_TEXT) { + strncpy(runtime.return_text, ttext, MAX_LINE_LENGTH-1); + runtime.return_text[MAX_LINE_LENGTH-1] = '\0'; + runtime.return_value = 0; + } else { + runtime.return_value = tnum; + runtime.return_text[0] = '\0'; + } + runtime.should_return = 1; + + /* also set RESULT inside the function (helpful for beginners) */ + set_result_from_return(); +} + +/* ============================================================================ + * IF / REPEAT / DO TIMES COMMANDS + * ============================================================================ */ + +/* Find matching ELSE and ENDIF for an IF at position start */ +void find_if_structure(char code[][MAX_LINE_LENGTH], int line_count, int start, + int *else_line, int *endif_line) { + int depth = 1; + *else_line = -1; + *endif_line = -1; + for (int i = start+1; i < line_count; i++) { + char w[MAX_LINE_LENGTH]; sscanf(trim(code[i]), "%s", w); to_upper(w); + if (strcmp(w,"IF")==0) depth++; + if (strcmp(w,"ENDIF")==0) { depth--; if (depth==0) { *endif_line = i; return; } } + if (strcmp(w,"ELSE")==0 && depth==1) *else_line = i; + } +} + +void cmd_if(char *cond, char code[][MAX_LINE_LENGTH], int line_count, int *line) { + int else_line, endif_line; + find_if_structure(code, line_count, *line, &else_line, &endif_line); + if (endif_line < 0) { error_message("IF without ENDIF","Add ENDIF after your IF block"); return; } + + int result = eval_yes_no_expr(cond); + if (result) { + int end = (else_line >= 0) ? else_line : endif_line; + execute_block(code, line_count, *line + 1, end); + } else if (else_line >= 0) { + execute_block(code, line_count, else_line + 1, endif_line); + } + *line = endif_line; +} + +void cmd_repeat(char *args, char code[][MAX_LINE_LENGTH], int line_count, int *line) { + int count = (int)eval_number_expr(args); + if (count <= 0) { error_message("REPEAT count must be a positive number","Try: REPEAT 5"); return; } + + int start = *line + 1; + int end = start; + int depth = 1; + while (end < line_count && depth > 0) { + char w[MAX_LINE_LENGTH]; sscanf(trim(code[end]), "%s", w); to_upper(w); + if (strcmp(w,"REPEAT")==0) depth++; + if (strcmp(w,"UNTIL")==0) depth--; + if (depth > 0) end++; + } + + if (depth != 0) { error_message("REPEAT without UNTIL","Add UNTIL after your REPEAT block"); return; } + + for (int i = 0; i < count; i++) { + execute_block(code, line_count, start, end); + } + *line = end; +} + +void cmd_do_times(char *count_str, char code[][MAX_LINE_LENGTH], int line_count, int *line) { + int count = (int)eval_number_expr(count_str); + if (count <= 0) { error_message("DO count must be a positive number","Try: DO 5 TIMES"); return; } + + int start = *line + 1; + int end = start; + int depth = 1; + while (end < line_count && depth > 0) { + char w[MAX_LINE_LENGTH]; sscanf(trim(code[end]), "%s", w); to_upper(w); + if (strcmp(w,"DO")==0) depth++; + if (strcmp(w,"DONE")==0) depth--; + if (depth > 0) end++; + } + + if (depth != 0) { error_message("DO without DONE","Add DONE after your DO block"); return; } + + for (int i = 0; i < count; i++) { + execute_block(code, line_count, start, end); + } + *line = end; +} + +/* ============================================================================ + * LINE EXECUTOR + * ============================================================================ */ + +void execute_line(char *raw_line) { + char line[MAX_LINE_LENGTH]; + strncpy(line, raw_line, MAX_LINE_LENGTH-1); + char *l = trim(line); + + if (strlen(l) == 0) return; + if (l[0] == '#') return; /* comment */ + + /* Debug step */ + if (runtime.debug_mode) { + print_colored(COLOR_CYAN, "[DEBUG] "); + printf("Line %d: %s\n", runtime.line_number, l); + } + + char cmd[MAX_LINE_LENGTH]; + sscanf(l, "%s", cmd); + to_upper(cmd); + + char *rest = l + strlen(cmd); + rest = trim(rest); + + /* ---- Commands ---- */ + if (strcmp(cmd,"SAY")==0) { cmd_say(rest); return; } + if (strcmp(cmd,"SET")==0) { cmd_set(rest); return; } + if (strcmp(cmd,"ASK")==0) { cmd_ask(rest); return; } + if (strcmp(cmd,"WAIT")==0) { cmd_wait(rest); return; } + if (strcmp(cmd,"VARS")==0) { cmd_vars(); return; } + + if (strcmp(cmd,"CALL")==0) { cmd_call(rest); return; } + if (strcmp(cmd,"RETURN")==0){ cmd_return(rest); return; } + + if (strcmp(cmd,"HELP")==0) { cmd_help(rest); return; } + + if (strcmp(cmd,"EXIT")==0 || strcmp(cmd,"QUIT")==0) { exit(0); } + + if (strcmp(cmd,"DEBUG")==0) { + to_upper(rest); + runtime.debug_mode = (strcmp(rest,"ON")==0) ? 1 : 0; + printf("Debug mode: %s\n", runtime.debug_mode ? "ON" : "OFF"); + return; + } + + /* Sprite commands */ + if (strcmp(cmd,"CREATE")==0) { + char sub[MAX_LINE_LENGTH]; sscanf(rest, "%s", sub); to_upper(sub); + if (strcmp(sub,"SPRITE")==0) { cmd_create_sprite(rest + strlen(sub) + 1); return; } + } + if (strcmp(cmd,"MOVE")==0) { + char sub[MAX_LINE_LENGTH]; sscanf(rest, "%s", sub); to_upper(sub); + if (strcmp(sub,"SPRITE")==0) { cmd_move_sprite(rest + strlen(sub) + 1); return; } + } + if (strcmp(cmd,"SHOW")==0) { + char sub[MAX_LINE_LENGTH]; sscanf(rest, "%s", sub); to_upper(sub); + if (strcmp(sub,"SPRITE")==0) { cmd_show_sprite(rest + strlen(sub) + 1); return; } + } + if (strcmp(cmd,"HIDE")==0) { + char sub[MAX_LINE_LENGTH]; sscanf(rest, "%s", sub); to_upper(sub); + if (strcmp(sub,"SPRITE")==0) { cmd_hide_sprite(rest + strlen(sub) + 1); return; } + } + + /* Unknown command */ + char msg[MAX_LINE_LENGTH]; + snprintf(msg, sizeof(msg), "Unknown command: %s", cmd); + error_message(msg, ""); + suggest_command(cmd); +} + +/* ============================================================================ + * MULTI-LINE EXECUTOR + * ============================================================================ */ + +void execute_block(char code[][MAX_LINE_LENGTH], int line_count, int start, int end) { + if (start < 0) start = 0; + if (end > line_count) end = line_count; + + for (int i = start; i < end; i++) { + runtime.line_number = i + 1; + + char line_copy[MAX_LINE_LENGTH]; + strncpy(line_copy, code[i], MAX_LINE_LENGTH-1); + line_copy[MAX_LINE_LENGTH-1] = '\0'; + + char *l = trim(line_copy); + if (strlen(l)==0 || l[0]=='#') continue; + + char cmd[MAX_LINE_LENGTH]; sscanf(l, "%s", cmd); to_upper(cmd); + char *rest = trim(l + strlen(cmd)); + + if (strcmp(cmd,"TEACH")==0) { + /* Skip function definition blocks during normal execution */ + int end = i + 1; + while (end < line_count) { + char w[MAX_LINE_LENGTH]; w[0] = '\0'; + sscanf(trim(code[end]), "%s", w); + to_upper(w); + if (strcmp(w, "LEARNED") == 0) break; + end++; + } + if (end >= line_count) { + error_message("TEACH without LEARNED", "Add LEARNED at the end of your TEACH block"); + return; + } + i = end; + continue; + } + + if (strcmp(cmd,"IF")==0) { + /* Find the THEN keyword if present, strip it */ + char cond[MAX_LINE_LENGTH]; strncpy(cond, rest, MAX_LINE_LENGTH-1); + cond[MAX_LINE_LENGTH-1] = '\0'; + char *then = strstr(cond," THEN"); if (then) *then = '\0'; + to_upper(cond); + cmd_if(cond, code, line_count, &i); + continue; + } + if (strcmp(cmd,"ELSE")==0 || strcmp(cmd,"ENDIF")==0 || + strcmp(cmd,"UNTIL")==0 || strcmp(cmd,"DONE")==0 || strcmp(cmd,"LEARNED")==0) continue; + + if (strcmp(cmd,"REPEAT")==0) { cmd_repeat(rest, code, line_count, &i); continue; } + + if (strcmp(cmd,"DO")==0) { + /* DO n TIMES */ + char count_s[MAX_LINE_LENGTH], times_kw[MAX_LINE_LENGTH]; + count_s[0] = '\0'; times_kw[0] = '\0'; + sscanf(rest, "%s %s", count_s, times_kw); + cmd_do_times(count_s, code, line_count, &i); + continue; + } + + execute_line(code[i]); + if (runtime.should_return) return; + } +} + +void execute_code(char code[][MAX_LINE_LENGTH], int line_count) { + /* First, learn any TEACH blocks in this code buffer */ + parse_functions(code, line_count); + execute_block(code, line_count, 0, line_count); +} + +/* ============================================================================ + * MAIN ENTRY POINTS + * ============================================================================ */ + +void run_interactive(void) { + print_colored(COLOR_GREEN, "\n=== Welcome to CLARO! ===\n\n"); + printf("A programming language designed for everyone.\n"); + printf("Type HELP for a list of commands. Type EXIT to quit.\n"); + printf("Tip: IF/DO/REPEAT can be typed as multi-line blocks here.\n\n"); + + char input[MAX_LINE_LENGTH]; + + /* REPL block buffer */ + char block[MAX_CODE_LINES][MAX_LINE_LENGTH]; + int block_count = 0; + int stack_depth = 0; + + while (1) { + if (stack_depth == 0) { + print_colored(COLOR_MAGENTA, "claro> "); + } else { + print_colored(COLOR_MAGENTA, "....> "); + } + + if (!fgets(input, sizeof(input), stdin)) break; + input[strcspn(input,"\n")] = 0; + + char tmp[MAX_LINE_LENGTH]; + strncpy(tmp, input, MAX_LINE_LENGTH-1); + tmp[MAX_LINE_LENGTH-1] = '\0'; + char *t = trim(tmp); + if (strlen(t) == 0) continue; + + /* If we're not currently in a block, decide whether to start one */ + if (stack_depth == 0) { + char first[MAX_LINE_LENGTH]; + sscanf(t, "%s", first); to_upper(first); + + if (strcmp(first,"IF")==0 || strcmp(first,"DO")==0 || strcmp(first,"REPEAT")==0 || strcmp(first,"TEACH")==0) { + block_count = 0; + stack_depth = 0; + } else { + runtime.line_number = 0; + execute_line(input); + continue; + } + } + + /* Add line to block buffer */ + if (block_count >= MAX_CODE_LINES) { + error_message("Block too long", "Tip: keep blocks shorter, or run from a file"); + block_count = 0; + stack_depth = 0; + continue; + } + strncpy(block[block_count], input, MAX_LINE_LENGTH-1); + block[block_count][MAX_LINE_LENGTH-1] = '\0'; + block_count++; + + /* Update block nesting stack by keywords */ + char kw[MAX_LINE_LENGTH]; + kw[0] = '\0'; + sscanf(t, "%s", kw); to_upper(kw); + + if (strcmp(kw,"IF")==0) stack_depth++; + if (strcmp(kw,"DO")==0) stack_depth++; + if (strcmp(kw,"REPEAT")==0) stack_depth++; + if (strcmp(kw,"TEACH")==0) stack_depth++; + + if (strcmp(kw,"ENDIF")==0) stack_depth--; + if (strcmp(kw,"DONE")==0) stack_depth--; + if (strcmp(kw,"UNTIL")==0) stack_depth--; + if (strcmp(kw,"LEARNED")==0) stack_depth--; + + if (stack_depth < 0) { + error_message("Unexpected block ending", "Tip: ENDIF closes IF, DONE closes DO, UNTIL closes REPEAT, LEARNED closes TEACH"); + block_count = 0; + stack_depth = 0; + continue; + } + + /* If stack is empty, execute the whole collected block */ + if (stack_depth == 0) { + runtime.line_number = 0; + execute_code(block, block_count); + block_count = 0; + } + } +} + +void run_file(const char *filename) { + FILE *file = fopen(filename, "r"); + if (!file) { + print_colored(COLOR_RED, "Error: "); + printf("Could not open file '%s'\n", filename); + return; + } + char code[MAX_CODE_LINES][MAX_LINE_LENGTH]; + int count = 0; + while (fgets(code[count], MAX_LINE_LENGTH, file) && count < MAX_CODE_LINES) { + code[count][strcspn(code[count],"\n")] = 0; + count++; + } + fclose(file); + execute_code(code, count); +} + +int main(int argc, char *argv[]) { + memset(&runtime, 0, sizeof(Runtime)); + srand((unsigned)time(NULL)); + + if (argc > 1) { + if (strcmp(argv[1],"-h")==0 || strcmp(argv[1],"--high-contrast")==0) { + runtime.high_contrast = 1; + if (argc > 2) run_file(argv[2]); + else run_interactive(); + } else { + run_file(argv[1]); + } + } else { + run_interactive(); + } + return 0; +}