/* * 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 #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; }