Files
armorpaint/base/sources/libs/minic.c
T
2026-05-15 10:06:54 +02:00

2435 lines
68 KiB
C

// Minimal C interpreter
#include "minic.h"
#include <ctype.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
// ████████╗ ██████╗ ██╗ ██╗███████╗███╗ ██╗
// ╚══██╔══╝██╔═══██╗██║ ██╔╝██╔════╝████╗ ██║
// ██║ ██║ ██║█████╔╝ █████╗ ██╔██╗ ██║
// ██║ ██║ ██║██╔═██╗ ██╔══╝ ██║╚██╗██║
// ██║ ╚██████╔╝██║ ██╗███████╗██║ ╚████║
// ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═══╝
typedef enum {
TOK_INT,
TOK_FLOAT,
TOK_CHAR,
TOK_DOUBLE,
TOK_BOOL,
TOK_RETURN,
TOK_IF,
TOK_ELSE,
TOK_WHILE,
TOK_FOR,
TOK_BREAK,
TOK_CONTINUE,
TOK_STRUCT,
TOK_TYPEDEF,
TOK_ENUM,
TOK_VOID,
TOK_IDENT,
TOK_NUMBER,
TOK_CHAR_LIT,
TOK_STR_LIT,
TOK_LPAREN,
TOK_RPAREN,
TOK_LBRACE,
TOK_RBRACE,
TOK_LBRACKET,
TOK_RBRACKET,
TOK_SEMICOLON,
TOK_COMMA,
TOK_ASSIGN,
TOK_PLUS_ASSIGN,
TOK_MINUS_ASSIGN,
TOK_MUL_ASSIGN,
TOK_DIV_ASSIGN,
TOK_EQ,
TOK_NEQ,
TOK_LT,
TOK_GT,
TOK_LE,
TOK_GE,
TOK_AND,
TOK_OR,
TOK_NOT,
TOK_AMP,
TOK_PLUS,
TOK_MINUS,
TOK_INC,
TOK_DEC,
TOK_STAR,
TOK_SLASH,
TOK_DOT,
TOK_ARROW,
TOK_EOF
} minic_tok_type_t;
typedef struct {
minic_tok_type_t type;
char text[64];
minic_val_t val; // TOK_NUMBER, TOK_CHAR_LIT, TOK_STR_LIT
} minic_token_t;
typedef struct {
const char *src;
int pos;
minic_token_t cur;
} minic_lexer_t;
static minic_u8 *minic_active_mem = NULL;
static int *minic_active_mem_used = NULL;
static void minic_lex_next(minic_lexer_t *l) {
// Skip whitespace and comments
for (;;) {
while (l->src[l->pos] != '\0' && isspace((unsigned char)l->src[l->pos])) {
l->pos++;
}
if (l->src[l->pos] == '/' && l->src[l->pos + 1] == '/') {
while (l->src[l->pos] != '\0' && l->src[l->pos] != '\n') {
l->pos++;
}
continue;
}
// Skip preprocessor directives
if (l->src[l->pos] == '#') {
while (l->src[l->pos] != '\0' && l->src[l->pos] != '\n') {
l->pos++;
}
continue;
}
if (l->src[l->pos] == '/' && l->src[l->pos + 1] == '*') {
l->pos += 2;
while (l->src[l->pos] != '\0' && !(l->src[l->pos] == '*' && l->src[l->pos + 1] == '/')) {
l->pos++;
}
if (l->src[l->pos] != '\0') {
l->pos += 2;
}
continue;
}
break;
}
if (l->src[l->pos] == '\0') {
l->cur.type = TOK_EOF;
return;
}
char c = l->src[l->pos];
if (c == '0' && (l->src[l->pos + 1] == 'x' || l->src[l->pos + 1] == 'X')) {
l->pos += 2; // Consume '0x'
unsigned int n = 0;
while (isxdigit((unsigned char)l->src[l->pos])) {
char h = l->src[l->pos++];
int digit = (h >= '0' && h <= '9') ? h - '0' : (h >= 'a' && h <= 'f') ? h - 'a' + 10 : h - 'A' + 10;
n = n * 16 + digit;
}
l->cur.val = minic_val_int((int)n);
l->cur.type = TOK_NUMBER;
return;
}
if (isdigit((unsigned char)c)) {
double n = 0;
while (isdigit((unsigned char)l->src[l->pos])) {
n = n * 10 + (l->src[l->pos++] - '0');
}
bool is_float = false;
if (l->src[l->pos] == '.') {
l->pos++;
double frac = 0.1;
while (isdigit((unsigned char)l->src[l->pos])) {
n += (l->src[l->pos++] - '0') * frac;
frac *= 0.1;
}
is_float = true;
}
if (l->src[l->pos] == 'f' || l->src[l->pos] == 'F') {
l->pos++;
is_float = true;
}
if (is_float) {
l->cur.val = minic_val_float((float)n);
}
else {
l->cur.val = minic_val_int((int)n);
}
l->cur.type = TOK_NUMBER;
return;
}
if (c == '"') {
l->pos++; // Consume opening '"'
// Allocate space in the active context's arena for the string
int start = (*minic_active_mem_used + 7) & ~7; // Aligned start
int wi = start;
while (l->src[l->pos] != '"' && l->src[l->pos] != '\0') {
char ch;
if (l->src[l->pos] == '\\') {
l->pos++;
switch (l->src[l->pos++]) {
case 'n':
ch = '\n';
break;
case 't':
ch = '\t';
break;
case '0':
ch = '\0';
break;
case '\\':
ch = '\\';
break;
case '"':
ch = '"';
break;
case 'r':
ch = '\r';
break;
case '\n': // Line continuation: backslash-newline, skip both
continue;
case '\r': // Handle \r\n line endings
if (l->src[l->pos] == '\n') {
l->pos++;
}
continue;
default:
ch = '\0';
break;
}
}
else {
ch = l->src[l->pos++];
}
minic_active_mem[wi++] = (minic_u8)ch;
}
minic_active_mem[wi++] = '\0';
*minic_active_mem_used = (wi + 7) & ~7;
if (l->src[l->pos] == '"') {
l->pos++; // Consume closing '"'
}
l->cur.type = TOK_STR_LIT;
// Store a real pointer into the context arena so the dispatcher passes it correctly
l->cur.val = minic_val_ptr((void *)&minic_active_mem[start]);
return;
}
if (c == '\'') {
l->pos++; // Consume opening '
int v = 0;
if (l->src[l->pos] == '\\') {
l->pos++;
switch (l->src[l->pos++]) {
case 'n':
v = '\n';
break;
case 't':
v = '\t';
break;
case '0':
v = '\0';
break;
case '\\':
v = '\\';
break;
case '\'':
v = '\'';
break;
default:
v = 0;
break;
}
}
else {
v = (unsigned char)l->src[l->pos++];
}
l->pos++; // Consume closing '
l->cur.type = TOK_CHAR_LIT;
l->cur.val = minic_val_int(v);
return;
}
if (isalpha((unsigned char)c) || c == '_') {
int i = 0;
while (isalnum((unsigned char)l->src[l->pos]) || l->src[l->pos] == '_') {
l->cur.text[i++] = l->src[l->pos++];
}
l->cur.text[i] = '\0';
if (strcmp(l->cur.text, "int") == 0) {
l->cur.type = TOK_INT;
return;
}
if (strcmp(l->cur.text, "float") == 0) {
l->cur.type = TOK_FLOAT;
return;
}
if (strcmp(l->cur.text, "char") == 0) {
l->cur.type = TOK_CHAR;
return;
}
if (strcmp(l->cur.text, "double") == 0) {
l->cur.type = TOK_DOUBLE;
return;
}
if (strcmp(l->cur.text, "bool") == 0) {
l->cur.type = TOK_BOOL;
return;
}
if (strcmp(l->cur.text, "true") == 0) {
l->cur.type = TOK_NUMBER;
l->cur.val = minic_val_int(1);
return;
}
if (strcmp(l->cur.text, "false") == 0) {
l->cur.type = TOK_NUMBER;
l->cur.val = minic_val_int(0);
return;
}
if (strcmp(l->cur.text, "return") == 0) {
l->cur.type = TOK_RETURN;
return;
}
if (strcmp(l->cur.text, "if") == 0) {
l->cur.type = TOK_IF;
return;
}
if (strcmp(l->cur.text, "else") == 0) {
l->cur.type = TOK_ELSE;
return;
}
if (strcmp(l->cur.text, "while") == 0) {
l->cur.type = TOK_WHILE;
return;
}
if (strcmp(l->cur.text, "for") == 0) {
l->cur.type = TOK_FOR;
return;
}
if (strcmp(l->cur.text, "break") == 0) {
l->cur.type = TOK_BREAK;
return;
}
if (strcmp(l->cur.text, "continue") == 0) {
l->cur.type = TOK_CONTINUE;
return;
}
if (strcmp(l->cur.text, "struct") == 0) {
l->cur.type = TOK_STRUCT;
return;
}
if (strcmp(l->cur.text, "typedef") == 0) {
l->cur.type = TOK_TYPEDEF;
return;
}
if (strcmp(l->cur.text, "enum") == 0) {
l->cur.type = TOK_ENUM;
return;
}
if (strcmp(l->cur.text, "void") == 0) {
l->cur.type = TOK_VOID;
return;
}
l->cur.type = TOK_IDENT;
return;
}
l->pos++;
switch (c) {
case '(':
l->cur.type = TOK_LPAREN;
return;
case ')':
l->cur.type = TOK_RPAREN;
return;
case '{':
l->cur.type = TOK_LBRACE;
return;
case '}':
l->cur.type = TOK_RBRACE;
return;
case '[':
l->cur.type = TOK_LBRACKET;
return;
case ']':
l->cur.type = TOK_RBRACKET;
return;
case ';':
l->cur.type = TOK_SEMICOLON;
return;
case ',':
l->cur.type = TOK_COMMA;
return;
case '.':
l->cur.type = TOK_DOT;
return;
case '*':
if (l->src[l->pos] == '=') {
l->pos++;
l->cur.type = TOK_MUL_ASSIGN;
}
else {
l->cur.type = TOK_STAR;
}
return;
case '/':
if (l->src[l->pos] == '=') {
l->pos++;
l->cur.type = TOK_DIV_ASSIGN;
}
else {
l->cur.type = TOK_SLASH;
}
return;
case '+':
if (l->src[l->pos] == '+') {
l->pos++;
l->cur.type = TOK_INC;
}
else if (l->src[l->pos] == '=') {
l->pos++;
l->cur.type = TOK_PLUS_ASSIGN;
}
else {
l->cur.type = TOK_PLUS;
}
return;
case '-':
if (l->src[l->pos] == '-') {
l->pos++;
l->cur.type = TOK_DEC;
}
else if (l->src[l->pos] == '=') {
l->pos++;
l->cur.type = TOK_MINUS_ASSIGN;
}
else if (l->src[l->pos] == '>') {
l->pos++;
l->cur.type = TOK_ARROW;
}
else {
l->cur.type = TOK_MINUS;
}
return;
case '=':
if (l->src[l->pos] == '=') {
l->pos++;
l->cur.type = TOK_EQ;
}
else {
l->cur.type = TOK_ASSIGN;
}
return;
case '!':
if (l->src[l->pos] == '=') {
l->pos++;
l->cur.type = TOK_NEQ;
}
else {
l->cur.type = TOK_NOT;
}
return;
case '&':
if (l->src[l->pos] == '&') {
l->pos++;
l->cur.type = TOK_AND;
}
else {
l->cur.type = TOK_AMP;
}
return;
case '|':
if (l->src[l->pos] == '|') {
l->pos++;
l->cur.type = TOK_OR;
}
return;
case '<':
if (l->src[l->pos] == '=') {
l->pos++;
l->cur.type = TOK_LE;
}
else {
l->cur.type = TOK_LT;
}
return;
case '>':
if (l->src[l->pos] == '=') {
l->pos++;
l->cur.type = TOK_GE;
}
else {
l->cur.type = TOK_GT;
}
return;
}
}
// ███████╗██╗ ██╗███╗ ██╗ ██████╗███████╗
// ██╔════╝██║ ██║████╗ ██║██╔════╝██╔════╝
// █████╗ ██║ ██║██╔██╗ ██║██║ ███████╗
// ██╔══╝ ██║ ██║██║╚██╗██║██║ ╚════██║
// ██║ ╚██████╔╝██║ ╚████║╚██████╗███████║
// ╚═╝ ╚═════╝ ╚═╝ ╚═══╝ ╚═════╝╚══════╝
void *minic_alloc(int size) {
// Align to 8 bytes
int aligned = (*minic_active_mem_used + 7) & ~7;
*minic_active_mem_used = aligned + size;
return &minic_active_mem[aligned];
}
typedef struct {
char name[64];
minic_val_t val;
} minic_var_t;
typedef struct {
char name[64];
int offset; // index into global arr_data[]
int count;
minic_type_t elem_type;
} minic_arr_t;
typedef struct {
char name[64];
char params[MINIC_MAX_PARAMS][64];
char param_structs[MINIC_MAX_PARAMS][64]; // struct type name per param, or ""
// Per-param declared type
minic_type_t param_types[MINIC_MAX_PARAMS];
int param_count;
int body_pos; // lexer position of '{' that starts the body
minic_type_t ret_type;
minic_ctx_t *ctx; // owning context, set at parse time
} minic_func_t;
#define MINIC_INC_DELTA(l) ((l)->cur.type == TOK_INC ? 1.0 : -1.0)
typedef struct {
char name[64];
char fields[MINIC_MAX_STRUCT_FIELDS][64];
char field_struct_names[MINIC_MAX_STRUCT_FIELDS][64]; // struct type name for PTR fields, or ""
int field_count;
int size; // sizeof the native C struct, 0 if unknown
bool has_native_layout;
int field_offsets[MINIC_MAX_STRUCT_FIELDS];
minic_type_t field_native_types[MINIC_MAX_STRUCT_FIELDS];
minic_type_t field_deref_types[MINIC_MAX_STRUCT_FIELDS]; // pointed-to type for PTR fields
} minic_struct_def_t;
// Maps a variable name to its struct type name
typedef struct {
char var_name[64];
char struct_name[64];
} minic_vartype_t;
typedef struct minic_env_s {
minic_lexer_t lex;
const char *filename;
minic_var_t *vars;
int var_count;
int var_cap;
minic_arr_t *arrs;
int arr_count;
int arr_cap;
minic_val_t *arr_data; // global array element storage
int *arr_data_used; // pointer to shared counter
minic_func_t *funcs;
int func_count;
int func_cap;
minic_struct_def_t *structs; // shared across calls
int struct_count;
int struct_cap;
minic_vartype_t *vartypes; // local: var->struct type mapping
int vartype_count;
int vartype_cap;
bool returning;
bool breaking;
bool continuing;
bool error;
minic_val_t return_val;
minic_type_t decl_type;
struct minic_env_s *global_env; // top-level env that owns the script globals
} minic_env_t;
struct minic_ctx_s {
minic_u8 *mem;
int mem_used;
minic_env_t e;
float result;
char *src_copy;
};
static minic_env_t *minic_active_env = NULL;
static minic_val_t minic_parse_expr(minic_env_t *e);
static minic_val_t minic_parse_cond(minic_env_t *e);
static void minic_skip_block(minic_env_t *e);
static void minic_parse_stmt(minic_env_t *e);
static void minic_parse_block(minic_env_t *e);
static const char *minic_tok_name(minic_tok_type_t t) {
switch (t) {
case TOK_INT:
return "'int'";
case TOK_FLOAT:
return "'float'";
case TOK_CHAR:
return "'char'";
case TOK_DOUBLE:
return "'double'";
case TOK_BOOL:
return "'bool'";
case TOK_RETURN:
return "'return'";
case TOK_IF:
return "'if'";
case TOK_ELSE:
return "'else'";
case TOK_WHILE:
return "'while'";
case TOK_FOR:
return "'for'";
case TOK_BREAK:
return "'break'";
case TOK_CONTINUE:
return "'continue'";
case TOK_IDENT:
return "identifier";
case TOK_NUMBER:
return "number";
case TOK_CHAR_LIT:
return "char literal";
case TOK_STR_LIT:
return "string literal";
case TOK_LPAREN:
return "'('";
case TOK_RPAREN:
return "')'";
case TOK_LBRACE:
return "'{'";
case TOK_RBRACE:
return "'}'";
case TOK_LBRACKET:
return "'['";
case TOK_RBRACKET:
return "']'";
case TOK_SEMICOLON:
return "';'";
case TOK_COMMA:
return "','";
case TOK_ASSIGN:
return "'='";
case TOK_EQ:
return "'=='";
case TOK_NEQ:
return "'!='";
case TOK_LT:
return "'<'";
case TOK_GT:
return "'>'";
case TOK_LE:
return "'<='";
case TOK_GE:
return "'>='";
case TOK_AND:
return "'&&'";
case TOK_OR:
return "'||'";
case TOK_AMP:
return "'&'";
case TOK_PLUS:
return "'+'";
case TOK_MINUS:
return "'-'";
case TOK_INC:
return "'++'";
case TOK_DEC:
return "'--'";
case TOK_PLUS_ASSIGN:
return "'+='";
case TOK_MINUS_ASSIGN:
return "'-='";
case TOK_MUL_ASSIGN:
return "'*='";
case TOK_DIV_ASSIGN:
return "'/='";
case TOK_STAR:
return "'*'";
case TOK_SLASH:
return "'/'";
case TOK_DOT:
return "'.'";
case TOK_ARROW:
return "'->'";
case TOK_STRUCT:
return "'struct'";
case TOK_VOID:
return "'void'";
case TOK_EOF:
return "end of file";
default:
return "unknown token";
}
}
static int minic_current_line(minic_env_t *e) {
int line = 1;
for (int i = 0; i < e->lex.pos; i++) {
if (e->lex.src[i] == '\n')
line++;
}
return line;
}
void console_log(char *s);
static void minic_error(minic_env_t *e, const char *fmt, ...) {
if (!e->error) {
char msg[256];
va_list args;
va_start(args, fmt);
vsnprintf(msg, sizeof(msg), fmt, args);
va_end(args);
char log[512];
snprintf(log, sizeof(log), "%s:%d: error: %s (got %s)", e->filename, minic_current_line(e), msg, minic_tok_name(e->lex.cur.type));
console_log(log);
e->error = true;
e->returning = true;
}
}
static void minic_expect(minic_env_t *e, minic_tok_type_t expected) {
if (e->lex.cur.type != expected) {
minic_error(e, "expected %s", minic_tok_name(expected));
return;
}
minic_lex_next(&e->lex);
}
static minic_val_t minic_var_get(minic_env_t *e, const char *name) {
for (int i = e->var_count - 1; i >= 0; --i) {
if (strcmp(e->vars[i].name, name) == 0) {
return e->vars[i].val;
}
}
if (e->global_env != NULL) {
for (int i = e->global_env->var_count - 1; i >= 0; --i) {
if (strcmp(e->global_env->vars[i].name, name) == 0) {
return e->global_env->vars[i].val;
}
}
}
return minic_val_int(0);
}
static void minic_var_set(minic_env_t *e, const char *name, minic_val_t val) {
for (int i = e->var_count - 1; i >= 0; --i) {
if (strcmp(e->vars[i].name, name) == 0) {
// Preserve declared type, coerce if needed
if (e->vars[i].val.type != val.type) {
val = minic_val_cast(val, e->vars[i].val.type);
}
// Preserve deref_type for pointer variables (it was set at declaration)
if (e->vars[i].val.type == MINIC_T_PTR && e->vars[i].val.deref_type != MINIC_T_PTR) {
val.deref_type = e->vars[i].val.deref_type;
}
e->vars[i].val = val;
return;
}
}
// Fall back to globals: write directly into the top-level env
if (e->global_env != NULL) {
minic_env_t *g = e->global_env;
for (int i = g->var_count - 1; i >= 0; --i) {
if (strcmp(g->vars[i].name, name) == 0) {
if (g->vars[i].val.type != val.type) {
val = minic_val_cast(val, g->vars[i].val.type);
}
if (g->vars[i].val.type == MINIC_T_PTR && g->vars[i].val.deref_type != MINIC_T_PTR) {
val.deref_type = g->vars[i].val.deref_type;
}
g->vars[i].val = val;
return;
}
}
}
if (e->var_count < e->var_cap) {
strncpy(e->vars[e->var_count].name, name, 63);
e->vars[e->var_count].val = val;
e->var_count++;
}
}
static void minic_var_decl(minic_env_t *e, const char *name, minic_type_t type, minic_val_t init) {
// Coerce init to declared type
minic_val_t v = minic_val_cast(init, type);
// If this is a typed pointer, preserve the deref_type from init
if (type == MINIC_T_PTR) {
v.deref_type = init.deref_type;
}
if (e->var_count < e->var_cap) {
strncpy(e->vars[e->var_count].name, name, 63);
e->vars[e->var_count].val = v;
e->var_count++;
}
}
static minic_val_t minic_var_addr(minic_env_t *e, const char *name) {
for (int i = e->var_count - 1; i >= 0; --i) {
if (strcmp(e->vars[i].name, name) == 0) {
// Address of a minic_val_t — deref reads a full minic_val_t
minic_val_t addr = minic_val_ptr(&e->vars[i].val);
addr.deref_type = MINIC_T_PTR; // sentinel: deref reads minic_val_t
return addr;
}
}
// Fall back to globals: return address into the top-level env's storage
if (e->global_env != NULL) {
minic_env_t *g = e->global_env;
for (int i = g->var_count - 1; i >= 0; --i) {
if (strcmp(g->vars[i].name, name) == 0) {
minic_val_t addr = minic_val_ptr(&g->vars[i].val);
addr.deref_type = MINIC_T_PTR;
return addr;
}
}
}
// Create it as a local
if (e->var_count < e->var_cap) {
strncpy(e->vars[e->var_count].name, name, 63);
e->vars[e->var_count].val = minic_val_int(0);
minic_val_t addr = minic_val_ptr(&e->vars[e->var_count].val);
addr.deref_type = MINIC_T_PTR;
e->var_count++;
return addr;
}
return minic_val_ptr(NULL);
}
static minic_arr_t *minic_arr_get(minic_env_t *e, const char *name) {
for (int i = 0; i < e->arr_count; ++i) {
if (strcmp(e->arrs[i].name, name) == 0) {
return &e->arrs[i];
}
}
if (e->global_env != NULL) {
minic_env_t *g = e->global_env;
for (int i = 0; i < g->arr_count; ++i) {
if (strcmp(g->arrs[i].name, name) == 0) {
return &g->arrs[i];
}
}
}
return NULL;
}
static void minic_arr_decl(minic_env_t *e, const char *name, int count, minic_type_t elem_type) {
if (e->arr_count >= e->arr_cap) {
return;
}
minic_arr_t *a = &e->arrs[e->arr_count++];
strncpy(a->name, name, 63);
a->offset = *e->arr_data_used;
a->count = count;
a->elem_type = elem_type;
*e->arr_data_used += count;
// Zero-initialise
for (int i = 0; i < count; i++) {
e->arr_data[a->offset + i] = minic_val_coerce(0.0, elem_type);
}
}
static minic_val_t minic_arr_elem_get(minic_env_t *e, const char *name, int idx) {
minic_arr_t *a = minic_arr_get(e, name);
if (a != NULL && idx >= 0 && idx < a->count) {
return e->arr_data[a->offset + idx];
}
// Fall back to native pointer subscript
minic_val_t pv = minic_var_get(e, name);
if (pv.type == MINIC_T_PTR && pv.p != NULL) {
switch (pv.deref_type) {
case MINIC_T_FLOAT:
return minic_val_float(((float *)pv.p)[idx]);
case MINIC_T_INT:
return minic_val_int(((int32_t *)pv.p)[idx]);
default:
return minic_val_ptr(((void **)pv.p)[idx]);
}
}
return minic_val_int(0);
}
static void minic_arr_elem_set(minic_env_t *e, const char *name, int idx, minic_val_t val) {
minic_arr_t *a = minic_arr_get(e, name);
if (a != NULL && idx >= 0 && idx < a->count) {
e->arr_data[a->offset + idx] = minic_val_cast(val, a->elem_type);
return;
}
// Fall back to native pointer subscript
minic_val_t pv = minic_var_get(e, name);
if (pv.type == MINIC_T_PTR && pv.p != NULL) {
switch (pv.deref_type) {
case MINIC_T_FLOAT:
((float *)pv.p)[idx] = (float)minic_val_to_d(val);
break;
case MINIC_T_INT:
((int32_t *)pv.p)[idx] = (int32_t)minic_val_to_d(val);
break;
default:
((void **)pv.p)[idx] = minic_val_to_ptr(val);
break;
}
}
}
static minic_func_t *minic_func_get(minic_env_t *e, const char *name) {
for (int i = 0; i < e->func_count; ++i) {
if (strcmp(e->funcs[i].name, name) == 0) {
return &e->funcs[i];
}
}
return NULL;
}
static minic_struct_def_t *minic_struct_get(minic_env_t *e, const char *name);
static void minic_vartype_set(minic_env_t *e, const char *var_name, const char *struct_name) {
if (e->vartype_count < e->vartype_cap) {
strncpy(e->vartypes[e->vartype_count].var_name, var_name, 63);
strncpy(e->vartypes[e->vartype_count].struct_name, struct_name, 63);
e->vartype_count++;
}
}
static minic_struct_def_t *minic_var_struct(minic_env_t *e, const char *var_name) {
for (int i = e->vartype_count - 1; i >= 0; --i) {
if (strcmp(e->vartypes[i].var_name, var_name) == 0) {
return minic_struct_get(e, e->vartypes[i].struct_name);
}
}
if (e->global_env != NULL) {
for (int i = e->global_env->vartype_count - 1; i >= 0; --i) {
if (strcmp(e->global_env->vartypes[i].var_name, var_name) == 0) {
return minic_struct_get(e, e->global_env->vartypes[i].struct_name);
}
}
}
return NULL;
}
static minic_struct_def_t *minic_struct_get(minic_env_t *e, const char *name) {
for (int i = 0; i < e->struct_count; ++i) {
if (strcmp(e->structs[i].name, name) == 0) {
return &e->structs[i];
}
}
return NULL;
}
static int minic_struct_field_idx(minic_struct_def_t *def, const char *field) {
for (int i = 0; i < def->field_count; ++i) {
if (strcmp(def->fields[i], field) == 0) {
return i;
}
}
return -1;
}
static minic_val_t minic_struct_field_get_base(minic_env_t *e, void *base, minic_struct_def_t *def, const char *field) {
int idx = minic_struct_field_idx(def, field);
if (idx < 0) {
minic_error(e, "struct '%s' has no field '%s'", def->name, field);
return minic_val_int(0);
}
bool in_arena = (base != NULL && (minic_u8 *)base >= minic_active_mem && (minic_u8 *)base < minic_active_mem + MINIC_MEM_SIZE);
if (def->has_native_layout && !in_arena) {
char *p = (char *)base + def->field_offsets[idx];
switch (def->field_native_types[idx]) {
case MINIC_T_PTR:
return minic_val_typed_ptr(*(void **)p, def->field_deref_types[idx]);
case MINIC_T_EMBED:
return minic_val_typed_ptr(p, def->field_deref_types[idx]);
case MINIC_T_FLOAT:
return minic_val_float(*(float *)p);
default:
return minic_val_int(*(int32_t *)p);
}
}
minic_val_t v;
memcpy(&v, (minic_val_t *)base + idx, sizeof(minic_val_t));
return v;
}
static void minic_struct_field_set_base(minic_env_t *e, void *base, minic_struct_def_t *def, const char *field, minic_val_t val) {
int idx = minic_struct_field_idx(def, field);
if (idx < 0) {
minic_error(e, "struct '%s' has no field '%s'", def->name, field);
return;
}
bool in_arena = (base != NULL && (minic_u8 *)base >= minic_active_mem && (minic_u8 *)base < minic_active_mem + MINIC_MEM_SIZE);
if (def->has_native_layout && !in_arena) {
char *p = (char *)base + def->field_offsets[idx];
switch (def->field_native_types[idx]) {
case MINIC_T_PTR:
*(void **)p = minic_val_to_ptr(val);
break;
case MINIC_T_EMBED:
break; // embedded structs are mutated through their own field accessors
case MINIC_T_FLOAT:
*(float *)p = (float)minic_val_to_d(val);
break;
default:
*(int32_t *)p = (int32_t)minic_val_to_d(val);
break;
}
return;
}
memcpy((minic_val_t *)base + idx, &val, sizeof(minic_val_t));
}
static minic_val_t minic_struct_field_get(minic_env_t *e, const char *var_name, minic_struct_def_t *def, const char *field) {
minic_val_t base_val = minic_var_get(e, var_name);
void *base = minic_val_to_ptr(base_val);
return minic_struct_field_get_base(e, base, def, field);
}
static void minic_struct_field_set(minic_env_t *e, const char *var_name, minic_struct_def_t *def, const char *field, minic_val_t val) {
minic_val_t base_val = minic_var_get(e, var_name);
void *base = minic_val_to_ptr(base_val);
minic_struct_field_set_base(e, base, def, field, val);
}
static minic_val_t minic_call(minic_env_t *e, minic_func_t *fn, minic_val_t *args, int argc) {
minic_env_t child = {0};
child.lex.src = e->lex.src;
child.lex.pos = fn->body_pos;
child.filename = e->filename;
child.var_cap = 64;
child.vars = minic_alloc(child.var_cap * sizeof(minic_var_t));
child.global_env = e->global_env != NULL ? e->global_env : e;
child.arr_cap = 32;
child.arrs = minic_alloc(child.arr_cap * sizeof(minic_arr_t));
child.arr_data = e->arr_data;
child.arr_data_used = e->arr_data_used;
child.func_count = e->func_count;
child.func_cap = e->func_cap;
child.funcs = e->funcs;
child.struct_count = e->struct_count;
child.struct_cap = e->struct_cap;
child.structs = e->structs;
child.vartype_cap = 32;
child.vartypes = minic_alloc(child.vartype_cap * sizeof(minic_vartype_t));
// Bind parameters
for (int i = 0; i < argc && i < fn->param_count; ++i) {
minic_val_t v = minic_val_cast(args[i], fn->param_types[i]);
minic_var_decl(&child, fn->params[i], fn->param_types[i], v);
if (fn->param_structs[i][0] != '\0') {
minic_vartype_set(&child, fn->params[i], fn->param_structs[i]);
}
}
minic_lex_next(&child.lex);
minic_parse_block(&child);
return child.return_val;
}
minic_val_t minic_call_fn(void *fn_ptr, minic_val_t *args, int argc) {
if (fn_ptr == NULL) {
return minic_val_int(0);
}
minic_func_t *fn = (minic_func_t *)fn_ptr;
minic_ctx_t *ctx = fn->ctx;
if (ctx == NULL) {
return minic_val_int(0);
}
minic_u8 *prev_mem = minic_active_mem;
int *prev_mem_used = minic_active_mem_used;
minic_env_t *prev_env = minic_active_env;
minic_active_mem = ctx->mem;
minic_active_mem_used = &ctx->mem_used;
minic_active_env = &ctx->e;
int saved_mem_used = ctx->mem_used;
minic_val_t r = minic_call(&ctx->e, fn, args, argc);
ctx->mem_used = saved_mem_used;
minic_active_env = prev_env;
minic_active_mem = prev_mem;
minic_active_mem_used = prev_mem_used;
return r;
}
static minic_val_t minic_arith(minic_val_t a, minic_val_t b, char op) {
// Determine result type (widening: int < float < ptr)
minic_type_t rt;
if (a.type == MINIC_T_PTR || b.type == MINIC_T_PTR)
rt = MINIC_T_PTR;
else if (a.type == MINIC_T_FLOAT || b.type == MINIC_T_FLOAT)
rt = MINIC_T_FLOAT;
else
rt = MINIC_T_INT;
double da = minic_val_to_d(a);
double db = minic_val_to_d(b);
double r;
switch (op) {
case '+':
r = da + db;
break;
case '-':
r = da - db;
break;
case '*':
r = da * db;
break;
case '/':
r = (db != 0.0) ? da / db : 0.0;
break;
default:
r = 0.0;
}
return minic_val_coerce(r, rt);
}
// primary: '&' IDENT | '*' primary | '-' primary | '++' IDENT | '--' IDENT | NUMBER | CHAR_LIT | IDENT ['[' expr ']'] ['(' args ')'] | '(' expr ')'
static minic_val_t minic_parse_primary(minic_env_t *e) {
if (e->lex.cur.type == TOK_AMP) {
minic_lex_next(&e->lex); // Consume '&'
const char *aname = e->lex.cur.text;
minic_arr_t *arr = minic_arr_get(e, aname);
minic_val_t addr;
if (arr != NULL) {
addr = minic_val_ptr(&e->arr_data[arr->offset]);
}
else if (minic_var_struct(e, aname) != NULL) {
// Struct var holds real base pointer
addr = minic_var_get(e, aname);
}
else {
addr = minic_var_addr(e, aname);
}
// Handle &var->field or &var.field: follow member-access chain
minic_struct_def_t *def = minic_var_struct(e, aname);
minic_lex_next(&e->lex); // Consume ident
while (def != NULL && (e->lex.cur.type == TOK_ARROW || e->lex.cur.type == TOK_DOT)) {
minic_lex_next(&e->lex); // Consume '->' or '.'
char field[64];
strncpy(field, e->lex.cur.text, 63);
minic_lex_next(&e->lex); // Consume field name
void *base = minic_val_to_ptr(addr);
addr = minic_struct_field_get_base(e, base, def, field);
// Advance def to the field's struct type for further chaining
int fidx = minic_struct_field_idx(def, field);
if (fidx >= 0 && def->field_struct_names[fidx][0] != '\0')
def = minic_struct_get(e, def->field_struct_names[fidx]);
else
def = NULL;
}
return addr;
}
if (e->lex.cur.type == TOK_STAR) {
// Dereference: *expr → read through pointer using deref_type
minic_lex_next(&e->lex); // consume '*'
minic_val_t pv = minic_parse_primary(e);
void *ptr = minic_val_to_ptr(pv);
if (ptr == NULL) {
return minic_val_int(0);
}
// Deref_type == MINIC_T_PTR means "pointer to minic_val_t" (interpreter-internal)
// any other deref_type means a native C scalar at that address
switch (pv.deref_type) {
case MINIC_T_INT: {
int v;
memcpy(&v, ptr, sizeof(int));
return minic_val_int(v);
}
case MINIC_T_FLOAT: {
float v;
memcpy(&v, ptr, sizeof(float));
return minic_val_float(v);
}
default: {
// MINIC_T_PTR or unknown: read a full minic_val_t (interpreter-internal ptr)
minic_val_t v;
memcpy(&v, ptr, sizeof(minic_val_t));
return v;
}
}
}
if (e->lex.cur.type == TOK_MINUS) {
minic_lex_next(&e->lex);
minic_val_t v = minic_parse_primary(e);
return minic_val_coerce(-minic_val_to_d(v), v.type);
}
if (e->lex.cur.type == TOK_NOT) {
minic_lex_next(&e->lex);
minic_val_t v = minic_parse_primary(e);
return minic_val_int(!minic_val_is_true(v));
}
if (e->lex.cur.type == TOK_INC || e->lex.cur.type == TOK_DEC) {
double delta = MINIC_INC_DELTA(&e->lex);
minic_lex_next(&e->lex);
char name[64];
strncpy(name, e->lex.cur.text, 63);
minic_lex_next(&e->lex);
minic_val_t ov = minic_var_get(e, name);
minic_val_t nv = minic_val_coerce(minic_val_to_d(ov) + delta, ov.type);
minic_var_set(e, name, nv);
return nv;
}
if (e->lex.cur.type == TOK_NUMBER || e->lex.cur.type == TOK_CHAR_LIT || e->lex.cur.type == TOK_STR_LIT) {
minic_val_t v = e->lex.cur.val;
minic_lex_next(&e->lex);
return v;
}
if (e->lex.cur.type == TOK_IDENT) {
char name[64];
strncpy(name, e->lex.cur.text, 63);
minic_lex_next(&e->lex);
if (strcmp(name, "sizeof") == 0) {
minic_expect(e, TOK_LPAREN); // checks and consumes '('
char type_name[64];
strncpy(type_name, e->lex.cur.text, 63);
minic_lex_next(&e->lex); // Consume type name
minic_expect(e, TOK_RPAREN); // checks and consumes ')'
minic_struct_def_t *def = minic_struct_get(e, type_name);
return minic_val_int(def != NULL ? def->size : 0);
}
if (e->lex.cur.type == TOK_LBRACKET) {
minic_lex_next(&e->lex); // Consume '['
int idx = (int)minic_val_to_d(minic_parse_expr(e));
minic_lex_next(&e->lex); // Consume ']'
return minic_arr_elem_get(e, name, idx);
}
if (e->lex.cur.type == TOK_LPAREN) {
minic_lex_next(&e->lex); // Consume '('
minic_val_t args[MINIC_MAX_PARAMS];
int argc = 0;
while (e->lex.cur.type != TOK_RPAREN && e->lex.cur.type != TOK_EOF) {
args[argc++] = minic_parse_expr(e);
if (e->lex.cur.type == TOK_COMMA) {
minic_lex_next(&e->lex);
}
}
minic_lex_next(&e->lex); // Consume ')'
minic_func_t *fn = minic_func_get(e, name);
if (fn != NULL) {
return minic_call(e, fn, args, argc);
}
minic_ext_func_t *ext = minic_ext_func_get(name);
if (ext != NULL) {
return minic_dispatch(ext, args, argc);
}
minic_error(e, "unknown function '%s'", name);
return minic_val_int(0);
}
if (e->lex.cur.type == TOK_DOT) {
minic_lex_next(&e->lex); // Consume '.'
char field[64];
strncpy(field, e->lex.cur.text, 63);
minic_lex_next(&e->lex);
minic_struct_def_t *def = minic_var_struct(e, name);
if (def == NULL) {
minic_error(e, "'%s' is not a struct", name);
return minic_val_int(0);
}
return minic_struct_field_get(e, name, def, field);
}
if (e->lex.cur.type == TOK_ARROW) {
minic_lex_next(&e->lex); // Consume '->'
char field[64];
strncpy(field, e->lex.cur.text, 63);
minic_lex_next(&e->lex);
minic_struct_def_t *def = minic_var_struct(e, name);
if (def == NULL) {
minic_error(e, "'%s' is not a struct pointer", name);
return minic_val_int(0);
}
minic_val_t base_val = minic_var_get(e, name);
void *base = minic_val_to_ptr(base_val);
minic_val_t field_val = minic_struct_field_get_base(e, base, def, field);
// Handle chained -> or . access (e.g. node->inputs->buffer)
while ((e->lex.cur.type == TOK_ARROW || e->lex.cur.type == TOK_DOT) && !e->error) {
int fidx = minic_struct_field_idx(def, field);
if (fidx < 0 || def->field_struct_names[fidx][0] == '\0')
break;
minic_struct_def_t *next_def = minic_struct_get(e, def->field_struct_names[fidx]);
if (next_def == NULL)
break;
minic_lex_next(&e->lex); // Consume '->' or '.'
strncpy(field, e->lex.cur.text, 63);
minic_lex_next(&e->lex);
base = minic_val_to_ptr(field_val);
def = next_def;
field_val = minic_struct_field_get_base(e, base, def, field);
}
if (e->lex.cur.type == TOK_LBRACKET) {
minic_lex_next(&e->lex); // Consume '['
int idx = (int)minic_val_to_d(minic_parse_expr(e));
minic_expect(e, TOK_RBRACKET);
if (field_val.type == MINIC_T_PTR && field_val.p != NULL) {
switch (field_val.deref_type) {
case MINIC_T_FLOAT:
return minic_val_float(((float *)field_val.p)[idx]);
case MINIC_T_INT:
return minic_val_int(((int32_t *)field_val.p)[idx]);
default:
return minic_val_ptr(((void **)field_val.p)[idx]);
}
}
return minic_val_int(0);
}
return field_val;
}
// If not a variable, check if it's a minic function (pass-as-pointer)
minic_func_t *fn_val = minic_func_get(e, name);
if (fn_val != NULL) {
return minic_val_ptr(fn_val);
}
// Check for known enum constant
{
int ec = minic_enum_const_get(name);
if (ec >= 0)
return minic_val_int(ec);
}
return minic_var_get(e, name);
}
if (e->lex.cur.type == TOK_LPAREN) {
minic_lex_next(&e->lex);
minic_val_t v = minic_parse_expr(e);
minic_lex_next(&e->lex); // Consume ')'
return v;
}
return minic_val_int(0);
}
// term: primary (('*' | '/') primary)*
static minic_val_t minic_parse_term(minic_env_t *e) {
minic_val_t v = minic_parse_primary(e);
while (e->lex.cur.type == TOK_STAR || e->lex.cur.type == TOK_SLASH) {
char op = (e->lex.cur.type == TOK_STAR) ? '*' : '/';
minic_lex_next(&e->lex);
minic_val_t r = minic_parse_primary(e);
v = minic_arith(v, r, op);
}
return v;
}
// expr: term (('+' | '-') term)*
static minic_val_t minic_parse_expr(minic_env_t *e) {
minic_val_t v = minic_parse_term(e);
while (e->lex.cur.type == TOK_PLUS || e->lex.cur.type == TOK_MINUS) {
char op = (e->lex.cur.type == TOK_PLUS) ? '+' : '-';
minic_lex_next(&e->lex);
minic_val_t r = minic_parse_term(e);
v = minic_arith(v, r, op);
}
return v;
}
// cmp: expr (('=='|'!='|'<'|'>'|'<='|'>=') expr)?
static minic_val_t minic_parse_cmp(minic_env_t *e) {
minic_val_t v = minic_parse_expr(e);
minic_tok_type_t op = e->lex.cur.type;
if (op == TOK_EQ || op == TOK_NEQ || op == TOK_LT || op == TOK_GT || op == TOK_LE || op == TOK_GE) {
minic_lex_next(&e->lex);
minic_val_t r = minic_parse_expr(e);
double dv = minic_val_to_d(v);
double dr = minic_val_to_d(r);
int res;
if (op == TOK_EQ)
res = dv == dr;
else if (op == TOK_NEQ)
res = dv != dr;
else if (op == TOK_LT)
res = dv < dr;
else if (op == TOK_GT)
res = dv > dr;
else if (op == TOK_LE)
res = dv <= dr;
else
res = dv >= dr;
return minic_val_int(res);
}
return v;
}
// cond: cmp (('&&' | '||') cmp)*
static minic_val_t minic_parse_cond(minic_env_t *e) {
minic_val_t v = minic_parse_cmp(e);
while (e->lex.cur.type == TOK_AND || e->lex.cur.type == TOK_OR) {
minic_tok_type_t op = e->lex.cur.type;
minic_lex_next(&e->lex);
minic_val_t r = minic_parse_cmp(e);
int vi = minic_val_is_true(v);
int ri = minic_val_is_true(r);
v = minic_val_int(op == TOK_AND ? (vi && ri) : (vi || ri));
}
return v;
}
static void minic_skip_block(minic_env_t *e) {
if (e->lex.cur.type != TOK_LBRACE) {
// Single-statement body: skip until ';'
while (e->lex.cur.type != TOK_SEMICOLON && e->lex.cur.type != TOK_EOF)
minic_lex_next(&e->lex);
minic_lex_next(&e->lex); // Consume ';'
return;
}
minic_lex_next(&e->lex); // Consume '{'
int depth = 1;
while (depth > 0 && e->lex.cur.type != TOK_EOF) {
if (e->lex.cur.type == TOK_LBRACE)
depth++;
if (e->lex.cur.type == TOK_RBRACE)
depth--;
minic_lex_next(&e->lex);
}
}
static minic_type_t minic_tok_to_type(minic_tok_type_t t) {
switch (t) {
case TOK_INT:
case TOK_CHAR:
case TOK_BOOL:
return MINIC_T_INT;
case TOK_FLOAT:
case TOK_DOUBLE:
return MINIC_T_FLOAT;
default:
return MINIC_T_PTR; // void * -> PTR
}
}
static void minic_parse_stmt(minic_env_t *e) {
// Skip bare typedef declarations inside function bodies
if (e->lex.cur.type == TOK_TYPEDEF) {
while (e->lex.cur.type != TOK_SEMICOLON && e->lex.cur.type != TOK_EOF) {
minic_lex_next(&e->lex);
}
if (e->lex.cur.type == TOK_SEMICOLON) {
minic_lex_next(&e->lex);
}
return;
}
if (e->lex.cur.type == TOK_STRUCT) {
minic_lex_next(&e->lex); // Consume 'struct'
char sname[64];
strncpy(sname, e->lex.cur.text, 63);
minic_lex_next(&e->lex); // Consume struct type name
bool is_ptr = false;
if (e->lex.cur.type == TOK_STAR) {
is_ptr = true;
minic_lex_next(&e->lex);
}
char vname[64];
strncpy(vname, e->lex.cur.text, 63);
minic_lex_next(&e->lex);
minic_struct_def_t *def = minic_struct_get(e, sname);
if (def == NULL) {
minic_error(e, "unknown struct '%s'", sname);
return;
}
minic_vartype_set(e, vname, sname);
if (is_ptr) {
minic_expect(e, TOK_ASSIGN);
minic_val_t v = minic_parse_expr(e);
minic_var_decl(e, vname, MINIC_T_PTR, v);
minic_expect(e, TOK_SEMICOLON);
}
else {
void *base = minic_alloc(def->field_count * (int)sizeof(minic_val_t));
memset(base, 0, def->field_count * sizeof(minic_val_t));
if (e->lex.cur.type == TOK_ASSIGN) {
minic_lex_next(&e->lex);
minic_val_t v = minic_parse_expr(e);
if (v.type == MINIC_T_PTR && v.p)
memcpy(base, v.p, def->field_count * sizeof(minic_val_t));
}
minic_var_decl(e, vname, MINIC_T_PTR, minic_val_ptr(base));
minic_expect(e, TOK_SEMICOLON);
}
return;
}
// Typedef'd int name (e.g. from typedef enum): alias_t var = expr;
if (e->lex.cur.type == TOK_IDENT && minic_is_int_typedef(e->lex.cur.text)) {
minic_lex_next(&e->lex); // Consume alias name
char vname[64];
strncpy(vname, e->lex.cur.text, 63);
minic_lex_next(&e->lex); // Consume var name
minic_val_t v = minic_val_int(0);
if (e->lex.cur.type == TOK_ASSIGN) {
minic_lex_next(&e->lex);
v = minic_parse_cond(e);
}
minic_var_decl(e, vname, MINIC_T_INT, minic_val_coerce(minic_val_to_d(v), MINIC_T_INT));
minic_expect(e, TOK_SEMICOLON);
return;
}
// Typedef'd struct name used as variable type: alias_t var; or alias_t *var = ...;
if (e->lex.cur.type == TOK_IDENT) {
minic_struct_def_t *def = minic_struct_get(e, e->lex.cur.text);
if (def != NULL) {
char sname[64];
strncpy(sname, e->lex.cur.text, 63);
minic_lex_next(&e->lex); // Consume alias name
bool is_ptr = (e->lex.cur.type == TOK_STAR);
if (is_ptr) {
minic_lex_next(&e->lex);
}
char vname[64];
strncpy(vname, e->lex.cur.text, 63);
minic_lex_next(&e->lex);
minic_vartype_set(e, vname, sname);
if (is_ptr) {
minic_val_t v = minic_val_ptr(NULL);
if (e->lex.cur.type == TOK_ASSIGN) {
minic_lex_next(&e->lex);
v = minic_parse_expr(e);
}
minic_var_decl(e, vname, MINIC_T_PTR, v);
}
else {
void *base = minic_alloc(def->field_count * (int)sizeof(minic_val_t));
memset(base, 0, def->field_count * sizeof(minic_val_t));
if (e->lex.cur.type == TOK_ASSIGN) {
minic_lex_next(&e->lex);
minic_val_t v = minic_parse_expr(e);
if (v.type == MINIC_T_PTR && v.p)
memcpy(base, v.p, def->field_count * sizeof(minic_val_t));
}
minic_var_decl(e, vname, MINIC_T_PTR, minic_val_ptr(base));
}
minic_expect(e, TOK_SEMICOLON);
return;
}
}
if (e->lex.cur.type == TOK_INT || e->lex.cur.type == TOK_FLOAT || e->lex.cur.type == TOK_CHAR || e->lex.cur.type == TOK_DOUBLE ||
e->lex.cur.type == TOK_BOOL || e->lex.cur.type == TOK_VOID) {
minic_type_t base_type = minic_tok_to_type(e->lex.cur.type); // Type before '*'
minic_type_t dtype = base_type;
minic_lex_next(&e->lex); // Consume type keyword
bool is_ptr = false;
if (e->lex.cur.type == TOK_STAR) {
is_ptr = true;
dtype = MINIC_T_PTR;
minic_lex_next(&e->lex);
}
char name[64];
strncpy(name, e->lex.cur.text, 63);
minic_lex_next(&e->lex);
if (e->lex.cur.type == TOK_LBRACKET) {
minic_lex_next(&e->lex); // Consume '['
int count = (int)minic_val_to_d(minic_parse_expr(e));
minic_expect(e, TOK_RBRACKET);
minic_arr_decl(e, name, count, is_ptr ? MINIC_T_PTR : dtype);
minic_expect(e, TOK_SEMICOLON);
return;
}
minic_expect(e, TOK_ASSIGN);
minic_val_t v = minic_parse_cond(e);
if (is_ptr) {
v.type = MINIC_T_PTR;
if (v.p == NULL) {
// NULL literal passed as integer 0 — convert
uintptr_t ua = (uintptr_t)(uint64_t)minic_val_to_d(v);
v.p = (ua == 0) ? NULL : (void *)ua;
}
// Only stamp the declared element type for native C pointers.
// Pointers into the active arena (e.g. from &var) use MINIC_T_PTR sentinel
// to signal that dereferencing reads a full minic_val_t.
bool in_minic = (v.p != NULL && (minic_u8 *)v.p >= minic_active_mem && (minic_u8 *)v.p < minic_active_mem + MINIC_MEM_SIZE);
if (!in_minic) {
v.deref_type = base_type;
}
}
minic_var_decl(e, name, dtype, v);
minic_expect(e, TOK_SEMICOLON);
return;
}
if (e->lex.cur.type == TOK_RETURN) {
minic_lex_next(&e->lex);
minic_val_t v = minic_parse_cond(e);
e->return_val = v;
e->returning = true;
minic_expect(e, TOK_SEMICOLON);
return;
}
if (e->lex.cur.type == TOK_IDENT) {
char name[64];
strncpy(name, e->lex.cur.text, 63);
minic_lex_next(&e->lex);
// Unknown opaque type followed by '*' + ident: local pointer declaration.
// e.g. ui_handle_t *h; or MyType *p = create_p();
if (e->lex.cur.type == TOK_STAR) {
minic_lex_next(&e->lex); // Consume '*'
char vname[64];
strncpy(vname, e->lex.cur.text, 63);
minic_lex_next(&e->lex); // Consume var name
minic_val_t v = minic_val_ptr(NULL);
if (e->lex.cur.type == TOK_ASSIGN) {
minic_lex_next(&e->lex);
v = minic_parse_expr(e);
}
minic_var_decl(e, vname, MINIC_T_PTR, v);
minic_expect(e, TOK_SEMICOLON);
return;
}
if (e->lex.cur.type == TOK_DOT || e->lex.cur.type == TOK_ARROW) {
bool is_arrow = (e->lex.cur.type == TOK_ARROW);
minic_lex_next(&e->lex);
char field[64];
strncpy(field, e->lex.cur.text, 63);
minic_lex_next(&e->lex);
minic_struct_def_t *def = minic_var_struct(e, name);
if (def == NULL) {
minic_error(e, "'%s' is not a struct%s", name, is_arrow ? " pointer" : "");
return;
}
void *base;
if (is_arrow) {
minic_val_t base_val = minic_var_get(e, name);
base = minic_val_to_ptr(base_val);
}
else {
minic_val_t base_val = minic_var_get(e, name);
base = minic_val_to_ptr(base_val);
}
if (e->lex.cur.type == TOK_LBRACKET) {
minic_lex_next(&e->lex);
int idx = (int)minic_val_to_d(minic_parse_expr(e));
minic_expect(e, TOK_RBRACKET);
minic_expect(e, TOK_ASSIGN);
minic_val_t v = minic_parse_expr(e);
minic_val_t field_val = minic_struct_field_get_base(e, base, def, field);
if (field_val.type == MINIC_T_PTR && field_val.p != NULL) {
switch (field_val.deref_type) {
case MINIC_T_FLOAT:
((float *)field_val.p)[idx] = (float)minic_val_to_d(v);
break;
case MINIC_T_INT:
((int32_t *)field_val.p)[idx] = (int32_t)minic_val_to_d(v);
break;
default:
((void **)field_val.p)[idx] = minic_val_to_ptr(v);
break;
}
}
}
else {
minic_expect(e, TOK_ASSIGN);
minic_val_t v = minic_parse_expr(e);
minic_struct_field_set_base(e, base, def, field, v);
}
minic_expect(e, TOK_SEMICOLON);
return;
}
if (e->lex.cur.type == TOK_LPAREN) {
minic_lex_next(&e->lex);
minic_val_t args[MINIC_MAX_PARAMS];
int argc = 0;
while (e->lex.cur.type != TOK_RPAREN && e->lex.cur.type != TOK_EOF) {
args[argc++] = minic_parse_expr(e);
if (e->lex.cur.type == TOK_COMMA) {
minic_lex_next(&e->lex);
}
}
minic_expect(e, TOK_RPAREN);
minic_func_t *fn = minic_func_get(e, name);
if (fn != NULL) {
minic_call(e, fn, args, argc);
}
else {
minic_ext_func_t *ext = minic_ext_func_get(name);
if (ext != NULL) {
minic_dispatch(ext, args, argc);
}
else {
minic_error(e, "unknown function '%s'", name);
}
}
minic_expect(e, TOK_SEMICOLON);
return;
}
if (e->lex.cur.type == TOK_INC || e->lex.cur.type == TOK_DEC) {
double delta = MINIC_INC_DELTA(&e->lex);
minic_lex_next(&e->lex);
minic_val_t ov = minic_var_get(e, name);
minic_var_set(e, name, minic_val_coerce(minic_val_to_d(ov) + delta, ov.type));
minic_expect(e, TOK_SEMICOLON);
return;
}
if (e->lex.cur.type == TOK_PLUS_ASSIGN || e->lex.cur.type == TOK_MINUS_ASSIGN || e->lex.cur.type == TOK_MUL_ASSIGN ||
e->lex.cur.type == TOK_DIV_ASSIGN) {
minic_tok_type_t op = e->lex.cur.type;
minic_lex_next(&e->lex);
minic_val_t dv = minic_parse_expr(e);
minic_val_t ov = minic_var_get(e, name);
double a = minic_val_to_d(ov), b = minic_val_to_d(dv);
double r = (op == TOK_PLUS_ASSIGN) ? a + b : (op == TOK_MINUS_ASSIGN) ? a - b : (op == TOK_MUL_ASSIGN) ? a * b : (b != 0.0 ? a / b : 0.0);
minic_var_set(e, name, minic_val_coerce(r, ov.type));
minic_expect(e, TOK_SEMICOLON);
return;
}
if (e->lex.cur.type == TOK_LBRACKET) {
minic_lex_next(&e->lex);
int idx = (int)minic_val_to_d(minic_parse_expr(e));
minic_expect(e, TOK_RBRACKET);
minic_expect(e, TOK_ASSIGN);
minic_val_t v = minic_parse_expr(e);
minic_arr_elem_set(e, name, idx, v);
minic_expect(e, TOK_SEMICOLON);
return;
}
minic_expect(e, TOK_ASSIGN);
minic_val_t v = minic_parse_expr(e);
minic_var_set(e, name, v);
minic_expect(e, TOK_SEMICOLON);
return;
}
if (e->lex.cur.type == TOK_IF) {
minic_lex_next(&e->lex);
minic_expect(e, TOK_LPAREN);
int taken = minic_val_is_true(minic_parse_cond(e));
minic_expect(e, TOK_RPAREN);
if (taken) {
minic_parse_block(e);
}
else {
minic_skip_block(e);
taken = 0;
}
while (!taken && e->lex.cur.type == TOK_ELSE) {
minic_lex_next(&e->lex);
if (e->lex.cur.type == TOK_IF) {
minic_lex_next(&e->lex);
minic_expect(e, TOK_LPAREN);
int c2 = minic_val_is_true(minic_parse_cond(e));
minic_expect(e, TOK_RPAREN);
if (c2) {
minic_parse_block(e);
taken = 1;
}
else {
minic_skip_block(e);
}
}
else {
minic_parse_block(e);
taken = 1;
}
}
while (e->lex.cur.type == TOK_ELSE) {
minic_lex_next(&e->lex);
if (e->lex.cur.type == TOK_IF) {
minic_lex_next(&e->lex);
minic_expect(e, TOK_LPAREN);
minic_parse_cond(e);
minic_expect(e, TOK_RPAREN);
}
minic_skip_block(e);
}
return;
}
if (e->lex.cur.type == TOK_FOR) {
minic_lex_next(&e->lex);
minic_expect(e, TOK_LPAREN);
if (e->lex.cur.type == TOK_INT || e->lex.cur.type == TOK_FLOAT || e->lex.cur.type == TOK_CHAR || e->lex.cur.type == TOK_DOUBLE ||
e->lex.cur.type == TOK_BOOL || e->lex.cur.type == TOK_VOID) {
minic_lex_next(&e->lex);
}
{
char iname[64];
strncpy(iname, e->lex.cur.text, 63);
minic_lex_next(&e->lex);
minic_expect(e, TOK_ASSIGN);
minic_val_t iv = minic_parse_expr(e);
minic_var_set(e, iname, iv);
}
int cond_pos = e->lex.pos;
minic_lex_next(&e->lex); // Consume ';'
int incr_pos, body_pos;
{
minic_lexer_t tmp = {0};
tmp.src = e->lex.src;
tmp.pos = cond_pos;
minic_lex_next(&tmp);
while (tmp.cur.type != TOK_SEMICOLON && tmp.cur.type != TOK_EOF) {
minic_lex_next(&tmp);
}
incr_pos = tmp.pos;
minic_lex_next(&tmp);
while (tmp.cur.type != TOK_RPAREN && tmp.cur.type != TOK_EOF) {
minic_lex_next(&tmp);
}
minic_lex_next(&tmp);
body_pos = tmp.pos - 1;
}
for (;;) {
e->continuing = false;
e->lex.pos = cond_pos;
minic_lex_next(&e->lex);
int cond = minic_val_is_true(minic_parse_cond(e));
if (!cond || e->returning || e->breaking) {
e->lex.pos = body_pos;
minic_lex_next(&e->lex);
minic_skip_block(e);
e->breaking = false;
break;
}
e->lex.pos = body_pos;
minic_lex_next(&e->lex);
minic_parse_block(e);
if (e->returning || e->breaking) {
e->breaking = false;
break;
}
e->lex.pos = incr_pos;
minic_lex_next(&e->lex);
if (e->lex.cur.type == TOK_INC || e->lex.cur.type == TOK_DEC) {
double delta = MINIC_INC_DELTA(&e->lex);
minic_lex_next(&e->lex);
char iname[64];
strncpy(iname, e->lex.cur.text, 63);
minic_val_t ov = minic_var_get(e, iname);
minic_var_set(e, iname, minic_val_coerce(minic_val_to_d(ov) + delta, ov.type));
}
else if (e->lex.cur.type == TOK_IDENT) {
char iname[64];
strncpy(iname, e->lex.cur.text, 63);
minic_lex_next(&e->lex);
if (e->lex.cur.type == TOK_INC || e->lex.cur.type == TOK_DEC) {
double delta = MINIC_INC_DELTA(&e->lex);
minic_val_t ov = minic_var_get(e, iname);
minic_var_set(e, iname, minic_val_coerce(minic_val_to_d(ov) + delta, ov.type));
}
else if (e->lex.cur.type == TOK_PLUS_ASSIGN || e->lex.cur.type == TOK_MINUS_ASSIGN || e->lex.cur.type == TOK_MUL_ASSIGN ||
e->lex.cur.type == TOK_DIV_ASSIGN) {
minic_tok_type_t op = e->lex.cur.type;
minic_lex_next(&e->lex);
minic_val_t dv = minic_parse_expr(e);
minic_val_t ov = minic_var_get(e, iname);
double a = minic_val_to_d(ov), b = minic_val_to_d(dv);
double r = (op == TOK_PLUS_ASSIGN) ? a + b : (op == TOK_MINUS_ASSIGN) ? a - b : (op == TOK_MUL_ASSIGN) ? a * b : (b != 0.0 ? a / b : 0.0);
minic_var_set(e, iname, minic_val_coerce(r, ov.type));
}
else if (e->lex.cur.type == TOK_ASSIGN) {
minic_lex_next(&e->lex);
minic_val_t v = minic_parse_expr(e);
minic_var_set(e, iname, v);
}
}
}
return;
}
if (e->lex.cur.type == TOK_STAR) {
// Pointer write: *expr = val; or *expr += val; or *expr++; etc.
minic_lex_next(&e->lex);
minic_val_t pv = minic_parse_primary(e);
void *ptr = minic_val_to_ptr(pv);
if (e->lex.cur.type == TOK_INC || e->lex.cur.type == TOK_DEC) {
double delta = MINIC_INC_DELTA(&e->lex);
minic_lex_next(&e->lex);
if (ptr != NULL) {
switch (pv.deref_type) {
case MINIC_T_INT: {
int ov = 0;
memcpy(&ov, ptr, sizeof(int));
ov += (int)delta;
memcpy(ptr, &ov, sizeof(int));
break;
}
case MINIC_T_FLOAT: {
float ov = 0.0f;
memcpy(&ov, ptr, sizeof(float));
ov += (float)delta;
memcpy(ptr, &ov, sizeof(float));
break;
}
default: {
minic_val_t ov;
memcpy(&ov, ptr, sizeof(minic_val_t));
minic_val_t nv = minic_val_coerce(minic_val_to_d(ov) + delta, ov.type);
memcpy(ptr, &nv, sizeof(minic_val_t));
break;
}
}
}
minic_expect(e, TOK_SEMICOLON);
return;
}
minic_tok_type_t op = e->lex.cur.type; // TOK_ASSIGN, TOK_PLUS_ASSIGN, TOK_MINUS_ASSIGN, TOK_MUL_ASSIGN, TOK_DIV_ASSIGN
minic_lex_next(&e->lex);
minic_val_t v = minic_parse_expr(e);
if (ptr != NULL) {
switch (pv.deref_type) {
case MINIC_T_INT: {
int ov = 0;
memcpy(&ov, ptr, sizeof(int));
double b = minic_val_to_d(v);
double r = (op == TOK_PLUS_ASSIGN) ? ov + b
: (op == TOK_MINUS_ASSIGN) ? ov - b
: (op == TOK_MUL_ASSIGN) ? ov * b
: (op == TOK_DIV_ASSIGN) ? (b != 0.0 ? ov / b : 0.0)
: b;
int iv = (int)r;
memcpy(ptr, &iv, sizeof(int));
break;
}
case MINIC_T_FLOAT: {
float ov = 0.0f;
memcpy(&ov, ptr, sizeof(float));
double b = minic_val_to_d(v);
double r = (op == TOK_PLUS_ASSIGN) ? ov + b
: (op == TOK_MINUS_ASSIGN) ? ov - b
: (op == TOK_MUL_ASSIGN) ? ov * b
: (op == TOK_DIV_ASSIGN) ? (b != 0.0 ? ov / b : 0.0)
: b;
float fv = (float)r;
memcpy(ptr, &fv, sizeof(float));
break;
}
default: {
minic_val_t ov;
memcpy(&ov, ptr, sizeof(minic_val_t));
double a = minic_val_to_d(ov), b = minic_val_to_d(v);
double r = (op == TOK_PLUS_ASSIGN) ? a + b
: (op == TOK_MINUS_ASSIGN) ? a - b
: (op == TOK_MUL_ASSIGN) ? a * b
: (op == TOK_DIV_ASSIGN) ? (b != 0.0 ? a / b : 0.0)
: b;
minic_val_t nv = (op == TOK_ASSIGN) ? v : minic_val_coerce(r, ov.type);
memcpy(ptr, &nv, sizeof(minic_val_t));
break;
}
}
}
minic_expect(e, TOK_SEMICOLON);
return;
}
if (e->lex.cur.type == TOK_BREAK) {
minic_lex_next(&e->lex);
minic_expect(e, TOK_SEMICOLON);
e->breaking = true;
return;
}
if (e->lex.cur.type == TOK_CONTINUE) {
minic_lex_next(&e->lex);
minic_expect(e, TOK_SEMICOLON);
e->continuing = true;
return;
}
if (e->lex.cur.type == TOK_WHILE) {
minic_lex_next(&e->lex);
int cond_pos = e->lex.pos - 1;
for (;;) {
e->continuing = false;
e->lex.pos = cond_pos;
minic_lex_next(&e->lex);
minic_lex_next(&e->lex); // Consume '('
int cond = minic_val_is_true(minic_parse_cond(e));
minic_lex_next(&e->lex); // Consume ')'
if (!cond || e->returning || e->breaking) {
minic_skip_block(e);
e->breaking = false;
break;
}
minic_parse_block(e);
if (e->breaking) {
e->breaking = false;
break;
}
}
return;
}
minic_error(e, "unexpected token at start of statement");
}
static void minic_parse_block(minic_env_t *e) {
int saved_var_count = e->var_count;
int saved_vartype_count = e->vartype_count;
if (e->lex.cur.type != TOK_LBRACE) {
// Single-statement body without braces
minic_parse_stmt(e);
e->var_count = saved_var_count;
e->vartype_count = saved_vartype_count;
return;
}
minic_expect(e, TOK_LBRACE);
while (e->lex.cur.type != TOK_RBRACE && e->lex.cur.type != TOK_EOF && !e->returning && !e->breaking && !e->continuing && !e->error) {
minic_parse_stmt(e);
}
if (e->lex.cur.type != TOK_RBRACE) {
int depth = 1;
while (depth > 0 && e->lex.cur.type != TOK_EOF) {
if (e->lex.cur.type == TOK_LBRACE)
depth++;
if (e->lex.cur.type == TOK_RBRACE)
depth--;
minic_lex_next(&e->lex);
}
}
else {
minic_lex_next(&e->lex); // Consume '}'
}
e->var_count = saved_var_count;
e->vartype_count = saved_vartype_count;
}
// ██████╗ ██╗ ██╗███╗ ██╗
// ██╔══██╗██║ ██║████╗ ██║
// ██████╔╝██║ ██║██╔██╗ ██║
// ██╔══██╗██║ ██║██║╚██╗██║
// ██║ ██║╚██████╔╝██║ ╚████║
// ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝
// Consume a type keyword (int, float, char, double, bool, void), return true if found
static bool minic_lex_type(minic_env_t *e) {
if (e->lex.cur.type == TOK_INT || e->lex.cur.type == TOK_FLOAT || e->lex.cur.type == TOK_CHAR || e->lex.cur.type == TOK_DOUBLE ||
e->lex.cur.type == TOK_BOOL || e->lex.cur.type == TOK_VOID) {
minic_lex_next(&e->lex);
return true;
}
if (e->lex.cur.type == TOK_STRUCT) {
minic_lex_next(&e->lex); // Consume 'struct'
minic_lex_next(&e->lex); // Consume struct name
return true;
}
// Typedef'd struct name used as a type specifier
if (e->lex.cur.type == TOK_IDENT && minic_struct_get(e, e->lex.cur.text) != NULL) {
minic_lex_next(&e->lex);
return true;
}
// Typedef'd int name (e.g. from typedef enum) used as a type specifier
if (e->lex.cur.type == TOK_IDENT && minic_is_int_typedef(e->lex.cur.text)) {
minic_lex_next(&e->lex);
return true;
}
return false;
}
// Zero pass: scan for struct definitions or typedef struct
static void minic_register_structs(minic_env_t *e) {
minic_lexer_t l = {0};
l.src = e->lex.src;
l.pos = 0;
minic_lex_next(&l);
while (l.cur.type != TOK_EOF) {
bool is_typedef = (l.cur.type == TOK_TYPEDEF);
if (is_typedef) {
minic_lex_next(&l); // Consume 'typedef'
}
if (l.cur.type == TOK_ENUM) {
minic_lex_next(&l); // Consume 'enum'
if (l.cur.type == TOK_IDENT) {
minic_lex_next(&l); // Optional tag name
}
if (l.cur.type != TOK_LBRACE) {
continue;
}
minic_lex_next(&l); // Consume '{'
int val = 0;
while (l.cur.type != TOK_RBRACE && l.cur.type != TOK_EOF) {
if (l.cur.type == TOK_IDENT && minic_enum_const_count < MINIC_MAX_ENUM_CONSTS) {
strncpy(minic_enum_consts[minic_enum_const_count].name, l.cur.text, 63);
minic_enum_consts[minic_enum_const_count].value = val;
minic_enum_const_count++;
minic_lex_next(&l);
if (l.cur.type == TOK_ASSIGN) {
minic_lex_next(&l); // Consume '='
val = (int)minic_val_to_d(l.cur.val);
minic_enum_consts[minic_enum_const_count - 1].value = val;
minic_lex_next(&l); // Consume number
}
val++;
}
else {
minic_lex_next(&l);
}
if (l.cur.type == TOK_COMMA) {
minic_lex_next(&l);
}
}
if (l.cur.type == TOK_RBRACE) {
minic_lex_next(&l);
}
if (is_typedef && l.cur.type == TOK_IDENT && minic_int_typedef_count < MINIC_MAX_INT_TYPEDEFS) {
strncpy(minic_int_typedefs[minic_int_typedef_count++].name, l.cur.text, 63);
minic_lex_next(&l);
}
while (l.cur.type != TOK_SEMICOLON && l.cur.type != TOK_EOF) {
minic_lex_next(&l);
}
if (l.cur.type == TOK_SEMICOLON) {
minic_lex_next(&l);
}
continue;
}
if (l.cur.type != TOK_STRUCT) {
minic_lex_next(&l);
continue;
}
minic_lex_next(&l); // Consume 'struct'
// Optional struct tag name
char struct_name[64] = "";
if (l.cur.type == TOK_IDENT) {
strncpy(struct_name, l.cur.text, 63);
minic_lex_next(&l); // Consume struct name
}
if (l.cur.type != TOK_LBRACE) {
continue; // Forward decl or typedef-without-body — skip
}
if (e->struct_count >= e->struct_cap) {
break;
}
minic_struct_def_t *def = &e->structs[e->struct_count];
strncpy(def->name, struct_name, 63);
def->field_count = 0;
minic_lex_next(&l); // Consume '{'
while (l.cur.type != TOK_RBRACE && l.cur.type != TOK_EOF) {
if (l.cur.type == TOK_STRUCT) {
minic_lex_next(&l);
minic_lex_next(&l);
}
else if (l.cur.type == TOK_INT || l.cur.type == TOK_FLOAT || l.cur.type == TOK_CHAR || l.cur.type == TOK_DOUBLE || l.cur.type == TOK_BOOL) {
minic_lex_next(&l);
}
else if (l.cur.type == TOK_IDENT) {
minic_lex_next(&l); // Typedef'd field type — consume and fall through to field name
}
else {
minic_lex_next(&l);
continue;
}
if (l.cur.type == TOK_STAR) {
minic_lex_next(&l);
}
if (l.cur.type == TOK_IDENT && def->field_count < MINIC_MAX_STRUCT_FIELDS) {
strncpy(def->fields[def->field_count++], l.cur.text, 63);
minic_lex_next(&l);
}
while (l.cur.type != TOK_SEMICOLON && l.cur.type != TOK_RBRACE && l.cur.type != TOK_EOF) {
minic_lex_next(&l);
}
if (l.cur.type == TOK_SEMICOLON) {
minic_lex_next(&l);
}
}
if (l.cur.type == TOK_RBRACE) {
minic_lex_next(&l);
}
if (is_typedef && l.cur.type == TOK_IDENT) {
// typedef struct [Name] { ... } alias;
char alias[64];
strncpy(alias, l.cur.text, 63);
minic_lex_next(&l); // Consume alias name
if (struct_name[0]) {
// Register under struct tag name first
e->struct_count++;
// Also register a copy under the alias name
if (e->struct_count < e->struct_cap) {
minic_struct_def_t *adef = &e->structs[e->struct_count];
*adef = *def;
strncpy(adef->name, alias, 63);
e->struct_count++;
}
}
else {
// Anonymous struct: name it after the alias
strncpy(def->name, alias, 63);
e->struct_count++;
}
}
else {
// Plain struct definition: must have a tag name to be usable
if (struct_name[0]) {
e->struct_count++;
}
}
while (l.cur.type != TOK_SEMICOLON && l.cur.type != TOK_EOF) {
minic_lex_next(&l);
}
if (l.cur.type == TOK_SEMICOLON) {
minic_lex_next(&l);
}
}
}
// First pass: register all function definitions, stop at 'main'
static void minic_register_funcs(minic_env_t *e) {
while (e->lex.cur.type != TOK_EOF) {
// Remember the return-type token before consuming it
minic_type_t ret_type = minic_tok_to_type(e->lex.cur.type);
char decl_struct[64] = "";
if (e->lex.cur.type == TOK_IDENT && minic_struct_get(e, e->lex.cur.text) != NULL) {
strncpy(decl_struct, e->lex.cur.text, 63);
}
if (!minic_lex_type(e)) {
if (e->lex.cur.type == TOK_IDENT) {
// Unknown typedef'd type (e.g. ui_handle_t) — consume it and
// treat as an opaque pointer type so the variable/function is
// registered properly.
strncpy(decl_struct, e->lex.cur.text, 63);
minic_lex_next(&e->lex);
ret_type = MINIC_T_PTR;
}
else {
minic_lex_next(&e->lex);
continue;
}
}
if (e->lex.cur.type == TOK_STAR) {
ret_type = MINIC_T_PTR;
minic_lex_next(&e->lex);
}
if (e->lex.cur.type != TOK_IDENT) {
continue;
}
char fname[64];
strncpy(fname, e->lex.cur.text, 63);
minic_lex_next(&e->lex);
if (e->lex.cur.type != TOK_LPAREN) {
// Global variable declaration: type [*] ident [= expr] ;
if (decl_struct[0] != '\0') {
minic_vartype_set(e, fname, decl_struct);
}
minic_val_t init = minic_val_coerce(0.0, ret_type);
if (e->lex.cur.type == TOK_ASSIGN) {
minic_lex_next(&e->lex); // Consume '='
e->decl_type = ret_type;
init = minic_parse_expr(e);
}
minic_var_decl(e, fname, ret_type, init);
while (e->lex.cur.type != TOK_SEMICOLON && e->lex.cur.type != TOK_EOF) {
minic_lex_next(&e->lex);
}
if (e->lex.cur.type == TOK_SEMICOLON) {
minic_lex_next(&e->lex);
}
continue;
}
minic_lex_next(&e->lex); // Consume '('
minic_func_t fn = {0};
strncpy(fn.name, fname, 63);
fn.ret_type = ret_type;
while (e->lex.cur.type != TOK_RPAREN && e->lex.cur.type != TOK_EOF) {
char pstruct[64] = "";
minic_type_t ptype = MINIC_T_INT;
if (e->lex.cur.type == TOK_STRUCT) {
minic_lex_next(&e->lex);
strncpy(pstruct, e->lex.cur.text, 63);
minic_lex_next(&e->lex);
ptype = MINIC_T_PTR;
}
else {
// Capture typedef'd struct name before consuming the type token
if (e->lex.cur.type == TOK_IDENT && minic_struct_get(e, e->lex.cur.text) != NULL) {
strncpy(pstruct, e->lex.cur.text, 63);
}
ptype = minic_tok_to_type(e->lex.cur.type);
minic_lex_type(e); // consume type keyword
}
if (e->lex.cur.type == TOK_STAR) {
ptype = MINIC_T_PTR;
minic_lex_next(&e->lex);
}
if (e->lex.cur.type == TOK_IDENT && fn.param_count < MINIC_MAX_PARAMS) {
int pi = fn.param_count++;
strncpy(fn.params[pi], e->lex.cur.text, 63);
strncpy(fn.param_structs[pi], pstruct, 63);
fn.param_types[pi] = ptype;
minic_lex_next(&e->lex);
}
if (e->lex.cur.type == TOK_COMMA) {
minic_lex_next(&e->lex);
}
}
minic_lex_next(&e->lex); // Consume ')'
fn.body_pos = e->lex.pos - 1;
if (strcmp(fname, "main") == 0) {
break;
}
if (e->func_count < e->func_cap) {
e->funcs[e->func_count++] = fn;
}
// Skip function body
int depth = 1;
minic_lex_next(&e->lex); // Consume '{'
while (depth > 0 && e->lex.cur.type != TOK_EOF) {
if (e->lex.cur.type == TOK_LBRACE)
depth++;
if (e->lex.cur.type == TOK_RBRACE)
depth--;
minic_lex_next(&e->lex);
}
}
}
minic_ctx_t *minic_eval_named(const char *src, const char *filename) {
minic_register_builtins();
minic_ctx_t *ctx = (minic_ctx_t *)malloc(sizeof(minic_ctx_t));
memset(ctx, 0, sizeof(minic_ctx_t));
ctx->mem = (minic_u8 *)malloc(MINIC_MEM_SIZE);
ctx->mem_used = 0;
// Copy source so the context stays valid after the caller frees its buffer
int src_len = (int)strlen(src);
ctx->src_copy = (char *)malloc(src_len + 1);
memcpy(ctx->src_copy, src, src_len + 1);
// Save and install arena pointers so minic_alloc and the lexer use this context
minic_u8 *prev_mem = minic_active_mem;
int *prev_mem_used = minic_active_mem_used;
minic_active_mem = ctx->mem;
minic_active_mem_used = &ctx->mem_used;
int var_cap = 64;
int arr_cap = 32;
int arr_data_cap = 512;
int func_cap = 32;
int struct_cap = MINIC_MAX_STRUCTS;
minic_env_t *e = &ctx->e;
e->lex.src = ctx->src_copy;
e->lex.pos = 0;
e->filename = filename;
e->var_cap = var_cap;
e->vars = minic_alloc(var_cap * sizeof(minic_var_t));
e->arr_cap = arr_cap;
e->arrs = minic_alloc(arr_cap * sizeof(minic_arr_t));
e->arr_data = minic_alloc(arr_data_cap * sizeof(minic_val_t));
e->arr_data_used = minic_alloc(sizeof(int));
*e->arr_data_used = 0;
e->func_cap = func_cap;
e->funcs = minic_alloc(func_cap * sizeof(minic_func_t));
e->struct_cap = struct_cap;
e->structs = minic_alloc(struct_cap * sizeof(minic_struct_def_t));
e->vartype_cap = 64;
e->vartypes = minic_alloc(e->vartype_cap * sizeof(minic_vartype_t));
// Seed env with globally pre-registered struct definitions
for (int i = 0; i < minic_global_struct_count && e->struct_count < e->struct_cap; ++i) {
minic_struct_def_t *dst = &e->structs[e->struct_count++];
strncpy(dst->name, minic_global_structs[i].name, 63);
dst->field_count = minic_global_structs[i].field_count;
dst->size = minic_global_structs[i].size;
dst->has_native_layout = minic_global_structs[i].has_native_layout;
for (int j = 0; j < dst->field_count; ++j) {
strncpy(dst->fields[j], minic_global_structs[i].fields[j], 63);
strncpy(dst->field_struct_names[j], minic_global_structs[i].field_struct_names[j], 63);
dst->field_offsets[j] = minic_global_structs[i].field_offsets[j];
dst->field_native_types[j] = minic_global_structs[i].field_native_types[j];
dst->field_deref_types[j] = minic_global_structs[i].field_deref_types[j];
}
}
minic_register_structs(e);
minic_lex_next(&e->lex);
minic_register_funcs(e);
for (int i = 0; i < e->func_count; ++i) {
e->funcs[i].ctx = ctx;
}
if (e->lex.cur.type == TOK_RPAREN) {
minic_lex_next(&e->lex);
}
minic_env_t *prev_env = minic_active_env;
minic_active_env = e;
minic_parse_block(e);
minic_active_env = prev_env;
minic_active_mem = prev_mem;
minic_active_mem_used = prev_mem_used;
ctx->result = e->error ? -1.0f : (float)minic_val_to_d(e->return_val);
return ctx;
}
minic_ctx_t *minic_eval(const char *src) {
return minic_eval_named(src, "<script>");
}
void minic_ctx_free(minic_ctx_t *ctx) {
if (ctx) {
free(ctx->mem);
free(ctx->src_copy);
free(ctx);
}
}
float minic_ctx_result(minic_ctx_t *ctx) {
return ctx ? ctx->result : -1.0f;
}
minic_val_t minic_ctx_call_fn(minic_ctx_t *ctx, void *fn_ptr, minic_val_t *args, int argc) {
if (!ctx || !fn_ptr) {
return minic_val_int(0);
}
minic_u8 *prev_mem = minic_active_mem;
int *prev_mem_used = minic_active_mem_used;
minic_env_t *prev_env = minic_active_env;
minic_active_mem = ctx->mem;
minic_active_mem_used = &ctx->mem_used;
minic_active_env = &ctx->e;
int saved_mem_used = ctx->mem_used;
minic_val_t r = minic_call(&ctx->e, (minic_func_t *)fn_ptr, args, argc);
ctx->mem_used = saved_mem_used;
minic_active_env = prev_env;
minic_active_mem = prev_mem;
minic_active_mem_used = prev_mem_used;
return r;
}