1859 lines
No EOL
70 KiB
C
1859 lines
No EOL
70 KiB
C
#include "parser.h"
|
|
#include "value.h"
|
|
#include "vm.h"
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <stdarg.h>
|
|
|
|
/* ---- 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 <ident>() { ... } wrapper.
|
|
* - Statements supported: print(expr), ident = expr, and if <expr> 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 <ident> (= 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 <fn> ; 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 <ident> in range(a, b)
|
|
* - for <ident> in <array-expr>
|
|
*/
|
|
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 <expr> ===== */
|
|
/* 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 <ident>() { ... } */
|
|
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 : "<input>", 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;
|
|
} |