#include "parser.h" #include "value.h" #include "vm.h" #include #include #include #include #include /* ---- parser error state ---- */ static int g_has_error = 0; static size_t g_err_pos = 0; static char g_err_msg[256]; static int g_err_line = 0; static int g_err_col = 0; /* ---- compiler-generated temporary counter ---- */ static int g_temp_counter = 0; static void parser_fail(size_t pos, const char *fmt, ...) { g_has_error = 1; g_err_pos = pos; va_list ap; va_start(ap, fmt); vsnprintf(g_err_msg, sizeof(g_err_msg), fmt, ap); va_end(ap); } static void calc_line_col(const char *src, size_t len, size_t pos, int *out_line, int *out_col) { int line = 1, col = 1; size_t limit = pos < len ? pos : len; for (size_t i = 0; i < limit; ++i) { if (src[i] == '\n') { line++; col = 1; } else { col++; } } if (out_line) *out_line = line; if (out_col) *out_col = col; } /* --------------------------- */ static char *read_file_all(const char *path, size_t *out_len) { FILE *f = fopen(path, "rb"); if (!f) return NULL; if (fseek(f, 0, SEEK_END) != 0) { fclose(f); return NULL; } long sz = ftell(f); if (sz < 0) { fclose(f); return NULL; } rewind(f); char *buf = (char*)malloc((size_t)sz + 1); if (!buf) { fclose(f); return NULL; } size_t n = fread(buf, 1, (size_t)sz, f); fclose(f); buf[n] = '\0'; if (out_len) *out_len = n; return buf; } static void skip_ws(const char *src, size_t len, size_t *pos) { while (*pos < len) { char c = src[*pos]; if (c == ' ' || c == '\t' || c == '\r' || c == '\n') { (*pos)++; continue; } break; } } static void skip_line(const char *src, size_t len, size_t *pos) { while (*pos < len && src[*pos] != '\n') (*pos)++; if (*pos < len && src[*pos] == '\n') (*pos)++; } static void skip_comments(const char *src, size_t len, size_t *pos) { for (;;) { skip_ws(src, len, pos); if (*pos + 1 < len && src[*pos] == '/' && src[*pos + 1] == '/') { *pos += 2; skip_line(src, len, pos); continue; } if (*pos + 1 < len && src[*pos] == '/' && src[*pos + 1] == '*') { *pos += 2; while (*pos + 1 < len && !(src[*pos] == '*' && src[*pos + 1] == '/')) (*pos)++; if (*pos + 1 < len) *pos += 2; continue; } break; } } static int starts_with(const char *src, size_t len, size_t pos, const char *kw) { size_t klen = strlen(kw); if (pos + klen > len) return 0; return strncmp(src + pos, kw, klen) == 0; } static void skip_shebang_if_present(const char *src, size_t len, size_t *pos) { if (*pos == 0 && starts_with(src, len, *pos, "#!")) { skip_line(src, len, pos); } } static void skip_identifier(const char *src, size_t len, size_t *pos) { size_t p = *pos; if (p < len && (isalpha((unsigned char)src[p]) || src[p] == '_')) { p++; while (p < len && (isalnum((unsigned char)src[p]) || src[p] == '_')) p++; } *pos = p; } static int consume_char(const char *src, size_t len, size_t *pos, char expected) { skip_ws(src, len, pos); if (*pos < len && src[*pos] == expected) { (*pos)++; return 1; } return 0; } static char *parse_string_literal_any_quote(const char *src, size_t len, size_t *pos) { skip_ws(src, len, pos); if (*pos >= len) return NULL; char quote = src[*pos]; if (quote != '"' && quote != '\'') return NULL; (*pos)++; // skip opening quote size_t cap = 64, out_len = 0; char *out = (char*)malloc(cap); if (!out) return NULL; while (*pos < len) { char c = src[*pos]; if (c == quote) { (*pos)++; break; } if (c == '\\') { (*pos)++; if (*pos >= len) break; char e = src[*pos]; switch (e) { case 'n': c = '\n'; break; case 'r': c = '\r'; break; case 't': c = '\t'; break; case '\\': c = '\\'; break; case '"': c = '"'; break; case '\'': c = '\''; break; default: c = e; break; } } if (out_len + 1 >= cap) { cap *= 2; char *tmp = (char*)realloc(out, cap); if (!tmp) { free(out); return NULL; } out = tmp; } out[out_len++] = c; (*pos)++; } if (out_len + 1 >= cap) { char *tmp = (char*)realloc(out, cap + 1); if (!tmp) { free(out); return NULL; } out = tmp; } out[out_len] = '\0'; return out; } /* === Helpers for identifiers, numbers, booleans, and globals === */ static void skip_spaces(const char *src, size_t len, size_t *pos) { while (*pos < len) { char c = src[*pos]; if (c == ' ' || c == '\t' || c == '\r') { (*pos)++; continue; } break; } } static int read_identifier_into(const char *src, size_t len, size_t *pos, char **out_name) { size_t p = *pos; if (p < len && (isalpha((unsigned char)src[p]) || src[p] == '_')) { size_t start = p; p++; while (p < len && (isalnum((unsigned char)src[p]) || src[p] == '_')) p++; size_t n = p - start; char *name = (char*)malloc(n + 1); if (!name) return 0; memcpy(name, src + start, n); name[n] = '\0'; *pos = p; *out_name = name; return 1; } return 0; } static int64_t parse_int_literal_value(const char *src, size_t len, size_t *pos, int *ok) { size_t p = *pos; skip_spaces(src, len, &p); int sign = 1; if (p < len && (src[p] == '+' || src[p] == '-')) { if (src[p] == '-') sign = -1; p++; } if (p >= len || !isdigit((unsigned char)src[p])) { *ok = 0; return 0; } int64_t val = 0; while (p < len && isdigit((unsigned char)src[p])) { val = val * 10 + (src[p] - '0'); p++; } *pos = p; *ok = 1; return sign * val; } /* very small global symbol table for LOAD_GLOBAL/STORE_GLOBAL */ static struct { char *names[VM_MAX_GLOBALS]; int count; } G = { {0}, 0 }; static int sym_index(const char *name) { for (int i = 0; i < G.count; ++i) { if (strcmp(G.names[i], name) == 0) return i; } if (G.count >= VM_MAX_GLOBALS) { parser_fail(0, "Too many globals (max %d)", VM_MAX_GLOBALS); return 0; } G.names[G.count] = strdup(name); return G.count++; } /* ---- locals environment for functions ---- */ typedef struct { char *names[FRAME_MAX_LOCALS]; int count; } LocalEnv; static LocalEnv *g_locals = NULL; /* loop context for break/continue patching */ typedef struct LoopCtx { int break_jumps[64]; int break_count; int continue_jumps[64]; int cont_count; struct LoopCtx *prev; } LoopCtx; static LoopCtx *g_loop_ctx = NULL; static int local_find(const char *name) { if (!g_locals) return -1; for (int i = 0; i < g_locals->count; ++i) { if (strcmp(g_locals->names[i], name) == 0) return i; } return -1; } static int local_add(const char *name) { if (!g_locals) return -1; if (g_locals->count >= FRAME_MAX_LOCALS) { parser_fail(0, "Too many local variables/parameters (max %d)", FRAME_MAX_LOCALS); return -1; } int idx = g_locals->count++; g_locals->names[idx] = strdup(name); return idx; } /* forward declaration so helpers can recurse */ static int emit_expression(Bytecode *bc, const char *src, size_t len, size_t *pos); /* primary: (expr) | string | number | true/false | identifier */ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos) { skip_spaces(src, len, pos); /* parenthesized */ if (*pos < len && src[*pos] == '(') { (*pos)++; /* '(' */ if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "Expected expression after '('"); return 0; } if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')'"); return 0; } /* postfix indexing or slice */ for (;;) { skip_spaces(src, len, pos); if (*pos < len && src[*pos] == '[') { (*pos)++; if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "Expected start expression"); return 0; } skip_spaces(src, len, pos); if (*pos < len && src[*pos] == ':') { (*pos)++; skip_spaces(src, len, pos); size_t savep4 = *pos; if (!emit_expression(bc, src, len, pos)) { *pos = savep4; int ci4 = bytecode_add_constant(bc, make_int(-1)); bytecode_add_instruction(bc, OP_LOAD_CONST, ci4); } if (!consume_char(src, len, pos, ']')) { parser_fail(*pos, "Expected ']' after slice"); return 0; } bytecode_add_instruction(bc, OP_SLICE, 0); continue; } else { if (!consume_char(src, len, pos, ']')) { parser_fail(*pos, "Expected ']' after index"); return 0; } bytecode_add_instruction(bc, OP_INDEX_GET, 0); continue; } } break; } return 1; } /* string */ char *s = parse_string_literal_any_quote(src, len, pos); if (s) { int ci = bytecode_add_constant(bc, make_string(s)); free(s); bytecode_add_instruction(bc, OP_LOAD_CONST, ci); /* postfix indexing or slice */ for (;;) { skip_spaces(src, len, pos); if (*pos < len && src[*pos] == '[') { (*pos)++; if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "Expected start expression"); return 0; } skip_spaces(src, len, pos); if (*pos < len && src[*pos] == ':') { (*pos)++; skip_spaces(src, len, pos); /* end is optional; if missing, use -1 (till end) */ int has_end = 0; size_t savep = *pos; if (emit_expression(bc, src, len, pos)) { has_end = 1; } else { *pos = savep; int ci = bytecode_add_constant(bc, make_int(-1)); bytecode_add_instruction(bc, OP_LOAD_CONST, ci); } if (!consume_char(src, len, pos, ']')) { parser_fail(*pos, "Expected ']' after slice"); return 0; } bytecode_add_instruction(bc, OP_SLICE, 0); continue; } else { if (!consume_char(src, len, pos, ']')) { parser_fail(*pos, "Expected ']' after index"); return 0; } bytecode_add_instruction(bc, OP_INDEX_GET, 0); continue; } } break; } return 1; } /* array literal: [expr, expr, ...] */ skip_spaces(src, len, pos); if (*pos < len && src[*pos] == '[') { (*pos)++; /* '[' */ int count = 0; skip_spaces(src, len, pos); if (*pos < len && src[*pos] != ']') { for (;;) { if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "Expected expression in array literal"); return 0; } count++; skip_spaces(src, len, pos); if (*pos < len && src[*pos] == ',') { (*pos)++; skip_spaces(src, len, pos); continue; } break; } } if (!consume_char(src, len, pos, ']')) { parser_fail(*pos, "Expected ']' to close array literal"); return 0; } bytecode_add_instruction(bc, OP_MAKE_ARRAY, count); /* postfix indexing or slice */ for (;;) { skip_spaces(src, len, pos); if (*pos < len && src[*pos] == '[') { (*pos)++; if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "Expected start expression"); return 0; } skip_spaces(src, len, pos); if (*pos < len && src[*pos] == ':') { (*pos)++; skip_spaces(src, len, pos); size_t savep3 = *pos; if (!emit_expression(bc, src, len, pos)) { *pos = savep3; int ci3 = bytecode_add_constant(bc, make_int(-1)); bytecode_add_instruction(bc, OP_LOAD_CONST, ci3); } if (!consume_char(src, len, pos, ']')) { parser_fail(*pos, "Expected ']' after slice"); return 0; } bytecode_add_instruction(bc, OP_SLICE, 0); continue; } else { if (!consume_char(src, len, pos, ']')) { parser_fail(*pos, "Expected ']' after index"); return 0; } bytecode_add_instruction(bc, OP_INDEX_GET, 0); continue; } } break; } return 1; } /* number */ int ok = 0; size_t save = *pos; int64_t ival = parse_int_literal_value(src, len, pos, &ok); if (ok) { int ci = bytecode_add_constant(bc, make_int(ival)); bytecode_add_instruction(bc, OP_LOAD_CONST, ci); /* postfix indexing or slice */ for (;;) { skip_spaces(src, len, pos); if (*pos < len && src[*pos] == '[') { (*pos)++; if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "Expected start expression"); return 0; } skip_spaces(src, len, pos); if (*pos < len && src[*pos] == ':') { (*pos)++; skip_spaces(src, len, pos); int has_end = 0; size_t savep2 = *pos; if (emit_expression(bc, src, len, pos)) { has_end = 1; } else { *pos = savep2; int ci2 = bytecode_add_constant(bc, make_int(-1)); bytecode_add_instruction(bc, OP_LOAD_CONST, ci2); } if (!consume_char(src, len, pos, ']')) { parser_fail(*pos, "Expected ']' after slice"); return 0; } bytecode_add_instruction(bc, OP_SLICE, 0); continue; } else { if (!consume_char(src, len, pos, ']')) { parser_fail(*pos, "Expected ']' after index"); return 0; } bytecode_add_instruction(bc, OP_INDEX_GET, 0); continue; } } break; } return 1; } *pos = save; /* identifier or keyword */ char *name = NULL; if (read_identifier_into(src, len, pos, &name)) { if (strcmp(name, "true") == 0 || strcmp(name, "false") == 0) { int ci = bytecode_add_constant(bc, make_int(strcmp(name, "true") == 0 ? 1 : 0)); free(name); bytecode_add_instruction(bc, OP_LOAD_CONST, ci); return 1; } /* call or variable load (locals preferred) */ skip_spaces(src, len, pos); int local_idx = local_find(name); int is_call = (*pos < len && src[*pos] == '('); if (is_call) { /* builtins */ if (strcmp(name, "len") == 0) { (*pos)++; /* '(' */ if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "len expects 1 argument"); free(name); return 0; } if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after len arg"); free(name); return 0; } bytecode_add_instruction(bc, OP_LEN, 0); free(name); return 1; } if (strcmp(name, "push") == 0) { (*pos)++; /* '(' */ if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "push expects array"); free(name); return 0; } if (*pos < len && src[*pos] == ',') { (*pos)++; skip_spaces(src, len, pos); } else { parser_fail(*pos, "push expects 2 args"); free(name); return 0; } if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "push expects value"); free(name); return 0; } if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after push args"); free(name); return 0; } bytecode_add_instruction(bc, OP_ARR_PUSH, 0); free(name); return 1; } if (strcmp(name, "pop") == 0) { (*pos)++; /* '(' */ if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "pop expects array"); free(name); return 0; } if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after pop arg"); free(name); return 0; } bytecode_add_instruction(bc, OP_ARR_POP, 0); free(name); return 1; } if (strcmp(name, "set") == 0) { (*pos)++; /* '(' */ if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "set expects array"); free(name); return 0; } if (*pos < len && src[*pos] == ',') { (*pos)++; skip_spaces(src, len, pos); } else { parser_fail(*pos, "set expects 3 args"); free(name); return 0; } if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "set expects index"); free(name); return 0; } if (*pos < len && src[*pos] == ',') { (*pos)++; skip_spaces(src, len, pos); } else { parser_fail(*pos, "set expects 3 args"); free(name); return 0; } if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "set expects value"); free(name); return 0; } if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after set args"); free(name); return 0; } bytecode_add_instruction(bc, OP_ARR_SET, 0); free(name); return 1; } if (strcmp(name, "insert") == 0) { (*pos)++; /* '(' */ if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "insert expects array"); free(name); return 0; } if (*pos < len && src[*pos] == ',') { (*pos)++; skip_spaces(src, len, pos); } else { parser_fail(*pos, "insert expects 3 args"); free(name); return 0; } if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "insert expects index"); free(name); return 0; } if (*pos < len && src[*pos] == ',') { (*pos)++; skip_spaces(src, len, pos); } else { parser_fail(*pos, "insert expects 3 args"); free(name); return 0; } if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "insert expects value"); free(name); return 0; } if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after insert args"); free(name); return 0; } bytecode_add_instruction(bc, OP_ARR_INSERT, 0); free(name); return 1; } if (strcmp(name, "remove") == 0) { (*pos)++; /* '(' */ if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "remove expects array"); free(name); return 0; } if (*pos < len && src[*pos] == ',') { (*pos)++; skip_spaces(src, len, pos); } else { parser_fail(*pos, "remove expects 2 args"); free(name); return 0; } if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "remove expects index"); free(name); return 0; } if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after remove args"); free(name); return 0; } bytecode_add_instruction(bc, OP_ARR_REMOVE, 0); free(name); return 1; } /* push function value first */ if (local_idx >= 0) { bytecode_add_instruction(bc, OP_LOAD_LOCAL, local_idx); } else { int gi = sym_index(name); bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi); } /* parse arguments */ (*pos)++; /* '(' */ int argc = 0; skip_spaces(src, len, pos); if (*pos < len && src[*pos] != ')') { do { if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "Expected expression as function argument"); free(name); return 0; } argc++; skip_spaces(src, len, pos); } while (*pos < len && src[*pos] == ',' && (++(*pos), skip_spaces(src, len, pos), 1)); } if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after arguments"); free(name); return 0; } #ifdef FUN_DEBUG /* DEBUG: show compiled call */ printf("compile: CALL %s with %d arg(s)\n", name, argc); #endif bytecode_add_instruction(bc, OP_CALL, argc); /* postfix indexing or slice */ for (;;) { skip_spaces(src, len, pos); if (*pos < len && src[*pos] == '[') { (*pos)++; if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "Expected start expression"); free(name); return 0; } skip_spaces(src, len, pos); if (*pos < len && src[*pos] == ':') { (*pos)++; skip_spaces(src, len, pos); size_t svp = *pos; if (!emit_expression(bc, src, len, pos)) { *pos = svp; int ci = bytecode_add_constant(bc, make_int(-1)); bytecode_add_instruction(bc, OP_LOAD_CONST, ci); } if (!consume_char(src, len, pos, ']')) { parser_fail(*pos, "Expected ']' after slice"); free(name); return 0; } bytecode_add_instruction(bc, OP_SLICE, 0); continue; } else { if (!consume_char(src, len, pos, ']')) { parser_fail(*pos, "Expected ']' after index"); free(name); return 0; } bytecode_add_instruction(bc, OP_INDEX_GET, 0); continue; } } break; } free(name); return 1; } else { if (local_idx >= 0) { bytecode_add_instruction(bc, OP_LOAD_LOCAL, local_idx); } else { int gi = sym_index(name); bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi); } /* postfix indexing or slice */ for (;;) { skip_spaces(src, len, pos); if (*pos < len && src[*pos] == '[') { (*pos)++; if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "Expected start expression"); free(name); return 0; } skip_spaces(src, len, pos); if (*pos < len && src[*pos] == ':') { (*pos)++; skip_spaces(src, len, pos); size_t svp = *pos; if (!emit_expression(bc, src, len, pos)) { *pos = svp; int ci = bytecode_add_constant(bc, make_int(-1)); bytecode_add_instruction(bc, OP_LOAD_CONST, ci); } if (!consume_char(src, len, pos, ']')) { parser_fail(*pos, "Expected ']' after slice"); free(name); return 0; } bytecode_add_instruction(bc, OP_SLICE, 0); continue; } else { if (!consume_char(src, len, pos, ']')) { parser_fail(*pos, "Expected ']' after index"); free(name); return 0; } bytecode_add_instruction(bc, OP_INDEX_GET, 0); continue; } } break; } free(name); return 1; } } return 0; } /* unary: '!' unary | '-' unary | primary */ static int emit_unary(Bytecode *bc, const char *src, size_t len, size_t *pos) { skip_spaces(src, len, pos); if (*pos < len && src[*pos] == '!') { (*pos)++; if (!emit_unary(bc, src, len, pos)) { parser_fail(*pos, "Expected expression after '!'"); return 0; } bytecode_add_instruction(bc, OP_NOT, 0); return 1; } if (*pos < len && src[*pos] == '-') { (*pos)++; /* unary minus -> 0 - expr */ int ci = bytecode_add_constant(bc, make_int(0)); bytecode_add_instruction(bc, OP_LOAD_CONST, ci); if (!emit_unary(bc, src, len, pos)) { parser_fail(*pos, "Expected expression after unary '-'"); return 0; } bytecode_add_instruction(bc, OP_SUB, 0); return 1; } return emit_primary(bc, src, len, pos); } /* multiplicative: unary (('*' | '/' | '%') unary)* */ static int emit_multiplicative(Bytecode *bc, const char *src, size_t len, size_t *pos) { if (!emit_unary(bc, src, len, pos)) return 0; for (;;) { skip_spaces(src, len, pos); /* Stop expression at start of inline comment */ if (*pos + 1 < len && src[*pos] == '/' && src[*pos + 1] == '/') { break; } /* Skip block comments inside expressions */ if (*pos + 1 < len && src[*pos] == '/' && src[*pos + 1] == '*') { size_t p = *pos + 2; while (p + 1 < len && !(src[p] == '*' && src[p + 1] == '/')) { p++; } if (p + 1 < len) p += 2; /* consume closing marker */ *pos = p; continue; } if (*pos < len && src[*pos] == '*') { (*pos)++; if (!emit_unary(bc, src, len, pos)) { parser_fail(*pos, "Expected expression after '*'"); return 0; } bytecode_add_instruction(bc, OP_MUL, 0); continue; } if (*pos < len && src[*pos] == '/') { (*pos)++; if (!emit_unary(bc, src, len, pos)) { parser_fail(*pos, "Expected expression after '/'"); return 0; } bytecode_add_instruction(bc, OP_DIV, 0); continue; } if (*pos < len && src[*pos] == '%') { (*pos)++; if (!emit_unary(bc, src, len, pos)) { parser_fail(*pos, "Expected expression after '%'"); return 0; } bytecode_add_instruction(bc, OP_MOD, 0); continue; } break; } return 1; } /* additive: multiplicative (('+' | '-') multiplicative)* */ static int emit_additive(Bytecode *bc, const char *src, size_t len, size_t *pos) { if (!emit_multiplicative(bc, src, len, pos)) return 0; for (;;) { skip_spaces(src, len, pos); if (*pos < len && src[*pos] == '+') { (*pos)++; if (!emit_multiplicative(bc, src, len, pos)) { parser_fail(*pos, "Expected expression after '+'"); return 0; } bytecode_add_instruction(bc, OP_ADD, 0); continue; } if (*pos < len && src[*pos] == '-') { (*pos)++; if (!emit_multiplicative(bc, src, len, pos)) { parser_fail(*pos, "Expected expression after '-'"); return 0; } bytecode_add_instruction(bc, OP_SUB, 0); continue; } break; } return 1; } /* relational: additive (('<' | '<=' | '>' | '>=') additive)* */ static int emit_relational(Bytecode *bc, const char *src, size_t len, size_t *pos) { if (!emit_additive(bc, src, len, pos)) return 0; for (;;) { skip_spaces(src, len, pos); if (*pos + 1 < len && src[*pos] == '<' && src[*pos + 1] == '=') { *pos += 2; if (!emit_additive(bc, src, len, pos)) { parser_fail(*pos, "Expected expression after '<='"); return 0; } bytecode_add_instruction(bc, OP_LTE, 0); continue; } if (*pos + 1 < len && src[*pos] == '>' && src[*pos + 1] == '=') { *pos += 2; if (!emit_additive(bc, src, len, pos)) { parser_fail(*pos, "Expected expression after '>='"); return 0; } bytecode_add_instruction(bc, OP_GTE, 0); continue; } if (*pos < len && src[*pos] == '<') { (*pos)++; if (!emit_additive(bc, src, len, pos)) { parser_fail(*pos, "Expected expression after '<'"); return 0; } bytecode_add_instruction(bc, OP_LT, 0); continue; } if (*pos < len && src[*pos] == '>') { (*pos)++; if (!emit_additive(bc, src, len, pos)) { parser_fail(*pos, "Expected expression after '>'"); return 0; } bytecode_add_instruction(bc, OP_GT, 0); continue; } break; } return 1; } /* equality: relational (('==' | '!=') relational)* */ static int emit_equality(Bytecode *bc, const char *src, size_t len, size_t *pos) { if (!emit_relational(bc, src, len, pos)) return 0; for (;;) { skip_spaces(src, len, pos); if (*pos + 1 < len && src[*pos] == '=' && src[*pos + 1] == '=') { *pos += 2; if (!emit_relational(bc, src, len, pos)) { parser_fail(*pos, "Expected expression after '=='"); return 0; } bytecode_add_instruction(bc, OP_EQ, 0); continue; } if (*pos + 1 < len && src[*pos] == '!' && src[*pos + 1] == '=') { *pos += 2; if (!emit_relational(bc, src, len, pos)) { parser_fail(*pos, "Expected expression after '!='"); return 0; } bytecode_add_instruction(bc, OP_NEQ, 0); continue; } break; } return 1; } /* logical AND with short-circuit: equality ( '&&' equality )* */ static int emit_and_expr(Bytecode *bc, const char *src, size_t len, size_t *pos) { int jf_idxs[64]; int jf_count = 0; int has_and = 0; /* first operand */ if (!emit_equality(bc, src, len, pos)) return 0; for (;;) { skip_spaces(src, len, pos); if (!(*pos + 1 < len && src[*pos] == '&' && src[*pos + 1] == '&')) break; *pos += 2; has_and = 1; /* if current value is false -> jump to false label (patched later) */ if (jf_count < (int)(sizeof(jf_idxs) / sizeof(jf_idxs[0]))) { jf_idxs[jf_count++] = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); } else { parser_fail(*pos, "Too many operands in '&&' chain"); return 0; } /* evaluate next operand */ if (!emit_equality(bc, src, len, pos)) { parser_fail(*pos, "Expected expression after '&&'"); return 0; } } if (has_and) { /* final: if last operand is false -> jump false */ if (jf_count < (int)(sizeof(jf_idxs) / sizeof(jf_idxs[0]))) { jf_idxs[jf_count++] = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); } else { parser_fail(*pos, "Too many operands in '&&' chain"); return 0; } /* all were truthy -> result true */ int c1 = bytecode_add_constant(bc, make_int(1)); bytecode_add_instruction(bc, OP_LOAD_CONST, c1); int j_end = bytecode_add_instruction(bc, OP_JUMP, 0); /* false label: patch all false jumps here, result false */ int l_false = bc->instr_count; for (int i = 0; i < jf_count; ++i) { bytecode_set_operand(bc, jf_idxs[i], l_false); } int c0 = bytecode_add_constant(bc, make_int(0)); bytecode_add_instruction(bc, OP_LOAD_CONST, c0); /* end */ int l_end = bc->instr_count; bytecode_set_operand(bc, j_end, l_end); } return 1; } /* logical OR with short-circuit: and_expr ( '||' and_expr )* */ static int emit_or_expr(Bytecode *bc, const char *src, size_t len, size_t *pos) { int true_jumps[64]; int tj_count = 0; int has_or = 0; /* first operand */ if (!emit_and_expr(bc, src, len, pos)) return 0; for (;;) { skip_spaces(src, len, pos); if (!(*pos + 1 < len && src[*pos] == '|' && src[*pos + 1] == '|')) break; *pos += 2; has_or = 1; /* if current value is false -> proceed to next; else -> result true */ int jf_proceed = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); /* true path: push 1 and jump to end */ int c1 = bytecode_add_constant(bc, make_int(1)); bytecode_add_instruction(bc, OP_LOAD_CONST, c1); if (tj_count < (int)(sizeof(true_jumps) / sizeof(true_jumps[0]))) { true_jumps[tj_count++] = bytecode_add_instruction(bc, OP_JUMP, 0); } else { parser_fail(*pos, "Too many operands in '||' chain"); return 0; } /* patch to start of next operand */ bytecode_set_operand(bc, jf_proceed, bc->instr_count); /* evaluate next operand */ if (!emit_and_expr(bc, src, len, pos)) { parser_fail(*pos, "Expected expression after '||'"); return 0; } } if (has_or) { /* After evaluating the last operand: test it and produce 1/0 */ int jf_last = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); int c1 = bytecode_add_constant(bc, make_int(1)); bytecode_add_instruction(bc, OP_LOAD_CONST, c1); int j_end_single = bytecode_add_instruction(bc, OP_JUMP, 0); int l_false = bc->instr_count; bytecode_set_operand(bc, jf_last, l_false); int c0 = bytecode_add_constant(bc, make_int(0)); bytecode_add_instruction(bc, OP_LOAD_CONST, c0); int l_end = bc->instr_count; bytecode_set_operand(bc, j_end_single, l_end); for (int i = 0; i < tj_count; ++i) { bytecode_set_operand(bc, true_jumps[i], l_end); } } return 1; } /* top-level expression */ static int emit_expression(Bytecode *bc, const char *src, size_t len, size_t *pos) { return emit_or_expr(bc, src, len, pos); } /* * Indentation-aware compiler (2 spaces): * - Optional shebang and a single fun () { ... } wrapper. * - Statements supported: print(expr), ident = expr, and if with an indented block. * - Literals: strings, integers, booleans; identifiers are globals. * - Ends with HALT. */ /* line/indent utilities */ static void skip_to_eol(const char *src, size_t len, size_t *pos) { while (*pos < len && src[*pos] != '\n') (*pos)++; if (*pos < len && src[*pos] == '\n') (*pos)++; } static int read_line_start(const char *src, size_t len, size_t *pos, int *out_indent) { while (*pos < len) { size_t p = *pos; int spaces = 0; while (p < len && src[p] == ' ') { spaces++; p++; } if (p < len && src[p] == '\t') { parser_fail(p, "Tabs are forbidden for indentation"); return 0; } if (p >= len) { *pos = p; return 0; } if (src[p] == '\n') { /* empty line */ p++; *pos = p; continue; } /* // comment-only line */ if (p + 1 < len && src[p] == '/' && src[p + 1] == '/') { /* skip entire line */ while (p < len && src[p] != '\n') p++; if (p < len && src[p] == '\n') p++; *pos = p; continue; } // block comment starting at line (treat as comment-only line) if (p + 1 < len && src[p] == '/' && src[p + 1] == '*') { p += 2; /* advance until we find closing block comment marker */ while (p + 1 < len && !(src[p] == '*' && src[p + 1] == '/')) { p++; } if (p + 1 < len) p += 2; /* consume closing block comment marker */ /* consume to end of current line (if any leftover) */ while (p < len && src[p] != '\n') p++; if (p < len && src[p] == '\n') p++; *pos = p; continue; } if (spaces % 2 != 0) { parser_fail(p, "Indentation must be multiples of two spaces"); return 0; } *out_indent = spaces / 2; *pos = p; /* point to first code char */ return 1; } return 0; } /* forward decl */ static void parse_block(Bytecode *bc, const char *src, size_t len, size_t *pos, int current_indent); /* parse and emit a single simple (non-if) statement on the current line */ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, size_t *pos) { size_t local_pos = *pos; char *name = NULL; if (read_identifier_into(src, len, &local_pos, &name)) { /* return statement */ if (strcmp(name, "return") == 0) { free(name); skip_spaces(src, len, &local_pos); /* optional expression */ size_t save_pos = local_pos; if (emit_expression(bc, src, len, &local_pos)) { /* expression result already on stack */ } else { /* no expression: return nil */ local_pos = save_pos; int ci = bytecode_add_constant(bc, make_nil()); bytecode_add_instruction(bc, OP_LOAD_CONST, ci); } bytecode_add_instruction(bc, OP_RETURN, 0); *pos = local_pos; skip_to_eol(src, len, pos); return; } /* break / continue */ if (strcmp(name, "break") == 0) { free(name); if (!g_loop_ctx) { parser_fail(local_pos, "break used outside of loop"); return; } int j = bytecode_add_instruction(bc, OP_JUMP, 0); if (g_loop_ctx->break_count < (int)(sizeof(g_loop_ctx->break_jumps) / sizeof(g_loop_ctx->break_jumps[0]))) { g_loop_ctx->break_jumps[g_loop_ctx->break_count++] = j; } else { parser_fail(local_pos, "Too many 'break' in one loop"); return; } *pos = local_pos; skip_to_eol(src, len, pos); return; } if (strcmp(name, "continue") == 0) { free(name); if (!g_loop_ctx) { parser_fail(local_pos, "continue used outside of loop"); return; } int j = bytecode_add_instruction(bc, OP_JUMP, 0); if (g_loop_ctx->cont_count < (int)(sizeof(g_loop_ctx->continue_jumps) / sizeof(g_loop_ctx->continue_jumps[0]))) { g_loop_ctx->continue_jumps[g_loop_ctx->cont_count++] = j; } else { parser_fail(local_pos, "Too many 'continue' in one loop"); return; } *pos = local_pos; skip_to_eol(src, len, pos); return; } /* typed declarations: number|string|boolean (= expr)? */ if (strcmp(name, "number") == 0 || strcmp(name, "string") == 0 || strcmp(name, "boolean") == 0) { int is_number = (strcmp(name, "number") == 0); int is_string = (strcmp(name, "string") == 0); int is_boolean = (strcmp(name, "boolean") == 0); free(name); /* read variable name */ char *varname = NULL; skip_spaces(src, len, &local_pos); if (!read_identifier_into(src, len, &local_pos, &varname)) { parser_fail(local_pos, "Expected identifier after type declaration"); return; } /* decide local vs global */ int lidx = -1; int gi = -1; if (g_locals) { int existing = local_find(varname); if (existing >= 0) lidx = existing; else lidx = local_add(varname); } else { gi = sym_index(varname); } free(varname); skip_spaces(src, len, &local_pos); if (local_pos < len && src[local_pos] == '=') { local_pos++; /* '=' */ if (!emit_expression(bc, src, len, &local_pos)) { parser_fail(local_pos, "Expected initializer expression after '='"); return; } if (lidx >= 0) { bytecode_add_instruction(bc, OP_STORE_LOCAL, lidx); } else { bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi); } } else { /* default initialize if no '=' given */ int ci = -1; if (is_number || is_boolean) { ci = bytecode_add_constant(bc, make_int(is_boolean ? 0 : 0)); } else if (is_string) { ci = bytecode_add_constant(bc, make_string("")); } if (ci >= 0) { bytecode_add_instruction(bc, OP_LOAD_CONST, ci); if (lidx >= 0) { bytecode_add_instruction(bc, OP_STORE_LOCAL, lidx); } else { bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi); } } } *pos = local_pos; skip_to_eol(src, len, pos); return; } if (strcmp(name, "print") == 0) { free(name); skip_spaces(src, len, &local_pos); (void)consume_char(src, len, &local_pos, '('); if (emit_expression(bc, src, len, &local_pos)) { (void)consume_char(src, len, &local_pos, ')'); bytecode_add_instruction(bc, OP_PRINT, 0); } else { (void)consume_char(src, len, &local_pos, ')'); } *pos = local_pos; skip_to_eol(src, len, pos); return; } /* assignment or simple call */ int lidx = local_find(name); int gi = (lidx < 0) ? sym_index(name) : -1; skip_spaces(src, len, &local_pos); /* array element assignment: name[expr] = expr */ if (local_pos < len && src[local_pos] == '[') { /* load array variable */ if (lidx >= 0) { bytecode_add_instruction(bc, OP_LOAD_LOCAL, lidx); } else { bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi); } local_pos++; /* '[' */ if (!emit_expression(bc, src, len, &local_pos)) { parser_fail(local_pos, "Expected index expression after '['"); free(name); return; } if (!consume_char(src, len, &local_pos, ']')) { parser_fail(local_pos, "Expected ']' after index"); free(name); return; } skip_spaces(src, len, &local_pos); if (local_pos >= len || src[local_pos] != '=') { parser_fail(local_pos, "Expected '=' after array index"); free(name); return; } local_pos++; /* '=' */ if (!emit_expression(bc, src, len, &local_pos)) { parser_fail(local_pos, "Expected expression after '='"); free(name); return; } /* perform set */ bytecode_add_instruction(bc, OP_INDEX_SET, 0); free(name); *pos = local_pos; skip_to_eol(src, len, pos); return; } free(name); if (local_pos < len && src[local_pos] == '=') { local_pos++; /* '=' */ if (emit_expression(bc, src, len, &local_pos)) { if (lidx >= 0) { bytecode_add_instruction(bc, OP_STORE_LOCAL, lidx); } else { bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi); } } *pos = local_pos; skip_to_eol(src, len, pos); return; } else if (local_pos < len && src[local_pos] == '(') { /* call as a statement: compile full call expression and drop result */ /* rewind to statement start and emit as expression */ local_pos = *pos; if (emit_expression(bc, src, len, &local_pos)) { bytecode_add_instruction(bc, OP_POP, 0); /* discard return value */ } *pos = local_pos; skip_to_eol(src, len, pos); return; } else { /* bare identifier line */ *pos = local_pos; skip_to_eol(src, len, pos); return; } } /* fallback: print(expr) without identifier read (unlikely) */ if (starts_with(src, len, *pos, "print")) { *pos += 5; skip_spaces(src, len, pos); (void)consume_char(src, len, pos, '('); if (emit_expression(bc, src, len, pos)) { (void)consume_char(src, len, pos, ')'); bytecode_add_instruction(bc, OP_PRINT, 0); } else { (void)consume_char(src, len, pos, ')'); } skip_to_eol(src, len, pos); return; } /* unknown token: report error */ parser_fail(*pos, "Unknown token at start of statement"); } /* parse a block with lines at indentation >= current_indent; stop at dedent */ static void parse_block(Bytecode *bc, const char *src, size_t len, size_t *pos, int current_indent) { while (*pos < len) { if (g_has_error) return; size_t line_start = *pos; int indent = 0; if (!read_line_start(src, len, pos, &indent)) { /* EOF or error */ return; } if (indent < current_indent) { /* dedent -> let caller handle this line */ *pos = line_start; return; } if (indent > current_indent) { /* nested block without a header (tolerate by parsing it and continuing) */ parse_block(bc, src, len, pos, indent); continue; } /* at same indent -> parse statement */ if (starts_with(src, len, *pos, "fun")) { /* parse header: fun name(arg, ...) */ *pos += 3; skip_spaces(src, len, pos); char *fname = NULL; if (!read_identifier_into(src, len, pos, &fname)) { parser_fail(*pos, "Expected function name after 'fun'"); return; } int fgi = sym_index(fname); skip_spaces(src, len, pos); if (!consume_char(src, len, pos, '(')) { parser_fail(*pos, "Expected '(' after function name"); free(fname); return; } /* build locals from parameters */ LocalEnv env = { {0}, 0 }; LocalEnv *prev = g_locals; g_locals = &env; skip_spaces(src, len, pos); if (*pos < len && src[*pos] != ')') { for (;;) { char *pname = NULL; if (!read_identifier_into(src, len, pos, &pname)) { parser_fail(*pos, "Expected parameter name"); g_locals = prev; free(fname); return; } if (local_find(pname) >= 0) { parser_fail(*pos, "Duplicate parameter name '%s'", pname); free(pname); g_locals = prev; free(fname); return; } local_add(pname); free(pname); skip_spaces(src, len, pos); if (*pos < len && src[*pos] == ',') { (*pos)++; skip_spaces(src, len, pos); continue; } break; } } if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after parameter list"); g_locals = prev; free(fname); return; } /* end header line */ skip_to_eol(src, len, pos); /* compile body into separate Bytecode */ Bytecode *fn_bc = bytecode_new(); /* parse body at increased indent if present */ int body_indent = 0; size_t look_body = *pos; if (read_line_start(src, len, &look_body, &body_indent) && body_indent > current_indent) { /* do not advance pos here; let parse_block consume the line */ parse_block(fn_bc, src, len, pos, body_indent); } else { /* empty body: ok */ } /* ensure function returns */ bytecode_add_instruction(fn_bc, OP_RETURN, 0); #ifdef FUN_DEBUG /* DEBUG: dump compiled function bytecode */ printf("=== compiled function %s (%d params) ===\n", fname, env.count); bytecode_dump(fn_bc); printf("=== end function %s ===\n", fname); #endif /* bind function to global: LOAD_CONST ; STORE_GLOBAL fgi */ int fci = bytecode_add_constant(bc, make_function(fn_bc)); bytecode_add_instruction(bc, OP_LOAD_CONST, fci); bytecode_add_instruction(bc, OP_STORE_GLOBAL, fgi); g_locals = prev; free(fname); continue; } /* for-sugar: * - for in range(a, b) * - for in */ if (starts_with(src, len, *pos, "for")) { *pos += 3; skip_spaces(src, len, pos); /* loop variable name */ char *ivar = NULL; if (!read_identifier_into(src, len, pos, &ivar)) { parser_fail(*pos, "Expected loop variable after 'for'"); return; } skip_spaces(src, len, pos); if (!starts_with(src, len, *pos, "in")) { parser_fail(*pos, "Expected 'in' after loop variable"); free(ivar); return; } *pos += 2; skip_spaces(src, len, pos); if (starts_with(src, len, *pos, "range")) { /* ===== range(a, b) variant ===== */ *pos += 5; if (!consume_char(src, len, pos, '(')) { parser_fail(*pos, "Expected '(' after range"); free(ivar); return; } /* Parse start expression */ if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "Expected start expression in range"); free(ivar); return; } /* Determine loop variable storage and store start value */ int lidx = local_find(ivar); int gi = -1; if (lidx < 0) { if (g_locals) lidx = local_add(ivar); else gi = sym_index(ivar); } if (lidx >= 0) { bytecode_add_instruction(bc, OP_STORE_LOCAL, lidx); } else { bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi); } /* comma */ skip_spaces(src, len, pos); if (*pos >= len || src[*pos] != ',') { parser_fail(*pos, "Expected ',' between range start and end"); free(ivar); return; } (*pos)++; /* consume ',' */ skip_spaces(src, len, pos); /* Parse end expression and store in a temp (local or global) */ if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "Expected end expression in range"); free(ivar); return; } char tmpname[64]; snprintf(tmpname, sizeof(tmpname), "__for_end_%d", g_temp_counter++); int lend = -1, gend = -1; if (g_locals) { lend = local_add(tmpname); bytecode_add_instruction(bc, OP_STORE_LOCAL, lend); } else { gend = sym_index(tmpname); bytecode_add_instruction(bc, OP_STORE_GLOBAL, gend); } if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after range arguments"); free(ivar); return; } /* end of header line */ skip_to_eol(src, len, pos); /* emit loop */ int loop_start = bc->instr_count; /* condition: ivar < end_tmp */ if (lidx >= 0) { bytecode_add_instruction(bc, OP_LOAD_LOCAL, lidx); } else { bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi); } if (lend >= 0) { bytecode_add_instruction(bc, OP_LOAD_LOCAL, lend); } else { bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gend); } bytecode_add_instruction(bc, OP_LT, 0); int jmp_false = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); /* enter loop context for break/continue */ LoopCtx ctx = { {0}, 0, {0}, 0, g_loop_ctx }; g_loop_ctx = &ctx; /* parse body at increased indent (peek) */ int body_indent = 0; size_t look_body = *pos; if (read_line_start(src, len, &look_body, &body_indent) && body_indent > current_indent) { parse_block(bc, src, len, pos, body_indent); } else { /* empty body ok */ } /* continue target: start of increment */ int cont_label = bc->instr_count; /* i = i + 1 */ int c1 = bytecode_add_constant(bc, make_int(1)); if (lidx >= 0) { bytecode_add_instruction(bc, OP_LOAD_LOCAL, lidx); bytecode_add_instruction(bc, OP_LOAD_CONST, c1); bytecode_add_instruction(bc, OP_ADD, 0); bytecode_add_instruction(bc, OP_STORE_LOCAL, lidx); } else { bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi); bytecode_add_instruction(bc, OP_LOAD_CONST, c1); bytecode_add_instruction(bc, OP_ADD, 0); bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi); } /* back edge and patch */ bytecode_add_instruction(bc, OP_JUMP, loop_start); /* end label (after loop) */ int end_label = bc->instr_count; bytecode_set_operand(bc, jmp_false, end_label); /* patch continue/break jumps */ for (int bi = 0; bi < ctx.cont_count; ++bi) { bytecode_set_operand(bc, ctx.continue_jumps[bi], cont_label); } for (int bi = 0; bi < ctx.break_count; ++bi) { bytecode_set_operand(bc, ctx.break_jumps[bi], end_label); } g_loop_ctx = ctx.prev; free(ivar); continue; } else { /* ===== array iteration: for ivar in ===== */ /* Evaluate the iterable once and store in a temp */ if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "Expected iterable expression after 'in'"); free(ivar); return; } char arrname[64]; snprintf(arrname, sizeof(arrname), "__for_arr_%d", g_temp_counter++); int larr = -1, garr = -1; if (g_locals) { larr = local_add(arrname); bytecode_add_instruction(bc, OP_STORE_LOCAL, larr); } else { garr = sym_index(arrname); bytecode_add_instruction(bc, OP_STORE_GLOBAL, garr); } /* Compute length once: len(arr) -> store temp */ if (larr >= 0) { bytecode_add_instruction(bc, OP_LOAD_LOCAL, larr); } else { bytecode_add_instruction(bc, OP_LOAD_GLOBAL, garr); } bytecode_add_instruction(bc, OP_LEN, 0); char lenname[64]; snprintf(lenname, sizeof(lenname), "__for_len_%d", g_temp_counter++); int llen = -1, glen = -1; if (g_locals) { llen = local_add(lenname); bytecode_add_instruction(bc, OP_STORE_LOCAL, llen); } else { glen = sym_index(lenname); bytecode_add_instruction(bc, OP_STORE_GLOBAL, glen); } /* Index temp: i = 0 */ int c0 = bytecode_add_constant(bc, make_int(0)); bytecode_add_instruction(bc, OP_LOAD_CONST, c0); char iname[64]; snprintf(iname, sizeof(iname), "__for_i_%d", g_temp_counter++); int li = -1, gi = -1; if (g_locals) { li = local_add(iname); bytecode_add_instruction(bc, OP_STORE_LOCAL, li); } else { gi = sym_index(iname); bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi); } /* end of header line */ skip_to_eol(src, len, pos); /* loop start label */ int loop_start = bc->instr_count; /* condition: i < len */ if (li >= 0) { bytecode_add_instruction(bc, OP_LOAD_LOCAL, li); } else { bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi); } if (llen >= 0) { bytecode_add_instruction(bc, OP_LOAD_LOCAL, llen); } else { bytecode_add_instruction(bc, OP_LOAD_GLOBAL, glen); } bytecode_add_instruction(bc, OP_LT, 0); int jmp_false = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); /* element: ivar = arr[i] */ if (larr >= 0) { bytecode_add_instruction(bc, OP_LOAD_LOCAL, larr); } else { bytecode_add_instruction(bc, OP_LOAD_GLOBAL, garr); } if (li >= 0) { bytecode_add_instruction(bc, OP_LOAD_LOCAL, li); } else { bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi); } bytecode_add_instruction(bc, OP_INDEX_GET, 0); /* assign to ivar (local preferred) */ int ldst = local_find(ivar); int gdst = -1; if (ldst < 0) { if (g_locals) ldst = local_add(ivar); else gdst = sym_index(ivar); } if (ldst >= 0) { bytecode_add_instruction(bc, OP_STORE_LOCAL, ldst); } else { bytecode_add_instruction(bc, OP_STORE_GLOBAL, gdst); } /* enter loop context for break/continue */ LoopCtx ctx = { {0}, 0, {0}, 0, g_loop_ctx }; g_loop_ctx = &ctx; /* parse body at increased indent */ int body_indent = 0; size_t look_body = *pos; if (read_line_start(src, len, &look_body, &body_indent) && body_indent > current_indent) { parse_block(bc, src, len, pos, body_indent); } else { /* empty body ok */ } /* continue target: start of increment */ int cont_label = bc->instr_count; /* i = i + 1 */ int c1 = bytecode_add_constant(bc, make_int(1)); if (li >= 0) { bytecode_add_instruction(bc, OP_LOAD_LOCAL, li); bytecode_add_instruction(bc, OP_LOAD_CONST, c1); bytecode_add_instruction(bc, OP_ADD, 0); bytecode_add_instruction(bc, OP_STORE_LOCAL, li); } else { bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi); bytecode_add_instruction(bc, OP_LOAD_CONST, c1); bytecode_add_instruction(bc, OP_ADD, 0); bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi); } /* back edge and patch */ bytecode_add_instruction(bc, OP_JUMP, loop_start); /* end label (after loop) */ int end_label = bc->instr_count; bytecode_set_operand(bc, jmp_false, end_label); /* patch continue/break jumps */ for (int bi = 0; bi < ctx.cont_count; ++bi) { bytecode_set_operand(bc, ctx.continue_jumps[bi], cont_label); } for (int bi = 0; bi < ctx.break_count; ++bi) { bytecode_set_operand(bc, ctx.break_jumps[bi], end_label); } g_loop_ctx = ctx.prev; free(ivar); continue; } } if (starts_with(src, len, *pos, "if")) { int end_jumps[64]; int end_count = 0; for (;;) { /* consume 'if' or 'else if' condition */ if (starts_with(src, len, *pos, "if")) { *pos += 2; } else { /* for 'else if' we arrive here with *pos already after 'if' */ } /* require at least one space before condition if present */ skip_spaces(src, len, pos); if (!emit_expression(bc, src, len, pos)) { /* no condition -> treat as false */ int ci = bytecode_add_constant(bc, make_int(0)); bytecode_add_instruction(bc, OP_LOAD_CONST, ci); } /* end of condition: ignore any trailing until EOL */ skip_to_eol(src, len, pos); /* conditional jump over this clause's block */ int jmp_false = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); /* parse nested block if next line is indented */ int next_indent = 0; size_t look_next = *pos; if (read_line_start(src, len, &look_next, &next_indent)) { if (next_indent > current_indent) { /* parse body at increased indent (let parse_block consume the line) */ parse_block(bc, src, len, pos, next_indent); } else { /* empty body; keep *pos at start of that line */ } } else { /* EOF -> empty body */ } /* after body, unconditionally jump to end of the whole chain */ int jmp_end = bytecode_add_instruction(bc, OP_JUMP, 0); if (end_count < (int)(sizeof(end_jumps) / sizeof(end_jumps[0]))) { end_jumps[end_count++] = jmp_end; } else { parser_fail(*pos, "Too many chained else/if clauses"); return; } /* patch false-jump target to start of next clause (or fallthrough) */ bytecode_set_operand(bc, jmp_false, bc->instr_count); /* look for else or else if at the same indentation */ size_t look = *pos; int look_indent = 0; if (!read_line_start(src, len, &look, &look_indent)) { /* EOF: break and patch end jumps */ break; } if (look_indent != current_indent) { /* dedent or deeper indent means no 'else' clause here */ break; } if (starts_with(src, len, look, "else")) { /* consume 'else' */ *pos = look + 4; skip_spaces(src, len, pos); if (starts_with(src, len, *pos, "if")) { /* else if -> consume 'if' token and continue loop to parse condition */ *pos += 2; continue; } else { /* plain else: parse its block and finish the chain */ skip_to_eol(src, len, pos); int else_indent = 0; size_t look_else = *pos; if (read_line_start(src, len, &look_else, &else_indent) && else_indent > current_indent) { /* let parse_block consume the line */ parse_block(bc, src, len, pos, else_indent); } else { /* empty else-body */ } /* end of chain after else */ break; } } else { /* next line is not an else/else if */ break; } } /* patch all end-of-clause jumps to the end of the chain */ for (int i = 0; i < end_count; ++i) { bytecode_set_operand(bc, end_jumps[i], bc->instr_count); } continue; } /* while loop */ if (starts_with(src, len, *pos, "while")) { *pos += 5; skip_spaces(src, len, pos); int loop_start = bc->instr_count; /* condition */ if (!emit_expression(bc, src, len, pos)) { int ci = bytecode_add_constant(bc, make_int(0)); bytecode_add_instruction(bc, OP_LOAD_CONST, ci); } /* end of condition */ skip_to_eol(src, len, pos); /* jump over body if false */ int jmp_false = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); /* enter loop context (continue -> condition) */ LoopCtx ctx = { {0}, 0, {0}, 0, g_loop_ctx }; g_loop_ctx = &ctx; /* parse body at increased indent (peek indent without advancing pos) */ int body_indent = 0; size_t look_body = *pos; if (read_line_start(src, len, &look_body, &body_indent) && body_indent > current_indent) { parse_block(bc, src, len, pos, body_indent); } else { /* empty body allowed */ } /* patch pending continues to loop_start (re-evaluate condition) */ for (int bi = 0; bi < ctx.cont_count; ++bi) { bytecode_set_operand(bc, ctx.continue_jumps[bi], loop_start); } /* back edge to loop start */ bytecode_add_instruction(bc, OP_JUMP, loop_start); /* end label and patches */ int end_label = bc->instr_count; bytecode_set_operand(bc, jmp_false, end_label); for (int bi = 0; bi < ctx.break_count; ++bi) { bytecode_set_operand(bc, ctx.break_jumps[bi], end_label); } g_loop_ctx = ctx.prev; continue; } /* otherwise: simple statement on this line */ parse_simple_statement(bc, src, len, pos); } } static Bytecode *compile_minimal(const char *src, size_t len) { Bytecode *bc = bytecode_new(); size_t pos = 0; skip_shebang_if_present(src, len, &pos); /* Optional: consume a single function wrapper line: fun () { ... } */ skip_comments(src, len, &pos); skip_ws(src, len, &pos); if (starts_with(src, len, pos, "fun")) { pos += 3; skip_comments(src, len, &pos); skip_ws(src, len, &pos); skip_identifier(src, len, &pos); /* name */ skip_comments(src, len, &pos); skip_ws(src, len, &pos); (void)consume_char(src, len, &pos, '('); (void)consume_char(src, len, &pos, ')'); skip_comments(src, len, &pos); skip_ws(src, len, &pos); (void)consume_char(src, len, &pos, '{'); /* tolerate */ /* move to next line start after wrapper */ skip_to_eol(src, len, &pos); } /* parse the top-level block at indent 0 */ parse_block(bc, src, len, &pos, 0); bytecode_add_instruction(bc, OP_HALT, 0); return bc; } Bytecode *parse_file_to_bytecode(const char *path) { size_t len = 0; char *src = read_file_all(path, &len); if (!src) { fprintf(stderr, "Error: cannot read file: %s\n", path); return NULL; } /* reset error state */ g_has_error = 0; g_err_pos = 0; g_err_msg[0] = '\0'; g_err_line = 0; g_err_col = 0; Bytecode *bc = compile_minimal(src, len); if (g_has_error) { int line = 1, col = 1; calc_line_col(src, len, g_err_pos, &line, &col); g_err_line = line; g_err_col = col; fprintf(stderr, "Parse error %s:%d:%d: %s\n", path ? path : "", line, col, g_err_msg); if (bc) bytecode_free(bc); free(src); return NULL; } free(src); return bc; } Bytecode *parse_string_to_bytecode(const char *source) { if (!source) { fprintf(stderr, "Error: null source provided\n"); return NULL; } size_t len = strlen(source); /* reset error state */ g_has_error = 0; g_err_pos = 0; g_err_msg[0] = '\0'; g_err_line = 0; g_err_col = 0; Bytecode *bc = compile_minimal(source, len); if (g_has_error) { int line = 1, col = 1; calc_line_col(source, len, g_err_pos, &line, &col); g_err_line = line; g_err_col = col; if (bc) bytecode_free(bc); return NULL; } return bc; } int parser_last_error(char *msgBuf, unsigned long msgCap, int *outLine, int *outCol) { if (!g_has_error) return 0; if (msgBuf && msgCap > 0) { snprintf(msgBuf, msgCap, "%s", g_err_msg); } if (outLine) *outLine = g_err_line; if (outCol) *outCol = g_err_col; return 1; }