From c4c198bcd24387ea3386c64c225e103c35c206fb Mon Sep 17 00:00:00 2001 From: luboslenco Date: Fri, 11 Sep 2026 10:26:30 +0200 Subject: [PATCH] libs: minic fixes --- base/sources/libs/minic.c | 2223 +++++++++++++++---------------- base/sources/libs/minic.h | 42 +- base/sources/libs/minic_tests.c | 293 +++- 3 files changed, 1421 insertions(+), 1137 deletions(-) diff --git a/base/sources/libs/minic.c b/base/sources/libs/minic.c index 24b5e13b0..45780265c 100644 --- a/base/sources/libs/minic.c +++ b/base/sources/libs/minic.c @@ -17,28 +17,29 @@ // ██║ ╚██████╔╝██║ ██╗███████╗██║ ╚████║ // ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═══╝ -#define MINIC_TOK_LIST \ - X(TOK_INT, "'int'") \ - X(TOK_FLOAT, "'float'") \ - X(TOK_CHAR, "'char'") \ - X(TOK_DOUBLE, "'double'") \ - X(TOK_BOOL, "'bool'") \ - X(TOK_RETURN, "'return'") \ - X(TOK_IF, "'if'") \ - X(TOK_ELSE, "'else'") \ - X(TOK_WHILE, "'while'") \ - X(TOK_FOR, "'for'") \ - X(TOK_BREAK, "'break'") \ - X(TOK_CONTINUE, "'continue'") \ - X(TOK_STRUCT, "'struct'") \ - X(TOK_TYPEDEF, "'typedef'") X(TOK_ENUM, "'enum'") X(TOK_VOID, "'void'") X(TOK_IDENT, "identifier") X(TOK_NUMBER, "number") X(TOK_CHAR_LIT, "char literal") \ - X(TOK_STR_LIT, "string literal") X(TOK_LPAREN, "'('") X(TOK_RPAREN, "')'") X(TOK_LBRACE, "'{'") X(TOK_RBRACE, "'}'") X(TOK_LBRACKET, "'['") \ - X(TOK_RBRACKET, "']'") X(TOK_SEMICOLON, "';'") X(TOK_COMMA, "','") X(TOK_ASSIGN, "'='") X(TOK_PLUS_ASSIGN, "'+='") X(TOK_MINUS_ASSIGN, "'-='") \ - X(TOK_MUL_ASSIGN, "'*='") X(TOK_DIV_ASSIGN, "'/='") X(TOK_MOD_ASSIGN, "'%='") X(TOK_SHL_ASSIGN, "'<<='") X(TOK_SHR_ASSIGN, "'>>='") \ - X(TOK_AND_ASSIGN, "'&='") X(TOK_OR_ASSIGN, "'|='") X(TOK_XOR_ASSIGN, "'^='") X(TOK_EQ, "'=='") X(TOK_NEQ, "'!='") X(TOK_LT, "'<'") \ - X(TOK_GT, "'>'") X(TOK_LE, "'<='") X(TOK_GE, "'>='") X(TOK_AND, "'&&'") X(TOK_OR, "'||'") X(TOK_NOT, "'!'") X(TOK_AMP, "'&'") \ - X(TOK_PLUS, "'+'") X(TOK_MINUS, "'-'") X(TOK_INC, "'++'") X(TOK_DEC, "'--'") X(TOK_STAR, "'*'") X(TOK_SLASH, "'/'") \ - X(TOK_PERCENT, "'%'") X(TOK_SHL, "'<<'") X(TOK_SHR, "'>>'") X(TOK_BITOR, "'|'") X(TOK_XOR, "'^'") X(TOK_BITNOT, "'~'") \ +#define MINIC_TOK_LIST \ + X(TOK_INT, "'int'") \ + X(TOK_FLOAT, "'float'") \ + X(TOK_CHAR, "'char'") \ + X(TOK_DOUBLE, "'double'") \ + X(TOK_BOOL, "'bool'") \ + X(TOK_RETURN, "'return'") \ + X(TOK_IF, "'if'") \ + X(TOK_ELSE, "'else'") \ + X(TOK_WHILE, "'while'") \ + X(TOK_FOR, "'for'") \ + X(TOK_BREAK, "'break'") \ + X(TOK_CONTINUE, "'continue'") \ + X(TOK_STRUCT, "'struct'") \ + X(TOK_TYPEDEF, "'typedef'") \ + X(TOK_ENUM, "'enum'") X(TOK_VOID, "'void'") X(TOK_IDENT, "identifier") X(TOK_NUMBER, "number") X(TOK_CHAR_LIT, "char literal") \ + X(TOK_STR_LIT, "string literal") X(TOK_LPAREN, "'('") X(TOK_RPAREN, "')'") X(TOK_LBRACE, "'{'") X(TOK_RBRACE, "'}'") X(TOK_LBRACKET, "'['") \ + X(TOK_RBRACKET, "']'") X(TOK_SEMICOLON, "';'") X(TOK_COMMA, "','") X(TOK_ASSIGN, "'='") X(TOK_PLUS_ASSIGN, "'+='") X(TOK_MINUS_ASSIGN, "'-='") \ + X(TOK_MUL_ASSIGN, "'*='") X(TOK_DIV_ASSIGN, "'/='") X(TOK_MOD_ASSIGN, "'%='") X(TOK_SHL_ASSIGN, "'<<='") X(TOK_SHR_ASSIGN, "'>>='") \ + X(TOK_AND_ASSIGN, "'&='") X(TOK_OR_ASSIGN, "'|='") X(TOK_XOR_ASSIGN, "'^='") X(TOK_EQ, "'=='") X(TOK_NEQ, "'!='") X(TOK_LT, "'<'") \ + X(TOK_GT, "'>'") X(TOK_LE, "'<='") X(TOK_GE, "'>='") X(TOK_AND, "'&&'") X(TOK_OR, "'||'") X(TOK_NOT, "'!'") X(TOK_AMP, "'&'") \ + X(TOK_PLUS, "'+'") X(TOK_MINUS, "'-'") X(TOK_INC, "'++'") X(TOK_DEC, "'--'") X(TOK_STAR, "'*'") X(TOK_SLASH, "'/'") \ + X(TOK_PERCENT, "'%'") X(TOK_SHL, "'<<'") X(TOK_SHR, "'>>'") X(TOK_BITOR, "'|'") X(TOK_XOR, "'^'") X(TOK_BITNOT, "'~'") \ X(TOK_DOT, "'.'") X(TOK_ARROW, "'->'") X(TOK_EOF, "end of file") typedef enum { @@ -65,9 +66,35 @@ typedef struct { minic_token_t cur; } minic_lexer_t; +// Direct-mapped cache of string literals already written into the arena, keyed by +// the source offset of the opening quote. +#define MINIC_STR_CACHE_SLOTS 512 + static minic_u8 *minic_active_mem = NULL; static int *minic_active_mem_used = NULL; static int *minic_active_mem_frame = NULL; +static int *minic_active_str_key = NULL; // Source offset + 1 per slot, 0 when empty +static int *minic_active_str_off = NULL; // Arena offset of that literal +static bool minic_mem_oom = false; +static bool minic_oom_reported = false; // Every unwinding scope sees the OOM, only the first reports it + +static char minic_str_empty[1] = ""; // Stand-in when the arena is full + +static int minic_str_cache_slot(int src_pos) { + return (int)(((unsigned int)src_pos * 2654435761u) % MINIC_STR_CACHE_SLOTS); +} + +// Arena offset of the literal lexed at src_pos, or -1 when it must be written again +static int minic_str_cache_get(int src_pos) { + int slot = minic_str_cache_slot(src_pos); + return minic_active_str_key[slot] == src_pos + 1 ? minic_active_str_off[slot] : -1; +} + +static void minic_str_cache_put(int src_pos, int off) { + int slot = minic_str_cache_slot(src_pos); + minic_active_str_key[slot] = src_pos + 1; + minic_active_str_off[slot] = off; +} static const struct { const char *kw; @@ -124,9 +151,9 @@ static int minic_escape(char c) { } } -static void minic_lex_next(minic_lexer_t *l) { +// Skip whitespace, comments and preprocessor directives +static void minic_lex_skip_trivia(minic_lexer_t *l) { for (;;) { - // Skip whitespace, comments and preprocessor directives while (l->src[l->pos] != '\0' && isspace((unsigned char)l->src[l->pos])) { l->pos++; } @@ -146,6 +173,13 @@ static void minic_lex_next(minic_lexer_t *l) { } continue; } + return; + } +} + +static void minic_lex_next(minic_lexer_t *l) { + for (;;) { + minic_lex_skip_trivia(l); char c = l->src[l->pos]; @@ -167,7 +201,8 @@ static void minic_lex_next(minic_lexer_t *l) { return; } - if (isdigit((unsigned char)c)) { + // Also accept a leading-dot float like .5 + if (isdigit((unsigned char)c) || (c == '.' && isdigit((unsigned char)l->src[l->pos + 1]))) { double n = 0; while (isdigit((unsigned char)l->src[l->pos])) { n = n * 10 + (l->src[l->pos++] - '0'); @@ -182,6 +217,23 @@ static void minic_lex_next(minic_lexer_t *l) { } is_float = true; } + // Exponent like 1e-3, only when digits follow so '1e' stays unconsumed + if (l->src[l->pos] == 'e' || l->src[l->pos] == 'E') { + int p = l->pos + 1; + if (l->src[p] == '+' || l->src[p] == '-') { + p++; + } + if (isdigit((unsigned char)l->src[p])) { + bool neg = l->src[l->pos + 1] == '-'; + int exp = 0; + while (isdigit((unsigned char)l->src[p])) { + exp = exp * 10 + (l->src[p++] - '0'); + } + n *= pow(10.0, neg ? -exp : exp); + l->pos = p; + is_float = true; + } + } if (l->src[l->pos] == 'f' || l->src[l->pos] == 'F') { l->pos++; is_float = true; @@ -192,34 +244,54 @@ static void minic_lex_next(minic_lexer_t *l) { } if (c == '"') { - l->pos++; // Consume opening '"' - // Write the string into the active context's arena - int start = (*minic_active_mem_used + 7) & ~7; - int wi = start; - while (l->src[l->pos] != '"' && l->src[l->pos] != '\0') { - char ch = l->src[l->pos++]; - if (ch == '\\') { - char esc = l->src[l->pos++]; - if (esc == '\n') { - continue; // Line continuation: backslash-newline, skip both - } - if (esc == '\r') { // Handle \r\n line endings - if (l->src[l->pos] == '\n') { - l->pos++; + // Write the string into the active context's arena, unless the same + // literal is already there from an earlier evaluation + int key = l->pos; // Source offset of the opening quote + int hit = minic_str_cache_get(key); + int start = (*minic_active_mem_used + 7) & ~7; + int wi = start; + bool store = hit < 0; + // Adjacent string literals concatenate into a single string + while (l->src[l->pos] == '"') { + l->pos++; // Consume opening '"' + while (l->src[l->pos] != '"' && l->src[l->pos] != '\0') { + char ch = l->src[l->pos++]; + if (ch == '\\') { + char esc = l->src[l->pos++]; + if (esc == '\n') { + continue; // Line continuation: backslash-newline, skip both } - continue; + if (esc == '\r') { // Handle \r\n line endings + if (l->src[l->pos] == '\n') { + l->pos++; + } + continue; + } + ch = (char)minic_escape(esc); } - ch = (char)minic_escape(esc); + if (store && wi + 1 < *minic_active_mem_frame) { + minic_active_mem[wi] = (minic_u8)ch; + } + wi++; } - minic_active_mem[wi++] = (minic_u8)ch; + if (l->src[l->pos] == '"') { + l->pos++; // Consume closing '"' + } + minic_lex_skip_trivia(l); // Whitespace or a comment may separate the literals } - minic_active_mem[wi++] = '\0'; - *minic_active_mem_used = (wi + 7) & ~7; - if (l->src[l->pos] == '"') { - l->pos++; // Consume closing '"' + if (store) { + if (wi + 1 >= *minic_active_mem_frame) { // Arena full, the script stops at the next statement + minic_mem_oom = true; + l->cur.type = TOK_STR_LIT; + l->cur.val = minic_val_typed_ptr((void *)minic_str_empty, MINIC_T_CHAR); + return; + } + minic_active_mem[wi++] = '\0'; + *minic_active_mem_used = (wi + 7) & ~7; + minic_str_cache_put(key, start); } l->cur.type = TOK_STR_LIT; - l->cur.val = minic_val_typed_ptr((void *)&minic_active_mem[start], MINIC_T_CHAR); + l->cur.val = minic_val_typed_ptr((void *)&minic_active_mem[store ? start : hit], MINIC_T_CHAR); return; } @@ -242,16 +314,22 @@ static void minic_lex_next(minic_lexer_t *l) { 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++]; + // Names past the cap are truncated, not overflowed; the rest is still consumed + // so the identifier does not split into two tokens + if (i < MINIC_MAX_NAME - 1) { + l->cur.text[i++] = l->src[l->pos]; + } + l->pos++; } l->cur.text[i] = '\0'; for (size_t k = 0; k < sizeof(minic_keywords) / sizeof(minic_keywords[0]); ++k) { - if (strcmp(l->cur.text, minic_keywords[k].kw) == 0) { + // The first character rules out most keywords without the call + if (minic_keywords[k].kw[0] == l->cur.text[0] && strcmp(l->cur.text, minic_keywords[k].kw) == 0) { l->cur.type = minic_keywords[k].tok; return; } } - if (strcmp(l->cur.text, "true") == 0 || strcmp(l->cur.text, "false") == 0) { + if ((l->cur.text[0] == 't' && strcmp(l->cur.text, "true") == 0) || (l->cur.text[0] == 'f' && strcmp(l->cur.text, "false") == 0)) { l->cur.type = TOK_NUMBER; l->cur.val = minic_val_int(l->cur.text[0] == 't'); return; @@ -286,17 +364,27 @@ static void minic_lex_next(minic_lexer_t *l) { // ██║ ╚██████╔╝██║ ╚████║╚██████╗███████║ // ╚═╝ ╚═════╝ ╚═╝ ╚═══╝ ╚═════╝╚══════╝ +static void *minic_alloc_aligned(int size, int alignment) { + uintptr_t start = (uintptr_t)minic_active_mem + *minic_active_mem_used; + uintptr_t address = (start + alignment - 1) & ~(uintptr_t)(alignment - 1); + size_t offset = address - (uintptr_t)minic_active_mem; + if (size < 0 || offset > (size_t)*minic_active_mem_frame || (size_t)size > (size_t)*minic_active_mem_frame - offset) { + minic_mem_oom = true; + return NULL; + } + *minic_active_mem_used = (int)offset + size; + return (void *)address; +} + 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]; + return minic_alloc_aligned(size, MINIC_ALIGNOF(long double)); } // Allocate a call frame from the top of the arena, released when the call returns static void *minic_frame_alloc(int size) { int top = (*minic_active_mem_frame - size) & ~7; if (size < 0 || top < *minic_active_mem_used) { + minic_mem_oom = true; // The frame stack met the heap, the script stops return NULL; } *minic_active_mem_frame = top; @@ -304,35 +392,43 @@ static void *minic_frame_alloc(int size) { } typedef struct { - char name[MINIC_MAX_NAME]; - minic_val_t val; + minic_type_t kind; + minic_type_t deref; + minic_struct_t *def; + int pointer; // pointer depth; zero for scalar or struct storage + int size; + int alignment; +} minic_ctype_t; + +typedef struct { + char name[MINIC_MAX_NAME]; + minic_ctype_t type; + void *address; + union { + int32_t i; + float f; + double d; + void *p; + } scalar; } minic_var_t; typedef struct { - char name[MINIC_MAX_NAME]; - int offset; // index into global arr_data[] - int count; - minic_type_t elem_type; + char name[MINIC_MAX_NAME]; + void *data; // contiguous native elements + int count; + minic_ctype_t elem_type; } minic_arr_t; typedef struct { - char name[MINIC_MAX_NAME]; - char params[MINIC_MAX_PARAMS][MINIC_MAX_NAME]; - char param_structs[MINIC_MAX_PARAMS][MINIC_MAX_NAME]; // struct type name per param, or "" - minic_type_t param_types[MINIC_MAX_PARAMS]; - minic_type_t param_deref_types[MINIC_MAX_PARAMS]; // element type of pointer 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 + char name[MINIC_MAX_NAME]; + char params[MINIC_MAX_PARAMS][MINIC_MAX_NAME]; + minic_ctype_t param_types[MINIC_MAX_PARAMS]; + int param_count; + int body_pos; // lexer position of '{' that starts the body + minic_ctype_t ret_type; + minic_ctx_t *ctx; // owning context, set at parse time } minic_func_t; -// Maps a variable name to its struct type name -typedef struct { - char var_name[MINIC_MAX_NAME]; - char struct_name[MINIC_MAX_NAME]; -} minic_vartype_t; - typedef struct minic_env_s { minic_lexer_t lex; const char *filename; @@ -342,17 +438,12 @@ typedef struct minic_env_s { 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_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; @@ -365,6 +456,8 @@ struct minic_ctx_s { minic_u8 *mem; int mem_used; int mem_frame; // Top of the call-frame stack, grows down from MINIC_MEM_SIZE + int str_key[MINIC_STR_CACHE_SLOTS]; + int str_off[MINIC_STR_CACHE_SLOTS]; minic_env_t e; float result; char *src_copy; @@ -373,9 +466,7 @@ struct minic_ctx_s { static minic_val_t minic_parse_cond(minic_env_t *e); static void minic_parse_stmt(minic_env_t *e); static void minic_parse_block(minic_env_t *e); -static bool minic_lex_type(minic_env_t *e); - -#define MINIC_INC_DELTA(l) ((l)->cur.type == TOK_INC ? 1.0 : -1.0) +static bool minic_parse_type(minic_env_t *e, minic_lexer_t *lex, bool opaque, minic_ctype_t *type); static int minic_current_line(minic_env_t *e) { int line = 1; @@ -420,20 +511,78 @@ static bool minic_tok_is_type(minic_tok_type_t t) { 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_CHAR: + return MINIC_T_CHAR; + case TOK_BOOL: + return MINIC_T_BOOL; case TOK_FLOAT: - case TOK_DOUBLE: return MINIC_T_FLOAT; + case TOK_DOUBLE: + return MINIC_T_DOUBLE; default: - return MINIC_T_PTR; // void * -> PTR + return MINIC_T_VOID; } } -// Element type stamped on a pointer declared with this base type -static minic_type_t minic_tok_to_deref_type(minic_tok_type_t t) { - return t == TOK_CHAR ? MINIC_T_CHAR : minic_tok_to_type(t); +static minic_ctype_t minic_scalar_type(minic_type_t kind) { + minic_ctype_t type = {0}; + type.kind = kind; + type.deref = kind; + switch (kind) { + case MINIC_T_INT: + type.size = sizeof(int32_t); + type.alignment = MINIC_ALIGNOF(int32_t); + break; + case MINIC_T_FLOAT: + type.size = sizeof(float); + type.alignment = MINIC_ALIGNOF(float); + break; + case MINIC_T_DOUBLE: + type.size = sizeof(double); + type.alignment = MINIC_ALIGNOF(double); + break; + case MINIC_T_CHAR: + type.size = sizeof(char); + type.alignment = MINIC_ALIGNOF(char); + break; + case MINIC_T_BOOL: + type.size = sizeof(bool); + type.alignment = MINIC_ALIGNOF(bool); + break; + case MINIC_T_PTR: + type.size = sizeof(void *); + type.alignment = MINIC_ALIGNOF(void *); + type.pointer = 1; + break; + default: + type.alignment = 1; + break; + } + return type; +} + +static minic_ctype_t minic_pointer_type(minic_ctype_t element) { + minic_ctype_t type = minic_scalar_type(MINIC_T_PTR); + type.pointer = element.pointer + 1; + type.deref = element.pointer ? element.deref : element.kind; + type.def = element.def; + return type; +} + +static minic_ctype_t minic_element_type(minic_ctype_t pointer) { + if (pointer.pointer > 1) { + pointer.pointer--; + return pointer; + } + minic_ctype_t type = minic_scalar_type(pointer.deref); + if (pointer.def != NULL) { + type.kind = MINIC_T_EMBED; + type.def = pointer.def; + type.size = pointer.def->size; + type.alignment = pointer.def->alignment; + } + return type; } // Compute the result of an (op)= compound assignment; TOK_ASSIGN returns b @@ -473,14 +622,15 @@ static bool minic_is_compound_assign(minic_tok_type_t t) { static minic_var_t *minic_var_find(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) { + // As in the keyword scan, compare the first character before calling strcmp + if (e->vars[i].name[0] == name[0] && strcmp(e->vars[i].name, name) == 0) { return &e->vars[i]; } } 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].name[0] == name[0] && strcmp(g->vars[i].name, name) == 0) { return &g->vars[i]; } } @@ -488,60 +638,6 @@ static minic_var_t *minic_var_find(minic_env_t *e, const char *name) { return NULL; } -static minic_var_t *minic_var_push(minic_env_t *e, const char *name, minic_val_t val) { - if (e->var_count >= e->var_cap) { - minic_error(e, "too many local variables (max %d), cannot declare '%s'", e->var_cap, name); - return NULL; - } - minic_var_t *v = &e->vars[e->var_count++]; - strncpy(v->name, name, MINIC_MAX_NAME - 1); - v->val = val; - return v; -} - -static minic_val_t minic_var_get(minic_env_t *e, const char *name) { - minic_var_t *v = minic_var_find(e, name); - return v != NULL ? v->val : minic_val_int(0); -} - -static void minic_var_set(minic_env_t *e, const char *name, minic_val_t val) { - minic_var_t *v = minic_var_find(e, name); - if (v == NULL) { - minic_var_push(e, name, val); - return; - } - // Preserve declared type, coerce if needed - if (v->val.type != val.type) { - val = minic_val_cast(val, v->val.type); - } - // Preserve deref_type for pointer variables (it was set at declaration) - if (v->val.type == MINIC_T_PTR && v->val.deref_type != MINIC_T_PTR) { - val.deref_type = v->val.deref_type; - } - v->val = val; -} - -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; typed pointers keep the deref_type from init - minic_val_t v = minic_val_cast(init, type); - if (type == MINIC_T_PTR) { - v.deref_type = init.deref_type; - } - minic_var_push(e, name, v); -} - -static minic_val_t minic_var_addr(minic_env_t *e, const char *name) { - minic_var_t *v = minic_var_find(e, name); - if (v == NULL) { - v = minic_var_push(e, name, minic_val_int(0)); - if (v == NULL) { - return minic_val_ptr(NULL); - } - } - // Address of a minic_val_t - return minic_val_typed_ptr(&v->val, MINIC_T_PTR); -} - 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) { @@ -559,148 +655,6 @@ static minic_arr_t *minic_arr_get(minic_env_t *e, const char *name) { 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, MINIC_MAX_NAME - 1); - 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); - } -} - -// Subscript a native C pointer by its deref element type -static minic_val_t minic_ptr_index_get(minic_val_t pv, int idx) { - if (pv.type != MINIC_T_PTR || pv.p == NULL) { - return minic_val_int(0); - } - 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]); - case MINIC_T_CHAR: - return minic_val_int(((minic_u8 *)pv.p)[idx]); - default: - return minic_val_ptr(((void **)pv.p)[idx]); - } -} - -static void minic_ptr_index_set(minic_val_t pv, int idx, minic_val_t val) { - if (pv.type != MINIC_T_PTR || pv.p == NULL) { - return; - } - 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; - case MINIC_T_CHAR: - ((minic_u8 *)pv.p)[idx] = (minic_u8)minic_val_to_d(val); - break; - default: - ((void **)pv.p)[idx] = minic_val_to_ptr(val); - break; - } -} - -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]; - } - if (idx < 0) { - minic_error(e, "negative index %d on '%s'", idx, name); - return minic_val_int(0); - } - return minic_ptr_index_get(minic_var_get(e, name), idx); -} - -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; - } - if (idx < 0) { - minic_error(e, "negative index %d on '%s'", idx, name); - return; - } - minic_ptr_index_set(minic_var_get(e, name), idx, val); // native pointer subscript -} - -// Read through a pointer: a MINIC_T_PTR deref_type means "points at a boxed minic_val_t" -// (interpreter-internal), any other deref_type means a native C scalar at that address -static minic_val_t minic_deref(minic_val_t pv) { - void *ptr = minic_val_to_ptr(pv); - if (ptr == NULL) { - return minic_val_int(0); - } - 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); - } - case MINIC_T_CHAR: - return minic_val_int(*(minic_u8 *)ptr); - default: { - minic_val_t v; - memcpy(&v, ptr, sizeof(minic_val_t)); - return v; - } - } -} - -// Write through a pointer, with optional compound op: *pv = v, *pv += v, ... -static void minic_store_op(minic_val_t pv, minic_tok_type_t op, minic_val_t v) { - void *ptr = minic_val_to_ptr(pv); - if (ptr == NULL) { - return; - } - switch (pv.deref_type) { - case MINIC_T_INT: { - int ov; - memcpy(&ov, ptr, sizeof(int)); - int nv = (int)minic_apply_op(op, (double)ov, minic_val_to_d(v)); - memcpy(ptr, &nv, sizeof(int)); - break; - } - case MINIC_T_FLOAT: { - float ov; - memcpy(&ov, ptr, sizeof(float)); - float nv = (float)minic_apply_op(op, (double)ov, minic_val_to_d(v)); - memcpy(ptr, &nv, sizeof(float)); - break; - } - case MINIC_T_CHAR: { - minic_u8 ov = *(minic_u8 *)ptr; - *(minic_u8 *)ptr = (minic_u8)minic_apply_op(op, (double)ov, minic_val_to_d(v)); - break; - } - default: { - minic_val_t ov; - memcpy(&ov, ptr, sizeof(minic_val_t)); - minic_val_t nv = (op == TOK_ASSIGN) ? v : minic_val_coerce(minic_apply_op(op, minic_val_to_d(ov), minic_val_to_d(v)), ov.type); - memcpy(ptr, &nv, sizeof(minic_val_t)); - 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) { @@ -719,31 +673,73 @@ static minic_struct_t *minic_struct_get(minic_env_t *e, const char *name) { return NULL; } -static void minic_vartype_set(minic_env_t *e, const char *var_name, const char *struct_name) { - if (e->vartype_count >= e->vartype_cap) { - minic_error(e, "too many struct-typed variables (max %d), cannot type '%s' as '%s'", e->vartype_cap, var_name, struct_name); - return; +// On failure the lexer is unchanged. Opaque names are accepted in declaration +// contexts; expression contexts only recognize registered types. +static bool minic_parse_type(minic_env_t *e, minic_lexer_t *lex, bool opaque, minic_ctype_t *type) { + minic_lexer_t l = *lex; + *type = (minic_ctype_t){0}; + type->deref = MINIC_T_PTR; + if (minic_tok_is_type(l.cur.type)) { + *type = minic_scalar_type(minic_tok_to_type(l.cur.type)); + minic_lex_next(&l); } - strncpy(e->vartypes[e->vartype_count].var_name, var_name, MINIC_MAX_NAME - 1); - strncpy(e->vartypes[e->vartype_count].struct_name, struct_name, MINIC_MAX_NAME - 1); - e->vartype_count++; + else { + bool tagged = l.cur.type == TOK_STRUCT; + if (tagged) { + minic_lex_next(&l); + } + if (l.cur.type != TOK_IDENT) { + return false; + } + type->def = minic_struct_get(e, l.cur.text); + bool integer = minic_is_int_typedef(l.cur.text); + if (!tagged && type->def == NULL && !integer && !opaque) { + return false; + } + minic_struct_t *def = type->def; + *type = minic_scalar_type(integer ? MINIC_T_INT : MINIC_T_EMBED); + type->def = def; + if (def != NULL) { + type->size = def->size; + type->alignment = def->alignment; + } + minic_lex_next(&l); + } + while (l.cur.type == TOK_STAR) { + *type = minic_pointer_type(*type); + minic_lex_next(&l); + } + *lex = l; + return true; } -static minic_struct_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); - } +// Native signatures encode typed pointer returns as "p:struct_name(...)". +static minic_ctype_t minic_call_type(minic_env_t *e, const char *name) { + // Script functions take precedence over native functions of the same name. + minic_func_t *fn = minic_func_get(e, name); + if (fn != NULL) { + return fn->ret_type; } - if (e->global_env != NULL) { - minic_env_t *g = e->global_env; - for (int i = g->vartype_count - 1; i >= 0; --i) { - if (strcmp(g->vartypes[i].var_name, var_name) == 0) { - return minic_struct_get(e, g->vartypes[i].struct_name); - } - } + minic_ext_func_t *ext = minic_ext_func_get(name); + if (ext == NULL || strncmp(ext->sig, "p:", 2) != 0) { + return minic_scalar_type(MINIC_T_PTR); } - return NULL; + const char *start = ext->sig + 2; + const char *end = strchr(start, '('); + if (end == NULL || end == start || end - start >= MINIC_MAX_NAME) { + return minic_scalar_type(MINIC_T_PTR); + } + char type_name[MINIC_MAX_NAME]; + memcpy(type_name, start, end - start); + type_name[end - start] = '\0'; + minic_lexer_t l = {0}; + l.src = type_name; + minic_lex_next(&l); + minic_ctype_t type; + if (minic_parse_type(e, &l, true, &type)) { + return minic_pointer_type(type); + } + return minic_scalar_type(MINIC_T_PTR); } static int minic_struct_field_idx(minic_struct_t *def, const char *field) { @@ -755,118 +751,257 @@ static int minic_struct_field_idx(minic_struct_t *def, const char *field) { return -1; } -bool minic_in_arena(void *p) { - return p != NULL && (minic_u8 *)p >= minic_active_mem && (minic_u8 *)p < minic_active_mem + MINIC_MEM_SIZE; +// Every reference addresses native-layout storage. Only temporary expression +// values and the host-call interface use minic_val_t. +typedef struct { + minic_val_t value; + void *address; + minic_ctype_t type; + minic_ctype_t *inferred; // undeclared variable: infer its type on first store + bool writable; + const char *call_name; + int length; // -1 when the pointer has no known bounds +} minic_expr_t; + +static minic_expr_t minic_value(minic_val_t value) { + minic_expr_t r = {0}; + r.value = value; + r.type = minic_scalar_type(value.type); + r.type.deref = value.deref_type; + r.length = -1; + return r; } -static minic_val_t minic_struct_field_get_base(minic_env_t *e, void *base, minic_struct_t *def, const char *field) { - if (base == NULL) { - minic_error(e, "null pointer access on '%s->%s'", def->name, field); +static minic_expr_t minic_reference(void *address, minic_ctype_t type) { + minic_expr_t r = minic_value(minic_val_int(0)); + r.address = address; + r.type = type; + r.writable = true; + return r; +} + +static minic_val_t minic_load(minic_expr_t r) { + if (!r.writable) { + return r.value; + } + void *p = r.address; + if (p == NULL) { return minic_val_int(0); } - int idx = minic_struct_field_idx(def, field); - if (idx < 0) { - minic_error(e, "struct '%s' has no field '%s'", def->name, field); + switch (r.type.kind) { + case MINIC_T_PTR: { + void *pointer; + memcpy(&pointer, p, sizeof(pointer)); + return minic_val_typed_ptr(pointer, r.type.pointer > 1 ? MINIC_T_PTR : r.type.deref); + } + case MINIC_T_EMBED: + return minic_val_typed_ptr(p, MINIC_T_EMBED); + case MINIC_T_FLOAT: { + float n; + memcpy(&n, p, sizeof(n)); + return minic_val_float(n); + } + case MINIC_T_DOUBLE: { + double n; + memcpy(&n, p, sizeof(n)); + return minic_val_double(n); + } + case MINIC_T_BOOL: + return minic_val_int(*(bool *)p); + case MINIC_T_CHAR: + return minic_val_int(*(minic_u8 *)p); + case MINIC_T_VOID: return minic_val_int(0); + default: { + int32_t n; + memcpy(&n, p, sizeof(n)); + return minic_val_int(n); } - if (def->native && !minic_in_arena(base)) { - char *p = (char *)base + def->offsets[idx]; - switch (def->types[idx]) { - case MINIC_T_PTR: - return minic_val_typed_ptr(*(void **)p, def->deref_types[idx]); - case MINIC_T_EMBED: - return minic_val_typed_ptr(p, def->deref_types[idx]); - case MINIC_T_FLOAT: - return minic_val_float(*(float *)p); - case MINIC_T_BOOL: - return minic_val_int(*(bool *)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_t *def, const char *field, minic_val_t val) { - if (base == NULL) { - minic_error(e, "null pointer access on '%s->%s'", def->name, field); +static void minic_store(minic_env_t *e, minic_expr_t r, minic_val_t v) { + if (!r.writable) { + minic_error(e, "expression is not writable"); return; } - int idx = minic_struct_field_idx(def, field); - if (idx < 0) { - minic_error(e, "struct '%s' has no field '%s'", def->name, field); + void *p = r.address; + if (p == NULL || e->error) { return; } - if (def->native && !minic_in_arena(base)) { - char *p = (char *)base + def->offsets[idx]; - switch (def->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; - case MINIC_T_BOOL: - *(bool *)p = (minic_val_to_d(val) != 0.0); - break; - default: - *(int32_t *)p = (int32_t)minic_val_to_d(val); - break; + if (r.inferred != NULL) { + r.type = minic_value(v).type; + *r.inferred = r.type; + } + switch (r.type.kind) { + case MINIC_T_PTR: { + void *pointer = minic_val_to_ptr(v); + memcpy(p, &pointer, sizeof(pointer)); + break; + } + case MINIC_T_EMBED: + if (v.type == MINIC_T_PTR && v.p != NULL) { + memmove(p, v.p, r.type.size); } - return; + break; + case MINIC_T_FLOAT: { + float n = (float)minic_val_to_d(v); + memcpy(p, &n, sizeof(n)); + break; + } + case MINIC_T_DOUBLE: { + double n = minic_val_to_d(v); + memcpy(p, &n, sizeof(n)); + break; + } + case MINIC_T_BOOL: + *(bool *)p = minic_val_is_true(v); + break; + case MINIC_T_CHAR: + *(minic_u8 *)p = (minic_u8)minic_val_to_d(v); + break; + default: { + int32_t n = (int32_t)minic_val_to_d(v); + memcpy(p, &n, sizeof(n)); + break; + } } - memcpy((minic_val_t *)base + idx, &val, sizeof(minic_val_t)); } -static bool minic_index_in_range(minic_env_t *e, void *base, minic_struct_t *def, const char *field, int idx) { - if (strcmp(field, "buffer") != 0 || minic_struct_field_idx(def, "length") < 0) { - return true; +static minic_var_t *minic_var_decl(minic_env_t *e, const char *name, minic_ctype_t type, minic_val_t init) { + if (e->var_count >= e->var_cap) { + minic_error(e, "too many local variables (max %d), cannot declare '%s'", e->var_cap, name); + return NULL; } - int len = (int)minic_val_to_d(minic_struct_field_get_base(e, base, def, "length")); - if (idx < 0 || idx >= len) { - minic_error(e, "index %d out of range for '%s' of length %d", idx, def->name, len); - return false; + minic_var_t *var = &e->vars[e->var_count++]; + memset(var, 0, sizeof(*var)); + strncpy(var->name, name, MINIC_MAX_NAME - 1); + var->type = type; + var->address = &var->scalar; + if (type.kind == MINIC_T_EMBED) { + if (type.size <= 0) { + minic_error(e, "incomplete struct type for '%s'", name); + return NULL; + } + var->address = minic_alloc_aligned(type.size, type.alignment); + if (var->address == NULL) { + minic_error(e, "out of script memory, cannot declare '%s'", name); + return NULL; + } + memset(var->address, 0, type.size); } - return true; + minic_store(e, minic_reference(var->address, type), init); + return var; +} + +static minic_arr_t *minic_arr_decl(minic_env_t *e, const char *name, int count, minic_ctype_t type) { + if (count < 0 || type.size <= 0 || count > MINIC_MEM_SIZE / type.size) { + minic_error(e, "invalid array size %d for '%s'", count, name); + return NULL; + } + if (e->arr_count >= e->arr_cap) { + minic_error(e, "too many arrays (max %d), cannot declare '%s'", e->arr_cap, name); + return NULL; + } + minic_arr_t *a = &e->arrs[e->arr_count++]; + strncpy(a->name, name, MINIC_MAX_NAME - 1); + a->data = minic_alloc_aligned(count * type.size, type.alignment); + if (a->data == NULL) { + e->arr_count--; + minic_error(e, "out of script memory, cannot declare '%s'", name); + return NULL; + } + a->count = count; + a->elem_type = type; + memset(a->data, 0, count * type.size); + return a; +} + +static minic_ctype_t minic_field_type(minic_env_t *e, minic_struct_t *def, int idx) { + minic_ctype_t type = minic_scalar_type(def->types[idx]); + type.deref = def->deref_types[idx]; + type.pointer = def->pointer_depths[idx]; + type.def = minic_struct_get(e, def->field_structs[idx]); + if (type.kind == MINIC_T_EMBED && type.def != NULL) { + type.size = type.def->size; + type.alignment = type.def->alignment; + } + return type; +} + +static minic_expr_t minic_field(minic_env_t *e, minic_expr_t owner, const char *name) { + minic_expr_t r = minic_value(minic_val_int(0)); + minic_struct_t *def = owner.type.def; + if (def == NULL) { + minic_error(e, "member access requires a known struct type"); + return r; + } + void *base = minic_val_to_ptr(minic_load(owner)); + int idx = minic_struct_field_idx(def, name); + if (base == NULL || idx < 0) { + minic_error(e, base == NULL ? "null pointer access on '%s->%s'" : "struct '%s' has no field '%s'", def->name, name); + return r; + } + minic_ctype_t type = minic_field_type(e, def, idx); + void *address = (char *)base + def->offsets[idx]; + if (def->counts[idx] > 0) { + r = minic_value(minic_val_typed_ptr(address, type.kind)); + r.type = minic_pointer_type(type); + r.length = def->counts[idx]; + } + else { + r = minic_reference(address, type); + } + if (strcmp(name, "buffer") == 0 && minic_struct_field_idx(def, "length") >= 0) { + r.length = (int)minic_val_to_d(minic_load(minic_field(e, owner, "length"))); + } + return r; +} + +static minic_expr_t minic_index(minic_env_t *e, minic_expr_t owner, int idx) { + if (idx < 0 || (owner.length >= 0 && idx >= owner.length)) { + minic_error(e, "index %d out of range (length %d)", idx, owner.length); + return minic_value(minic_val_int(0)); + } + minic_val_t pointer = minic_load(owner); + minic_ctype_t element = minic_element_type(owner.type); + void *base = pointer.type == MINIC_T_PTR ? pointer.p : NULL; + return minic_reference(base != NULL ? (char *)base + (size_t)idx * element.size : NULL, element); } static minic_val_t minic_call(minic_env_t *e, minic_func_t *fn, minic_val_t *args, int argc) { int saved_frame = *minic_active_mem_frame; // The frame is released when the call returns - minic_env_t child = {0}; - child.lex.src = e->lex.src; - child.lex.pos = fn->body_pos; - child.filename = e->filename; - child.var_cap = MINIC_MAX_VARS; - child.vars = minic_frame_alloc(child.var_cap * (int)sizeof(minic_var_t)); - child.global_env = e->global_env != NULL ? e->global_env : e; - child.arr_cap = 32; - child.arrs = minic_frame_alloc(child.arr_cap * (int)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 = MINIC_MAX_VARTYPES; - child.vartypes = minic_frame_alloc(child.vartype_cap * (int)sizeof(minic_vartype_t)); + minic_env_t child = {0}; + child.lex.src = e->lex.src; + child.lex.pos = fn->body_pos; + child.filename = e->filename; + child.var_cap = MINIC_MAX_VARS; + child.vars = minic_frame_alloc(child.var_cap * (int)sizeof(minic_var_t)); + child.global_env = e->global_env != NULL ? e->global_env : e; + child.arr_cap = 32; + child.arrs = minic_frame_alloc(child.arr_cap * (int)sizeof(minic_arr_t)); + 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; + // The arena cannot hold another frame, usually runaway recursion. Report against the + // call site, which still has a line number, and unwind without touching the body + if (child.vars == NULL || child.arrs == NULL) { + if (!minic_oom_reported) { + minic_oom_reported = true; + minic_error(e, "out of script memory (%d KB) calling '%s', recursion too deep", MINIC_MEM_SIZE / 1024, fn->name); + } + e->error = true; + e->returning = true; + *minic_active_mem_frame = saved_frame; + return minic_val_void(); + } // Bind parameters for (int i = 0; i < argc && i < fn->param_count; ++i) { - minic_val_t av = minic_val_cast(args[i], fn->param_types[i]); - // Stamp the declared element type on untyped native pointers - if (fn->param_types[i] == MINIC_T_PTR && av.deref_type == MINIC_T_PTR && !minic_in_arena(av.p)) { - av.deref_type = fn->param_deref_types[i]; - } - minic_var_decl(&child, fn->params[i], fn->param_types[i], av); - if (fn->param_structs[i][0] != '\0') { - minic_vartype_set(&child, fn->params[i], fn->param_structs[i]); - } + minic_var_decl(&child, fn->params[i], fn->param_types[i], args[i]); } minic_lex_next(&child.lex); minic_parse_block(&child); @@ -878,15 +1013,23 @@ static minic_val_t minic_call_in_ctx(minic_ctx_t *ctx, minic_func_t *fn, minic_v minic_u8 *prev_mem = minic_active_mem; int *prev_mem_used = minic_active_mem_used; int *prev_mem_frame = minic_active_mem_frame; + int *prev_str_key = minic_active_str_key; + int *prev_str_off = minic_active_str_off; minic_active_mem = ctx->mem; minic_active_mem_used = &ctx->mem_used; minic_active_mem_frame = &ctx->mem_frame; + minic_active_str_key = ctx->str_key; + minic_active_str_off = ctx->str_off; int saved_used = ctx->mem_used; minic_val_t r = minic_call(&ctx->e, fn, args, argc); ctx->mem_used = saved_used; // Rewind, the arena is free again - minic_active_mem = prev_mem; - minic_active_mem_used = prev_mem_used; - minic_active_mem_frame = prev_mem_frame; + // The released region may be handed out again, so its cached literals are gone + memset(ctx->str_key, 0, sizeof(ctx->str_key)); + minic_active_mem = prev_mem; + minic_active_mem_used = prev_mem_used; + minic_active_mem_frame = prev_mem_frame; + minic_active_str_key = prev_str_key; + minic_active_str_off = prev_str_off; return r; } @@ -906,11 +1049,14 @@ minic_val_t minic_ctx_call_fn(minic_ctx_t *ctx, void *fn_ptr, minic_val_t *args, } static minic_val_t minic_arith(minic_val_t a, minic_val_t b, minic_tok_type_t op) { - // Determine result type (widening: int < float < ptr) + // Determine result type (widening: int < float < double < 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_DOUBLE || b.type == MINIC_T_DOUBLE) { + rt = MINIC_T_DOUBLE; + } else if (a.type == MINIC_T_FLOAT || b.type == MINIC_T_FLOAT) { rt = MINIC_T_FLOAT; } @@ -921,7 +1067,7 @@ static minic_val_t minic_arith(minic_val_t a, minic_val_t b, minic_tok_type_t op double db = minic_val_to_d(b); if (op == TOK_PERCENT) { double r = 0.0; - if (rt == MINIC_T_FLOAT) { + if (rt == MINIC_T_FLOAT || rt == MINIC_T_DOUBLE) { r = db != 0.0 ? fmod(da, db) : 0.0; } else { @@ -936,18 +1082,26 @@ static minic_val_t minic_arith(minic_val_t a, minic_val_t b, minic_tok_type_t op // Parse a call argument list (after '(') and invoke a script or extern function static minic_val_t minic_parse_call(minic_env_t *e, const char *name) { - minic_val_t args[MINIC_MAX_PARAMS]; - int argc = 0; - while (e->lex.cur.type != TOK_RPAREN && e->lex.cur.type != TOK_EOF) { + minic_val_t args[MINIC_MAX_ARGS]; + int argc = 0; + int dropped = 0; + while (e->lex.cur.type != TOK_RPAREN && e->lex.cur.type != TOK_EOF && !e->error) { minic_val_t v = minic_parse_cond(e); - if (argc < MINIC_MAX_PARAMS) { + if (argc < MINIC_MAX_ARGS) { args[argc++] = v; } + else { + dropped++; + } if (e->lex.cur.type == TOK_COMMA) { minic_lex_next(&e->lex); } } minic_expect(e, TOK_RPAREN); + if (dropped > 0) { + minic_error(e, "too many arguments (max %d)", MINIC_MAX_ARGS); + return minic_val_int(0); + } minic_func_t *fn = minic_func_get(e, name); if (fn != NULL) { return minic_call(e, fn, args, argc); @@ -960,311 +1114,317 @@ static minic_val_t minic_parse_call(minic_env_t *e, const char *name) { return minic_val_int(0); } -// primary: '&' IDENT | '*' primary | '-' primary | '!' primary | '~' primary | '++'/'--' IDENT | -// NUMBER | CHAR_LIT | STR_LIT | IDENT ['[' expr ']' | '(' args ')' | ('.'|'->') field...] | '(' 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 '&' - char aname[MINIC_MAX_NAME]; - strncpy(aname, e->lex.cur.text, MINIC_MAX_NAME - 1); - minic_arr_t *arr = minic_arr_get(e, aname); - minic_struct_t *def = minic_var_struct(e, aname); - minic_val_t addr; - if (arr != NULL) { - addr = minic_val_ptr(&e->arr_data[arr->offset]); - } - else if (def != NULL) { - addr = minic_var_get(e, aname); // struct var holds the real base pointer - } - else { - addr = minic_var_addr(e, aname); - } - minic_lex_next(&e->lex); // Consume ident - // Handle &var->field or &var.field: follow the member-access chain - while (def != NULL && (e->lex.cur.type == TOK_ARROW || e->lex.cur.type == TOK_DOT)) { - minic_lex_next(&e->lex); // Consume '->' or '.' - char field[MINIC_MAX_NAME]; - strncpy(field, e->lex.cur.text, MINIC_MAX_NAME - 1); - minic_lex_next(&e->lex); // Consume field name - addr = minic_struct_field_get_base(e, minic_val_to_ptr(addr), def, field); - // Advance def to the field's struct type for further chaining - int fidx = minic_struct_field_idx(def, field); - def = (fidx >= 0 && def->field_structs[fidx][0] != '\0') ? minic_struct_get(e, def->field_structs[fidx]) : NULL; - } - return addr; +static minic_val_t minic_parse_sizeof(minic_env_t *e) { + minic_expect(e, TOK_LPAREN); + minic_ctype_t type; + if (minic_parse_type(e, &e->lex, false, &type)) { + minic_expect(e, TOK_RPAREN); + return minic_val_int(type.size); } - if (e->lex.cur.type == TOK_STAR) { - minic_lex_next(&e->lex); // Consume '*' - return minic_deref(minic_parse_primary(e)); + char name[MINIC_MAX_NAME]; + strncpy(name, e->lex.cur.text, MINIC_MAX_NAME - 1); + name[MINIC_MAX_NAME - 1] = '\0'; + minic_expect(e, TOK_IDENT); + minic_expect(e, TOK_RPAREN); + minic_arr_t *arr = minic_arr_get(e, name); + if (arr != NULL) { + return minic_val_int(arr->count * arr->elem_type.size); } - 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); - return minic_val_int(!minic_val_is_true(minic_parse_primary(e))); - } - if (e->lex.cur.type == TOK_BITNOT) { - minic_lex_next(&e->lex); - return minic_val_int(~(int)minic_val_to_d(minic_parse_primary(e))); - } - 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[MINIC_MAX_NAME]; - strncpy(name, e->lex.cur.text, MINIC_MAX_NAME - 1); - 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; + minic_var_t *var = minic_var_find(e, name); + if (var == NULL) { + return minic_val_int(0); } + return minic_val_int(var->type.size); +} + +static minic_expr_t minic_parse_assignment(minic_env_t *e); +static minic_expr_t minic_parse_unary(minic_env_t *e); + +static minic_expr_t minic_parse_atom(minic_env_t *e) { 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; + return minic_value(v); } if (e->lex.cur.type == TOK_IDENT) { char name[MINIC_MAX_NAME]; strncpy(name, e->lex.cur.text, MINIC_MAX_NAME - 1); + name[MINIC_MAX_NAME - 1] = '\0'; minic_lex_next(&e->lex); - if (strcmp(name, "sizeof") == 0) { - minic_expect(e, TOK_LPAREN); - char type_name[MINIC_MAX_NAME]; - strncpy(type_name, e->lex.cur.text, MINIC_MAX_NAME - 1); - minic_lex_next(&e->lex); // Consume type name - minic_expect(e, TOK_RPAREN); - minic_struct_t *def = minic_struct_get(e, type_name); - return minic_val_int(def != NULL ? def->size : 0); + return minic_value(minic_parse_sizeof(e)); } - - if (e->lex.cur.type == TOK_LBRACKET) { - minic_lex_next(&e->lex); // Consume '[' - int idx = (int)minic_val_to_d(minic_parse_cond(e)); - minic_expect(e, TOK_RBRACKET); - return minic_arr_elem_get(e, name, idx); + minic_func_t *fn = minic_func_get(e, name); + minic_ext_func_t *ext = minic_ext_func_get(name); + if (fn != NULL || (ext != NULL && e->lex.cur.type == TOK_LPAREN)) { + minic_expr_t r = minic_value(minic_val_ptr(fn)); + r.call_name = fn != NULL ? fn->name : ext->name; + return r; } - if (e->lex.cur.type == TOK_LPAREN) { - minic_lex_next(&e->lex); // Consume '(' - return minic_parse_call(e, name); + minic_error(e, "unknown function '%s'", name); + return minic_value(minic_val_int(0)); } - - if (e->lex.cur.type == TOK_DOT || e->lex.cur.type == TOK_ARROW) { - minic_struct_t *def = minic_var_struct(e, name); - if (def == NULL) { - minic_error(e, "'%s' is not a struct", name); - return minic_val_int(0); - } - void *base = minic_val_to_ptr(minic_var_get(e, name)); - char field[MINIC_MAX_NAME]; - minic_lex_next(&e->lex); // Consume '.' or '->' - strncpy(field, e->lex.cur.text, MINIC_MAX_NAME - 1); - minic_lex_next(&e->lex); - minic_val_t v = minic_struct_field_get_base(e, base, def, field); - // Handle chained '->' / '.' 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_structs[fidx][0] == '\0') { - break; - } - minic_struct_t *next_def = minic_struct_get(e, def->field_structs[fidx]); - if (next_def == NULL) { - break; - } - minic_lex_next(&e->lex); // Consume '->' or '.' - strncpy(field, e->lex.cur.text, MINIC_MAX_NAME - 1); - minic_lex_next(&e->lex); - base = minic_val_to_ptr(v); - def = next_def; - v = 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_cond(e)); - minic_expect(e, TOK_RBRACKET); - if (!minic_index_in_range(e, base, def, field, idx)) { - return minic_val_int(0); - } - return minic_ptr_index_get(v, idx); - } - return v; - } - - // If not a variable, check if it's a minic function (pass-as-pointer) - minic_func_t *fn = minic_func_get(e, name); - if (fn != NULL) { - return minic_val_ptr(fn); - } - // Check for known enum constant int ec = minic_enum_const_get(name); if (ec >= 0) { - return minic_val_int(ec); + return minic_value(minic_val_int(ec)); } - // Check for a registered host global - minic_val_t gv; - if (minic_var_find(e, name) == NULL && minic_global_get(name, &gv)) { - return gv; + minic_arr_t *arr = minic_arr_get(e, name); + if (arr != NULL) { + minic_expr_t r = minic_value(minic_val_typed_ptr(arr->data, arr->elem_type.kind)); + r.type = minic_pointer_type(arr->elem_type); + r.length = arr->count; + return r; } - return minic_var_get(e, name); + minic_var_t *var = minic_var_find(e, name); + minic_val_t global; + bool assigning = e->lex.cur.type == TOK_ASSIGN || minic_is_compound_assign(e->lex.cur.type) || e->lex.cur.type == TOK_INC || e->lex.cur.type == TOK_DEC; + if (var == NULL && !assigning && minic_global_get(name, &global)) { + return minic_value(global); + } + if (var == NULL) { + var = minic_var_decl(e, name, minic_scalar_type(MINIC_T_VOID), minic_val_int(0)); + } + if (var == NULL) { + return minic_value(minic_val_int(0)); + } + minic_expr_t r = minic_reference(var->address, var->type); + if (var->type.kind == MINIC_T_VOID) { + r.inferred = &var->type; + } + return r; } if (e->lex.cur.type == TOK_LPAREN) { - // Cast: '(' type '*'... ')' primary. Rewind and parse as a parenthesized - // expression when the parens do not hold a type specifier minic_lexer_t saved = e->lex; - minic_lex_next(&e->lex); // Consume '(' - - minic_tok_type_t base_tok = e->lex.cur.type; - bool is_struct = base_tok == TOK_IDENT && minic_struct_get(e, e->lex.cur.text) != NULL; - if (minic_lex_type(e)) { - int stars = 0; - while (e->lex.cur.type == TOK_STAR) { - stars++; - minic_lex_next(&e->lex); + minic_lex_next(&e->lex); + minic_ctype_t type; + if (minic_parse_type(e, &e->lex, false, &type) && e->lex.cur.type == TOK_RPAREN) { + minic_lex_next(&e->lex); + minic_val_t v = minic_load(minic_parse_unary(e)); + if (type.pointer) { + v = minic_val_typed_ptr(minic_val_to_ptr(v), type.deref); } - if (e->lex.cur.type == TOK_RPAREN) { - minic_lex_next(&e->lex); // Consume ')' - minic_val_t v = minic_parse_primary(e); - if (stars > 0) { - // Pointer cast: reinterpret, stamping the element type - return minic_val_typed_ptr(minic_val_to_ptr(v), stars == 1 ? minic_tok_to_deref_type(base_tok) : MINIC_T_PTR); - } - if (is_struct) { - return v; // Struct value cast, nothing to convert - } - // A typedef'd name that is not a struct is an int alias (enum, handle) - return minic_val_cast(v, base_tok == TOK_IDENT ? MINIC_T_INT : minic_tok_to_type(base_tok)); + else if (type.def == NULL) { + v = minic_val_cast(v, type.kind); } + minic_expr_t r = minic_value(v); + r.type = type; + return r; } e->lex = saved; - minic_lex_next(&e->lex); // Consume '(' - minic_val_t v = minic_parse_cond(e); + minic_lex_next(&e->lex); + minic_expr_t r = minic_parse_assignment(e); minic_expect(e, TOK_RPAREN); - return v; + return r; } - return minic_val_int(0); + // A missing expression is used by void returns and empty loop clauses. + return minic_value(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 || e->lex.cur.type == TOK_PERCENT) { - minic_tok_type_t op = e->lex.cur.type; - minic_lex_next(&e->lex); - v = minic_arith(v, minic_parse_primary(e), op); +static minic_expr_t minic_increment(minic_env_t *e, minic_expr_t r, minic_tok_type_t op, bool prefix) { + minic_val_t old = minic_load(r); + int delta = op == TOK_INC ? 1 : -1; + minic_val_t next; + if (old.type == MINIC_T_PTR) { + int stride = minic_element_type(r.type).size; + next = old; // Keep the pointee type as well as the pointer's exact bits. + if (old.p != NULL) { + next.p = (char *)old.p + delta * stride; + } } - return v; + else { + next = minic_val_coerce(minic_val_to_d(old) + delta, old.type); + } + minic_store(e, r, next); + minic_expr_t result = minic_value(prefix ? next : old); + result.type = r.type; + return result; } -// 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) { - minic_tok_type_t op = e->lex.cur.type; - minic_lex_next(&e->lex); - v = minic_arith(v, minic_parse_term(e), op); - } - return v; -} - -// shift: expr (('<<' | '>>') expr)* -static minic_val_t minic_parse_shift(minic_env_t *e) { - minic_val_t v = minic_parse_expr(e); - while (e->lex.cur.type == TOK_SHL || e->lex.cur.type == TOK_SHR) { - minic_tok_type_t op = e->lex.cur.type; - minic_lex_next(&e->lex); - int a = (int)minic_val_to_d(v); - int b = (int)minic_val_to_d(minic_parse_expr(e)); - v = minic_val_int(op == TOK_SHL ? (int)((unsigned int)a << b) : (a >> b)); - } - return v; -} - -// cmp: shift (('=='|'!='|'<'|'>'|'<='|'>=') shift)? -static minic_val_t minic_parse_cmp(minic_env_t *e) { - minic_val_t v = minic_parse_shift(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); - double a = minic_val_to_d(v); - double b = minic_val_to_d(minic_parse_shift(e)); - int res; - switch (op) { - case TOK_EQ: - res = a == b; - break; - case TOK_NEQ: - res = a != b; - break; - case TOK_LT: - res = a < b; - break; - case TOK_GT: - res = a > b; - break; - case TOK_LE: - res = a <= b; - break; - default: - res = a >= b; +static minic_expr_t minic_parse_postfix(minic_env_t *e) { + minic_expr_t r = minic_parse_atom(e); + while (!e->error) { + if (e->lex.cur.type == TOK_LPAREN) { + if (r.call_name == NULL) { + minic_error(e, "expression is not callable"); + break; + } + const char *name = r.call_name; + minic_lex_next(&e->lex); + r = minic_value(minic_parse_call(e, name)); + if (r.value.type == MINIC_T_PTR) { + r.type = minic_call_type(e, name); + } + } + else if (e->lex.cur.type == TOK_DOT || e->lex.cur.type == TOK_ARROW) { + minic_lex_next(&e->lex); + char field[MINIC_MAX_NAME]; + strncpy(field, e->lex.cur.text, MINIC_MAX_NAME - 1); + field[MINIC_MAX_NAME - 1] = '\0'; + minic_expect(e, TOK_IDENT); + r = minic_field(e, r, field); + } + else if (e->lex.cur.type == TOK_LBRACKET) { + minic_lex_next(&e->lex); + int idx = (int)minic_val_to_d(minic_parse_cond(e)); + minic_expect(e, TOK_RBRACKET); + r = minic_index(e, r, idx); + } + else if (e->lex.cur.type == TOK_INC || e->lex.cur.type == TOK_DEC) { + minic_tok_type_t op = e->lex.cur.type; + minic_lex_next(&e->lex); + r = minic_increment(e, r, op, false); + } + else { break; } - return minic_val_int(res); } - return v; + return r; } -// bitand: cmp ('&' cmp)* -// '&' is only binary here; a leading '&' is consumed as address-of by minic_parse_primary -static minic_val_t minic_parse_bitand(minic_env_t *e) { - minic_val_t v = minic_parse_cmp(e); - while (e->lex.cur.type == TOK_AMP) { +static minic_expr_t minic_parse_unary(minic_env_t *e) { + minic_tok_type_t op = e->lex.cur.type; + if (op != TOK_AMP && op != TOK_STAR && op != TOK_MINUS && op != TOK_NOT && op != TOK_BITNOT && op != TOK_INC && op != TOK_DEC) { + return minic_parse_postfix(e); + } + minic_lex_next(&e->lex); + minic_expr_t r = minic_parse_unary(e); + minic_val_t v = minic_load(r); + switch (op) { + case TOK_AMP: { + if (!r.writable && r.length < 0) { + minic_error(e, "expression has no address"); + return minic_value(minic_val_ptr(NULL)); + } + if (!r.writable) { + return r; // Arrays already decay to the address of their first element. + } + minic_expr_t address = minic_value(minic_val_typed_ptr(r.address, r.type.kind)); + address.type = minic_pointer_type(r.type); + return address; + } + case TOK_STAR: + return minic_reference(minic_val_to_ptr(v), minic_element_type(r.type)); + case TOK_INC: + case TOK_DEC: + return minic_increment(e, r, op, true); + case TOK_MINUS: + return minic_value(minic_val_coerce(-minic_val_to_d(v), v.type)); + case TOK_NOT: + return minic_value(minic_val_int(!minic_val_is_true(v))); + default: + return minic_value(minic_val_int(~(int)minic_val_to_d(v))); + } +} + +// C precedence, loosest first; every level is left-associative. +#define MINIC_PREC_MAX 10 +static int minic_binary_precedence(minic_tok_type_t op) { + switch (op) { + case TOK_OR: + return 1; + case TOK_AND: + return 2; + case TOK_BITOR: + return 3; + case TOK_XOR: + return 4; + case TOK_AMP: + return 5; + case TOK_EQ: + case TOK_NEQ: + return 6; + case TOK_LT: + case TOK_GT: + case TOK_LE: + case TOK_GE: + return 7; + case TOK_SHL: + case TOK_SHR: + return 8; + case TOK_PLUS: + case TOK_MINUS: + return 9; + case TOK_STAR: + case TOK_SLASH: + case TOK_PERCENT: + return 10; + default: + return 0; + } +} + +static minic_val_t minic_binary(minic_val_t lhs, minic_val_t rhs, minic_tok_type_t op) { + double a = minic_val_to_d(lhs); + double b = minic_val_to_d(rhs); + switch (op) { + case TOK_AND: + return minic_val_int(minic_val_is_true(lhs) && minic_val_is_true(rhs)); + case TOK_OR: + return minic_val_int(minic_val_is_true(lhs) || minic_val_is_true(rhs)); + case TOK_BITOR: + return minic_val_int((int)a | (int)b); + case TOK_XOR: + return minic_val_int((int)a ^ (int)b); + case TOK_AMP: + return minic_val_int((int)a & (int)b); + case TOK_EQ: + return minic_val_int(a == b); + case TOK_NEQ: + return minic_val_int(a != b); + case TOK_LT: + return minic_val_int(a < b); + case TOK_GT: + return minic_val_int(a > b); + case TOK_LE: + return minic_val_int(a <= b); + case TOK_GE: + return minic_val_int(a >= b); + case TOK_SHL: + return minic_val_int((int)((unsigned int)(int)a << (int)b)); + case TOK_SHR: + return minic_val_int((int)a >> (int)b); + default: + return minic_arith(lhs, rhs, op); + } +} + +// Each level consumes the next tighter level, keeping left-to-right evaluation. +static minic_expr_t minic_parse_binary(minic_env_t *e, int level) { + if (level > MINIC_PREC_MAX) { + return minic_parse_unary(e); + } + + minic_expr_t r = minic_parse_binary(e, level + 1); + while (!e->error && minic_binary_precedence(e->lex.cur.type) == level) { + minic_tok_type_t op = e->lex.cur.type; + minic_val_t lhs = minic_load(r); minic_lex_next(&e->lex); - int a = (int)minic_val_to_d(v); - int b = (int)minic_val_to_d(minic_parse_cmp(e)); - v = minic_val_int(a & b); + minic_val_t rhs = minic_load(minic_parse_binary(e, level + 1)); + r = minic_value(minic_binary(lhs, rhs, op)); } - return v; + return r; } -// bitxor: bitand ('^' bitand)* -static minic_val_t minic_parse_bitxor(minic_env_t *e) { - minic_val_t v = minic_parse_bitand(e); - while (e->lex.cur.type == TOK_XOR) { +static minic_expr_t minic_parse_assignment(minic_env_t *e) { + minic_expr_t target = minic_parse_binary(e, 1); + minic_tok_type_t op = e->lex.cur.type; + if (!e->error && (op == TOK_ASSIGN || minic_is_compound_assign(op))) { minic_lex_next(&e->lex); - int a = (int)minic_val_to_d(v); - int b = (int)minic_val_to_d(minic_parse_bitand(e)); - v = minic_val_int(a ^ b); + minic_val_t v = minic_load(minic_parse_assignment(e)); + if (op != TOK_ASSIGN) { + // Existing assignments read the old value after evaluating the RHS. + minic_val_t old = minic_load(target); + v = minic_val_coerce(minic_apply_op(op, minic_val_to_d(old), minic_val_to_d(v)), old.type); + } + minic_store(e, target, v); + return minic_value(v); } - return v; + return target; } -// bitor: bitxor ('|' bitxor)* -static minic_val_t minic_parse_bitor(minic_env_t *e) { - minic_val_t v = minic_parse_bitxor(e); - while (e->lex.cur.type == TOK_BITOR) { - minic_lex_next(&e->lex); - int a = (int)minic_val_to_d(v); - int b = (int)minic_val_to_d(minic_parse_bitxor(e)); - v = minic_val_int(a | b); - } - return v; -} - -// cond: bitor (('&&' | '||') bitor)* static minic_val_t minic_parse_cond(minic_env_t *e) { - minic_val_t v = minic_parse_bitor(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); - int vi = minic_val_is_true(v); - int ri = minic_val_is_true(minic_parse_bitor(e)); - v = minic_val_int(op == TOK_AND ? (vi && ri) : (vi || ri)); - } - return v; + return minic_load(minic_parse_assignment(e)); } // Skip the parenthesised header of an if/for/while, leaving the first body token current @@ -1327,79 +1487,116 @@ static void minic_skip_block(minic_env_t *e) { } } -// struct value or pointer declaration; the struct type name has been consumed -static void minic_parse_struct_decl(minic_env_t *e, const char *sname) { - minic_struct_t *def = minic_struct_get(e, sname); - if (def == NULL) { - minic_error(e, "unknown struct '%s'", sname); - return; +// Count the top-level elements of a brace initializer without evaluating it, +// so that an unsized 'type name[] = {...}' can be allocated up front. +// The lexer sits on '{' and is left untouched. +static int minic_init_list_count(minic_env_t *e) { + if (e->lex.cur.type != TOK_LBRACE) { + return 0; } - bool is_ptr = (e->lex.cur.type == TOK_STAR); - if (is_ptr) { - minic_lex_next(&e->lex); - } - char vname[MINIC_MAX_NAME]; - strncpy(vname, e->lex.cur.text, MINIC_MAX_NAME - 1); - 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_cond(e); + minic_lexer_t l = e->lex; // Scan on a copy + int depth = 0; + int count = 0; + bool in_elem = false; + while (l.cur.type != TOK_EOF) { + if (l.cur.type == TOK_RBRACE && --depth == 0) { + break; } - minic_var_decl(e, vname, MINIC_T_PTR, v); - } - else { - // Value declaration: boxed field storage in the arena - 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_cond(e); - if (v.type == MINIC_T_PTR && v.p != NULL) { - memcpy(base, v.p, def->field_count * sizeof(minic_val_t)); + if (depth == 1) { + if (l.cur.type == TOK_COMMA) { + in_elem = false; // The next token starts another element + } + else if (!in_elem) { + in_elem = true; // First token of an element, a nested '{' included + count++; } } - minic_var_decl(e, vname, MINIC_T_PTR, minic_val_ptr(base)); + if (l.cur.type == TOK_LBRACE) { + depth++; + } + minic_lex_next(&l); } + return count; +} + +// Local declarations and globals use the same allocation and initialization path. +static void minic_parse_decl(minic_env_t *e, minic_ctype_t type) { + char name[MINIC_MAX_NAME]; + strncpy(name, e->lex.cur.text, MINIC_MAX_NAME - 1); + name[MINIC_MAX_NAME - 1] = '\0'; + minic_expect(e, TOK_IDENT); + if (e->lex.cur.type == TOK_LBRACKET) { + minic_lex_next(&e->lex); // Consume '[' + bool sized = e->lex.cur.type != TOK_RBRACKET; + int count = sized ? (int)minic_val_to_d(minic_parse_cond(e)) : 0; + minic_expect(e, TOK_RBRACKET); + if (e->lex.cur.type == TOK_ASSIGN) { + minic_lex_next(&e->lex); // Consume '=' + int listed = minic_init_list_count(e); + if (!sized) { // 'name[]' takes its size from the initializer + count = listed; + } + minic_arr_t *a = minic_arr_decl(e, name, count, type); + minic_expect(e, TOK_LBRACE); + for (int i = 0; e->lex.cur.type != TOK_RBRACE && !e->error; ++i) { + minic_val_t v = minic_parse_cond(e); + if (a != NULL && i < a->count) { + minic_store(e, minic_reference((char *)a->data + i * type.size, type), v); + } + if (e->lex.cur.type != TOK_COMMA) { + break; + } + minic_lex_next(&e->lex); // Consume ',' + } + minic_expect(e, TOK_RBRACE); + if (sized && listed > count) { + minic_error(e, "%d initializers for '%s[%d]'", listed, name, count); + } + } + else { + minic_arr_decl(e, name, count, type); + } + minic_expect(e, TOK_SEMICOLON); + return; + } + minic_val_t v = minic_val_coerce(0.0, type.kind); + v.deref_type = type.deref; + bool initialized = e->lex.cur.type == TOK_ASSIGN; + if (initialized) { + minic_lex_next(&e->lex); + v = minic_parse_cond(e); + } + minic_var_decl(e, name, type, v); minic_expect(e, TOK_SEMICOLON); } -// The increment clause of a for loop: ++i, i++, i += x, i = x -static void minic_parse_for_incr(minic_env_t *e) { - 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, e->lex.cur.text); - minic_var_set(e, e->lex.cur.text, minic_val_coerce(minic_val_to_d(ov) + delta, ov.type)); - return; +// Recognize opaque pointer declarations without mistaking 'value * value' for a type. +static bool minic_decl_type(minic_env_t *e, minic_ctype_t *type) { + if (minic_parse_type(e, &e->lex, false, type)) { + return true; } - if (e->lex.cur.type != TOK_IDENT) { - return; - } - char name[MINIC_MAX_NAME]; - strncpy(name, e->lex.cur.text, MINIC_MAX_NAME - 1); - 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, name); - minic_var_set(e, name, minic_val_coerce(minic_val_to_d(ov) + delta, ov.type)); - } - else if (minic_is_compound_assign(e->lex.cur.type)) { - minic_tok_type_t op = e->lex.cur.type; - minic_lex_next(&e->lex); - minic_val_t dv = minic_parse_cond(e); - minic_val_t ov = minic_var_get(e, name); - minic_var_set(e, name, minic_val_coerce(minic_apply_op(op, minic_val_to_d(ov), minic_val_to_d(dv)), ov.type)); - } - else if (e->lex.cur.type == TOK_ASSIGN) { - minic_lex_next(&e->lex); - minic_var_set(e, name, minic_parse_cond(e)); + if (e->lex.cur.type == TOK_IDENT && minic_var_find(e, e->lex.cur.text) == NULL) { + minic_lexer_t l = e->lex; + if (minic_parse_type(e, &l, true, type) && type->pointer && l.cur.type == TOK_IDENT) { + e->lex = l; + return true; + } } + return false; } static void minic_parse_stmt(minic_env_t *e) { + if (minic_mem_oom) { + if (!minic_oom_reported) { + minic_oom_reported = true; + minic_error(e, "out of script memory (%d KB)", MINIC_MEM_SIZE / 1024); + } + else { // Already reported further in, just keep unwinding + e->error = true; + e->returning = true; + } + return; + } // 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) { @@ -1411,87 +1608,9 @@ static void minic_parse_stmt(minic_env_t *e) { return; } - if (e->lex.cur.type == TOK_STRUCT) { - minic_lex_next(&e->lex); // Consume 'struct' - char sname[MINIC_MAX_NAME]; - strncpy(sname, e->lex.cur.text, MINIC_MAX_NAME - 1); - minic_lex_next(&e->lex); // Consume struct type name - minic_parse_struct_decl(e, sname); - 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[MINIC_MAX_NAME]; - strncpy(vname, e->lex.cur.text, MINIC_MAX_NAME - 1); - 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_cast(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_get(e, e->lex.cur.text) != NULL) { - char sname[MINIC_MAX_NAME]; - strncpy(sname, e->lex.cur.text, MINIC_MAX_NAME - 1); - minic_lex_next(&e->lex); // Consume alias name - minic_parse_struct_decl(e, sname); - return; - } - - if (minic_tok_is_type(e->lex.cur.type)) { - minic_type_t base_type = minic_tok_to_type(e->lex.cur.type); // Type before '*' - minic_type_t elem_type = minic_tok_to_deref_type(e->lex.cur.type); // Element type when it is a pointer - minic_type_t dtype = base_type; - minic_lex_next(&e->lex); // Consume type keyword - - bool is_ptr = (e->lex.cur.type == TOK_STAR); - if (is_ptr) { - dtype = MINIC_T_PTR; - minic_lex_next(&e->lex); - } - char name[MINIC_MAX_NAME]; - strncpy(name, e->lex.cur.text, MINIC_MAX_NAME - 1); - 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_cond(e)); - minic_expect(e, TOK_RBRACKET); - minic_arr_decl(e, name, count, dtype); - minic_expect(e, TOK_SEMICOLON); - return; - } - - // Optional initializer: default to 0 / NULL when omitted - minic_val_t v = is_ptr ? minic_val_ptr(NULL) : minic_val_coerce(0.0, dtype); - if (is_ptr) { - v.deref_type = elem_type; - } - if (e->lex.cur.type == TOK_ASSIGN) { - minic_lex_next(&e->lex); - v = minic_parse_cond(e); - if (is_ptr) { - if (v.type != MINIC_T_PTR) { - // NULL literal passed as integer 0 - v = minic_val_ptr((void *)(uintptr_t)(uint64_t)minic_val_to_d(v)); - } - // Only stamp the declared element type for native C pointers. - // Pointers into the active arena (e.g. from &var) use the MINIC_T_PTR sentinel - // to signal that dereferencing reads a full minic_val_t. - if (!minic_in_arena(v.p)) { - v.deref_type = elem_type; - } - } - } - minic_var_decl(e, name, dtype, v); - minic_expect(e, TOK_SEMICOLON); + minic_ctype_t type; + if (minic_decl_type(e, &type)) { + minic_parse_decl(e, type); return; } @@ -1503,129 +1622,6 @@ static void minic_parse_stmt(minic_env_t *e) { return; } - if (e->lex.cur.type == TOK_IDENT) { - char name[MINIC_MAX_NAME]; - strncpy(name, e->lex.cur.text, MINIC_MAX_NAME - 1); - minic_lex_next(&e->lex); - - // Unknown opaque type followed by '*' + ident: local pointer declaration. - // e.g. ui_handle_t *h; or my_t *p = create_p(); - if (e->lex.cur.type == TOK_STAR) { - minic_lex_next(&e->lex); // Consume '*' - char vname[MINIC_MAX_NAME]; - strncpy(vname, e->lex.cur.text, MINIC_MAX_NAME - 1); - 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_cond(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[MINIC_MAX_NAME]; - strncpy(field, e->lex.cur.text, MINIC_MAX_NAME - 1); - minic_lex_next(&e->lex); - minic_struct_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 = minic_val_to_ptr(minic_var_get(e, name)); - // Descend chained member access to the last field (e.g. o->transform->radius = x) - while ((e->lex.cur.type == TOK_DOT || e->lex.cur.type == TOK_ARROW) && !e->error) { - int fidx = minic_struct_field_idx(def, field); - if (fidx < 0 || def->field_structs[fidx][0] == '\0') { - break; - } - minic_struct_t *next_def = minic_struct_get(e, def->field_structs[fidx]); - if (next_def == NULL) { - break; - } - minic_lex_next(&e->lex); // Consume '->' or '.' - base = minic_val_to_ptr(minic_struct_field_get_base(e, base, def, field)); - def = next_def; - strncpy(field, e->lex.cur.text, MINIC_MAX_NAME - 1); - minic_lex_next(&e->lex); - } - if (e->lex.cur.type == TOK_LBRACKET) { - minic_lex_next(&e->lex); - int idx = (int)minic_val_to_d(minic_parse_cond(e)); - minic_expect(e, TOK_RBRACKET); - minic_expect(e, TOK_ASSIGN); - minic_val_t v = minic_parse_cond(e); - if (minic_index_in_range(e, base, def, field, idx)) { - minic_ptr_index_set(minic_struct_field_get_base(e, base, def, field), idx, v); - } - } - else 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_struct_field_get_base(e, base, def, field); - minic_struct_field_set_base(e, base, def, field, minic_val_coerce(minic_val_to_d(ov) + delta, ov.type)); - } - else if (minic_is_compound_assign(e->lex.cur.type)) { - minic_tok_type_t op = e->lex.cur.type; - minic_lex_next(&e->lex); - minic_val_t dv = minic_parse_cond(e); - minic_val_t ov = minic_struct_field_get_base(e, base, def, field); - minic_struct_field_set_base(e, base, def, field, minic_val_coerce(minic_apply_op(op, minic_val_to_d(ov), minic_val_to_d(dv)), ov.type)); - } - else { - minic_expect(e, TOK_ASSIGN); - minic_struct_field_set_base(e, base, def, field, minic_parse_cond(e)); - } - minic_expect(e, TOK_SEMICOLON); - return; - } - - if (e->lex.cur.type == TOK_LPAREN) { - minic_lex_next(&e->lex); - minic_parse_call(e, 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 (minic_is_compound_assign(e->lex.cur.type)) { - minic_tok_type_t op = e->lex.cur.type; - minic_lex_next(&e->lex); - minic_val_t dv = minic_parse_cond(e); - minic_val_t ov = minic_var_get(e, name); - minic_var_set(e, name, minic_val_coerce(minic_apply_op(op, minic_val_to_d(ov), minic_val_to_d(dv)), 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_cond(e)); - minic_expect(e, TOK_RBRACKET); - minic_expect(e, TOK_ASSIGN); - minic_arr_elem_set(e, name, idx, minic_parse_cond(e)); - minic_expect(e, TOK_SEMICOLON); - return; - } - - minic_expect(e, TOK_ASSIGN); - minic_var_set(e, name, minic_parse_cond(e)); - minic_expect(e, TOK_SEMICOLON); - return; - } - if (e->lex.cur.type == TOK_IF) { minic_lex_next(&e->lex); minic_expect(e, TOK_LPAREN); @@ -1662,40 +1658,21 @@ static void minic_parse_stmt(minic_env_t *e) { minic_expect(e, TOK_LPAREN); // Init clause - int saved_var_count = e->var_count; - bool is_decl = false; - minic_type_t itype = MINIC_T_INT; - if (minic_tok_is_type(e->lex.cur.type)) { - itype = minic_tok_to_type(e->lex.cur.type); - is_decl = true; - minic_lex_next(&e->lex); + int saved_var_count = e->var_count; + if (minic_decl_type(e, &type)) { + minic_parse_decl(e, type); } - { - char iname[MINIC_MAX_NAME]; - strncpy(iname, e->lex.cur.text, MINIC_MAX_NAME - 1); - minic_lex_next(&e->lex); - minic_expect(e, TOK_ASSIGN); - minic_val_t init = minic_parse_cond(e); - if (is_decl) { - // 'for (int i = ...)' declares a loop-local, it must not assign to an - // outer variable of the same name - minic_var_decl(e, iname, itype, init); - } - else { - minic_var_set(e, iname, init); - } + else { + minic_parse_cond(e); + minic_expect(e, TOK_SEMICOLON); } - int cond_pos = e->lex.pos; - minic_lex_next(&e->lex); // Consume ';' + minic_lexer_t condition = e->lex; // Scan ahead for the increment clause and body positions int incr_pos, body_pos; { - minic_lexer_t tmp = {0}; - tmp.src = e->lex.src; - tmp.pos = cond_pos; - minic_lex_next(&tmp); - int depth = 0; + minic_lexer_t tmp = condition; + int depth = 0; while (tmp.cur.type != TOK_EOF && !(tmp.cur.type == TOK_SEMICOLON && depth == 0)) { if (tmp.cur.type == TOK_LPAREN) { depth++; @@ -1723,9 +1700,8 @@ static void minic_parse_stmt(minic_env_t *e) { for (;;) { e->continuing = false; - e->lex.pos = cond_pos; - minic_lex_next(&e->lex); - int cond = minic_val_is_true(minic_parse_cond(e)); + e->lex = condition; + int cond = e->lex.cur.type == TOK_SEMICOLON || minic_val_is_true(minic_parse_cond(e)); if (!cond || e->returning || e->breaking) { e->lex.pos = body_pos; minic_lex_next(&e->lex); @@ -1742,30 +1718,12 @@ static void minic_parse_stmt(minic_env_t *e) { } e->lex.pos = incr_pos; minic_lex_next(&e->lex); - minic_parse_for_incr(e); + minic_parse_cond(e); } e->var_count = saved_var_count; // The loop variable goes out of scope 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); - 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_store_op(pv, TOK_PLUS_ASSIGN, minic_val_float((float)delta)); - minic_expect(e, TOK_SEMICOLON); - return; - } - minic_tok_type_t op = e->lex.cur.type; // TOK_ASSIGN or a compound assign - minic_lex_next(&e->lex); - minic_store_op(pv, op, minic_parse_cond(e)); - minic_expect(e, TOK_SEMICOLON); - return; - } - if (e->lex.cur.type == TOK_BREAK) { minic_lex_next(&e->lex); minic_expect(e, TOK_SEMICOLON); @@ -1804,12 +1762,13 @@ static void minic_parse_stmt(minic_env_t *e) { return; } - minic_error(e, "unexpected token at start of statement"); + minic_parse_cond(e); + minic_expect(e, TOK_SEMICOLON); } static void minic_parse_block(minic_env_t *e) { - int saved_var_count = e->var_count; - int saved_vartype_count = e->vartype_count; + int saved_var_count = e->var_count; + int saved_arr_count = e->arr_count; if (e->lex.cur.type != TOK_LBRACE) { // Single-statement body without braces minic_parse_stmt(e); @@ -1836,8 +1795,8 @@ static void minic_parse_block(minic_env_t *e) { } } } - e->var_count = saved_var_count; - e->vartype_count = saved_vartype_count; + e->var_count = saved_var_count; + e->arr_count = saved_arr_count; } // ██████╗ ██╗ ██╗███╗ ██╗ @@ -1847,25 +1806,6 @@ static void minic_parse_block(minic_env_t *e) { // ██║ ██║╚██████╔╝██║ ╚████║ // ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝ -// Consume a type specifier, return true if found -static bool minic_lex_type(minic_env_t *e) { - if (minic_tok_is_type(e->lex.cur.type)) { - 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 or int name used as a type specifier - if (e->lex.cur.type == TOK_IDENT && (minic_struct_get(e, e->lex.cur.text) != NULL || minic_is_int_typedef(e->lex.cur.text))) { - minic_lex_next(&e->lex); - return true; - } - return false; -} - // Zero pass: scan for enum and struct definitions static void minic_register_structs(minic_env_t *e) { minic_lexer_t l = {0}; @@ -1935,32 +1875,64 @@ static void minic_register_structs(minic_env_t *e) { strncpy(def->name, struct_name, MINIC_MAX_NAME - 1); minic_lex_next(&l); // Consume '{' - while (l.cur.type != TOK_RBRACE && l.cur.type != TOK_EOF) { - // Field type: builtin keyword, 'struct name' or typedef'd name - if (l.cur.type == TOK_STRUCT) { - minic_lex_next(&l); - minic_lex_next(&l); + while (l.cur.type != TOK_RBRACE && l.cur.type != TOK_EOF && !e->error) { + // Keep the name as well as its resolved type for forward/self pointers. + minic_lexer_t type_start = l; + if (type_start.cur.type == TOK_STRUCT) { + minic_lex_next(&type_start); } - else if (minic_tok_is_type(l.cur.type) || l.cur.type == TOK_IDENT) { - minic_lex_next(&l); + minic_ctype_t field_type; + if (!minic_parse_type(e, &l, true, &field_type)) { + minic_error(e, "expected field type in '%s'", def->name); + return; } - else { - minic_lex_next(&l); - continue; + minic_ctype_t field_base = field_type; + while (field_base.pointer > 0) { + field_base = minic_element_type(field_base); } - if (l.cur.type == TOK_STAR) { + for (;;) { + if (l.cur.type != TOK_IDENT || def->field_count >= MINIC_MAX_STRUCT_FIELDS) { + minic_error(e, "invalid or too many fields in '%s'", def->name); + return; + } + int idx = def->field_count++; + strncpy(def->fields[idx], l.cur.text, MINIC_MAX_NAME - 1); + def->types[idx] = field_type.kind; + def->deref_types[idx] = field_type.deref; + def->pointer_depths[idx] = field_type.pointer; + if (field_type.kind == MINIC_T_EMBED || field_type.deref == MINIC_T_EMBED) { + strncpy(def->field_structs[idx], type_start.cur.text, MINIC_MAX_NAME - 1); + } 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, MINIC_MAX_NAME - 1); + if (l.cur.type == TOK_LBRACKET) { + minic_lex_next(&l); + if (l.cur.type != TOK_NUMBER || l.cur.val.type != MINIC_T_INT || l.cur.val.i <= 0) { + minic_error(e, "field array requires a positive integer size"); + return; + } + def->counts[idx] = l.cur.val.i; + minic_lex_next(&l); + if (l.cur.type != TOK_RBRACKET) { + minic_error(e, "expected ']' after field array size"); + return; + } + minic_lex_next(&l); + } + if (l.cur.type != TOK_COMMA) { + break; + } minic_lex_next(&l); + field_type = field_base; + while (l.cur.type == TOK_STAR) { + field_type = minic_pointer_type(field_type); + 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_SEMICOLON) { + minic_error(e, "expected ';' after struct field"); + return; } + minic_lex_next(&l); } if (l.cur.type == TOK_RBRACE) { minic_lex_next(&l); @@ -2005,63 +1977,94 @@ static void minic_register_structs(minic_env_t *e) { } } +// Resolve script layouts after collecting all definitions. Native descriptors +// already have their compiler-provided sizes, offsets, and alignment. +static bool minic_layout_struct(minic_env_t *e, minic_struct_t *def) { + if (def->layout_state == 2) { + return true; + } + if (def->layout_state == 1) { + minic_error(e, "recursive embedded struct '%s'", def->name); + return false; + } + def->layout_state = 1; + def->size = 0; + def->alignment = 1; + for (int i = 0; i < def->field_count; ++i) { + if (def->types[i] == MINIC_T_EMBED) { + minic_struct_t *child = minic_struct_get(e, def->field_structs[i]); + if (child == NULL) { + minic_error(e, "unknown embedded struct '%s'", def->field_structs[i]); + return false; + } + if (!minic_layout_struct(e, child)) { + return false; + } + } + minic_ctype_t type = minic_field_type(e, def, i); + int count = def->counts[i] > 0 ? def->counts[i] : 1; + if (type.size <= 0 || count > (MINIC_MEM_SIZE - def->size) / type.size) { + minic_error(e, "invalid field size in '%s'", def->name); + return false; + } + int offset = (def->size + type.alignment - 1) / type.alignment * type.alignment; + def->offsets[i] = offset; + def->size = offset + count * type.size; + if (type.alignment > def->alignment) { + def->alignment = type.alignment; + } + } + def->size = (def->size + def->alignment - 1) / def->alignment * def->alignment; + def->layout_state = 2; + return true; +} + // First pass: register all function definitions and globals, 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); - minic_type_t elem_type = minic_tok_to_deref_type(e->lex.cur.type); - char decl_struct[MINIC_MAX_NAME] = ""; - if (e->lex.cur.type == TOK_IDENT && minic_struct_get(e, e->lex.cur.text) != NULL) { - strncpy(decl_struct, e->lex.cur.text, MINIC_MAX_NAME - 1); - } - if (!minic_lex_type(e)) { - if (e->lex.cur.type == TOK_IDENT) { - // Unknown typedef type - strncpy(decl_struct, e->lex.cur.text, MINIC_MAX_NAME - 1); - minic_lex_next(&e->lex); - ret_type = MINIC_T_PTR; - elem_type = MINIC_T_PTR; + while (e->lex.cur.type != TOK_EOF && !e->error) { + if (e->lex.cur.type == TOK_TYPEDEF || e->lex.cur.type == TOK_ENUM || e->lex.cur.type == TOK_STRUCT) { + minic_lexer_t scan = e->lex; + bool definition = scan.cur.type == TOK_TYPEDEF || scan.cur.type == TOK_ENUM; + minic_lex_next(&scan); + if (scan.cur.type == TOK_IDENT) { + minic_lex_next(&scan); } - else { - minic_lex_next(&e->lex); + definition = definition || scan.cur.type == TOK_LBRACE; + if (definition) { + int depth = 0; + do { + if (e->lex.cur.type == TOK_LBRACE) { + depth++; + } + if (e->lex.cur.type == TOK_RBRACE) { + depth--; + } + minic_lex_next(&e->lex); + } while (e->lex.cur.type != TOK_EOF && !(e->lex.cur.type == TOK_SEMICOLON && depth == 0)); + if (e->lex.cur.type == TOK_SEMICOLON) { + minic_lex_next(&e->lex); + } continue; } } - if (e->lex.cur.type == TOK_STAR) { - ret_type = MINIC_T_PTR; + minic_ctype_t type; + if (!minic_parse_type(e, &e->lex, true, &type)) { minic_lex_next(&e->lex); + continue; } if (e->lex.cur.type != TOK_IDENT) { continue; } - char fname[MINIC_MAX_NAME]; + minic_lexer_t declaration = e->lex; + char fname[MINIC_MAX_NAME]; strncpy(fname, e->lex.cur.text, MINIC_MAX_NAME - 1); 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 (ret_type == MINIC_T_PTR) { - init.deref_type = elem_type; - } - if (e->lex.cur.type == TOK_ASSIGN) { - minic_lex_next(&e->lex); // Consume '=' - init = minic_parse_cond(e); - // Stamp the declared element type on native C pointers, as in the local decl path - if (ret_type == MINIC_T_PTR && init.type == MINIC_T_PTR && !minic_in_arena(init.p)) { - init.deref_type = elem_type; - } - } - 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); + e->lex = declaration; + minic_parse_decl(e, type); + if (e->error) { + return; } continue; } @@ -2069,37 +2072,18 @@ static void minic_register_funcs(minic_env_t *e) { minic_func_t fn = {0}; strncpy(fn.name, fname, MINIC_MAX_NAME - 1); - fn.ret_type = ret_type; + fn.ret_type = type; - while (e->lex.cur.type != TOK_RPAREN && e->lex.cur.type != TOK_EOF) { - char pstruct[MINIC_MAX_NAME] = ""; - minic_type_t ptype = MINIC_T_INT; - minic_type_t pelem = MINIC_T_PTR; - if (e->lex.cur.type == TOK_STRUCT) { - minic_lex_next(&e->lex); - strncpy(pstruct, e->lex.cur.text, MINIC_MAX_NAME - 1); - 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, MINIC_MAX_NAME - 1); - } - ptype = minic_tok_to_type(e->lex.cur.type); - pelem = minic_tok_to_deref_type(e->lex.cur.type); - minic_lex_type(e); // Consume type - } - if (e->lex.cur.type == TOK_STAR) { - ptype = MINIC_T_PTR; - minic_lex_next(&e->lex); + while (e->lex.cur.type != TOK_RPAREN && e->lex.cur.type != TOK_EOF && !e->error) { + minic_ctype_t parameter; + if (!minic_parse_type(e, &e->lex, true, ¶meter)) { + minic_error(e, "expected parameter type"); + return; } 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, MINIC_MAX_NAME - 1); - strncpy(fn.param_structs[pi], pstruct, MINIC_MAX_NAME - 1); - fn.param_types[pi] = ptype; - fn.param_deref_types[pi] = pelem; + fn.param_types[pi] = parameter; minic_lex_next(&e->lex); } if (e->lex.cur.type == TOK_COMMA) { @@ -2147,26 +2131,27 @@ minic_ctx_t *minic_eval_named(const char *src, const char *filename) { minic_u8 *prev_mem = minic_active_mem; int *prev_mem_used = minic_active_mem_used; int *prev_mem_frame = minic_active_mem_frame; + int *prev_str_key = minic_active_str_key; + int *prev_str_off = minic_active_str_off; minic_active_mem = ctx->mem; minic_active_mem_used = &ctx->mem_used; minic_active_mem_frame = &ctx->mem_frame; + minic_active_str_key = ctx->str_key; + minic_active_str_off = ctx->str_off; + minic_mem_oom = false; + minic_oom_reported = false; - minic_env_t *e = &ctx->e; - e->lex.src = ctx->src_copy; - e->filename = filename; - e->var_cap = MINIC_MAX_VARS; - e->vars = minic_alloc(e->var_cap * (int)sizeof(minic_var_t)); - e->arr_cap = 32; - e->arrs = minic_alloc(e->arr_cap * (int)sizeof(minic_arr_t)); - e->arr_data = minic_alloc(512 * (int)sizeof(minic_val_t)); - e->arr_data_used = minic_alloc((int)sizeof(int)); - *e->arr_data_used = 0; - e->func_cap = 32; - e->funcs = minic_alloc(e->func_cap * (int)sizeof(minic_func_t)); - e->struct_cap = MINIC_MAX_STRUCTS; - e->structs = minic_alloc(e->struct_cap * (int)sizeof(minic_struct_t)); - e->vartype_cap = MINIC_MAX_VARTYPES; - e->vartypes = minic_alloc(e->vartype_cap * (int)sizeof(minic_vartype_t)); + minic_env_t *e = &ctx->e; + e->lex.src = ctx->src_copy; + e->filename = filename; + e->var_cap = MINIC_MAX_VARS; + e->vars = minic_alloc(e->var_cap * (int)sizeof(minic_var_t)); + e->arr_cap = 32; + e->arrs = minic_alloc(e->arr_cap * (int)sizeof(minic_arr_t)); + e->func_cap = 32; + e->funcs = minic_alloc(e->func_cap * (int)sizeof(minic_func_t)); + e->struct_cap = MINIC_MAX_STRUCTS; + e->structs = minic_alloc(e->struct_cap * (int)sizeof(minic_struct_t)); // Seed env with globally pre-registered struct definitions for (int i = 0; i < minic_struct_count && e->struct_count < e->struct_cap; ++i) { @@ -2174,6 +2159,9 @@ minic_ctx_t *minic_eval_named(const char *src, const char *filename) { } minic_register_structs(e); + for (int i = 0; i < e->struct_count && !e->error; ++i) { + minic_layout_struct(e, &e->structs[i]); + } minic_lex_next(&e->lex); minic_register_funcs(e); for (int i = 0; i < e->func_count; ++i) { @@ -2188,8 +2176,12 @@ minic_ctx_t *minic_eval_named(const char *src, const char *filename) { minic_active_mem = prev_mem; minic_active_mem_used = prev_mem_used; minic_active_mem_frame = prev_mem_frame; + minic_active_str_key = prev_str_key; + minic_active_str_off = prev_str_off; - ctx->result = e->error ? -1.0f : (float)minic_val_to_d(e->return_val); + // A frame or arena overflow deep in a call is reported on that scope's env, which does + // not propagate outward, so consult the sticky flag too rather than return a partial value + ctx->result = (e->error || minic_mem_oom) ? -1.0f : (float)minic_val_to_d(e->return_val); return ctx; } @@ -2240,7 +2232,7 @@ minic_struct_t minic_structs[MINIC_MAX_STRUCTS]; int minic_struct_count = 0; static minic_struct_t *minic_struct_cur = NULL; -void minic_struct_begin(const char *name, int size) { +void minic_struct_begin(const char *name, int size, int alignment) { minic_struct_cur = NULL; for (int i = 0; i < minic_struct_count; ++i) { if (strcmp(minic_structs[i].name, name) == 0) { @@ -2256,7 +2248,9 @@ void minic_struct_begin(const char *name, int size) { } memset(minic_struct_cur, 0, sizeof(minic_struct_t)); strncpy(minic_struct_cur->name, name, MINIC_MAX_NAME - 1); - minic_struct_cur->size = size; + minic_struct_cur->size = size; + minic_struct_cur->alignment = alignment; + minic_struct_cur->layout_state = 2; } void minic_struct_field(const char *field, int offset, minic_type_t type, minic_type_t deref_type, const char *struct_type) { @@ -2266,21 +2260,19 @@ void minic_struct_field(const char *field, int offset, minic_type_t type, minic_ } int i = s->field_count++; strncpy(s->fields[i], field, MINIC_MAX_NAME - 1); - s->offsets[i] = offset; - s->types[i] = type; - s->deref_types[i] = deref_type; + s->offsets[i] = offset; + s->types[i] = type; + s->deref_types[i] = deref_type; + s->pointer_depths[i] = type == MINIC_T_PTR ? 1 : 0; if (struct_type != NULL) { strncpy(s->field_structs[i], struct_type, MINIC_MAX_NAME - 1); } - if (offset >= 0) { - s->native = true; - } } void minic_register_struct(const char *name, const char **fields, int field_count) { - minic_struct_begin(name, 0); + minic_struct_begin(name, field_count * (int)sizeof(int32_t), MINIC_ALIGNOF(int32_t)); for (int i = 0; i < field_count; ++i) { - minic_struct_field(fields[i], -1, MINIC_T_INT, MINIC_T_INT, NULL); + minic_struct_field(fields[i], i * (int)sizeof(int32_t), MINIC_T_INT, MINIC_T_INT, NULL); } } @@ -2327,12 +2319,7 @@ void minic_register_global(const char *name, const void *ptr, minic_type_t type) bool minic_global_get(const char *name, minic_val_t *out) { for (int i = 0; i < minic_global_count; ++i) { if (strcmp(minic_globals[i].name, name) == 0) { - if (minic_globals[i].type == MINIC_T_FLOAT) { - *out = minic_val_float(*(const float *)minic_globals[i].ptr); - } - else { - *out = minic_val_int(*(const int *)minic_globals[i].ptr); - } + *out = minic_load(minic_reference((void *)minic_globals[i].ptr, minic_scalar_type(minic_globals[i].type))); return true; } } diff --git a/base/sources/libs/minic.h b/base/sources/libs/minic.h index d31d29011..876872afa 100644 --- a/base/sources/libs/minic.h +++ b/base/sources/libs/minic.h @@ -6,8 +6,8 @@ #define MINIC_MEM_SIZE (8 * 1024 * 1024) #define MINIC_MAX_PARAMS 20 -#define MINIC_MAX_VARS 128 // locals per scope -#define MINIC_MAX_VARTYPES 128 // struct-typed locals per scope +#define MINIC_MAX_ARGS 64 // Call argc, including variadic natives such as sprintf +#define MINIC_MAX_VARS 256 // locals per scope #define MINIC_MAX_EXTFUNS 1024 #define MINIC_MAX_SIG 64 #define MINIC_MAX_ENUM_CONSTS 512 @@ -15,7 +15,7 @@ #define MINIC_MAX_STRUCT_FIELDS 32 #define MINIC_MAX_STRUCTS 64 #define MINIC_MAX_GLOBALS 64 -#define MINIC_MAX_NAME 64 +#define MINIC_MAX_NAME 48 typedef unsigned char minic_u8; @@ -26,7 +26,8 @@ typedef enum { MINIC_T_BOOL = 3, // used in extern-call ABI, stored as INT in vals MINIC_T_CHAR = 4, // used in extern-call ABI, stored as INT in vals MINIC_T_VOID = 5, // void return only; stored as INT/0 in vals - MINIC_T_EMBED = 6, // embedded struct field; field address is the value (no indirection) + MINIC_T_EMBED = 6, // struct storage; expression values carry its address + MINIC_T_DOUBLE = 7, } minic_type_t; typedef struct { @@ -35,20 +36,23 @@ typedef struct { union { int i; // MINIC_T_INT float f; // MINIC_T_FLOAT + double d; // MINIC_T_DOUBLE void *p; // MINIC_T_PTR }; } minic_val_t; -// Struct descriptor. When native is true, offsets/types describe a real C layout; -// otherwise instances are stored as an array of boxed minic_val_t (script layout) +// All struct instances use C layout, whether allocated by the script or the host. typedef struct { char name[MINIC_MAX_NAME]; - int size; // sizeof the native C struct, 0 if unknown - bool native; // field offsets/types describe a native C layout + int size; + int alignment; + int layout_state; // 0 = unresolved, 1 = resolving, 2 = complete int field_count; char fields[MINIC_MAX_STRUCT_FIELDS][MINIC_MAX_NAME]; int offsets[MINIC_MAX_STRUCT_FIELDS]; // byte offset in the native C struct minic_type_t types[MINIC_MAX_STRUCT_FIELDS]; // storage type of each field + int pointer_depths[MINIC_MAX_STRUCT_FIELDS]; + int counts[MINIC_MAX_STRUCT_FIELDS]; // 0 for a scalar, otherwise fixed array length minic_type_t deref_types[MINIC_MAX_STRUCT_FIELDS]; // pointed-to type for PTR fields char field_structs[MINIC_MAX_STRUCT_FIELDS][MINIC_MAX_NAME]; // struct type name for struct-typed fields, or "" } minic_struct_t; @@ -61,7 +65,7 @@ typedef minic_val_t (*minic_native_fn_t)(minic_val_t *args, int argc); typedef struct { char name[MINIC_MAX_NAME]; - char sig[MINIC_MAX_SIG]; // documentation only + char sig[MINIC_MAX_SIG]; // typed pointer returns (p:name) also resolve member access minic_native_fn_t fn; } minic_ext_func_t; @@ -75,14 +79,13 @@ minic_val_t minic_ctx_call_fn(minic_ctx_t *ctx, void *fn_ptr, minic_val_t *args // valid as long as the owning minic_ctx_t has not been freed minic_val_t minic_call_fn(void *fn_ptr, minic_val_t *args, int argc); void *minic_alloc(int size); // allocate in the active context's arena -bool minic_in_arena(void *p); // true if p points into the active arena (script value) vs native C memory // Host api registration (idempotent, safe to re-run) void minic_register(const char *name, const char *sig, minic_native_fn_t fn); // sig like "f(p,i)" using i/f/p/b/c/v void minic_register_native(const char *name, minic_native_fn_t fn); // no sig, for variadic natives -void minic_struct_begin(const char *name, int size); +void minic_struct_begin(const char *name, int size, int alignment); void minic_struct_field(const char *field, int offset, minic_type_t type, minic_type_t deref_type, const char *struct_type); -void minic_register_struct(const char *name, const char **fields, int field_count); // script-layout struct (boxed fields) +void minic_register_struct(const char *name, const char **fields, int field_count); // contiguous int fields void minic_register_enum(const char *typedef_name, const char **names, const int *values, int count); // values NULL = 0,1,2... void minic_enum_const_add(const char *name, int value); void minic_int_typedef_add(const char *name); @@ -113,10 +116,11 @@ bool minic_global_get(const char *name, minic_val_t *out); // false // Native struct registration helpers: // MINIC_STRUCT(my_t); MINIC_I(count); MINIC_S(name); MINIC_O(child, other_t); MINIC_END(); +#define MINIC_ALIGNOF(T) offsetof(struct { char pad; T value; }, value) #define MINIC_STRUCT(T) \ { \ typedef T minic_st_t; \ - minic_struct_begin(#T, (int)sizeof(minic_st_t)) + minic_struct_begin(#T, (int)sizeof(minic_st_t), (int)MINIC_ALIGNOF(minic_st_t)) #define MINIC_FIELD(f, t, dt, s) minic_struct_field(#f, (int)offsetof(minic_st_t, f), t, dt, s) #define MINIC_I(f) MINIC_FIELD(f, MINIC_T_INT, MINIC_T_INT, NULL) // int #define MINIC_F(f) MINIC_FIELD(f, MINIC_T_FLOAT, MINIC_T_FLOAT, NULL) // float @@ -151,6 +155,14 @@ static inline minic_val_t minic_val_float(float v) { return r; } +static inline minic_val_t minic_val_double(double v) { + minic_val_t r = {0}; + r.type = MINIC_T_DOUBLE; + r.deref_type = MINIC_T_DOUBLE; + r.d = v; + return r; +} + static inline minic_val_t minic_val_ptr(void *v) { minic_val_t r; r.type = MINIC_T_PTR; @@ -179,6 +191,8 @@ static inline double minic_val_to_d(minic_val_t v) { return (double)v.i; case MINIC_T_FLOAT: return (double)v.f; + case MINIC_T_DOUBLE: + return v.d; case MINIC_T_PTR: return (double)(uintptr_t)v.p; default: @@ -198,6 +212,8 @@ static inline minic_val_t minic_val_coerce(double d, minic_type_t t) { switch (t) { case MINIC_T_FLOAT: return minic_val_float((float)d); + case MINIC_T_DOUBLE: + return minic_val_double(d); case MINIC_T_PTR: return minic_val_ptr((void *)(uintptr_t)(uint64_t)d); default: diff --git a/base/sources/libs/minic_tests.c b/base/sources/libs/minic_tests.c index eb6cb4927..2f6b7759b 100644 --- a/base/sources/libs/minic_tests.c +++ b/base/sources/libs/minic_tests.c @@ -3,6 +3,7 @@ #include "minic.h" #include +#include const char *test0 = " \n\ float hello(float a, float b) { \n\ @@ -78,7 +79,7 @@ const char *test4 = " \n\ int a = 3; \n\ int *b = &a; \n\ a += 2; \n\ - *b++; \n\ + (*b)++; \n\ int c[4]; \n\ c[2] = *b; \n\ if (c[2] == (2 + 1) * 2) { \n\ @@ -189,13 +190,264 @@ const char *test12 = " \n\ } \n\ "; -#define MINIC_TEST(n, src) \ - do { \ - minic_ctx_t *_c = minic_eval(src); \ - minic_ctx_result(_c) == 0.0f ? printf(#n ": PASS\n") : printf(#n ": FAIL\n"); \ - minic_ctx_free(_c); \ +typedef struct minic_test_context_s { + int ddirty; + int calls; + int *buffer; + int length; + float weight; + bool active; + struct minic_test_context_s *child; +} minic_test_context_t; + +static minic_test_context_t test_context; + +static minic_val_t mw_test_get_context(minic_val_t *args, int argc) { + (void)args; + (void)argc; + test_context.calls++; + return minic_val_ptr(&test_context); +} + +const char *test13 = + "float main() {" + " test_get_context()->ddirty = 4;" + " int value = test_get_context()->ddirty;" + " if (value != 4) { return 1; }" + " test_get_context()->ddirty += 2;" + " test_get_context()->ddirty++;" + " test_get_context()->child->ddirty = 9;" + " if (test_get_context()->child->ddirty != 9) { return 2; }" + " test_get_context()->buffer[1] = 7;" + " if (test_get_context()->buffer[1] != 7) { return 3; }" + " minic_test_context_t *ctx = test_get_context();" + " ctx->ddirty += 1;" + " if (ctx->ddirty != 10) { return 4; }" + " test_get_context();" + " if (ctx->calls != 10) { return 5; }" + " return 0;" + "}"; + +const char *test14 = + "int calls = 0;" + "int tick() { calls += 1; return 1; }" + "float main() {" + " if (2 + 3 * 4 != 14) { return 1; }" + " if ((32 >> 1 + 2) != 4) { return 2; }" + " if ((3 | 4 ^ 6 & 2) != 7) { return 3; }" + " if ((1 || 0 && 0) != 1) { return 4; }" + " if ((2 + 3 < 4 * 2) != 1) { return 5; }" + " if ((5.5 % 2.0) != 1.5) { return 6; }" + " if ((20 / 2 / 2) != 5) { return 7; }" + " int a = 0 && tick();" + " int b = 1 || tick();" + " if (calls != 2) { return 8; }" + " return 0;" + "}"; + +const char *test15 = + "typedef struct pair { int x; int y; } pair_t;" + "float main() {" + " int values[] = {1, 2, 3};" + " int i = 0;" + " values[i++] += 4;" + " ++values[1];" + " int *p = &values[0];" + " (*p) += 2;" + " if (values[0] != 7 || values[1] != 3 || i != 1) { return 1; }" + " int a = 0; int b = 0; a = b = 4;" + " if (a + b != 8) { return 2; }" + " pair_t item; item.x = 5;" + " if (true) { int item = 2; item += 1; if (item != 3) { return 3; } }" + " if (item.x != 5) { return 4; }" + " for (i = 0; i < 3; values[i++] += 1) {}" + " if (values[0] != 8 || values[2] != 4) { return 5; }" + " if (p[1] != values[1]) { return 6; }" + " char *text = \"abc\"; text[1] = 100;" + " if (text[1] != 100) { return 6; }" + " return 0;" + "}"; + +const char *test16 = + "minic_test_context_t *context() { return test_get_context(); }" + "int change(minic_test_context_t *ctx) { ctx->ddirty = 10; return 2; }" + "float main() {" + " minic_test_context_t *ctx = context();" + " int calls = ctx->calls;" + " context()->ddirty = 4;" + " context()->ddirty += change(ctx);" + " if (ctx->ddirty != 12 || ctx->calls != calls + 2) { return 1; }" + " int *dirty = &ctx->ddirty; *dirty += 1;" + " if (context()->ddirty != 13) { return 2; }" + " (*ctx).ddirty = 7;" + " if ((&*ctx)->ddirty != 7) { return 3; }" + " ctx->weight = 1.5; ctx->weight *= 2;" + " ctx->active = true;" + " if (ctx->weight != 3.0 || !ctx->active) { return 4; }" + " return 0;" + "}"; + +const char *test20 = + "float main() {" + " int values[] = {10, 20, 30};" + " int *p = &values[0];" + " int old = *p++;" + " if (old != 10 || *p != 20 || values[0] != 10) { return 1; }" + " old = (*p)++;" + " if (old != 20 || *p != 21) { return 2; }" + " old = *++p;" + " if (old != 30 || *p != 30) { return 3; }" + " old = ++*p;" + " if (old != 31 || values[2] != 31) { return 4; }" + " old = *p--;" + " if (old != 31 || *p != 21) { return 5; }" + " old = *--p;" + " if (old != 10 || *p != 10) { return 6; }" + " *p++ = 7;" + " if (values[0] != 7 || *p != 21) { return 7; }" + " char *text = \"abc\";" + " int ch = *text++;" + " if (ch != 97 || *text != 98) { return 8; }" + " return 0;" + "}"; + +static minic_val_t mw_test_ints(minic_val_t *args, int argc) { + (void)args; + (void)argc; + static int values[] = {4, 8, 12}; + return minic_val_typed_ptr(values, MINIC_T_INT); +} + +const char *test21 = + "float main() {" + " int *p = test_ints();" + " int a = *p++;" + " int b = *++p;" + " int c = *p--;" + " if (a != 4 || b != 12 || c != 12 || *p != 8) { return 1; }" + " if (*--p != 4) { return 2; }" + " return 0;" + "}"; + +typedef struct minic_test_inner_s { + char tag; + int count; + double weight; + bool active; +} minic_test_inner_t; + +typedef struct minic_test_outer_s { + char prefix; + minic_test_inner_t item; + float samples[3]; + minic_test_inner_t *link; +} minic_test_outer_t; + +static minic_val_t mw_test_layout(minic_val_t *args, int argc) { + minic_test_outer_t *value = minic_arg_p(args, argc, 0); + if (value == NULL || (uintptr_t)value % MINIC_ALIGNOF(minic_test_outer_t) != 0 || + value->prefix != 'P' || value->item.tag != 'T' || value->item.count != 7 || + value->item.weight != 16777217.0 || !value->item.active || value->samples[2] != 3.5f || + value->link != &value->item) { + return minic_val_int(0); + } + value->item.count = 19; + value->item.weight += 1.0; + value->samples[1] = 4.5f; + return minic_val_int(sizeof(minic_test_outer_t)); +} + +static minic_val_t mw_test_native_buffers(minic_val_t *args, int argc) { + int *ints = minic_arg_p(args, argc, 0); + float *floats = minic_arg_p(args, argc, 1); + double *doubles = minic_arg_p(args, argc, 2); + char *chars = minic_arg_p(args, argc, 3); + bool *bools = minic_arg_p(args, argc, 4); + int **pointer = minic_arg_p(args, argc, 5); + if (ints[1] != 2 || floats[1] != 2.5f || doubles[1] != 16777217.0 || chars[1] != 'b' || !bools[1]) { + return minic_val_int(1); + } + ints[1] = 9; + floats[1] = 4.5f; + doubles[1] += 1.0; + snprintf(chars, 8, "raw"); + bools[0] = true; + *pointer = &ints[1]; + return minic_val_int(0); +} + +const char *test22 = + "typedef struct native_inner { char tag; int count; double weight; bool active; } native_inner_t;" + "typedef struct native_outer { char prefix; native_inner_t item; float samples[3]; native_inner_t *link; } native_outer_t;" + "int copy_count(native_inner_t item) { item.count = 99; return item.count; }" + "float main() {" + " native_outer_t value;" + " value.prefix = 'P'; value.item.tag = 'T'; value.item.count = 7;" + " value.item.weight = 16777217; value.item.active = true;" + " value.samples[2] = 3.5; value.link = &value.item;" + " if (test_layout(&value) != sizeof(native_outer_t)) { return 1; }" + " if (value.item.count != 19 || value.item.weight != 16777218 || value.samples[1] != 4.5) { return 2; }" + " native_inner_t copy = value.item; copy.count = 3;" + " if (value.item.count != 19 || copy_count(copy) != 99 || copy.count != 3) { return 3; }" + " native_outer_t array[2]; array[0] = value; array[1] = value;" + " native_outer_t *p = &array[0]; p++; p->item.count = 42;" + " if (array[1].item.count != 42 || array[0].item.count != 19) { return 4; }" + " return 0;" + "}"; + +const char *test23 = + "float main() {" + " int ints[] = {1, 2, 3}; float floats[] = {1.5, 2.5};" + " double doubles[] = {1, 16777217}; char chars[8]; bool bools[] = {false, true};" + " chars[1] = 'b'; int *p = &ints[0];" + " if (test_native_buffers(ints, floats, doubles, chars, bools, &p) != 0) { return 1; }" + " if (*p != 9 || floats[1] != 4.5 || doubles[1] != 16777218 || chars[2] != 'w' || !bools[0]) { return 2; }" + " int **pp = &p; **pp = 12;" + " if (ints[1] != 12) { return 3; }" + " if (sizeof(chars) != 8 || sizeof(bools) != 2 || sizeof(doubles) != 2 * sizeof(double)) { return 4; }" + " return 0;" + "}"; + +const char *test24 = + "float main() {" + " vec4_t v; v.x = 1.5; v.y = 2; v.z = 3; v.w = 1;" + " vec4_t scaled = vec4_mult(v, 2);" + " if (scaled.x != 3 || scaled.z != 6 || v.x != 1.5) { return 1; }" + " mat4_t m = mat4_identity(); mat4_t n = mat4_mult_mat(m, m);" + " if (n.m00 != 1 || n.m33 != 1) { return 2; }" + " mat3_t small = mat3_identity(); if (small.m22 != 1) { return 3; }" + " char buf[16]; int len = sprintf(buf, \"%s %d\", \"raw\", 12);" + " if (len != 6 || buf[0] != 'r' || buf[4] != '1' || buf[6] != 0) { return 4; }" + " return 0;" + "}"; + +static void minic_test_register_context(void) { + static int buffer[2]; + test_context = (minic_test_context_t){0}; + test_context.buffer = buffer; + test_context.length = 2; + test_context.child = &test_context; + minic_struct_begin("minic_test_context_t", sizeof(minic_test_context_t), MINIC_ALIGNOF(minic_test_context_t)); + minic_struct_field("ddirty", offsetof(minic_test_context_t, ddirty), MINIC_T_INT, MINIC_T_INT, NULL); + minic_struct_field("calls", offsetof(minic_test_context_t, calls), MINIC_T_INT, MINIC_T_INT, NULL); + minic_struct_field("buffer", offsetof(minic_test_context_t, buffer), MINIC_T_PTR, MINIC_T_INT, NULL); + minic_struct_field("length", offsetof(minic_test_context_t, length), MINIC_T_INT, MINIC_T_INT, NULL); + minic_struct_field("weight", offsetof(minic_test_context_t, weight), MINIC_T_FLOAT, MINIC_T_FLOAT, NULL); + minic_struct_field("active", offsetof(minic_test_context_t, active), MINIC_T_BOOL, MINIC_T_BOOL, NULL); + minic_struct_field("child", offsetof(minic_test_context_t, child), MINIC_T_PTR, MINIC_T_PTR, "minic_test_context_t"); + minic_register("test_get_context", "p:minic_test_context_t()", mw_test_get_context); +} + +#define MINIC_TEST_EXPECT(n, src, expected) \ + do { \ + minic_ctx_t *_c = minic_eval(src); \ + bool passed = minic_ctx_result(_c) == (expected); \ + printf(#n ": %s\n", passed ? "PASS" : "FAIL"); \ + minic_ctx_free(_c); \ } while (0) +#define MINIC_TEST(n, src) MINIC_TEST_EXPECT(n, src, 0.0f) + static minic_val_t mw_test2_get(minic_val_t *a, int n) { return minic_val_ptr(test2_get(minic_arg_i(a, n, 0))); } @@ -225,6 +477,35 @@ void minic_tests() { MINIC_TEST(10, test10); MINIC_TEST(11, test11); MINIC_TEST(12, test12); + minic_test_register_context(); + MINIC_TEST(13, test13); + MINIC_TEST(14, test14); + MINIC_TEST(15, test15); + MINIC_TEST(16, test16); + MINIC_TEST(20, test20); + minic_register("test_ints", "p:int()", mw_test_ints); + MINIC_TEST(21, test21); + minic_register("test_layout", "i(p)", mw_test_layout); + minic_register("test_native_buffers", "i(p,p,p,p,p,p)", mw_test_native_buffers); + MINIC_TEST(22, test22); + MINIC_TEST(23, test23); + MINIC_TEST(24, test24); + MINIC_TEST_EXPECT(25, "float main() { float a[] = {.25, 1, .5f}; return a[0] + a[2] - .75; }", 0.0f); + MINIC_TEST_EXPECT(26, "float main() { float a[] = {1e3, 2.5E-2, .5e+1f, 3e0}; return a[0] + a[1] * 40 + a[2] + a[3] - 1009; }", 0.0f); + MINIC_TEST_EXPECT(27, + "float main() {" + " if ((1 || 1 && 0) != 1) { return 1; }" + " if ((3 == 3 < 4) != 0) { return 2; }" + " if ((3 > 2 > 1) != 0) { return 3; }" + " if ((1 | 2 ^ 3 & 1) != 3) { return 4; }" + " if ((1 != 2 == 1) != 1) { return 5; }" + " if ((1 << 2 < 5) != 1) { return 6; }" + " return 0;" + "}", + 0.0f); + MINIC_TEST_EXPECT(17, "float main() { int a[2]; return a[2]; }", -1.0f); + MINIC_TEST_EXPECT(18, "float main() { test_get_context()->buffer[2] = 99; return 0; }", -1.0f); + MINIC_TEST_EXPECT(19, "float main() { minic_test_context_t *p = NULL; return p->ddirty; }", -1.0f); } #endif