Files
armorpaint/base/sources/libs/minic.c
T
2026-10-01 09:34:24 +02:00

3173 lines
101 KiB
C

// Minimal C interpreter
// The source is tokenized and compiled to bytecode once, then run on a small stack VM.
// Names resolve at compile time: variables to frame or global slots, fields to byte offsets,
// functions to indices. Values keep their runtime type tags, arithmetic widens like C.
#include "minic.h"
#include <ctype.h>
#include <math.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// ████████╗ ██████╗ ██╗ ██╗███████╗███╗ ██╗
// ╚══██╔══╝██╔═══██╗██║ ██╔╝██╔════╝████╗ ██║
// ██║ ██║ ██║█████╔╝ █████╗ ██╔██╗ ██║
// ██║ ██║ ██║██╔═██╗ ██╔══╝ ██║╚██╗██║
// ██║ ╚██████╔╝██║ ██╗███████╗██║ ╚████║
// ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═══╝
#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_INT16, "'int16_t'") \
X(TOK_UINT16, "'uint16_t'") \
X(TOK_VOID, "'void'") \
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_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_QUESTION, "'?'") \
X(TOK_COLON, "':'") X(TOK_EOF, "end of file")
typedef enum {
#define X(t, s) t,
MINIC_TOK_LIST
#undef X
} minic_tok_type_t;
static const char *minic_tok_names[] = {
#define X(t, s) s,
MINIC_TOK_LIST
#undef X
};
typedef struct {
minic_tok_type_t type;
int pos; // source offset, for line numbers
minic_val_t val; // TOK_NUMBER, TOK_CHAR_LIT, TOK_STR_LIT
char text[MINIC_MAX_NAME];
} minic_token_t;
static minic_ctx_t *minic_active = NULL; // Context whose arena minic_alloc uses
static bool minic_mem_oom = false;
static minic_ext_func_t minic_ext_funcs[MINIC_MAX_EXTFUNS]; // Registry, defined with the other externals below
static const void *minic_global_ptr(const char *name, minic_type_t *type);
static int minic_enum_const_find(const char *name);
static const struct {
const char *kw;
minic_tok_type_t tok;
} minic_keywords[] = {
{"int", TOK_INT}, {"float", TOK_FLOAT}, {"char", TOK_CHAR}, {"double", TOK_DOUBLE}, {"bool", TOK_BOOL}, {"void", TOK_VOID},
{"int16_t", TOK_INT16}, {"short", TOK_INT16}, {"uint16_t", TOK_UINT16},
{"return", TOK_RETURN}, {"if", TOK_IF}, {"else", TOK_ELSE}, {"while", TOK_WHILE}, {"for", TOK_FOR}, {"break", TOK_BREAK},
{"continue", TOK_CONTINUE}, {"struct", TOK_STRUCT}, {"typedef", TOK_TYPEDEF}, {"enum", TOK_ENUM},
};
// Longer operators must come before their prefixes
static const struct {
const char *op;
minic_tok_type_t tok;
} minic_ops[] = {
{"<<=", TOK_SHL_ASSIGN}, {">>=", TOK_SHR_ASSIGN}, {"++", TOK_INC}, {"+=", TOK_PLUS_ASSIGN}, {"--", TOK_DEC}, {"-=", TOK_MINUS_ASSIGN},
{"->", TOK_ARROW}, {"*=", TOK_MUL_ASSIGN}, {"/=", TOK_DIV_ASSIGN}, {"==", TOK_EQ}, {"!=", TOK_NEQ}, {"&&", TOK_AND},
{"||", TOK_OR}, {"<=", TOK_LE}, {">=", TOK_GE}, {"<<", TOK_SHL}, {">>", TOK_SHR}, {"%=", TOK_MOD_ASSIGN},
{"&=", TOK_AND_ASSIGN}, {"|=", TOK_OR_ASSIGN}, {"^=", TOK_XOR_ASSIGN}, {"+", TOK_PLUS}, {"-", TOK_MINUS}, {"*", TOK_STAR},
{"/", TOK_SLASH}, {"%", TOK_PERCENT}, {"=", TOK_ASSIGN}, {"!", TOK_NOT}, {"&", TOK_AMP}, {"|", TOK_BITOR},
{"^", TOK_XOR}, {"~", TOK_BITNOT}, {"<", TOK_LT}, {">", TOK_GT}, {"(", TOK_LPAREN}, {")", TOK_RPAREN},
{"{", TOK_LBRACE}, {"}", TOK_RBRACE}, {"[", TOK_LBRACKET}, {"]", TOK_RBRACKET}, {";", TOK_SEMICOLON}, {",", TOK_COMMA},
{".", TOK_DOT}, {"?", TOK_QUESTION}, {":", TOK_COLON},
};
static int minic_escape(char c) {
const char *escapes = "ntr\\\"'";
const char *values = "\n\t\r\\\"'";
const char *e = c != '\0' ? strchr(escapes, c) : NULL;
return e != NULL ? values[e - escapes] : '\0';
}
// Skip whitespace, comments and preprocessor directives
static int minic_lex_skip_trivia(const char *src, int pos) {
for (;;) {
while (src[pos] != '\0' && isspace((unsigned char)src[pos])) {
pos++;
}
if ((src[pos] == '/' && src[pos + 1] == '/') || src[pos] == '#') {
while (src[pos] != '\0' && src[pos] != '\n') {
pos++;
}
continue;
}
if (src[pos] == '/' && src[pos + 1] == '*') {
pos += 2;
while (src[pos] != '\0' && !(src[pos] == '*' && src[pos + 1] == '/')) {
pos++;
}
if (src[pos] != '\0') {
pos += 2;
}
continue;
}
return pos;
}
}
// Lex the token at src[pos] into t, return the position after it
static int minic_lex(const char *src, int pos, char *str_pool, minic_token_t *t) {
for (;;) {
pos = minic_lex_skip_trivia(src, pos);
t->pos = pos;
char c = src[pos];
if (c == '\0') {
t->type = TOK_EOF;
return pos;
}
if (c == '0' && (src[pos + 1] == 'x' || src[pos + 1] == 'X')) {
char *end;
t->val = minic_val_int((int)(unsigned int)strtoull(src + pos, &end, 16));
t->type = TOK_NUMBER;
return (int)(end - src);
}
// Also accept a leading-dot float like .5
if (isdigit((unsigned char)c) || (c == '.' && isdigit((unsigned char)src[pos + 1]))) {
double n = 0;
while (isdigit((unsigned char)src[pos])) {
n = n * 10 + (src[pos++] - '0');
}
bool is_float = false;
if (src[pos] == '.') {
pos++;
double frac = 0.1;
while (isdigit((unsigned char)src[pos])) {
n += (src[pos++] - '0') * frac;
frac *= 0.1;
}
is_float = true;
}
// Exponent like 1e-3, only when digits follow so '1e' stays unconsumed
if (src[pos] == 'e' || src[pos] == 'E') {
int p = pos + 1;
if (src[p] == '+' || src[p] == '-') {
p++;
}
if (isdigit((unsigned char)src[p])) {
bool neg = src[pos + 1] == '-';
int exp = 0;
while (isdigit((unsigned char)src[p])) {
exp = exp * 10 + (src[p++] - '0');
}
n *= pow(10.0, neg ? -exp : exp);
pos = p;
is_float = true;
}
}
if (src[pos] == 'f' || src[pos] == 'F') {
pos++;
is_float = true;
}
t->val = is_float ? minic_val_float((float)n) : minic_val_int((int)n);
t->type = TOK_NUMBER;
return pos;
}
if (c == '"') {
// Literals live in the context's pool at the source offset of their opening quote,
// valid for the context's lifetime, like static storage in C. The decoded text is
// never longer than its source span, so literals cannot overlap.
char *dst = str_pool + pos;
int wi = 0;
// Adjacent string literals concatenate into a single string
while (src[pos] == '"') {
pos++; // Consume opening '"'
while (src[pos] != '"' && src[pos] != '\0') {
char ch = src[pos++];
if (ch == '\\') {
char esc = src[pos++];
if (esc == '\n') {
continue; // Line continuation: backslash-newline, skip both
}
if (esc == '\r') { // Handle \r\n line endings
if (src[pos] == '\n') {
pos++;
}
continue;
}
ch = (char)minic_escape(esc);
}
dst[wi++] = ch;
}
if (src[pos] == '"') {
pos++; // Consume closing '"'
}
int next = minic_lex_skip_trivia(src, pos); // Whitespace or a comment may separate the literals
if (src[next] != '"') {
break;
}
pos = next;
}
dst[wi] = '\0';
t->type = TOK_STR_LIT;
t->val = minic_val_typed_ptr((void *)dst, MINIC_T_CHAR);
return pos;
}
if (c == '\'') {
pos++; // Consume opening '
int v;
if (src[pos] == '\\') {
pos++;
v = minic_escape(src[pos++]);
}
else {
v = (unsigned char)src[pos++];
}
pos++; // Consume closing '
t->type = TOK_CHAR_LIT;
t->val = minic_val_int(v);
return pos;
}
if (isalpha((unsigned char)c) || c == '_') {
int i = 0;
while (isalnum((unsigned char)src[pos]) || src[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) {
t->text[i++] = src[pos];
}
pos++;
}
t->text[i] = '\0';
for (size_t k = 0; k < sizeof(minic_keywords) / sizeof(minic_keywords[0]); ++k) {
if (minic_keywords[k].kw[0] == t->text[0] && strcmp(t->text, minic_keywords[k].kw) == 0) {
t->type = minic_keywords[k].tok;
return pos;
}
}
if (strcmp(t->text, "true") == 0 || strcmp(t->text, "false") == 0) {
t->type = TOK_NUMBER;
t->val = minic_val_int(t->text[0] == 't');
return pos;
}
if (strcmp(t->text, "NULL") == 0) {
t->type = TOK_NUMBER;
t->val = minic_val_int(0);
return pos;
}
t->type = TOK_IDENT;
return pos;
}
for (size_t k = 0; k < sizeof(minic_ops) / sizeof(minic_ops[0]); ++k) {
const char *op = minic_ops[k].op;
int n = 0;
while (op[n] != '\0' && op[n] == src[pos + n]) {
n++;
}
if (op[n] == '\0') {
t->type = minic_ops[k].tok;
return pos + n;
}
}
pos++; // Unknown character: skip it
}
}
// Lex the whole source once, the array ends with a TOK_EOF
static minic_token_t *minic_tokenize(const char *src, char *str_pool) {
int cap = 256;
int n = 0;
int pos = 0;
minic_token_t *toks = malloc(cap * sizeof(minic_token_t));
for (;;) {
if (n == cap) {
cap *= 2;
toks = realloc(toks, cap * sizeof(minic_token_t));
}
minic_token_t *t = &toks[n++];
pos = minic_lex(src, pos, str_pool, t);
if (t->type == TOK_EOF) {
return toks;
}
}
}
// ████████╗██╗ ██╗██████╗ ███████╗███████╗
// ╚══██╔══╝╚██╗ ██╔╝██╔══██╗██╔════╝██╔════╝
// ██║ ╚████╔╝ ██████╔╝█████╗ ███████╗
// ██║ ╚██╔╝ ██╔═══╝ ██╔══╝ ╚════██║
// ██║ ██║ ██║ ███████╗███████║
// ╚═╝ ╚═╝ ╚═╝ ╚══════╝╚══════╝
typedef struct {
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];
char params[MINIC_MAX_PARAMS][MINIC_MAX_NAME];
minic_ctype_t param_types[MINIC_MAX_PARAMS];
int param_count;
int body; // token index of the '{' that starts the body, -1 for a prototype
int entry; // bytecode offset
int slot_count;
minic_ctype_t ret_type;
minic_ctx_t *ctx; // owning context
} minic_func_t;
typedef struct {
int pc;
minic_val_t *fp;
} minic_frame_t;
#define MINIC_STACK_SIZE (1024 * 1024) // Top of the arena, VM values: call frames and temporaries
#define MINIC_STACK_SLACK 1024 // Temporaries a frame may push on top of its slots
#define MINIC_MAX_FRAMES 4096
#define MINIC_MAX_GLOBAL_VARS 1024
struct minic_ctx_s {
minic_u8 *mem;
int mem_used;
int mem_frame; // End of the heap, the VM stack sits above it
char *str_pool; // String literals, indexed by source offset
char *src_copy;
const char *filename;
int *code;
int *code_pos; // Source offset of each code word, for runtime errors
int code_len;
int code_cap;
minic_val_t *consts;
int const_count;
int const_cap;
minic_func_t *funcs;
int func_count;
int func_cap;
minic_struct_t *structs;
int struct_count;
minic_val_t *globals;
int global_count;
minic_func_t init; // Global initializers
minic_val_t *stack_end;
minic_val_t *sp;
minic_frame_t *frames;
int depth;
minic_val_t return_val;
float result;
};
static void *minic_alloc_aligned(int size, int alignment) {
minic_ctx_t *ctx = minic_active;
uintptr_t start = (uintptr_t)ctx->mem + ctx->mem_used;
uintptr_t address = (start + alignment - 1) & ~(uintptr_t)(alignment - 1);
size_t offset = address - (uintptr_t)ctx->mem;
if (size < 0 || offset > (size_t)ctx->mem_frame || (size_t)size > (size_t)ctx->mem_frame - offset) {
minic_mem_oom = true;
return NULL;
}
ctx->mem_used = (int)offset + size;
return (void *)address;
}
void *minic_alloc(int size) {
return minic_alloc_aligned(size, MINIC_ALIGNOF(long double));
}
static bool minic_tok_is_type(minic_tok_type_t t) {
return t >= TOK_INT && t <= TOK_VOID;
}
static minic_type_t minic_tok_to_type(minic_tok_type_t t) {
static const minic_type_t types[] = {MINIC_T_INT, MINIC_T_FLOAT, MINIC_T_CHAR, MINIC_T_DOUBLE, MINIC_T_BOOL, MINIC_T_I16, MINIC_T_U16, MINIC_T_VOID};
return types[t - TOK_INT];
}
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_I16:
case MINIC_T_U16:
type.size = sizeof(int16_t);
type.alignment = MINIC_ALIGNOF(int16_t);
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.kind == MINIC_T_VOID) {
pointer = minic_scalar_type(MINIC_T_PTR); // Only known at run time, index it as a pointer array
}
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;
}
// The pointee tag a loaded pointer carries
static minic_type_t minic_load_deref(minic_ctype_t type) {
return type.pointer > 1 ? MINIC_T_PTR : type.deref;
}
static minic_struct_t *minic_struct_get(minic_ctx_t *ctx, const char *name) {
for (int i = 0; i < ctx->struct_count; ++i) {
if (strcmp(ctx->structs[i].name, name) == 0) {
return &ctx->structs[i];
}
}
return NULL;
}
static int minic_struct_field_idx(minic_struct_t *def, const char *field) {
for (int i = 0; i < def->field_count; ++i) {
if (strcmp(def->fields[i], field) == 0) {
return i;
}
}
return -1;
}
static minic_ctype_t minic_field_type(minic_ctx_t *ctx, 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(ctx, 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;
}
// ██╗ ██╗ █████╗ ██╗ ██╗ ██╗███████╗███████╗
// ██║ ██║██╔══██╗██║ ██║ ██║██╔════╝██╔════╝
// ██║ ██║███████║██║ ██║ ██║█████╗ ███████╗
// ╚██╗ ██╔╝██╔══██║██║ ██║ ██║██╔══╝ ╚════██║
// ╚████╔╝ ██║ ██║███████╗╚██████╔╝███████╗███████║
// ╚═══╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚══════╝╚══════╝
typedef enum {
OP_HALT,
OP_INT, // imm: push an int
OP_CONST, // k: push consts[k]
OP_POP,
OP_DUP,
OP_LOADV, // ref: push a variable slot
OP_STOREV, // ref kind size: store the top into a variable, the value stays
OP_INITV, // ref kind deref: pop the initial value of a declared variable
OP_INIT_EMBED, // ref size alignment: allocate struct storage for a variable
OP_INIT_ARR, // ref kind size alignment: pop the count, allocate an array
OP_ADDRV, // ref deref: push the address of a variable
OP_LOADM, // kind deref: replace an address with the value stored there
OP_STOREM, // kind size: pop value and address, store, push the value
OP_LOADH, // k: push a host global, consts[k] holds its address and type
OP_FIELD, // offset field: replace a struct pointer with a field address
OP_INDEX, // size length: pop index and pointer, push the element address
OP_INDEX_ARR, // ref size: pop index, push the element address of an array variable
OP_INDEX_BUF, // size delta: pop index and a buffer field address, bounded by its length field
OP_INCV, // ref kind delta post stride
OP_INCM, // kind deref delta post stride size
OP_COMPV, // ref op kind size: compound assignment to a variable
OP_COMPM, // op kind deref size: compound assignment through an address
OP_ADD, // Binary operators, in the order of minic_binop
OP_SUB,
OP_MUL,
OP_DIV,
OP_MOD,
OP_SHL,
OP_SHR,
OP_BAND,
OP_BOR,
OP_XOR,
OP_EQ,
OP_NE,
OP_LT,
OP_GT,
OP_LE,
OP_GE,
OP_NEG,
OP_NOT,
OP_BNOT,
OP_CAST, // kind
OP_TOPTR, // deref
OP_JMP, // target
OP_JZ, // target: pop, jump when false
OP_JNZ, // target: pop, jump when true
OP_CALL, // func argc
OP_CALLN, // ext argc
OP_FNPTR, // func
OP_RET, // kind deref
} minic_op_t;
static inline int minic_val_to_i(minic_val_t v) {
return v.type == MINIC_T_INT ? v.i : (int)minic_val_to_d(v);
}
static minic_val_t minic_mem_load(void *p, minic_type_t kind, minic_type_t deref) {
if (p == NULL) {
return minic_val_int(0);
}
switch (kind) {
case MINIC_T_PTR: {
void *pointer;
memcpy(&pointer, p, sizeof(pointer));
return minic_val_typed_ptr(pointer, 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_I16: {
int16_t n;
memcpy(&n, p, sizeof(n));
return minic_val_int(n);
}
case MINIC_T_U16: {
uint16_t n;
memcpy(&n, p, sizeof(n));
return minic_val_int(n);
}
case MINIC_T_VOID:
return minic_val_int(0);
default: {
int32_t n;
memcpy(&n, p, sizeof(n));
return minic_val_int(n);
}
}
}
static void minic_mem_store(void *p, minic_val_t v, minic_type_t kind, int size) {
if (p == NULL) {
return;
}
switch (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, size);
}
break;
case MINIC_T_FLOAT: {
float n = v.type == MINIC_T_FLOAT ? v.f : (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_i(v);
break;
case MINIC_T_I16:
case MINIC_T_U16: {
uint16_t n = (uint16_t)minic_val_to_i(v);
memcpy(p, &n, sizeof(n));
break;
}
default: {
int32_t n = minic_val_to_i(v);
memcpy(p, &n, sizeof(n));
break;
}
}
}
// Variables live in minic_val_t slots whose tag is set when they are declared: int, char,
// int16_t, uint16_t and bool use an INT tag, struct variables hold a pointer to their storage. The union
// is the variable's native storage, so '&x' points at it. MINIC_T_VOID is a variable
// typed by its first value.
static void minic_slot_store(minic_val_t *s, minic_val_t v, minic_type_t kind, int size) {
switch (kind) {
case MINIC_T_INT:
s->i = minic_val_to_i(v);
break;
case MINIC_T_CHAR:
s->i = (minic_u8)minic_val_to_i(v);
break;
case MINIC_T_I16:
s->i = (int16_t)minic_val_to_i(v);
break;
case MINIC_T_U16:
s->i = (uint16_t)minic_val_to_i(v);
break;
case MINIC_T_BOOL:
s->i = minic_val_is_true(v);
break;
case MINIC_T_FLOAT:
s->f = v.type == MINIC_T_FLOAT ? v.f : (float)minic_val_to_d(v);
break;
case MINIC_T_DOUBLE:
s->d = minic_val_to_d(v);
break;
case MINIC_T_PTR:
s->p = minic_val_to_ptr(v);
break;
case MINIC_T_EMBED:
if (v.type == MINIC_T_PTR && v.p != NULL && v.p != s->p) {
memmove(s->p, v.p, size);
}
break;
default: {
minic_type_t deref = s->deref_type;
*s = minic_val_cast(v, s->type);
s->deref_type = deref;
break;
}
}
}
static void minic_slot_init(minic_val_t *s, minic_val_t v, minic_type_t kind, minic_type_t deref) {
if (kind == MINIC_T_VOID) {
*s = v;
return;
}
s->type = kind == MINIC_T_CHAR || kind == MINIC_T_BOOL || kind == MINIC_T_I16 || kind == MINIC_T_U16 ? MINIC_T_INT : kind;
s->deref_type = deref;
s->d = 0.0;
minic_slot_store(s, v, kind, 0);
}
static minic_val_t minic_arith(minic_val_t a, minic_val_t b, int op) {
if (a.type == MINIC_T_INT && b.type == MINIC_T_INT) {
unsigned int x = (unsigned int)a.i;
unsigned int y = (unsigned int)b.i;
switch (op) {
case OP_ADD:
return minic_val_int((int)(x + y));
case OP_SUB:
return minic_val_int((int)(x - y));
case OP_MUL:
return minic_val_int((int)(x * y));
case OP_DIV:
return minic_val_int(b.i == 0 ? 0 : b.i == -1 ? (int)(0u - x) : a.i / b.i);
default:
return minic_val_int(b.i == 0 || b.i == -1 ? 0 : a.i % b.i);
}
}
if (a.type == MINIC_T_FLOAT && b.type == MINIC_T_FLOAT) {
// Same results as the double path below: one float op rounds exactly like double then float
switch (op) {
case OP_ADD:
return minic_val_float(a.f + b.f);
case OP_SUB:
return minic_val_float(a.f - b.f);
case OP_MUL:
return minic_val_float(a.f * b.f);
case OP_DIV:
return minic_val_float(b.f != 0.0f ? a.f / b.f : 0.0f);
default:
return minic_val_float(b.f != 0.0f ? fmodf(a.f, b.f) : 0.0f);
}
}
// 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;
}
else {
rt = MINIC_T_INT;
}
double da = minic_val_to_d(a);
double db = minic_val_to_d(b);
double r;
switch (op) {
case OP_ADD:
r = da + db;
break;
case OP_SUB:
r = da - db;
break;
case OP_MUL:
r = da * db;
break;
case OP_DIV:
r = db != 0.0 ? da / db : 0.0;
break;
default:
if (rt == MINIC_T_FLOAT || rt == MINIC_T_DOUBLE) {
r = db != 0.0 ? fmod(da, db) : 0.0;
}
else {
int ib = (int)db;
r = ib != 0 ? (double)((int)da % ib) : 0.0;
}
break;
}
return minic_val_coerce(r, rt);
}
static minic_val_t minic_binop(int op, minic_val_t a, minic_val_t b) {
switch (op) {
case OP_ADD:
case OP_SUB:
case OP_MUL:
case OP_DIV:
case OP_MOD:
return minic_arith(a, b, op);
case OP_SHL:
return minic_val_int((int)((unsigned int)minic_val_to_i(a) << (minic_val_to_i(b) & 31)));
case OP_SHR:
return minic_val_int(minic_val_to_i(a) >> (minic_val_to_i(b) & 31));
case OP_BAND:
return minic_val_int(minic_val_to_i(a) & minic_val_to_i(b));
case OP_BOR:
return minic_val_int(minic_val_to_i(a) | minic_val_to_i(b));
case OP_XOR:
return minic_val_int(minic_val_to_i(a) ^ minic_val_to_i(b));
case OP_EQ:
return minic_val_int(minic_val_to_d(a) == minic_val_to_d(b));
case OP_NE:
return minic_val_int(minic_val_to_d(a) != minic_val_to_d(b));
case OP_LT:
return minic_val_int(minic_val_to_d(a) < minic_val_to_d(b));
case OP_GT:
return minic_val_int(minic_val_to_d(a) > minic_val_to_d(b));
case OP_LE:
return minic_val_int(minic_val_to_d(a) <= minic_val_to_d(b));
default:
return minic_val_int(minic_val_to_d(a) >= minic_val_to_d(b));
}
}
static minic_val_t minic_step(minic_val_t old, int delta, int stride) {
if (old.type == MINIC_T_PTR) {
if (old.p != NULL) {
old.p = (char *)old.p + delta * stride; // Keep the pointee type as well
}
return old;
}
if (old.type == MINIC_T_INT) {
return minic_val_int((int)((unsigned int)old.i + (unsigned int)delta));
}
return minic_val_coerce(minic_val_to_d(old) + delta, old.type);
}
static minic_val_t minic_cast(minic_val_t v, minic_type_t kind) {
switch (kind) {
case MINIC_T_FLOAT:
case MINIC_T_DOUBLE:
case MINIC_T_PTR:
return minic_val_cast(v, kind);
case MINIC_T_CHAR:
return minic_val_int((minic_u8)minic_val_to_i(v));
case MINIC_T_I16:
return minic_val_int((int16_t)minic_val_to_i(v));
case MINIC_T_U16:
return minic_val_int((uint16_t)minic_val_to_i(v));
case MINIC_T_BOOL:
return minic_val_int(minic_val_is_true(v));
case MINIC_T_EMBED:
return v;
default:
return minic_val_cast(v, MINIC_T_INT);
}
}
// ██████╗ ██████╗ ███╗ ███╗██████╗ ██╗██╗ ███████╗
// ██╔════╝██╔═══██╗████╗ ████║██╔══██╗██║██║ ██╔════╝
// ██║ ██║ ██║██╔████╔██║██████╔╝██║██║ █████╗
// ██║ ██║ ██║██║╚██╔╝██║██╔═══╝ ██║██║ ██╔══╝
// ╚██████╗╚██████╔╝██║ ╚═╝ ██║██║ ██║███████╗███████╗
// ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚══════╝
typedef struct {
char name[MINIC_MAX_NAME];
minic_ctype_t type; // Element type for arrays, MINIC_T_VOID when typed by its first value
int ref; // Frame slot, or -(global slot + 1)
bool array; // The slot holds the data pointer, the next one the element count
} minic_sym_t;
typedef struct {
int breaks; // Chains of jump operands to patch, linked through the operands
int continues;
} minic_loop_t;
typedef struct {
minic_ctx_t *ctx;
minic_token_t *toks;
int i;
bool error;
minic_sym_t *locals;
int local_count;
minic_sym_t *globals;
int global_count;
minic_func_t *fn; // Function being compiled
bool in_main;
int depth; // Block depth, the top level of main() is 1
int slot_count;
minic_loop_t *loop;
} minic_comp_t;
typedef enum {
MINIC_E_VALUE, // On the stack
MINIC_E_VAR, // In a variable slot
MINIC_E_MEM, // At the address on the stack
MINIC_E_FUNC, // A function name
MINIC_E_NEWVAR, // Unknown name about to be assigned, declares a variable
} minic_emode_t;
typedef struct {
minic_emode_t mode;
minic_ctype_t type; // MINIC_T_VOID when only known at run time
minic_sym_t *sym;
int length; // Static element count of a decayed array field, else -1
int buf_delta; // '->buffer' field address to its 'length' field, 0 if none
int fn; // Script function index, or -1 for a native
minic_ext_func_t *ext;
const char *name;
} minic_cexpr_t;
void console_log(char *s);
static int minic_line_at(const char *src, int pos) {
int line = 1;
for (int i = 0; i < pos && src[i] != '\0'; i++) {
if (src[i] == '\n') {
line++;
}
}
return line;
}
static minic_token_t *minic_tok(minic_comp_t *c) {
return &c->toks[c->i];
}
static minic_tok_type_t minic_cur(minic_comp_t *c) {
return c->toks[c->i].type;
}
static minic_tok_type_t minic_peek(minic_comp_t *c, int k) {
int i = c->i;
while (k-- > 0 && c->toks[i].type != TOK_EOF) {
i++;
}
return c->toks[i].type;
}
static void minic_next(minic_comp_t *c) {
if (c->toks[c->i].type != TOK_EOF) {
c->i++;
}
}
// Skip to the next 'stop' token outside of nested parentheses and braces
static void minic_skip_to(minic_comp_t *c, minic_tok_type_t stop) {
int depth = 0;
while (minic_cur(c) != TOK_EOF && !(minic_cur(c) == stop && depth == 0)) {
depth += minic_cur(c) == TOK_LBRACE || minic_cur(c) == TOK_LPAREN;
depth -= minic_cur(c) == TOK_RBRACE || minic_cur(c) == TOK_RPAREN;
minic_next(c);
}
}
static void minic_error(minic_comp_t *c, const char *fmt, ...) {
if (c->error) {
return;
}
char msg[256];
va_list args;
va_start(args, fmt);
vsnprintf(msg, sizeof(msg), fmt, args);
va_end(args);
char log[512];
snprintf(log, sizeof(log), "%s:%d: error: %s (got %s)", c->ctx->filename, minic_line_at(c->ctx->src_copy, minic_tok(c)->pos), msg,
minic_tok_names[minic_cur(c)]);
console_log(log);
c->error = true;
}
static void minic_expect(minic_comp_t *c, minic_tok_type_t expected) {
if (minic_cur(c) != expected) {
minic_error(c, "expected %s", minic_tok_names[expected]);
return;
}
minic_next(c);
}
static void minic_emit_word(minic_comp_t *c, int word) {
minic_ctx_t *ctx = c->ctx;
if (ctx->code_len == ctx->code_cap) {
ctx->code_cap = ctx->code_cap > 0 ? ctx->code_cap * 2 : 1024;
ctx->code = realloc(ctx->code, ctx->code_cap * sizeof(int));
ctx->code_pos = realloc(ctx->code_pos, ctx->code_cap * sizeof(int));
}
ctx->code_pos[ctx->code_len] = minic_tok(c)->pos;
ctx->code[ctx->code_len++] = word;
}
static void minic_emit(minic_comp_t *c, int op, int n, ...) {
minic_emit_word(c, op);
va_list args;
va_start(args, n);
for (int k = 0; k < n; ++k) {
minic_emit_word(c, va_arg(args, int));
}
va_end(args);
}
// Emit a jump, return its operand for minic_patch
static int minic_emit_jump(minic_comp_t *c, int op, int target) {
minic_emit(c, op, 1, target);
return c->ctx->code_len - 1;
}
// Point a chain of jump operands at the next instruction
static void minic_patch(minic_comp_t *c, int chain) {
while (chain > 0) {
int next = c->ctx->code[chain];
c->ctx->code[chain] = c->ctx->code_len;
chain = next;
}
}
static int minic_const(minic_comp_t *c, minic_val_t v) {
minic_ctx_t *ctx = c->ctx;
if (ctx->const_count == ctx->const_cap) {
ctx->const_cap = ctx->const_cap > 0 ? ctx->const_cap * 2 : 64;
ctx->consts = realloc(ctx->consts, ctx->const_cap * sizeof(minic_val_t));
}
ctx->consts[ctx->const_count] = v;
return ctx->const_count++;
}
static void minic_emit_val(minic_comp_t *c, minic_val_t v) {
if (v.type == MINIC_T_INT && v.deref_type == MINIC_T_INT) {
minic_emit(c, OP_INT, 1, v.i);
}
else {
minic_emit(c, OP_CONST, 1, minic_const(c, v));
}
}
// On failure the index is unchanged. Opaque names are accepted in declaration
// contexts; expression contexts only recognize registered types.
static bool minic_parse_type(minic_comp_t *c, int *index, bool opaque, minic_ctype_t *type) {
int i = *index;
*type = (minic_ctype_t){0};
type->deref = MINIC_T_PTR;
if (minic_tok_is_type(c->toks[i].type)) {
*type = minic_scalar_type(minic_tok_to_type(c->toks[i].type));
i++;
}
else {
bool tagged = c->toks[i].type == TOK_STRUCT;
if (tagged) {
i++;
}
if (c->toks[i].type != TOK_IDENT) {
return false;
}
minic_struct_t *def = minic_struct_get(c->ctx, c->toks[i].text);
bool integer = minic_is_int_typedef(c->toks[i].text);
if (!tagged && def == NULL && !integer && !opaque) {
return false;
}
*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;
}
i++;
}
while (c->toks[i].type == TOK_STAR) {
*type = minic_pointer_type(*type);
i++;
}
*index = i;
return true;
}
// Type of a native call result, from its signature: "f(...)", or "p:struct_name(...)" for typed pointers
static minic_ctype_t minic_native_type(minic_comp_t *c, minic_ext_func_t *ext) {
switch (ext->sig[0]) {
case 'f':
return minic_scalar_type(MINIC_T_FLOAT);
case 'd':
return minic_scalar_type(MINIC_T_DOUBLE);
case 'i':
case 'b':
case 'c':
case 'v':
return minic_scalar_type(MINIC_T_INT);
case 'p':
break;
default:
return minic_scalar_type(MINIC_T_VOID); // Unknown until it returns
}
minic_ctype_t type = minic_scalar_type(MINIC_T_PTR);
if (ext->sig[1] != ':') {
return type;
}
char name[MINIC_MAX_NAME];
const char *start = ext->sig + 2;
int n = 0;
while (start[n] != '\0' && start[n] != '(' && start[n] != '*' && n < MINIC_MAX_NAME - 1) {
name[n] = start[n];
n++;
}
name[n] = '\0';
minic_ctype_t target = minic_scalar_type(MINIC_T_EMBED);
target.def = minic_struct_get(c->ctx, name);
for (size_t k = 0; k < sizeof(minic_keywords) / sizeof(minic_keywords[0]); ++k) {
if (strcmp(name, minic_keywords[k].kw) == 0 && minic_tok_is_type(minic_keywords[k].tok)) {
target = minic_scalar_type(minic_tok_to_type(minic_keywords[k].tok));
}
}
if (minic_is_int_typedef(name)) {
target = minic_scalar_type(MINIC_T_INT);
}
if (target.def != NULL) {
target.size = target.def->size;
target.alignment = target.def->alignment;
}
type = minic_pointer_type(target);
for (const char *p = start + n; *p == '*'; ++p) {
type = minic_pointer_type(type);
}
return type;
}
static int minic_func_index(minic_ctx_t *ctx, const char *name) {
for (int i = 0; i < ctx->func_count; ++i) {
if (strcmp(ctx->funcs[i].name, name) == 0) {
return i;
}
}
return -1;
}
static minic_sym_t *minic_sym_find(minic_comp_t *c, const char *name) {
for (int i = c->local_count - 1; i >= 0; --i) {
if (c->locals[i].name[0] == name[0] && strcmp(c->locals[i].name, name) == 0) {
return &c->locals[i];
}
}
for (int i = c->global_count - 1; i >= 0; --i) {
if (c->globals[i].name[0] == name[0] && strcmp(c->globals[i].name, name) == 0) {
return &c->globals[i];
}
}
return NULL;
}
// Globals are the top-level declarations plus the top level of main(), which every function sees
static minic_sym_t *minic_declare(minic_comp_t *c, const char *name, minic_ctype_t type, bool array) {
int slots = array ? 2 : 1;
bool global = c->fn == &c->ctx->init || (c->in_main && c->depth == 1);
minic_sym_t *sym;
if (global) {
if (c->global_count >= MINIC_MAX_GLOBAL_VARS) {
minic_error(c, "too many global variables (max %d), cannot declare '%s'", MINIC_MAX_GLOBAL_VARS, name);
return NULL;
}
sym = &c->globals[c->global_count++];
sym->ref = -(c->ctx->global_count + 1);
c->ctx->global_count += slots;
}
else {
if (c->local_count >= MINIC_MAX_VARS) {
minic_error(c, "too many local variables (max %d), cannot declare '%s'", MINIC_MAX_VARS, name);
return NULL;
}
sym = &c->locals[c->local_count++];
sym->ref = c->slot_count;
c->slot_count += slots;
if (c->slot_count > c->fn->slot_count) {
c->fn->slot_count = c->slot_count;
}
}
strncpy(sym->name, name, MINIC_MAX_NAME - 1);
sym->name[MINIC_MAX_NAME - 1] = '\0';
sym->type = type;
sym->array = array;
return sym;
}
// Initialize a declared variable, from the value on the stack when there is one
static void minic_init_var(minic_comp_t *c, minic_sym_t *sym, bool has_value) {
minic_ctype_t type = sym->type;
if (type.kind == MINIC_T_EMBED) {
if (type.size <= 0) {
minic_error(c, "incomplete struct type for '%s'", sym->name);
return;
}
minic_emit(c, OP_INIT_EMBED, 3, sym->ref, type.size, type.alignment);
if (has_value) {
minic_emit(c, OP_STOREV, 3, sym->ref, MINIC_T_EMBED, type.size);
minic_emit(c, OP_POP, 0);
}
return;
}
if (!has_value) {
minic_emit(c, OP_INT, 1, 0);
}
minic_emit(c, OP_INITV, 3, sym->ref, type.kind, minic_load_deref(type));
}
static void minic_scope_push(minic_comp_t *c, int *saved) {
saved[0] = c->local_count;
saved[1] = c->slot_count;
c->depth++;
}
static void minic_scope_pop(minic_comp_t *c, int *saved) {
c->local_count = saved[0];
c->slot_count = saved[1];
c->depth--;
}
static minic_cexpr_t minic_c_assign(minic_comp_t *c);
static minic_cexpr_t minic_c_unary(minic_comp_t *c);
static minic_cexpr_t minic_c_ternary(minic_comp_t *c);
static void minic_c_stmt(minic_comp_t *c);
static minic_cexpr_t minic_expr(minic_emode_t mode, minic_ctype_t type) {
minic_cexpr_t r = {0};
r.mode = mode;
r.type = type;
r.length = -1;
r.fn = -1;
return r;
}
// Turn an expression into a value on the stack
static minic_cexpr_t minic_c_load(minic_comp_t *c, minic_cexpr_t e) {
switch (e.mode) {
case MINIC_E_VAR:
minic_emit(c, OP_LOADV, 1, e.sym->ref);
break;
case MINIC_E_MEM:
if (e.type.kind != MINIC_T_EMBED) { // Struct storage is its own address
minic_emit(c, OP_LOADM, 2, e.type.kind, minic_load_deref(e.type));
}
break;
case MINIC_E_FUNC:
if (e.fn < 0) {
minic_error(c, "native function '%s' cannot be used as a value", e.name);
}
minic_emit(c, OP_FNPTR, 1, e.fn);
e.type = minic_scalar_type(MINIC_T_PTR);
break;
case MINIC_E_NEWVAR:
minic_error(c, "unknown identifier '%s'", e.name);
break;
default:
break;
}
e.mode = MINIC_E_VALUE;
e.buf_delta = 0;
return e;
}
static minic_cexpr_t minic_c_value(minic_comp_t *c) {
return minic_c_load(c, minic_c_assign(c));
}
static minic_cexpr_t minic_c_call(minic_comp_t *c, minic_cexpr_t f) {
minic_next(c); // Consume '('
int argc = 0;
while (minic_cur(c) != TOK_RPAREN && minic_cur(c) != TOK_EOF && !c->error) {
minic_c_value(c);
argc++;
if (minic_cur(c) == TOK_COMMA) {
minic_next(c);
}
else if (minic_cur(c) != TOK_RPAREN) {
minic_error(c, "expected ',' or ')' in call to '%s'", f.name);
}
}
minic_expect(c, TOK_RPAREN);
if (argc > MINIC_MAX_ARGS) {
minic_error(c, "too many arguments (max %d)", MINIC_MAX_ARGS);
}
if (f.fn >= 0) {
minic_func_t *fn = &c->ctx->funcs[f.fn];
if (argc != fn->param_count) {
minic_error(c, "'%s' expects %d arguments, got %d", fn->name, fn->param_count, argc);
}
minic_emit(c, OP_CALL, 2, f.fn, argc);
minic_ctype_t type = fn->ret_type.kind == MINIC_T_VOID ? minic_scalar_type(MINIC_T_INT) : fn->ret_type;
return minic_expr(MINIC_E_VALUE, type);
}
const char *open = strchr(f.ext->sig, '(');
if (open != NULL && strstr(f.ext->sig, "...") == NULL) {
int count = open[1] == ')' ? 0 : 1;
for (const char *p = open + 1; *p != '\0' && *p != ')'; ++p) {
count += *p == ',';
}
if (argc != count) {
minic_error(c, "'%s' expects %d arguments, got %d", f.name, count, argc);
}
}
minic_emit(c, OP_CALLN, 2, (int)(f.ext - minic_ext_funcs), argc);
return minic_expr(MINIC_E_VALUE, minic_native_type(c, f.ext));
}
static minic_cexpr_t minic_c_sizeof(minic_comp_t *c) {
minic_expect(c, TOK_LPAREN);
minic_ctype_t type;
int i = c->i;
if (minic_parse_type(c, &i, false, &type)) {
c->i = i;
minic_emit(c, OP_INT, 1, type.size);
}
else {
minic_sym_t *sym = minic_cur(c) == TOK_IDENT ? minic_sym_find(c, minic_tok(c)->text) : NULL;
minic_expect(c, TOK_IDENT);
if (sym != NULL && sym->array) {
minic_emit(c, OP_LOADV, 1, sym->ref >= 0 ? sym->ref + 1 : sym->ref - 1); // Element count
minic_emit(c, OP_INT, 1, sym->type.size);
minic_emit(c, OP_MUL, 0);
}
else {
minic_emit(c, OP_INT, 1, sym != NULL ? sym->type.size : 0);
}
}
minic_expect(c, TOK_RPAREN);
return minic_expr(MINIC_E_VALUE, minic_scalar_type(MINIC_T_INT));
}
static minic_cexpr_t minic_c_primary(minic_comp_t *c) {
minic_token_t *t = minic_tok(c);
if (t->type == TOK_NUMBER || t->type == TOK_CHAR_LIT || t->type == TOK_STR_LIT) {
minic_next(c);
minic_emit_val(c, t->val);
bool string = t->type == TOK_STR_LIT;
return minic_expr(MINIC_E_VALUE, string ? minic_pointer_type(minic_scalar_type(MINIC_T_CHAR)) : minic_scalar_type(t->val.type));
}
if (t->type == TOK_IDENT) {
const char *name = t->text;
minic_next(c);
if (strcmp(name, "sizeof") == 0) {
return minic_c_sizeof(c);
}
minic_cexpr_t r = minic_expr(MINIC_E_FUNC, minic_scalar_type(MINIC_T_PTR));
r.name = name;
if (minic_cur(c) == TOK_LPAREN) {
r.fn = minic_func_index(c->ctx, name);
r.ext = r.fn < 0 ? minic_ext_func_get(name) : NULL;
if (r.fn < 0 && r.ext == NULL) {
minic_error(c, "unknown function '%s'", name);
}
if (r.fn >= 0 && c->ctx->funcs[r.fn].body < 0) {
minic_error(c, "function '%s' is declared but not defined", name);
}
return r;
}
minic_sym_t *sym = minic_sym_find(c, name);
if (sym != NULL) {
r.mode = MINIC_E_VAR;
r.sym = sym;
r.type = sym->array ? minic_pointer_type(sym->type) : sym->type;
return r;
}
r.fn = minic_func_index(c->ctx, name);
if (r.fn >= 0) {
return r; // A script function passed as a callback
}
int ec = minic_enum_const_find(name);
if (ec >= 0) {
minic_emit(c, OP_INT, 1, minic_enum_const_value_at(ec));
return minic_expr(MINIC_E_VALUE, minic_scalar_type(MINIC_T_INT));
}
minic_tok_type_t next = minic_cur(c);
if (next == TOK_ASSIGN) {
r.mode = MINIC_E_NEWVAR;
return r;
}
minic_type_t kind;
const void *host = minic_global_ptr(name, &kind);
if (host != NULL && next != TOK_INC && next != TOK_DEC && (next < TOK_PLUS_ASSIGN || next > TOK_XOR_ASSIGN)) {
minic_emit(c, OP_LOADH, 1, minic_const(c, minic_val_typed_ptr((void *)host, kind)));
return minic_expr(MINIC_E_VALUE, minic_scalar_type(kind));
}
minic_error(c, "unknown identifier '%s'", name);
return minic_expr(MINIC_E_VALUE, minic_scalar_type(MINIC_T_INT));
}
if (t->type == TOK_LPAREN) {
minic_ctype_t type;
int i = c->i + 1;
if (minic_parse_type(c, &i, false, &type) && c->toks[i].type == TOK_RPAREN) {
c->i = i + 1;
minic_cexpr_t v = minic_c_load(c, minic_c_unary(c));
if (type.pointer) {
minic_emit(c, OP_TOPTR, 1, type.deref);
}
else if (type.def == NULL && v.type.kind != type.kind) {
minic_emit(c, OP_CAST, 1, type.kind);
}
return minic_expr(MINIC_E_VALUE, type);
}
minic_next(c);
minic_cexpr_t r = minic_c_assign(c);
minic_expect(c, TOK_RPAREN);
return r;
}
minic_error(c, "expected expression");
return minic_expr(MINIC_E_VALUE, minic_scalar_type(MINIC_T_INT));
}
static minic_cexpr_t minic_c_field(minic_comp_t *c, minic_cexpr_t owner, const char *name) {
minic_struct_t *def = owner.type.def;
if (def == NULL) {
minic_error(c, "member access requires a known struct type");
return minic_expr(MINIC_E_VALUE, minic_scalar_type(MINIC_T_INT));
}
int idx = minic_struct_field_idx(def, name);
if (idx < 0) {
minic_error(c, "struct '%s' has no field '%s'", def->name, name);
return minic_expr(MINIC_E_VALUE, minic_scalar_type(MINIC_T_INT));
}
minic_c_load(c, owner); // The struct pointer
minic_emit(c, OP_FIELD, 2, def->offsets[idx], (int)(def - c->ctx->structs) * MINIC_MAX_STRUCT_FIELDS + idx);
minic_ctype_t type = minic_field_type(c->ctx, def, idx);
if (def->counts[idx] > 0) {
minic_cexpr_t r = minic_expr(MINIC_E_VALUE, minic_pointer_type(type));
r.length = def->counts[idx];
return r;
}
minic_cexpr_t r = minic_expr(MINIC_E_MEM, type);
int l = strcmp(name, "buffer") == 0 ? minic_struct_field_idx(def, "length") : -1;
if (l >= 0 && def->offsets[l] != def->offsets[idx]) {
r.buf_delta = def->offsets[l] - def->offsets[idx]; // Indexing checks against the array's length
}
return r;
}
static minic_cexpr_t minic_c_index(minic_comp_t *c, minic_cexpr_t owner) {
if (owner.mode == MINIC_E_VAR && owner.sym->array) {
minic_c_value(c);
minic_emit(c, OP_INDEX_ARR, 2, owner.sym->ref, owner.sym->type.size);
return minic_expr(MINIC_E_MEM, owner.sym->type);
}
minic_ctype_t element = minic_element_type(owner.type);
if (owner.mode == MINIC_E_MEM && owner.buf_delta != 0) {
minic_c_value(c);
minic_emit(c, OP_INDEX_BUF, 2, element.size, owner.buf_delta);
return minic_expr(MINIC_E_MEM, element);
}
owner = minic_c_load(c, owner);
minic_c_value(c);
minic_emit(c, OP_INDEX, 2, element.size, owner.length);
return minic_expr(MINIC_E_MEM, element);
}
static minic_cexpr_t minic_c_increment(minic_comp_t *c, minic_cexpr_t r, int delta, bool post) {
int stride = r.type.kind == MINIC_T_PTR ? minic_element_type(r.type).size : 0;
if (r.mode == MINIC_E_VAR && !r.sym->array) {
minic_emit(c, OP_INCV, 5, r.sym->ref, r.sym->type.kind, delta, post, stride);
}
else if (r.mode == MINIC_E_MEM) {
minic_emit(c, OP_INCM, 6, r.type.kind, minic_load_deref(r.type), delta, post, stride, r.type.size);
}
else {
minic_error(c, "expression is not writable");
}
return minic_expr(MINIC_E_VALUE, r.type);
}
static minic_cexpr_t minic_c_postfix(minic_comp_t *c) {
minic_cexpr_t r = minic_c_primary(c);
while (!c->error) {
minic_tok_type_t t = minic_cur(c);
if (t == TOK_LPAREN) {
if (r.mode != MINIC_E_FUNC) {
minic_error(c, "expression is not callable");
break;
}
r = minic_c_call(c, r);
}
else if (t == TOK_DOT || t == TOK_ARROW) {
minic_next(c);
const char *field = minic_tok(c)->text;
minic_expect(c, TOK_IDENT);
if (!c->error) {
r = minic_c_field(c, r, field);
}
}
else if (t == TOK_LBRACKET) {
minic_next(c);
r = minic_c_index(c, r);
minic_expect(c, TOK_RBRACKET);
}
else if (t == TOK_INC || t == TOK_DEC) {
minic_next(c);
r = minic_c_increment(c, r, t == TOK_INC ? 1 : -1, true);
}
else {
break;
}
}
return r;
}
static minic_cexpr_t minic_c_unary(minic_comp_t *c) {
minic_tok_type_t op = minic_cur(c);
if (op != TOK_AMP && op != TOK_STAR && op != TOK_MINUS && op != TOK_NOT && op != TOK_BITNOT && op != TOK_INC && op != TOK_DEC) {
return minic_c_postfix(c);
}
minic_next(c);
minic_cexpr_t r = minic_c_unary(c);
switch (op) {
case TOK_AMP: {
minic_ctype_t type = minic_pointer_type(r.type);
if (r.mode == MINIC_E_VAR && r.sym->array) {
return minic_c_load(c, r); // Arrays already decay to the address of their first element
}
if (r.mode == MINIC_E_VAR) {
if (r.sym->type.kind == MINIC_T_EMBED) {
minic_emit(c, OP_LOADV, 1, r.sym->ref); // The slot holds the storage address
}
else {
minic_emit(c, OP_ADDRV, 2, r.sym->ref, r.sym->type.kind);
}
return minic_expr(MINIC_E_VALUE, type);
}
if (r.mode == MINIC_E_MEM) {
return minic_expr(MINIC_E_VALUE, type); // The address is already on the stack
}
if (r.mode == MINIC_E_VALUE && r.length >= 0) {
return r;
}
minic_error(c, "expression has no address");
return r;
}
case TOK_STAR:
r = minic_c_load(c, r);
return minic_expr(MINIC_E_MEM, minic_element_type(r.type));
case TOK_INC:
case TOK_DEC:
return minic_c_increment(c, r, op == TOK_INC ? 1 : -1, false);
case TOK_MINUS:
r = minic_c_load(c, r);
minic_emit(c, OP_NEG, 0);
r.length = -1;
return r;
case TOK_NOT:
minic_c_load(c, r);
minic_emit(c, OP_NOT, 0);
return minic_expr(MINIC_E_VALUE, minic_scalar_type(MINIC_T_INT));
default:
minic_c_load(c, r);
minic_emit(c, OP_BNOT, 0);
return minic_expr(MINIC_E_VALUE, minic_scalar_type(MINIC_T_INT));
}
}
// C precedence, loosest first, every level is left-associative. Compound assignments
// have no precedence but share the opcode of their operator.
#define MINIC_PREC_MAX 10
static const struct {
int prec;
int op;
} minic_binops[TOK_EOF + 1] = {
[TOK_OR] = {1, 0},
[TOK_AND] = {2, 0},
[TOK_BITOR] = {3, OP_BOR},
[TOK_XOR] = {4, OP_XOR},
[TOK_AMP] = {5, OP_BAND},
[TOK_EQ] = {6, OP_EQ},
[TOK_NEQ] = {6, OP_NE},
[TOK_LT] = {7, OP_LT},
[TOK_GT] = {7, OP_GT},
[TOK_LE] = {7, OP_LE},
[TOK_GE] = {7, OP_GE},
[TOK_SHL] = {8, OP_SHL},
[TOK_SHR] = {8, OP_SHR},
[TOK_PLUS] = {9, OP_ADD},
[TOK_MINUS] = {9, OP_SUB},
[TOK_STAR] = {10, OP_MUL},
[TOK_SLASH] = {10, OP_DIV},
[TOK_PERCENT] = {10, OP_MOD},
[TOK_PLUS_ASSIGN] = {0, OP_ADD},
[TOK_MINUS_ASSIGN] = {0, OP_SUB},
[TOK_MUL_ASSIGN] = {0, OP_MUL},
[TOK_DIV_ASSIGN] = {0, OP_DIV},
[TOK_MOD_ASSIGN] = {0, OP_MOD},
[TOK_SHL_ASSIGN] = {0, OP_SHL},
[TOK_SHR_ASSIGN] = {0, OP_SHR},
[TOK_AND_ASSIGN] = {0, OP_BAND},
[TOK_OR_ASSIGN] = {0, OP_BOR},
[TOK_XOR_ASSIGN] = {0, OP_XOR},
};
// Static result type of arithmetic, following the widening in minic_arith
static minic_ctype_t minic_arith_type(minic_ctype_t a, minic_ctype_t b) {
if (a.kind == MINIC_T_VOID || b.kind == MINIC_T_VOID) {
return minic_scalar_type(MINIC_T_VOID);
}
if (a.kind == MINIC_T_PTR || b.kind == MINIC_T_PTR || a.kind == MINIC_T_EMBED || b.kind == MINIC_T_EMBED) {
return minic_scalar_type(MINIC_T_PTR);
}
if (a.kind == MINIC_T_DOUBLE || b.kind == MINIC_T_DOUBLE) {
return minic_scalar_type(MINIC_T_DOUBLE);
}
if (a.kind == MINIC_T_FLOAT || b.kind == MINIC_T_FLOAT) {
return minic_scalar_type(MINIC_T_FLOAT);
}
return minic_scalar_type(MINIC_T_INT);
}
static minic_cexpr_t minic_c_binary(minic_comp_t *c, int level) {
if (level > MINIC_PREC_MAX) {
return minic_c_unary(c);
}
minic_cexpr_t r = minic_c_binary(c, level + 1);
while (!c->error && minic_binops[minic_cur(c)].prec == level) {
minic_tok_type_t op = minic_cur(c);
r = minic_c_load(c, r);
minic_next(c);
if (op == TOK_AND || op == TOK_OR) {
// Short-circuit: the right side only runs when it decides the result
int jump = op == TOK_AND ? OP_JZ : OP_JNZ;
int j1 = minic_emit_jump(c, jump, 0);
minic_c_load(c, minic_c_binary(c, level + 1));
int j2 = minic_emit_jump(c, jump, j1);
minic_emit(c, OP_INT, 1, op == TOK_AND);
int end = minic_emit_jump(c, OP_JMP, 0);
minic_patch(c, j2);
minic_emit(c, OP_INT, 1, op != TOK_AND);
minic_patch(c, end);
r = minic_expr(MINIC_E_VALUE, minic_scalar_type(MINIC_T_INT));
continue;
}
minic_cexpr_t b = minic_c_load(c, minic_c_binary(c, level + 1));
int o = minic_binops[op].op;
minic_emit(c, o, 0);
r = minic_expr(MINIC_E_VALUE, o <= OP_MOD ? minic_arith_type(r.type, b.type) : minic_scalar_type(MINIC_T_INT));
}
return r;
}
static minic_cexpr_t minic_c_ternary(minic_comp_t *c) {
minic_cexpr_t r = minic_c_binary(c, 1);
if (c->error || minic_cur(c) != TOK_QUESTION) {
return r;
}
minic_c_load(c, r);
minic_next(c); // Consume '?'
int skip = minic_emit_jump(c, OP_JZ, 0);
minic_cexpr_t a = minic_c_value(c);
minic_expect(c, TOK_COLON);
int end = minic_emit_jump(c, OP_JMP, 0);
minic_patch(c, skip);
minic_cexpr_t b = minic_c_load(c, minic_c_ternary(c));
minic_patch(c, end);
return minic_expr(MINIC_E_VALUE, a.type.kind == b.type.kind ? a.type : minic_scalar_type(MINIC_T_VOID));
}
static minic_cexpr_t minic_c_assign(minic_comp_t *c) {
minic_cexpr_t target = minic_c_ternary(c);
minic_tok_type_t op = minic_cur(c);
if (c->error || (op != TOK_ASSIGN && (op < TOK_PLUS_ASSIGN || op > TOK_XOR_ASSIGN))) {
return target;
}
minic_next(c);
if (target.mode == MINIC_E_NEWVAR) {
// A plain store to an unknown name declares a variable typed by the value
minic_cexpr_t v = minic_c_value(c);
minic_sym_t *sym = minic_declare(c, target.name, v.type, false);
if (sym != NULL) {
minic_emit(c, OP_DUP, 0);
minic_init_var(c, sym, true);
}
return v;
}
bool var = target.mode == MINIC_E_VAR && !target.sym->array;
minic_ctype_t t = target.type;
if (!var && target.mode != MINIC_E_MEM) {
minic_error(c, "expression is not writable");
return target;
}
minic_cexpr_t v = minic_c_value(c);
if (op == TOK_ASSIGN) {
if (var) {
minic_emit(c, OP_STOREV, 3, target.sym->ref, t.kind, t.size);
}
else {
minic_emit(c, OP_STOREM, 2, t.kind, t.size);
}
return v;
}
// The old value is read after the right side runs
if (var) {
minic_emit(c, OP_COMPV, 4, target.sym->ref, minic_binops[op].op, t.kind, t.size);
}
else {
minic_emit(c, OP_COMPM, 4, minic_binops[op].op, t.kind, minic_load_deref(t), t.size);
}
return minic_expr(MINIC_E_VALUE, t);
}
// Count the top-level elements of the brace initializer that starts at token 'start'
static int minic_init_list_count(minic_comp_t *c, int start) {
int depth = 0;
int count = 0;
bool in_elem = false;
for (int i = start; c->toks[i].type != TOK_EOF; ++i) {
minic_tok_type_t t = c->toks[i].type;
if (t == TOK_RBRACE && --depth == 0) {
break;
}
if (depth == 1) {
if (t == 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++;
}
}
if (t == TOK_LBRACE) {
depth++;
}
}
return count;
}
// Local declarations and globals use the same allocation and initialization path.
static void minic_c_decl(minic_comp_t *c, minic_ctype_t type) {
minic_ctype_t base = type;
while (base.pointer > 0) {
base = minic_element_type(base);
}
for (;;) {
const char *name = minic_tok(c)->text;
minic_expect(c, TOK_IDENT);
if (c->error) {
return;
}
if (minic_cur(c) == TOK_LBRACKET) {
minic_next(c); // Consume '['
bool sized = minic_cur(c) != TOK_RBRACKET;
if (sized) {
minic_c_value(c);
}
minic_expect(c, TOK_RBRACKET);
bool listed = minic_cur(c) == TOK_ASSIGN && minic_peek(c, 1) == TOK_LBRACE;
if (!sized) { // 'name[]' takes its size from the initializer
minic_emit(c, OP_INT, 1, listed ? minic_init_list_count(c, c->i + 1) : 0);
}
if (type.size <= 0) {
minic_error(c, "invalid array element type for '%s'", name);
return;
}
minic_sym_t *sym = minic_declare(c, name, type, true);
if (sym == NULL) {
return;
}
minic_emit(c, OP_INIT_ARR, 4, sym->ref, type.kind, type.size, type.alignment);
if (listed) {
minic_next(c); // Consume '='
minic_next(c); // Consume '{'
for (int i = 0; minic_cur(c) != TOK_RBRACE && !c->error; ++i) {
minic_emit(c, OP_INT, 1, i);
minic_emit(c, OP_INDEX_ARR, 2, sym->ref, type.size);
minic_c_value(c);
minic_emit(c, OP_STOREM, 2, type.kind, type.size);
minic_emit(c, OP_POP, 0);
if (minic_cur(c) != TOK_COMMA) {
break;
}
minic_next(c); // Consume ','
}
minic_expect(c, TOK_RBRACE);
}
}
else {
bool initialized = minic_cur(c) == TOK_ASSIGN;
if (initialized) {
minic_next(c);
minic_c_value(c);
}
minic_sym_t *sym = minic_declare(c, name, type, false);
if (sym == NULL) {
return;
}
minic_init_var(c, sym, initialized);
}
if (minic_cur(c) != TOK_COMMA || c->error) {
break;
}
minic_next(c); // 'int *a, b' declares an int b
type = base;
while (minic_cur(c) == TOK_STAR) {
type = minic_pointer_type(type);
minic_next(c);
}
}
minic_expect(c, TOK_SEMICOLON);
}
// Recognize opaque pointer declarations without mistaking 'value * value' for a type.
static bool minic_decl_type(minic_comp_t *c, minic_ctype_t *type) {
int i = c->i;
if (minic_parse_type(c, &i, false, type)) {
c->i = i;
return true;
}
if (minic_cur(c) == TOK_IDENT && minic_sym_find(c, minic_tok(c)->text) == NULL) {
if (minic_parse_type(c, &i, true, type) && type->pointer && c->toks[i].type == TOK_IDENT) {
c->i = i;
return true;
}
}
return false;
}
// A statement in its own scope, the body of a control statement
static void minic_c_body(minic_comp_t *c) {
int saved[2];
minic_scope_push(c, saved);
minic_c_stmt(c);
minic_scope_pop(c, saved);
}
static void minic_c_block(minic_comp_t *c) {
int saved[2];
minic_scope_push(c, saved);
minic_expect(c, TOK_LBRACE);
while (minic_cur(c) != TOK_RBRACE && minic_cur(c) != TOK_EOF && !c->error) {
minic_c_stmt(c);
}
minic_expect(c, TOK_RBRACE);
minic_scope_pop(c, saved);
}
static void minic_c_loop_body(minic_comp_t *c, minic_loop_t *loop) {
minic_loop_t *outer = c->loop;
c->loop = loop;
minic_c_body(c);
c->loop = outer;
}
static void minic_c_stmt(minic_comp_t *c) {
minic_tok_type_t t = minic_cur(c);
if (t == TOK_LBRACE) {
minic_c_block(c);
return;
}
if (t == TOK_SEMICOLON) {
minic_next(c);
return;
}
// Skip bare typedef declarations inside function bodies
if (t == TOK_TYPEDEF) {
minic_skip_to(c, TOK_SEMICOLON);
minic_next(c);
return;
}
minic_ctype_t type;
if (minic_decl_type(c, &type)) {
minic_c_decl(c, type);
return;
}
if (t == TOK_RETURN) {
minic_next(c);
if (minic_cur(c) == TOK_SEMICOLON) {
minic_emit(c, OP_INT, 1, 0);
}
else {
minic_c_value(c);
}
minic_emit(c, OP_RET, 2, c->fn->ret_type.kind, minic_load_deref(c->fn->ret_type));
minic_expect(c, TOK_SEMICOLON);
return;
}
if (t == TOK_IF) {
minic_next(c);
minic_expect(c, TOK_LPAREN);
minic_c_value(c);
minic_expect(c, TOK_RPAREN);
int skip = minic_emit_jump(c, OP_JZ, 0);
minic_c_body(c);
if (minic_cur(c) == TOK_ELSE) {
minic_next(c);
int end = minic_emit_jump(c, OP_JMP, 0);
minic_patch(c, skip);
minic_c_body(c);
minic_patch(c, end);
}
else {
minic_patch(c, skip);
}
return;
}
if (t == TOK_FOR) {
int saved[2];
minic_scope_push(c, saved); // The loop variable goes out of scope after the loop
minic_next(c);
minic_expect(c, TOK_LPAREN);
if (minic_decl_type(c, &type)) {
minic_c_decl(c, type);
}
else {
if (minic_cur(c) != TOK_SEMICOLON) {
minic_c_value(c);
minic_emit(c, OP_POP, 0);
}
minic_expect(c, TOK_SEMICOLON);
}
minic_loop_t loop = {0};
int top = c->ctx->code_len;
if (minic_cur(c) != TOK_SEMICOLON) {
minic_c_value(c);
loop.breaks = minic_emit_jump(c, OP_JZ, 0);
}
minic_expect(c, TOK_SEMICOLON);
// The increment runs after the body, compile it there
int step = c->i;
minic_skip_to(c, TOK_RPAREN);
minic_expect(c, TOK_RPAREN);
minic_c_loop_body(c, &loop);
minic_patch(c, loop.continues);
int after = c->i;
c->i = step;
if (minic_cur(c) != TOK_RPAREN) {
minic_c_value(c);
minic_emit(c, OP_POP, 0);
}
c->i = after;
minic_emit(c, OP_JMP, 1, top);
minic_patch(c, loop.breaks);
minic_scope_pop(c, saved);
return;
}
if (t == TOK_WHILE) {
minic_next(c);
minic_loop_t loop = {0};
int top = c->ctx->code_len;
minic_expect(c, TOK_LPAREN);
minic_c_value(c);
minic_expect(c, TOK_RPAREN);
loop.breaks = minic_emit_jump(c, OP_JZ, 0);
minic_c_loop_body(c, &loop);
minic_patch(c, loop.continues);
minic_emit(c, OP_JMP, 1, top);
minic_patch(c, loop.breaks);
return;
}
if (t == TOK_BREAK || t == TOK_CONTINUE) {
if (c->loop == NULL) {
minic_error(c, "%s outside a loop", minic_tok_names[t]);
return;
}
minic_next(c);
int *chain = t == TOK_BREAK ? &c->loop->breaks : &c->loop->continues;
*chain = minic_emit_jump(c, OP_JMP, *chain);
minic_expect(c, TOK_SEMICOLON);
return;
}
minic_c_value(c);
minic_emit(c, OP_POP, 0);
minic_expect(c, TOK_SEMICOLON);
}
static void minic_c_function(minic_comp_t *c, minic_func_t *fn) {
c->fn = fn;
c->in_main = strcmp(fn->name, "main") == 0;
c->depth = 0;
c->local_count = 0;
c->slot_count = 0;
c->loop = NULL;
fn->entry = c->ctx->code_len;
c->i = fn->body;
// Arguments arrive in the first slots, give them the parameter types
for (int i = 0; i < fn->param_count; ++i) {
minic_sym_t *sym = minic_declare(c, fn->params[i], fn->param_types[i], false);
if (sym == NULL) {
return;
}
minic_emit(c, OP_LOADV, 1, sym->ref);
minic_init_var(c, sym, true);
}
minic_c_block(c);
minic_emit(c, OP_INT, 1, 0);
minic_emit(c, OP_RET, 2, fn->ret_type.kind, minic_load_deref(fn->ret_type));
}
// Constant integer expression of an enum value: literals, earlier enum constants and operators
static int minic_const_expr(minic_token_t *toks, int *i, int level) {
if (level > MINIC_PREC_MAX) {
minic_token_t *t = &toks[*i];
if (t->type == TOK_EOF) {
return 0;
}
(*i)++;
switch (t->type) {
case TOK_NUMBER:
case TOK_CHAR_LIT:
return minic_val_to_i(t->val);
case TOK_IDENT: {
int k = minic_enum_const_find(t->text);
return k >= 0 ? minic_enum_const_value_at(k) : 0;
}
case TOK_PLUS:
return minic_const_expr(toks, i, level);
case TOK_MINUS:
return (int)(0u - (unsigned int)minic_const_expr(toks, i, level));
case TOK_BITNOT:
return ~minic_const_expr(toks, i, level);
case TOK_NOT:
return !minic_const_expr(toks, i, level);
case TOK_LPAREN: {
int v = minic_const_expr(toks, i, 1);
if (toks[*i].type == TOK_RPAREN) {
(*i)++;
}
return v;
}
default:
(*i)--; // Not part of the expression
return 0;
}
}
int v = minic_const_expr(toks, i, level + 1);
while (minic_binops[toks[*i].type].prec == level) {
minic_tok_type_t op = toks[(*i)++].type;
int b = minic_const_expr(toks, i, level + 1);
if (op == TOK_AND || op == TOK_OR) {
v = op == TOK_AND ? v && b : v || b;
}
else {
v = minic_val_to_i(minic_binop(minic_binops[op].op, minic_val_int(v), minic_val_int(b)));
}
}
return v;
}
// Zero pass: scan for enum and struct definitions
static void minic_register_structs(minic_comp_t *c) {
minic_ctx_t *ctx = c->ctx;
minic_token_t *toks = c->toks;
int i = 0;
while (toks[i].type != TOK_EOF) {
bool is_typedef = toks[i].type == TOK_TYPEDEF;
if (is_typedef) {
i++; // Consume 'typedef'
}
if (toks[i].type == TOK_ENUM) {
i++; // Consume 'enum'
if (toks[i].type == TOK_IDENT) {
i++; // Optional tag name
}
if (toks[i].type != TOK_LBRACE) {
continue;
}
i++; // Consume '{'
int val = 0;
while (toks[i].type != TOK_RBRACE && toks[i].type != TOK_EOF) {
if (toks[i].type == TOK_IDENT) {
const char *cname = toks[i].text;
i++;
if (toks[i].type == TOK_ASSIGN) {
i++; // Consume '='
val = minic_const_expr(toks, &i, 1);
}
minic_enum_const_add(cname, val);
val++;
}
else {
i++;
}
if (toks[i].type == TOK_COMMA) {
i++;
}
}
if (toks[i].type == TOK_RBRACE) {
i++;
}
if (is_typedef && toks[i].type == TOK_IDENT) {
minic_int_typedef_add(toks[i].text);
i++;
}
}
else if (toks[i].type == TOK_STRUCT) {
i++; // Consume 'struct'
// Optional struct tag name
char struct_name[MINIC_MAX_NAME] = "";
if (toks[i].type == TOK_IDENT) {
strncpy(struct_name, toks[i].text, MINIC_MAX_NAME - 1);
i++; // Consume struct name
}
if (toks[i].type != TOK_LBRACE) {
continue; // Forward decl or typedef-without-body
}
if (ctx->struct_count >= MINIC_MAX_STRUCTS) {
break;
}
minic_struct_t *def = &ctx->structs[ctx->struct_count];
memset(def, 0, sizeof(minic_struct_t));
strncpy(def->name, struct_name, MINIC_MAX_NAME - 1);
i++; // Consume '{'
while (toks[i].type != TOK_RBRACE && toks[i].type != TOK_EOF && !c->error) {
// Keep the name as well as its resolved type for forward/self pointers.
int type_start = toks[i].type == TOK_STRUCT ? i + 1 : i;
c->i = i;
minic_ctype_t field_type;
if (!minic_parse_type(c, &i, true, &field_type)) {
minic_error(c, "expected field type in '%s'", def->name);
return;
}
minic_ctype_t field_base = field_type;
while (field_base.pointer > 0) {
field_base = minic_element_type(field_base);
}
for (;;) {
c->i = i;
if (toks[i].type != TOK_IDENT || def->field_count >= MINIC_MAX_STRUCT_FIELDS) {
minic_error(c, "invalid or too many fields in '%s'", def->name);
return;
}
int idx = def->field_count++;
strncpy(def->fields[idx], toks[i].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], toks[type_start].text, MINIC_MAX_NAME - 1);
}
i++;
if (toks[i].type == TOK_LBRACKET) {
i++;
c->i = i;
if (toks[i].type != TOK_NUMBER || toks[i].val.type != MINIC_T_INT || toks[i].val.i <= 0) {
minic_error(c, "field array requires a positive integer size");
return;
}
def->counts[idx] = toks[i].val.i;
i++;
c->i = i;
if (toks[i].type != TOK_RBRACKET) {
minic_error(c, "expected ']' after field array size");
return;
}
i++;
}
if (toks[i].type != TOK_COMMA) {
break;
}
i++;
field_type = field_base;
while (toks[i].type == TOK_STAR) {
field_type = minic_pointer_type(field_type);
i++;
}
}
c->i = i;
if (toks[i].type != TOK_SEMICOLON) {
minic_error(c, "expected ';' after struct field");
return;
}
i++;
}
if (toks[i].type == TOK_RBRACE) {
i++;
}
if (is_typedef && toks[i].type == TOK_IDENT) {
// typedef struct [Name] { ... } alias;
const char *alias = toks[i].text;
i++; // Consume alias name
if (struct_name[0] != '\0') {
// Register under the tag name, plus a copy under the alias name
ctx->struct_count++;
if (ctx->struct_count < MINIC_MAX_STRUCTS) {
minic_struct_t *adef = &ctx->structs[ctx->struct_count++];
*adef = *def;
strncpy(adef->name, alias, MINIC_MAX_NAME - 1);
}
}
else {
// Anonymous struct: name it after the alias
strncpy(def->name, alias, MINIC_MAX_NAME - 1);
ctx->struct_count++;
}
}
else if (struct_name[0] != '\0') {
// Plain struct definition: must have a tag name to be usable
ctx->struct_count++;
}
}
else {
i++;
continue;
}
while (toks[i].type != TOK_SEMICOLON && toks[i].type != TOK_EOF) {
i++;
}
if (toks[i].type == TOK_SEMICOLON) {
i++;
}
}
}
// Resolve script layouts after collecting all definitions. Native descriptors
// already have their compiler-provided sizes, offsets, and alignment.
static bool minic_layout_struct(minic_comp_t *c, minic_struct_t *def) {
if (def->layout_state == 2) {
return true;
}
if (def->layout_state == 1) {
minic_error(c, "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(c->ctx, def->field_structs[i]);
if (child == NULL) {
minic_error(c, "unknown embedded struct '%s'", def->field_structs[i]);
return false;
}
if (!minic_layout_struct(c, child)) {
return false;
}
}
minic_ctype_t type = minic_field_type(c->ctx, 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(c, "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;
}
// Walk the top level: collect function signatures, or compile the global declarations
static void minic_scan_top_level(minic_comp_t *c, bool globals) {
minic_token_t *toks = c->toks;
c->i = 0;
while (minic_cur(c) != TOK_EOF && !c->error) {
minic_tok_type_t t = minic_cur(c);
if (t == TOK_TYPEDEF || t == TOK_ENUM || t == TOK_STRUCT) {
int scan = c->i + 1;
bool definition = t == TOK_TYPEDEF || t == TOK_ENUM;
if (toks[scan].type == TOK_IDENT) {
scan++;
}
if (definition || toks[scan].type == TOK_LBRACE) {
minic_skip_to(c, TOK_SEMICOLON);
minic_next(c);
continue;
}
}
minic_ctype_t type;
if (!minic_parse_type(c, &c->i, true, &type)) {
if (t != TOK_SEMICOLON) {
minic_error(c, "unexpected token outside of a function");
return;
}
minic_next(c);
continue;
}
if (minic_cur(c) != TOK_IDENT) {
// Qualifiers such as const are parsed as an opaque type, the declared type follows
if (!minic_tok_is_type(minic_cur(c)) && minic_cur(c) != TOK_STRUCT) {
minic_error(c, "statement outside of a function");
return;
}
continue;
}
if (minic_peek(c, 1) != TOK_LPAREN) {
// A global declaration, skipped until the second walk compiles it
if (globals) {
minic_c_decl(c, type);
continue;
}
minic_skip_to(c, TOK_SEMICOLON);
minic_next(c);
continue;
}
minic_func_t fn = {0};
strncpy(fn.name, minic_tok(c)->text, MINIC_MAX_NAME - 1);
fn.ret_type = type;
fn.ctx = c->ctx;
minic_next(c); // Consume the name
minic_next(c); // Consume '('
while (minic_cur(c) != TOK_RPAREN && minic_cur(c) != TOK_EOF && !c->error) {
minic_ctype_t parameter;
if (!minic_parse_type(c, &c->i, true, &parameter)) {
minic_error(c, "expected parameter type");
return;
}
if (minic_cur(c) == TOK_IDENT) {
if (fn.param_count >= MINIC_MAX_PARAMS) {
minic_error(c, "too many parameters (max %d)", MINIC_MAX_PARAMS);
return;
}
int pi = fn.param_count++;
strncpy(fn.params[pi], minic_tok(c)->text, MINIC_MAX_NAME - 1);
fn.param_types[pi] = parameter;
minic_next(c);
}
if (minic_cur(c) == TOK_COMMA) {
minic_next(c);
}
}
minic_next(c); // Consume ')'
fn.body = minic_cur(c) == TOK_LBRACE ? c->i : -1;
// Skip the body, or the ';' of a prototype
if (fn.body >= 0) {
minic_next(c);
minic_skip_to(c, TOK_RBRACE);
}
else {
minic_skip_to(c, TOK_SEMICOLON);
}
minic_next(c);
if (globals) {
continue;
}
minic_ctx_t *ctx = c->ctx;
int idx = minic_func_index(ctx, fn.name);
if (idx >= 0) {
if (ctx->funcs[idx].body < 0) {
ctx->funcs[idx] = fn; // The definition after a prototype
}
continue;
}
if (ctx->func_count == ctx->func_cap) {
ctx->func_cap = ctx->func_cap > 0 ? ctx->func_cap * 2 : 32;
ctx->funcs = realloc(ctx->funcs, ctx->func_cap * sizeof(minic_func_t));
}
ctx->funcs[ctx->func_count++] = fn;
}
}
static bool minic_compile(minic_ctx_t *ctx) {
minic_comp_t c = {0};
c.ctx = ctx;
c.toks = minic_tokenize(ctx->src_copy, ctx->str_pool);
minic_emit_word(&c, OP_HALT); // Offset 0 terminates the jump patch chains
c.locals = malloc(MINIC_MAX_VARS * sizeof(minic_sym_t));
c.globals = malloc(MINIC_MAX_GLOBAL_VARS * sizeof(minic_sym_t));
// Seed with globally pre-registered struct definitions
for (int i = 0; i < minic_struct_count && ctx->struct_count < MINIC_MAX_STRUCTS; ++i) {
ctx->structs[ctx->struct_count++] = minic_structs[i];
}
minic_register_structs(&c);
for (int i = 0; i < ctx->struct_count && !c.error; ++i) {
minic_layout_struct(&c, &ctx->structs[i]);
}
if (!c.error) {
minic_scan_top_level(&c, false);
}
// Global initializers, then main() so that its top level joins the globals, then the rest
ctx->init.ctx = ctx;
ctx->init.ret_type = minic_scalar_type(MINIC_T_VOID);
ctx->init.entry = ctx->code_len;
c.fn = &ctx->init;
if (!c.error) {
minic_scan_top_level(&c, true);
}
minic_emit(&c, OP_INT, 1, 0);
minic_emit(&c, OP_RET, 2, MINIC_T_VOID, MINIC_T_VOID);
int main_idx = minic_func_index(ctx, "main");
if (main_idx >= 0 && ctx->funcs[main_idx].body >= 0 && !c.error) {
minic_c_function(&c, &ctx->funcs[main_idx]);
}
for (int i = 0; i < ctx->func_count && !c.error; ++i) {
if (i != main_idx && ctx->funcs[i].body >= 0) {
minic_c_function(&c, &ctx->funcs[i]);
}
}
free(c.toks);
free(c.locals);
free(c.globals);
return !c.error;
}
// ██████╗ ██╗ ██╗███╗ ██╗
// ██╔══██╗██║ ██║████╗ ██║
// ██████╔╝██║ ██║██╔██╗ ██║
// ██╔══██╗██║ ██║██║╚██╗██║
// ██║ ██║╚██████╔╝██║ ╚████║
// ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝
static void minic_runtime_error(minic_ctx_t *ctx, int pc, const char *fmt, ...) {
char msg[256];
va_list args;
va_start(args, fmt);
vsnprintf(msg, sizeof(msg), fmt, args);
va_end(args);
char log[512];
snprintf(log, sizeof(log), "%s:%d: error: %s", ctx->filename, minic_line_at(ctx->src_copy, ctx->code_pos[pc]), msg);
console_log(log);
}
// Run a function to completion. Script calls stay in this loop, natives that call back
// into the context start a nested run above the current stack top.
static bool minic_run(minic_ctx_t *ctx, minic_func_t *fn, minic_val_t *args, int argc, minic_val_t *ret) {
minic_val_t *base = ctx->sp;
int base_depth = ctx->depth;
minic_val_t *globals = ctx->globals;
const int *code = ctx->code;
*ret = minic_val_int(0);
if (base + fn->slot_count + MINIC_STACK_SLACK > ctx->stack_end || ctx->depth >= MINIC_MAX_FRAMES) {
minic_runtime_error(ctx, fn->entry, "out of script memory calling '%s', recursion too deep", fn->name);
return false;
}
minic_val_t *fp = base;
for (int i = 0; i < fn->slot_count; ++i) {
fp[i] = i < argc && i < fn->param_count ? args[i] : minic_val_int(0);
}
minic_val_t *sp = fp + fn->slot_count;
ctx->frames[ctx->depth].pc = 0;
ctx->frames[ctx->depth++].fp = NULL;
int pc = fn->entry;
#define MINIC_SLOT(r) ((r) >= 0 ? fp + (r) : globals - (r) - 1)
#define MINIC_FAIL(...) \
do { \
minic_runtime_error(ctx, pc - 1, __VA_ARGS__); \
goto fail; \
} while (0)
#define MINIC_ARITH(OPC, IEXPR, FEXPR) \
case OPC: { \
minic_val_t *a = sp - 2; \
minic_val_t *b = sp - 1; \
if (a->type == MINIC_T_INT && b->type == MINIC_T_INT) { \
a->i = (IEXPR); \
} \
else if (a->type == MINIC_T_FLOAT && b->type == MINIC_T_FLOAT) { \
a->f = (FEXPR); \
} \
else { \
*a = minic_binop(OPC, *a, *b); \
} \
sp--; \
break; \
}
#define MINIC_CMP(OPC, OP) \
case OPC: { \
minic_val_t *a = sp - 2; \
minic_val_t *b = sp - 1; \
int r; \
if (a->type == MINIC_T_INT && b->type == MINIC_T_INT) { \
r = a->i OP b->i; \
} \
else if (a->type == MINIC_T_FLOAT && b->type == MINIC_T_FLOAT) { \
r = a->f OP b->f; \
} \
else { \
r = minic_val_to_d(*a) OP minic_val_to_d(*b); \
} \
*a = minic_val_int(r); \
sp--; \
break; \
}
for (;;) {
switch ((minic_op_t)code[pc++]) {
case OP_HALT:
goto fail;
case OP_INT:
*sp++ = minic_val_int(code[pc++]);
break;
case OP_CONST:
*sp++ = ctx->consts[code[pc++]];
break;
case OP_POP:
sp--;
break;
case OP_DUP:
*sp = sp[-1];
sp++;
break;
case OP_LOADV:
*sp++ = *MINIC_SLOT(code[pc]);
pc++;
break;
case OP_STOREV: {
minic_val_t *s = MINIC_SLOT(code[pc]);
minic_val_t v = sp[-1];
if (v.type == s->type && (v.type == MINIC_T_FLOAT || (v.type == MINIC_T_INT && code[pc + 1] == MINIC_T_INT))) {
s->d = v.d; // Same representation, copy the bits
}
else {
minic_slot_store(s, v, code[pc + 1], code[pc + 2]);
}
pc += 3;
break;
}
case OP_INITV:
minic_slot_init(MINIC_SLOT(code[pc]), *--sp, code[pc + 1], code[pc + 2]);
pc += 3;
break;
case OP_INIT_EMBED: {
void *p = minic_alloc_aligned(code[pc + 1], code[pc + 2]);
if (p == NULL) {
MINIC_FAIL("out of script memory (%d KB)", MINIC_MEM_SIZE / 1024);
}
memset(p, 0, code[pc + 1]);
*MINIC_SLOT(code[pc]) = minic_val_typed_ptr(p, MINIC_T_EMBED);
pc += 3;
break;
}
case OP_INIT_ARR: {
int count = minic_val_to_i(*--sp);
int size = code[pc + 2];
if (count < 0 || count > MINIC_MEM_SIZE / size) {
MINIC_FAIL("invalid array size %d", count);
}
void *p = minic_alloc_aligned(count * size, code[pc + 3]);
if (p == NULL) {
MINIC_FAIL("out of script memory (%d KB)", MINIC_MEM_SIZE / 1024);
}
memset(p, 0, count * size);
minic_val_t *s = MINIC_SLOT(code[pc]);
s[0] = minic_val_typed_ptr(p, code[pc + 1]);
s[1] = minic_val_int(count);
pc += 4;
break;
}
case OP_ADDRV:
*sp++ = minic_val_typed_ptr(&MINIC_SLOT(code[pc])->i, code[pc + 1]);
pc += 2;
break;
case OP_LOADM:
sp[-1] = minic_mem_load(minic_val_to_ptr(sp[-1]), code[pc], code[pc + 1]);
pc += 2;
break;
case OP_STOREM:
minic_mem_store(minic_val_to_ptr(sp[-2]), sp[-1], code[pc], code[pc + 1]);
sp[-2] = sp[-1];
sp--;
pc += 2;
break;
case OP_LOADH: {
minic_val_t h = ctx->consts[code[pc++]];
*sp++ = minic_mem_load(h.p, h.deref_type, h.deref_type);
break;
}
case OP_FIELD: {
char *base = minic_val_to_ptr(sp[-1]);
if (base == NULL) {
int f = code[pc + 1];
minic_struct_t *def = &ctx->structs[f / MINIC_MAX_STRUCT_FIELDS];
pc += 2;
MINIC_FAIL("null pointer access on '%s->%s'", def->name, def->fields[f % MINIC_MAX_STRUCT_FIELDS]);
}
sp[-1] = minic_val_ptr(base + code[pc]);
pc += 2;
break;
}
case OP_INDEX: {
int idx = minic_val_to_i(*--sp);
int len = code[pc + 1];
char *base = minic_val_to_ptr(sp[-1]);
pc += 2;
if (idx < 0 || (len >= 0 && idx >= len)) {
MINIC_FAIL("index %d out of range (length %d)", idx, len);
}
sp[-1] = minic_val_ptr(base != NULL ? base + (size_t)idx * code[pc - 2] : NULL);
break;
}
case OP_INDEX_ARR: {
minic_val_t *s = MINIC_SLOT(code[pc]);
int idx = minic_val_to_i(sp[-1]);
pc += 2;
if (idx < 0 || idx >= s[1].i) {
MINIC_FAIL("index %d out of range (length %d)", idx, s[1].i);
}
sp[-1] = minic_val_ptr((char *)s[0].p + (size_t)idx * code[pc - 1]);
break;
}
case OP_INDEX_BUF: {
int idx = minic_val_to_i(*--sp);
char *field = sp[-1].p;
char *base;
int len;
memcpy(&base, field, sizeof(base));
memcpy(&len, field + code[pc + 1], sizeof(len));
pc += 2;
if (idx < 0 || idx >= len) {
MINIC_FAIL("index %d out of range (length %d)", idx, len);
}
sp[-1] = minic_val_ptr(base != NULL ? base + (size_t)idx * code[pc - 2] : NULL);
break;
}
case OP_INCV: {
minic_val_t *s = MINIC_SLOT(code[pc]);
minic_val_t old = *s;
minic_val_t next = minic_step(old, code[pc + 2], code[pc + 4]);
if (code[pc + 1] == MINIC_T_INT && old.type == MINIC_T_INT) {
s->i = next.i;
}
else {
minic_slot_store(s, next, code[pc + 1], 0);
}
*sp++ = code[pc + 3] ? old : next;
pc += 5;
break;
}
case OP_INCM: {
void *p = minic_val_to_ptr(sp[-1]);
minic_val_t old = minic_mem_load(p, code[pc], code[pc + 1]);
minic_val_t next = minic_step(old, code[pc + 2], code[pc + 4]);
minic_mem_store(p, next, code[pc], code[pc + 5]);
sp[-1] = code[pc + 3] ? old : next;
pc += 6;
break;
}
case OP_COMPV: {
minic_val_t *s = MINIC_SLOT(code[pc]);
minic_val_t r = minic_binop(code[pc + 1], *s, sp[-1]);
if (r.type != s->type) {
r = minic_val_cast(r, s->type);
}
if (code[pc + 2] == (int)s->type && (s->type == MINIC_T_FLOAT || s->type == MINIC_T_INT)) {
s->d = r.d; // Same representation, copy the bits
}
else {
minic_slot_store(s, r, code[pc + 2], code[pc + 3]);
}
sp[-1] = r;
pc += 4;
break;
}
case OP_COMPM: {
void *p = minic_val_to_ptr(sp[-2]);
minic_val_t old = minic_mem_load(p, code[pc + 1], code[pc + 2]);
minic_val_t r = minic_val_cast(minic_binop(code[pc], old, sp[-1]), old.type);
minic_mem_store(p, r, code[pc + 1], code[pc + 3]);
sp[-2] = r;
sp--;
pc += 4;
break;
}
MINIC_ARITH(OP_ADD, (int)((unsigned int)a->i + (unsigned int)b->i), a->f + b->f)
MINIC_ARITH(OP_SUB, (int)((unsigned int)a->i - (unsigned int)b->i), a->f - b->f)
MINIC_ARITH(OP_MUL, (int)((unsigned int)a->i * (unsigned int)b->i), a->f * b->f)
MINIC_ARITH(OP_DIV, minic_arith(*a, *b, OP_DIV).i, b->f != 0.0f ? a->f / b->f : 0.0f)
MINIC_CMP(OP_EQ, ==)
MINIC_CMP(OP_NE, !=)
MINIC_CMP(OP_LT, <)
MINIC_CMP(OP_GT, >)
MINIC_CMP(OP_LE, <=)
MINIC_CMP(OP_GE, >=)
case OP_MOD:
case OP_SHL:
case OP_SHR:
case OP_BAND:
case OP_BOR:
case OP_XOR:
sp[-2] = minic_binop(code[pc - 1], sp[-2], sp[-1]);
sp--;
break;
case OP_NEG: {
minic_val_t v = sp[-1];
sp[-1] = v.type == MINIC_T_INT ? minic_val_int((int)(0u - (unsigned int)v.i)) : minic_val_coerce(-minic_val_to_d(v), v.type);
break;
}
case OP_NOT:
sp[-1] = minic_val_int(!minic_val_is_true(sp[-1]));
break;
case OP_BNOT:
sp[-1] = minic_val_int(~minic_val_to_i(sp[-1]));
break;
case OP_CAST:
sp[-1] = minic_cast(sp[-1], code[pc++]);
break;
case OP_TOPTR:
sp[-1] = minic_val_typed_ptr(minic_val_to_ptr(sp[-1]), code[pc++]);
break;
case OP_JMP:
pc = code[pc];
break;
case OP_JZ: {
minic_val_t v = *--sp;
pc = (v.type == MINIC_T_INT ? v.i != 0 : minic_val_is_true(v)) ? pc + 1 : code[pc];
break;
}
case OP_JNZ: {
minic_val_t v = *--sp;
pc = (v.type == MINIC_T_INT ? v.i != 0 : minic_val_is_true(v)) ? code[pc] : pc + 1;
break;
}
case OP_CALL: {
minic_func_t *f = &ctx->funcs[code[pc]];
int n = code[pc + 1];
pc += 2;
if (sp + f->slot_count + MINIC_STACK_SLACK > ctx->stack_end || ctx->depth >= MINIC_MAX_FRAMES) {
MINIC_FAIL("out of script memory calling '%s', recursion too deep", f->name);
}
ctx->frames[ctx->depth].pc = pc;
ctx->frames[ctx->depth++].fp = fp;
fp = sp - n;
for (; sp < fp + f->slot_count; ++sp) {
*sp = minic_val_int(0);
}
sp = fp + f->slot_count;
pc = f->entry;
break;
}
case OP_CALLN: {
minic_ext_func_t *ef = &minic_ext_funcs[code[pc]];
int n = code[pc + 1];
pc += 2;
ctx->sp = sp; // A native may call back into this context
minic_val_t r = minic_dispatch(ef, sp - n, n);
sp -= n;
*sp++ = r;
if (minic_mem_oom) {
MINIC_FAIL("out of script memory (%d KB)", MINIC_MEM_SIZE / 1024);
}
break;
}
case OP_FNPTR:
*sp++ = minic_val_ptr(&ctx->funcs[code[pc++]]);
break;
case OP_RET: {
minic_val_t v = sp[-1];
minic_type_t kind = code[pc];
if (kind == MINIC_T_PTR) {
v = minic_val_typed_ptr(minic_val_to_ptr(v), code[pc + 1]);
}
else if (kind != MINIC_T_VOID && kind != MINIC_T_EMBED && v.type != kind) {
v = minic_cast(v, kind);
}
sp = fp;
minic_frame_t *frame = &ctx->frames[--ctx->depth];
if (ctx->depth == base_depth) {
*ret = v;
ctx->sp = base;
return true;
}
pc = frame->pc;
fp = frame->fp;
*sp++ = v;
break;
}
}
}
fail:
ctx->depth = base_depth;
ctx->sp = base;
return false;
#undef MINIC_SLOT
#undef MINIC_FAIL
#undef MINIC_ARITH
#undef MINIC_CMP
}
static minic_val_t minic_call_in_ctx(minic_ctx_t *ctx, minic_func_t *fn, minic_val_t *args, int argc) {
minic_ctx_t *prev = minic_active;
int saved_used = ctx->mem_used;
minic_active = ctx;
minic_val_t r;
minic_run(ctx, fn, args, argc, &r);
ctx->mem_used = saved_used; // Rewind, the arena is free again
minic_active = prev;
return r;
}
minic_val_t minic_call_fn(void *fn_ptr, minic_val_t *args, int argc) {
minic_func_t *fn = (minic_func_t *)fn_ptr;
if (fn == NULL || fn->ctx == NULL) {
return minic_val_int(0);
}
return minic_call_in_ctx(fn->ctx, fn, args, argc);
}
minic_val_t minic_ctx_call_fn(minic_ctx_t *ctx, void *fn_ptr, minic_val_t *args, int argc) {
if (ctx == NULL || fn_ptr == NULL) {
return minic_val_int(0);
}
return minic_call_in_ctx(ctx, (minic_func_t *)fn_ptr, args, argc);
}
minic_ctx_t *minic_eval_named(const char *src, const char *filename) {
minic_register_builtins();
minic_ctx_t *ctx = (minic_ctx_t *)calloc(1, sizeof(minic_ctx_t));
ctx->filename = filename;
ctx->mem = (minic_u8 *)calloc(1, MINIC_MEM_SIZE);
ctx->mem_frame = MINIC_MEM_SIZE - MINIC_STACK_SIZE;
ctx->stack_end = (minic_val_t *)(ctx->mem + MINIC_MEM_SIZE);
ctx->sp = (minic_val_t *)(ctx->mem + ctx->mem_frame);
ctx->frames = malloc(MINIC_MAX_FRAMES * sizeof(minic_frame_t));
ctx->structs = malloc(MINIC_MAX_STRUCTS * sizeof(minic_struct_t));
// Copy the source so the context stays valid after the caller frees its buffer
int src_len = (int)strlen(src);
ctx->src_copy = (char *)malloc(src_len + 1);
memcpy(ctx->src_copy, src, src_len + 1);
ctx->str_pool = (char *)malloc(src_len + 1);
// Install the arena so minic_alloc uses this context
minic_ctx_t *prev = minic_active;
minic_active = ctx;
minic_mem_oom = false;
bool ok = minic_compile(ctx);
if (ok) {
ctx->globals = calloc(ctx->global_count + 1, sizeof(minic_val_t));
minic_val_t r;
ok = minic_run(ctx, &ctx->init, NULL, 0, &r);
int main_idx = minic_func_index(ctx, "main");
if (ok && main_idx >= 0 && ctx->funcs[main_idx].body >= 0) {
ok = minic_run(ctx, &ctx->funcs[main_idx], NULL, 0, &ctx->return_val);
}
}
minic_active = prev;
ctx->result = (!ok || minic_mem_oom) ? -1.0f : (float)minic_val_to_d(ctx->return_val);
return ctx;
}
minic_ctx_t *minic_eval(const char *src) {
return minic_eval_named(src, "<script>");
}
void minic_ctx_free(minic_ctx_t *ctx) {
if (ctx != NULL) {
free(ctx->mem);
free(ctx->funcs);
free(ctx->structs);
free(ctx->frames);
free(ctx->globals);
free(ctx->code);
free(ctx->code_pos);
free(ctx->consts);
free(ctx->src_copy);
free(ctx->str_pool);
free(ctx);
}
}
float minic_ctx_result(minic_ctx_t *ctx) {
return ctx != NULL ? ctx->result : -1.0f;
}
minic_val_t minic_ctx_return_val(minic_ctx_t *ctx) {
return ctx != NULL ? ctx->return_val : minic_val_int(0);
}
// ███████╗██╗ ██╗████████╗███████╗██████╗ ███╗ ██╗ █████╗ ██╗
// ██╔════╝╚██╗██╔╝╚══██╔══╝██╔════╝██╔══██╗████╗ ██║██╔══██╗██║
// █████╗ ╚███╔╝ ██║ █████╗ ██████╔╝██╔██╗ ██║███████║██║
// ██╔══╝ ██╔██╗ ██║ ██╔══╝ ██╔══██╗██║╚██╗██║██╔══██║██║
// ███████╗██╔╝ ██╗ ██║ ███████╗██║ ██║██║ ╚████║██║ ██║███████╗
// ╚══════╝╚═╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝╚══════╝
typedef struct {
char name[MINIC_MAX_NAME];
int value;
} minic_enum_const_t;
typedef struct {
char name[MINIC_MAX_NAME];
const void *ptr; // points at the live host variable
minic_type_t type; // MINIC_T_INT or MINIC_T_FLOAT
} minic_global_t;
static int minic_ext_func_count = 0;
static minic_enum_const_t minic_enum_consts[MINIC_MAX_ENUM_CONSTS];
static int minic_enum_const_count = 0;
static char minic_int_typedefs[MINIC_MAX_INT_TYPEDEFS][MINIC_MAX_NAME];
static int minic_int_typedef_count = 0;
static minic_global_t minic_globals[MINIC_MAX_GLOBALS];
static int minic_global_count = 0;
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, int alignment) {
minic_struct_cur = NULL;
for (int i = 0; i < minic_struct_count; ++i) {
if (strcmp(minic_structs[i].name, name) == 0) {
minic_struct_cur = &minic_structs[i];
break;
}
}
if (minic_struct_cur == NULL) {
if (minic_struct_count >= MINIC_MAX_STRUCTS) {
return;
}
minic_struct_cur = &minic_structs[minic_struct_count++];
}
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->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) {
minic_struct_t *s = minic_struct_cur;
if (s == NULL || s->field_count >= MINIC_MAX_STRUCT_FIELDS) {
return;
}
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->pointer_depths[i] = type == MINIC_T_PTR ? 1 : 0;
if (struct_type != NULL) {
strncpy(s->field_structs[i], struct_type, MINIC_MAX_NAME - 1);
}
}
void minic_register_struct(const char *name, const char **fields, int field_count) {
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], i * (int)sizeof(int32_t), MINIC_T_INT, MINIC_T_INT, NULL);
}
}
static int minic_enum_const_find(const char *name) {
for (int i = 0; i < minic_enum_const_count; ++i) {
if (strcmp(minic_enum_consts[i].name, name) == 0) {
return i;
}
}
return -1;
}
void minic_enum_const_add(const char *name, int value) {
if (minic_enum_const_find(name) >= 0 || minic_enum_const_count >= MINIC_MAX_ENUM_CONSTS) {
return;
}
strncpy(minic_enum_consts[minic_enum_const_count].name, name, MINIC_MAX_NAME - 1);
minic_enum_consts[minic_enum_const_count++].value = value;
}
int minic_enum_const_get(const char *name) {
int i = minic_enum_const_find(name);
return i >= 0 ? minic_enum_consts[i].value : -1;
}
static int minic_global_find(const char *name) {
for (int i = 0; i < minic_global_count; ++i) {
if (strcmp(minic_globals[i].name, name) == 0) {
return i;
}
}
return -1;
}
void minic_register_global(const char *name, const void *ptr, minic_type_t type) {
int i = minic_global_find(name);
if (i < 0) {
if (minic_global_count >= MINIC_MAX_GLOBALS) {
return;
}
i = minic_global_count++;
strncpy(minic_globals[i].name, name, MINIC_MAX_NAME - 1);
}
minic_globals[i].ptr = ptr;
minic_globals[i].type = type;
}
static const void *minic_global_ptr(const char *name, minic_type_t *type) {
int i = minic_global_find(name);
if (i < 0) {
return NULL;
}
*type = minic_globals[i].type;
return minic_globals[i].ptr;
}
bool minic_global_get(const char *name, minic_val_t *out) {
minic_type_t type;
const void *ptr = minic_global_ptr(name, &type);
if (ptr != NULL) {
*out = minic_mem_load((void *)ptr, type, type);
}
return ptr != NULL;
}
void minic_int_typedef_add(const char *name) {
if (minic_is_int_typedef(name) || minic_int_typedef_count >= MINIC_MAX_INT_TYPEDEFS) {
return;
}
strncpy(minic_int_typedefs[minic_int_typedef_count++], name, MINIC_MAX_NAME - 1);
}
bool minic_is_int_typedef(const char *name) {
for (int i = 0; i < minic_int_typedef_count; ++i) {
if (strcmp(minic_int_typedefs[i], name) == 0) {
return true;
}
}
return false;
}
void minic_register_enum(const char *typedef_name, const char **names, const int *values, int count) {
if (typedef_name != NULL) {
minic_int_typedef_add(typedef_name);
}
for (int i = 0; i < count; ++i) {
minic_enum_const_add(names[i], values != NULL ? values[i] : i);
}
}
static minic_ext_func_t *minic_ext_func_add(const char *name) {
minic_ext_func_t *ef = minic_ext_func_get(name);
if (ef == NULL && minic_ext_func_count < MINIC_MAX_EXTFUNS) {
ef = &minic_ext_funcs[minic_ext_func_count++];
memset(ef, 0, sizeof(*ef));
strncpy(ef->name, name, MINIC_MAX_NAME - 1);
}
return ef;
}
void minic_register(const char *name, const char *sig, minic_native_fn_t fn) {
minic_ext_func_t *ef = minic_ext_func_add(name);
if (ef == NULL) {
return;
}
strncpy(ef->sig, sig != NULL ? sig : "i()", MINIC_MAX_SIG - 1);
ef->fn = fn;
}
void minic_register_native(const char *name, minic_native_fn_t fn) {
minic_ext_func_t *ef = minic_ext_func_add(name);
if (ef != NULL) {
ef->fn = fn;
}
}
#define MINIC_EXT_HASH_SIZE 2048
static int16_t minic_ext_hash[MINIC_EXT_HASH_SIZE];
static int minic_ext_hash_count = -1;
static unsigned minic_name_hash(const char *s) {
unsigned h = 2166136261u; // FNV-1a
while (*s != '\0') {
h ^= (unsigned char)*s++;
h *= 16777619u;
}
return h & (MINIC_EXT_HASH_SIZE - 1);
}
static void minic_ext_hash_build(void) {
for (int i = 0; i < MINIC_EXT_HASH_SIZE; ++i) {
minic_ext_hash[i] = -1;
}
for (int i = 0; i < minic_ext_func_count; ++i) {
unsigned h = minic_name_hash(minic_ext_funcs[i].name);
while (minic_ext_hash[h] != -1) {
h = (h + 1) & (MINIC_EXT_HASH_SIZE - 1);
}
minic_ext_hash[h] = (int16_t)i;
}
minic_ext_hash_count = minic_ext_func_count;
}
minic_ext_func_t *minic_ext_func_get(const char *name) {
if (minic_ext_hash_count != minic_ext_func_count) {
minic_ext_hash_build();
}
unsigned h = minic_name_hash(name);
while (minic_ext_hash[h] != -1) {
minic_ext_func_t *ef = &minic_ext_funcs[minic_ext_hash[h]];
if (strcmp(ef->name, name) == 0) {
return ef;
}
h = (h + 1) & (MINIC_EXT_HASH_SIZE - 1);
}
return NULL;
}
int minic_ext_func_count_get(void) {
return minic_ext_func_count;
}
const char *minic_ext_func_name_at(int i) {
return minic_ext_funcs[i].name;
}
const char *minic_ext_func_sig_at(int i) {
return minic_ext_funcs[i].sig;
}
int minic_global_count_get(void) {
return minic_global_count;
}
const char *minic_global_name_at(int i) {
return minic_globals[i].name;
}
minic_type_t minic_global_type_at(int i) {
return minic_globals[i].type;
}
int minic_enum_const_count_get(void) {
return minic_enum_const_count;
}
const char *minic_enum_const_name_at(int i) {
return minic_enum_consts[i].name;
}
int minic_enum_const_value_at(int i) {
return minic_enum_consts[i].value;
}
// ██████╗ ██╗███████╗██████╗ █████╗ ████████╗ ██████╗██╗ ██╗
// ██╔══██╗██║██╔════╝██╔══██╗██╔══██╗╚══██╔══╝██╔════╝██║ ██║
// ██║ ██║██║███████╗██████╔╝███████║ ██║ ██║ ███████║
// ██║ ██║██║╚════██║██╔═══╝ ██╔══██║ ██║ ██║ ██╔══██║
// ██████╔╝██║███████║██║ ██║ ██║ ██║ ╚██████╗██║ ██║
// ╚═════╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝
//
float minic_arg_f(minic_val_t *args, int argc, int i) {
return i < argc ? (float)minic_val_to_d(args[i]) : 0.0f;
}
int minic_arg_i(minic_val_t *args, int argc, int i) {
return i < argc ? (int)minic_val_to_d(args[i]) : 0;
}
void *minic_arg_p(minic_val_t *args, int argc, int i) {
return i < argc ? minic_val_to_ptr(args[i]) : NULL;
}
minic_val_t minic_dispatch(minic_ext_func_t *ef, minic_val_t *args, int argc) {
if (ef->fn == NULL) {
fprintf(stderr, "minic: '%s' has no thunk, add it to minic_api_list.h\n", ef->name);
return minic_val_int(0);
}
return ef->fn(args, argc);
}