From 413910f8fe6fd4c28c839e98e311444cdbf2017f Mon Sep 17 00:00:00 2001 From: SIGMazer Date: Sun, 8 Mar 2026 04:53:54 +0200 Subject: [PATCH 1/8] refactor stdrot dispatch to fully dynamic shared-library handlers --- Makefile | 28 +- ast.c | 11 - interpreter.c | 19 +- lang.y | 191 +----------- stdrot.c | 711 +++++++++++++++++++++----------------------- stdrot.h | 45 ++- stdrot/baka.c | 112 +++++++ stdrot/ragequit.c | 43 +++ stdrot/registry.c | 21 ++ stdrot/slorp.c | 151 ++++++++++ stdrot/stdrot_api.h | 107 +++++++ stdrot/yapping.c | 142 +++++++++ 12 files changed, 972 insertions(+), 609 deletions(-) create mode 100644 stdrot/baka.c create mode 100644 stdrot/ragequit.c create mode 100644 stdrot/registry.c create mode 100644 stdrot/slorp.c create mode 100644 stdrot/stdrot_api.h create mode 100644 stdrot/yapping.c diff --git a/Makefile b/Makefile index f024dcb..54de731 100644 --- a/Makefile +++ b/Makefile @@ -6,15 +6,21 @@ PYTHON := python3 # Compiler and linker flags CFLAGS := -Wall -Wextra -Wpedantic -Werror -O2 -Wuninitialized -LDFLAGS := -lfl -lm +LDFLAGS := -lfl -lm -ldl +SO_CFLAGS := -fPIC -shared # Source files and directories SRC_DIR := lib DEBUG_FLAGS := -g -SRCS := $(SRC_DIR)/hm.c $(SRC_DIR)/mem.c $(SRC_DIR)/input.c $(SRC_DIR)/arena.c ast.c visitor.c semantic_analyzer.c interpreter.c stdrot.c +SRCS := $(SRC_DIR)/hm.c $(SRC_DIR)/mem.c $(SRC_DIR)/arena.c ast.c visitor.c semantic_analyzer.c interpreter.c stdrot.c GENERATED_SRCS := lang.tab.c lex.yy.c ALL_SRCS := $(SRCS) $(GENERATED_SRCS) +# stdrot shared library +STDROT_DIR := stdrot +STDROT_SRCS := $(STDROT_DIR)/yapping.c $(STDROT_DIR)/baka.c $(STDROT_DIR)/ragequit.c $(STDROT_DIR)/slorp.c $(STDROT_DIR)/registry.c $(SRC_DIR)/input.c +STDROT_LIB := libstdrot.so + # Output files TARGET := brainrot BISON_OUTPUT := lang.tab.c @@ -22,18 +28,26 @@ FLEX_OUTPUT := lex.yy.c # Default target .PHONY: all -all: $(TARGET) +all: $(STDROT_LIB) $(TARGET) + +# Build only the standard library +.PHONY: lib +lib: $(STDROT_LIB) # Debug target .PHONY: debug debug: CFLAGS += $(DEBUG_FLAGS) -debug: clean $(TARGET) +debug: clean all @echo "Debug build compiled with -g. Time to sigma grind with GDB." +# stdrot shared library build +$(STDROT_LIB): $(STDROT_SRCS) + $(CC) $(SO_CFLAGS) -I. -o $@ $^ -lm + @echo "libstdrot.so compiled with max rizz." # Main executable build -$(TARGET): $(ALL_SRCS) - $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) +$(TARGET): $(ALL_SRCS) $(STDROT_LIB) + $(CC) $(CFLAGS) -o $@ $(ALL_SRCS) $(LDFLAGS) @echo "Skibidi toilet: $(TARGET) compiled with max gyatt." # Generate parser files using Bison @@ -55,7 +69,7 @@ test: # Clean build artifacts .PHONY: clean clean: - rm -f $(TARGET) $(GENERATED_SRCS) lang.tab.h + rm -f $(TARGET) $(STDROT_LIB) $(GENERATED_SRCS) lang.tab.h rm -f *.o @echo "Blud cleaned up the mess like a true sigma coder." diff --git a/ast.c b/ast.c index c6b73ad..051551c 100644 --- a/ast.c +++ b/ast.c @@ -28,17 +28,6 @@ Scope *current_scope; /* Include the symbol table functions */ extern void yyerror(const char *s); extern void cleanup(void); -extern void ragequit(int exit_code); -extern void chill(unsigned int seconds); -extern void yapping(const char *format, ...); -extern void yappin(const char *format, ...); -extern void baka(const char *format, ...); -extern char slorp_char(char chr); -extern char *slorp_string(char *string, size_t size); -extern int slorp_int(int val); -extern short slorp_short(short val); -extern float slorp_float(float var); -extern double slorp_double(double var); extern TypeModifiers get_variable_modifiers(const char *name); extern int yylineno; diff --git a/interpreter.c b/interpreter.c index fadf514..2725266 100644 --- a/interpreter.c +++ b/interpreter.c @@ -8,8 +8,7 @@ extern void yyerror(const char *s); extern char *safe_strdup(const char *str); -extern void yapping(const char* format, ...); -extern void baka(const char* format, ...); +extern void execute_func_call(const char *func_name, ArgumentList *args); /* External functions we need from the original implementation */ extern Variable *variable_new(char *name); @@ -646,12 +645,8 @@ void interpreter_visit_print_statement(Visitor *self, ASTNode *node) { if (!node || !node->data.op.left) return; ASTNode *expr = node->data.op.left; - if (expr->type == NODE_STRING_LITERAL) { - yapping("%s", expr->data.strvalue); - } else { - int value = evaluate_expression_int(expr); - yapping("%d", value); - } + ArgumentList args = {expr, NULL}; + execute_func_call("yapping", &args); } void interpreter_visit_error_statement(Visitor *self, ASTNode *node) { @@ -659,10 +654,6 @@ void interpreter_visit_error_statement(Visitor *self, ASTNode *node) { if (!node || !node->data.op.left) return; ASTNode *expr = node->data.op.left; - if (expr->type == NODE_STRING_LITERAL) { - baka("%s", expr->data.strvalue); - } else { - int value = evaluate_expression_int(expr); - baka("%d", value); - } + ArgumentList args = {expr, NULL}; + execute_func_call("baka", &args); } diff --git a/lang.y b/lang.y index dd84bad..edd4ad2 100644 --- a/lang.y +++ b/lang.y @@ -4,28 +4,17 @@ #include "visitor.h" #include "semantic_analyzer.h" #include "interpreter.h" +#include "stdrot.h" #include "lib/mem.h" -#include "lib/input.h" #include #include #include #include #include -#include int yylex(void); int yylex_destroy(void); void yyerror(const char *s); -void ragequit(int exit_code); -void yapping(const char* format, ...); -void yappin(const char* format, ...); -void baka(const char* format, ...); -char slorp_char(char chr); -char *slorp_string(char *string, size_t size); -int slorp_int(int val); -short slorp_short(short val); -float slorp_float(float var); -double slorp_double(double var); void cleanup(); TypeModifiers get_variable_modifiers(const char* name); extern TypeModifiers current_modifiers; @@ -624,6 +613,9 @@ int main(int argc, char *argv[]) { yyin = source; current_scope = create_scope(NULL); + /* Phase 0: Load standard library (needed for semantic analysis) */ + stdrot_load(); + /* Phase 1: Parse the source code to build AST */ if (yyparse() != 0) { fprintf(stderr, "Parsing failed\n"); @@ -651,6 +643,7 @@ int main(int argc, char *argv[]) { /* Cleanup */ cleanup(); + stdrot_unload(); return 0; } @@ -659,180 +652,6 @@ void yyerror(const char *s) { fprintf(stderr, "Error: %s at line %d\n", s, yylineno - 1); } -void ragequit(int exit_code) { - cleanup(); - exit(exit_code); -} - -void chill(unsigned int seconds) { - sleep(seconds); -} - -void yapping(const char* format, ...) { - va_list args; - va_start(args, format); - vprintf(format, args); - va_end(args); - printf("\n"); -} - -void yappin(const char* format, ...) { - va_list args; - va_start(args, format); - vprintf(format, args); - va_end(args); -} - -void baka(const char* format, ...) { - va_list args; - va_start(args, format); - vfprintf(stderr, format, args); - va_end(args); -} - -char slorp_char(char chr) { - input_status status; - - status = input_char(&chr); - if (status == INPUT_SUCCESS) - { - return chr; - } - else if (status == INPUT_INVALID_LENGTH) - { - fprintf(stderr, "Error: Invalid input length.\n"); - exit(EXIT_FAILURE); - } - else - { - fprintf(stderr, "Error reading char: %d\n", status); - exit(EXIT_FAILURE); - } -} - -char *slorp_string(char *string, size_t size) { - size_t chars_read; - input_status status; - - status = input_string(string, size, &chars_read); - if (status == INPUT_SUCCESS) - { - return string; - } - else if (status == INPUT_BUFFER_OVERFLOW) - { - fprintf(stderr, "Error: Input exceeded buffer size.\n"); - exit(EXIT_FAILURE); - } - else - { - fprintf(stderr, "Error reading string: %d\n", status); - exit(EXIT_FAILURE); - } -} - -int slorp_int(int val) { - input_status status; - - status = input_int(&val); - if (status == INPUT_SUCCESS) - { - return val; - } - else if (status == INPUT_INTEGER_OVERFLOW) - { - fprintf(stderr, "Error: Integer value out of range.\n"); - exit(EXIT_FAILURE); - } - else if (status == INPUT_CONVERSION_ERROR) - { - fprintf(stderr, "Error: Invalid integer format.\n"); - exit(EXIT_FAILURE); - } - else - { - fprintf(stderr, "Error reading integer: %d\n", status); - exit(EXIT_FAILURE); - } - return 0; -} - -short slorp_short(short val) { - input_status status; - - status = input_short(&val); - if (status == INPUT_SUCCESS) - { - return val; - } - else if (status == INPUT_SHORT_OVERFLOW) - { - fprintf(stderr, "Error: short value out of range.\n"); - exit(EXIT_FAILURE); - } - else if (status == INPUT_CONVERSION_ERROR) - { - fprintf(stderr, "Error: short integer format.\n"); - exit(EXIT_FAILURE); - } - else - { - fprintf(stderr, "Error reading short: %d\n", status); - exit(EXIT_FAILURE); - } - return 0; -} - -float slorp_float(float var) { - input_status status; - - status = input_float(&var); - if (status == INPUT_SUCCESS) - { - return var; - } - else if (status == INPUT_FLOAT_OVERFLOW) - { - fprintf(stderr, "Error: Double value out of range.\n"); - exit(EXIT_FAILURE); - } - else if (status == INPUT_CONVERSION_ERROR) - { - fprintf(stderr, "Error: Invalid float format.\n"); - exit(EXIT_FAILURE); - } - else - { - fprintf(stderr, "Error reading float: %d\n", status); - exit(EXIT_FAILURE); - } -} - -double slorp_double(double var) { - input_status status; - - status = input_double(&var); - if (status == INPUT_SUCCESS) - { - return var; - } - else if (status == INPUT_DOUBLE_OVERFLOW) - { - fprintf(stderr, "Error: Double value out of range.\n"); - exit(EXIT_FAILURE); - } - else if (status == INPUT_CONVERSION_ERROR) - { - fprintf(stderr, "Error: Invalid double format.\n"); - exit(EXIT_FAILURE); - } - else - { - fprintf(stderr, "Error reading double: %d\n", status); - exit(EXIT_FAILURE); - } -} - void cleanup() { // Free the global interpreter if it exists if (global_interpreter) { diff --git a/stdrot.c b/stdrot.c index b0fe14d..2acc824 100644 --- a/stdrot.c +++ b/stdrot.c @@ -1,4 +1,16 @@ -/* stdrot.c - Standard Brainrot built-in functions implementation */ +/* stdrot.c - Standard Brainrot library loader and AST bridge + * + * This file is the glue between: + * • the AST/interpreter (understands ASTNode, ArgumentList, Variable, etc.) + * • libstdrot.so (pure I/O functions, zero interpreter dependency) + * + * It provides: + * 1. Dynamic loader (stdrot_load/unload) that opens libstdrot.so and + * discovers all functions via stdrot_get_api() + * 2. Thin varargs stubs (yapping/yappin/baka) that forward to the .so + * 3. AST bridge functions (execute_*_call) that evaluate arguments and + * call the raw implementations + */ #include "stdrot.h" #include "ast.h" @@ -6,22 +18,11 @@ #include #include #include -#include +#include +#include -/* External function declarations */ +/* ── External interpreter functions ──────────────────────────────────────── */ extern void yyerror(const char *s); -extern void yapping(const char* format, ...); -extern void yappin(const char* format, ...); -extern void baka(const char* format, ...); -extern void ragequit(int exit_code); -extern void chill(unsigned int seconds); -extern char slorp_char(char chr); -extern char *slorp_string(char *string, size_t size); -extern int slorp_int(int val); -extern short slorp_short(short val); -extern float slorp_float(float var); -extern double slorp_double(double var); - extern int evaluate_expression_int(ASTNode *node); extern float evaluate_expression_float(ASTNode *node); extern double evaluate_expression_double(ASTNode *node); @@ -35,420 +36,376 @@ extern bool set_int_variable(const char *name, int value, TypeModifiers mods); extern bool set_float_variable(const char *name, float value, TypeModifiers mods); extern bool set_double_variable(const char *name, double value, TypeModifiers mods); extern bool set_short_variable(const char *name, short value, TypeModifiers mods); - -/* Built-in function names */ -const char* BUILTIN_FUNCTIONS[] = { - "yapping", - "yappin", - "baka", - "ragequit", - "chill", - "slorp" -}; - -const int BUILTIN_FUNCTION_COUNT = sizeof(BUILTIN_FUNCTIONS) / sizeof(BUILTIN_FUNCTIONS[0]); - -/* Check if a function name is a built-in function */ -bool is_builtin_function(const char *func_name) { - if (!func_name) return false; - - for (int i = 0; i < BUILTIN_FUNCTION_COUNT; i++) { - if (strcmp(func_name, BUILTIN_FUNCTIONS[i]) == 0) { - return true; +extern bool set_bool_variable(const char *name, bool value, TypeModifiers mods); + +/* ── Dynamic library state ────────────────────────────────────────────────── */ +static void *lib_handle = NULL; +static StdrotEntry *functions = NULL; +static int function_count = 0; + +/* Symbol cache to avoid repeated dlsym calls */ +#define STDROT_CACHE_SIZE 64 +typedef struct { + const char *name; + void *ptr; +} SymbolCache; + +static SymbolCache symbol_cache[STDROT_CACHE_SIZE]; +static int cache_count = 0; + +/* ── Forward declarations of stub functions ──────────────────────────────── */ +void yapping(const char* format, ...); +void yappin(const char* format, ...); +void baka(const char* format, ...); +void ragequit(int exit_code); +void chill(unsigned int seconds); +char slorp_char(char chr); +char *slorp_string(char *string, size_t size); +int slorp_int(int val); +short slorp_short(short val); +float slorp_float(float var); +double slorp_double(double var); + +/* ── Dynamic symbol lookup with caching ──────────────────────────────────── */ + +static void *stdrot_lookup_symbol(const char *symbol_name) +{ + if (!lib_handle || !symbol_name) return NULL; + + /* Check cache first */ + for (int i = 0; i < cache_count; i++) { + if (strcmp(symbol_cache[i].name, symbol_name) == 0) { + return symbol_cache[i].ptr; } } - return false; -} -/* Execute a built-in function call */ -void execute_builtin_function(const char *func_name, ArgumentList *args) { - if (!func_name) return; - - if (strcmp(func_name, "yapping") == 0) { - execute_yapping_call(args); - } else if (strcmp(func_name, "yappin") == 0) { - execute_yappin_call(args); - } else if (strcmp(func_name, "baka") == 0) { - execute_baka_call(args); - } else if (strcmp(func_name, "ragequit") == 0) { - execute_ragequit_call(args); - } else if (strcmp(func_name, "chill") == 0) { - execute_chill_call(args); - } else if (strcmp(func_name, "slorp") == 0) { - execute_slorp_call(args); + /* Not in cache, lookup via dlsym */ + void *ptr = dlsym(lib_handle, symbol_name); + if (ptr && cache_count < STDROT_CACHE_SIZE) { + symbol_cache[cache_count].name = symbol_name; + symbol_cache[cache_count].ptr = ptr; + cache_count++; } + + return ptr; } -/* Built-in function implementations */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpedantic" + +/* ── Loader ──────────────────────────────────────────────────────────────── */ -void execute_yapping_call(ArgumentList *args) { - if (!args) { - yyerror("No arguments provided for yapping function call"); +void stdrot_load(void) +{ + /* Open libstdrot.so from the same directory as the binary, or LD_LIBRARY_PATH */ + lib_handle = dlopen("./libstdrot.so", RTLD_LAZY | RTLD_LOCAL); + if (!lib_handle) { + lib_handle = dlopen("libstdrot.so", RTLD_LAZY | RTLD_LOCAL); + } + if (!lib_handle) { + fprintf(stderr, "Failed to load libstdrot.so: %s\n", dlerror()); exit(EXIT_FAILURE); } - ASTNode *formatNode = args->expr; - if (formatNode->type != NODE_STRING_LITERAL) { - yyerror("First argument to yapping must be a string literal"); - return; + /* Get the API entrypoint */ + StdrotAPI (*get_api)(void); + *(void **)(&get_api) = dlsym(lib_handle, "stdrot_get_api"); + if (!get_api) { + fprintf(stderr, "libstdrot.so missing stdrot_get_api(): %s\n", dlerror()); + dlclose(lib_handle); + exit(EXIT_FAILURE); } - const char *format = formatNode->data.name; // The format string - char buffer[1024]; // Buffer for the final formatted output - int buffer_offset = 0; + /* Discover all functions */ + StdrotAPI api = get_api(); + functions = api.functions; + function_count = api.count; +} - ArgumentList *cur = args->next; +void stdrot_unload(void) +{ + if (lib_handle) { + dlclose(lib_handle); + lib_handle = NULL; + functions = NULL; + function_count = 0; + cache_count = 0; + } +} - while (*format != '\0') { - if (*format == '%' && cur != NULL) { - // Start extracting the format specifier - const char *start = format; - format++; // Move past '%' +/* ── Runtime query ────────────────────────────────────────────────────────── */ - // Extract until a valid specifier character is found - while (strchr("diouxXfFeEgGaAcspnb%", *format) == NULL && *format != '\0') { - format++; - } +bool is_builtin_function(const char *func_name) +{ + if (!func_name || !functions) return false; - if (*format == '\0') { - yyerror("Invalid format specifier"); - exit(EXIT_FAILURE); - } + for (int i = 0; i < function_count; i++) { + if (strcmp(func_name, functions[i].name) == 0) { + return true; + } + } + return false; +} - // Copy the format specifier into a temporary buffer - char specifier[32]; - int length = format - start + 1; - strncpy(specifier, start, length); - specifier[length] = '\0'; - - // Process the argument based on the format specifier - ASTNode *expr = cur->expr; - if (!expr) { - yyerror("Invalid argument in yapping call"); - exit(EXIT_FAILURE); - } +void execute_builtin_function(const char *func_name, ArgumentList *args) +{ + execute_func_call(func_name, args); +} - if (*format == 'b') { - // Handle boolean values - bool val = evaluate_expression_bool(expr); - buffer_offset += snprintf(buffer + buffer_offset, sizeof(buffer) - buffer_offset, "%s", val ? "W" : "L"); - } else if (strchr("diouxX", *format)) { - // Integer or unsigned integer - volatile bool is_unsigned = (expr->type == NODE_IDENTIFIER && - get_variable_modifiers(expr->data.name).is_unsigned); - - if (is_unsigned) { - if (is_expression(expr, VAR_SHORT)) { - unsigned short val = evaluate_expression_short(expr); - buffer_offset += snprintf(buffer + buffer_offset, sizeof(buffer) - buffer_offset, specifier, val); - } else { - unsigned int val = (unsigned int)evaluate_expression_int(expr); - buffer_offset += snprintf(buffer + buffer_offset, sizeof(buffer) - buffer_offset, specifier, val); - } - } else { - if (is_expression(expr, VAR_SHORT)) { - short val = evaluate_expression_short(expr); - buffer_offset += snprintf(buffer + buffer_offset, sizeof(buffer) - buffer_offset, specifier, val); - } else { - int val = evaluate_expression_int(expr); - buffer_offset += snprintf(buffer + buffer_offset, sizeof(buffer) - buffer_offset, specifier, val); - } - } - } else if (strchr("fFeEgGa", *format)) { - // Floating-point numbers - if (expr->type == NODE_ARRAY_ACCESS) { - // Special handling for array access - const char *array_name = expr->data.array.name; - void *element = evaluate_multi_array_access(expr); - if (!element) { - yyerror("Invalid array access in floating-point format specifier"); - exit(EXIT_FAILURE); - } - - Variable *var = get_variable(array_name); - if (var != NULL) { - if (var->var_type == VAR_FLOAT) { - float val = *(float*)element; - buffer_offset += snprintf(buffer + buffer_offset, - sizeof(buffer) - buffer_offset, - specifier, val); - } else if (var->var_type == VAR_DOUBLE) { - double val = *(double*)element; - buffer_offset += snprintf(buffer + buffer_offset, - sizeof(buffer) - buffer_offset, - specifier, val); - } - break; - } - } else if (is_expression(expr, VAR_FLOAT)) { - float val = evaluate_expression_float(expr); - buffer_offset += snprintf(buffer + buffer_offset, sizeof(buffer) - buffer_offset, specifier, val); - } else if (is_expression(expr, VAR_DOUBLE)) { - double val = evaluate_expression_double(expr); - buffer_offset += snprintf(buffer + buffer_offset, sizeof(buffer) - buffer_offset, specifier, val); - } else { - yyerror("Invalid argument type for floating-point format specifier"); - exit(EXIT_FAILURE); - } - } else if (*format == 'c') { - // Character - int val = evaluate_expression_int(expr); - buffer_offset += snprintf(buffer + buffer_offset, sizeof(buffer) - buffer_offset, specifier, val); - } else if (*format == 's') { - // String - const Variable *var = get_variable(expr->data.name); - if (var != NULL) { - if (!var->is_array && var->var_type != VAR_STRING) { - yyerror("Invalid argument type for %s"); - exit(EXIT_FAILURE); - } - buffer_offset += snprintf(buffer + buffer_offset, sizeof(buffer) - buffer_offset, specifier, var->value.array_data); - } else if (expr->type != NODE_STRING_LITERAL) { - yyerror("Invalid argument type for %s"); - exit(EXIT_FAILURE); - } else { - buffer_offset += snprintf(buffer + buffer_offset, sizeof(buffer) - buffer_offset, specifier, expr->data.name); - } +static void ast_expr_to_stdrot_value(ASTNode *expr, StdrotValue *out) +{ + out->type = STDROT_NONE; + if (!expr) return; + + switch (expr->type) { + case NODE_STRING_LITERAL: + out->type = STDROT_STRING; + out->val.str = expr->data.name; + return; + case NODE_INT: + out->type = STDROT_INT; + out->val.i = expr->data.ivalue; + return; + case NODE_SHORT: + out->type = STDROT_SHORT; + out->val.s = expr->data.svalue; + return; + case NODE_FLOAT: + out->type = STDROT_FLOAT; + out->val.f = expr->data.fvalue; + return; + case NODE_DOUBLE: + out->type = STDROT_DOUBLE; + out->val.d = expr->data.dvalue; + return; + case NODE_CHAR: + out->type = STDROT_CHAR; + out->val.c = (char)expr->data.ivalue; + return; + case NODE_BOOLEAN: + out->type = STDROT_BOOL; + out->val.b = expr->data.bvalue; + return; + case NODE_SIZEOF: + out->type = STDROT_INT; + out->val.i = evaluate_expression_int(expr); + return; + case NODE_IDENTIFIER: { + Variable *var = get_variable(expr->data.name); + if (!var) return; + switch (var->var_type) { + case VAR_INT: + out->type = STDROT_INT; + out->val.i = var->value.ivalue; + return; + case VAR_FLOAT: + out->type = STDROT_FLOAT; + out->val.f = var->value.fvalue; + return; + case VAR_DOUBLE: + out->type = STDROT_DOUBLE; + out->val.d = var->value.dvalue; + return; + case VAR_SHORT: + out->type = STDROT_SHORT; + out->val.s = var->value.svalue; + return; + case VAR_BOOL: + out->type = STDROT_BOOL; + out->val.b = var->value.bvalue; + return; + case VAR_CHAR: + if (var->is_array) { + out->type = STDROT_STRING; + out->val.str = (char *)var->value.array_data; } else { - yyerror("Unsupported format specifier"); - exit(EXIT_FAILURE); + out->type = STDROT_CHAR; + out->val.c = (char)var->value.ivalue; } - - cur = cur->next; // Move to the next argument - format++; // Move past the format specifier - } else { - // Copy non-format characters to the buffer - buffer[buffer_offset++] = *format++; - } - - // Check for buffer overflow - if (buffer_offset >= (int)sizeof(buffer)) { - yyerror("Buffer overflow in yapping call"); - exit(EXIT_FAILURE); + return; + case VAR_STRING: + out->type = STDROT_STRING; + out->val.str = (char *)var->value.array_data; + return; + default: + return; } } + default: + break; + } - buffer[buffer_offset] = '\0'; // Null-terminate the string - - // Print the final formatted output - yapping("%s", buffer); + /* General expression fallback */ + if (is_expression(expr, VAR_BOOL)) { + out->type = STDROT_BOOL; + out->val.b = evaluate_expression_bool(expr); + } else if (is_expression(expr, VAR_SHORT)) { + out->type = STDROT_SHORT; + out->val.s = evaluate_expression_short(expr); + } else if (is_expression(expr, VAR_FLOAT)) { + out->type = STDROT_FLOAT; + out->val.f = evaluate_expression_float(expr); + } else if (is_expression(expr, VAR_DOUBLE)) { + out->type = STDROT_DOUBLE; + out->val.d = evaluate_expression_double(expr); + } else if (is_expression(expr, VAR_INT) || expr->type == NODE_ARRAY_ACCESS || expr->type == NODE_OPERATION || expr->type == NODE_UNARY_OPERATION) { + out->type = STDROT_INT; + out->val.i = evaluate_expression_int(expr); + } } -void execute_yappin_call(ArgumentList *args) { - if (!args) { - yyerror("No arguments provided for yappin function call"); - exit(EXIT_FAILURE); +void execute_func_call(const char *func_name, ArgumentList *args) +{ + if (!func_name || !functions) { + yyerror("Function not found"); + return; } - ASTNode *formatNode = args->expr; - if (formatNode->type != NODE_STRING_LITERAL) { - yyerror("First argument to yappin must be a string literal"); - exit(EXIT_FAILURE); + /* Look up function in the registry */ + StdrotEntry *entry = NULL; + for (int i = 0; i < function_count; i++) { + if (strcmp(functions[i].name, func_name) == 0) { + entry = &functions[i]; + break; + } } - const char *format = formatNode->data.name; // The format string - char buffer[1024]; // Buffer for the final formatted output - int buffer_offset = 0; - - ArgumentList *cur = args->next; + if (!entry || !entry->fn) { + yyerror("Unknown function"); + return; + } - while (*format != '\0') { - if (*format == '%' && cur != NULL) { - // Start extracting the format specifier - const char *start = format; - format++; // Move past '%' + /* Generic function call - evaluate all arguments to StdrotValue */ + StdrotValue arg_values[64]; + int arg_count = 0; - // Extract until a valid specifier character is found - while (strchr("diouxXfFeEgGaAcspnb%", *format) == NULL && *format != '\0') { - format++; - } + ArgumentList *cur = args; + while (cur && arg_count < 64) { + ASTNode *expr = cur->expr; + if (!expr) break; - if (*format == '\0') { - yyerror("Invalid format specifier"); - exit(EXIT_FAILURE); - } + ast_expr_to_stdrot_value(expr, &arg_values[arg_count]); - // Copy the format specifier into a temporary buffer - char specifier[32]; - int length = format - start + 1; - strncpy(specifier, start, length); - specifier[length] = '\0'; - - // Process the argument based on the format specifier - ASTNode *expr = cur->expr; - if (!expr) { - yyerror("Invalid argument in yappin call"); - exit(EXIT_FAILURE); - } + arg_count++; + cur = cur->next; + } - if (*format == 'b') { - // Handle boolean values - bool val = evaluate_expression_bool(expr); - buffer_offset += snprintf(buffer + buffer_offset, sizeof(buffer) - buffer_offset, "%s", val ? "W" : "L"); - } else if (strchr("diouxX", *format)) { - if (is_expression(expr, VAR_SHORT)) { - short val = evaluate_expression_short(expr); - buffer_offset += snprintf(buffer + buffer_offset, sizeof(buffer) - buffer_offset, specifier, val); - } else { - int val = evaluate_expression_int(expr); - buffer_offset += snprintf(buffer + buffer_offset, sizeof(buffer) - buffer_offset, specifier, val); - } - } else if (strchr("fFeEgGa", *format)) { - // Handle floating-point numbers - if (is_expression(expr, VAR_FLOAT)) { - float val = evaluate_expression_float(expr); - buffer_offset += snprintf(buffer + buffer_offset, sizeof(buffer) - buffer_offset, specifier, val); - } else if (is_expression(expr, VAR_DOUBLE)) { - double val = evaluate_expression_double(expr); - buffer_offset += snprintf(buffer + buffer_offset, sizeof(buffer) - buffer_offset, specifier, val); - } else { - yyerror("Invalid argument type for floating-point format specifier"); - exit(EXIT_FAILURE); + StdrotValue result = entry->fn(arg_values, arg_count); + + /* Generic write-back: if first arg is an identifier and function returned a value, + * write the returned value back to that variable. */ + if (result.type != STDROT_NONE && args && args->expr && args->expr->type == NODE_IDENTIFIER) { + const char *name = args->expr->data.name; + Variable *var = get_variable(name); + if (var) { + switch (result.type) { + case STDROT_INT: + set_int_variable(name, result.val.i, var->modifiers); + break; + case STDROT_FLOAT: + set_float_variable(name, result.val.f, var->modifiers); + break; + case STDROT_DOUBLE: + set_double_variable(name, result.val.d, var->modifiers); + break; + case STDROT_SHORT: + set_short_variable(name, result.val.s, var->modifiers); + break; + case STDROT_CHAR: + set_int_variable(name, result.val.c, var->modifiers); + break; + case STDROT_STRING: + if (var->is_array && var->array_length > 0 && result.val.str) { + char *dst = (char *)var->value.array_data; + strncpy(dst, result.val.str, var->array_length - 1); + dst[var->array_length - 1] = '\0'; } - } else if (*format == 'c') { - // Handle character values - int val = evaluate_expression_int(expr); - buffer_offset += snprintf(buffer + buffer_offset, sizeof(buffer) - buffer_offset, specifier, val); - } else if (*format == 's') { - const Variable *var = get_variable(expr->data.name); - if (var != NULL) { - if (!var->is_array && var->var_type != VAR_STRING) { - yyerror("Invalid argument type for %s"); - exit(EXIT_FAILURE); - } - buffer_offset += snprintf(buffer + buffer_offset, sizeof(buffer) - buffer_offset, specifier, var->value.array_data); - } else if (expr->type != NODE_STRING_LITERAL) { - yyerror("Invalid argument type for %s"); - exit(EXIT_FAILURE); - } else { - buffer_offset += snprintf(buffer + buffer_offset, sizeof(buffer) - buffer_offset, specifier, expr->data.name); - } - } else { - yyerror("Unsupported format specifier"); - exit(EXIT_FAILURE); + break; + case STDROT_BOOL: + set_bool_variable(name, result.val.b, var->modifiers); + break; + default: + break; } - - cur = cur->next; // Move to the next argument - format++; // Move past the format specifier - } else { - // Copy non-format characters to the buffer - buffer[buffer_offset++] = *format++; - } - - // Check for buffer overflow - if (buffer_offset >= (int)sizeof(buffer)) { - yyerror("Buffer overflow in yappin call"); - exit(EXIT_FAILURE); } } - - buffer[buffer_offset] = '\0'; // Null-terminate the string - - // Print the final formatted output - yappin("%s", buffer); } -void execute_baka_call(ArgumentList *args) { - if (!args) { - baka("\n"); - return; - } +/* ── Stub functions (thin wrappers that forward to .so) ────────────────────── */ - ASTNode *formatNode = args->expr; - if (formatNode->type != NODE_STRING_LITERAL) { - yyerror("First argument to baka must be a string literal"); - return; - } +void yapping(const char* format, ...) +{ + va_list ap; + va_start(ap, format); + void (*fn)(const char *, va_list) = (void (*)(const char *, va_list))stdrot_lookup_symbol("v_yapping"); + if (fn) fn(format, ap); + va_end(ap); +} - baka(formatNode->data.name); +void yappin(const char* format, ...) +{ + va_list ap; + va_start(ap, format); + void (*fn)(const char *, va_list) = (void (*)(const char *, va_list))stdrot_lookup_symbol("v_yappin"); + if (fn) fn(format, ap); + va_end(ap); } -void execute_ragequit_call(ArgumentList *args) { - if (!args) { - yyerror("No arguments provided for ragequit function call"); - exit(EXIT_FAILURE); - } +void baka(const char* format, ...) +{ + va_list ap; + va_start(ap, format); + void (*fn)(const char *, va_list) = (void (*)(const char *, va_list))stdrot_lookup_symbol("v_baka"); + if (fn) fn(format, ap); + va_end(ap); +} - ASTNode *formatNode = args->expr; - if (formatNode->type != NODE_INT) { - yyerror("First argument to ragequit must be an integer"); - exit(EXIT_FAILURE); - } +void ragequit(int exit_code) +{ + void (*fn)(int) = (void (*)(int))stdrot_lookup_symbol("ragequit"); + if (fn) fn(exit_code); +} - ragequit(formatNode->data.ivalue); +void chill(unsigned int seconds) +{ + void (*fn)(unsigned int) = (void (*)(unsigned int))stdrot_lookup_symbol("chill"); + if (fn) fn(seconds); } -void execute_chill_call(ArgumentList *args) { - if (!args) { - yyerror("No arguments provided for chill function call"); - exit(EXIT_FAILURE); - } +char slorp_char(char chr) +{ + char (*fn)(char) = (char (*)(char))stdrot_lookup_symbol("slorp_char"); + return fn ? fn(chr) : chr; +} - ASTNode *formatNode = args->expr; - if (formatNode->type != NODE_INT && !formatNode->modifiers.is_unsigned) { - yyerror("First argument to chill must be an unsigned integer"); - exit(EXIT_FAILURE); - } +char *slorp_string(char *string, size_t size) +{ + char *(*fn)(char *, size_t) = (char *(*)(char *, size_t))stdrot_lookup_symbol("slorp_string"); + return fn ? fn(string, size) : string; +} - chill(formatNode->data.ivalue); +int slorp_int(int val) +{ + int (*fn)(int) = (int (*)(int))stdrot_lookup_symbol("slorp_int"); + return fn ? fn(val) : val; } -void execute_slorp_call(ArgumentList *args) { - if (!args || args->expr->type != NODE_IDENTIFIER) { - yyerror("slorp requires a variable identifier"); - return; - } +short slorp_short(short val) +{ + short (*fn)(short) = (short (*)(short))stdrot_lookup_symbol("slorp_short"); + return fn ? fn(val) : val; +} - char *name = args->expr->data.name; - Variable *var = get_variable(name); - if (!var) { - yyerror("Undefined variable"); - return; - } +float slorp_float(float var) +{ + float (*fn)(float) = (float (*)(float))stdrot_lookup_symbol("slorp_float"); + return fn ? fn(var) : var; +} - switch (var->var_type) { - case VAR_INT: { - int val = 0; - val = slorp_int(val); - set_int_variable(name, val, var->modifiers); - break; - } - case VAR_FLOAT: { - float val = 0.0f; - val = slorp_float(val); - set_float_variable(name, val, var->modifiers); - break; - } - case VAR_DOUBLE: { - double val = 0.0; - val = slorp_double(val); - set_double_variable(name, val, var->modifiers); - break; - } - case VAR_SHORT: { - short val = 0; - val = slorp_short(val); - set_short_variable(name, val, var->modifiers); - break; - } - case VAR_CHAR: { - if (var->is_array) { - char val[var->array_length]; - slorp_string(val, sizeof(val)); - strncpy(var->value.strvalue, val, var->array_length - 1); - ((char *)var->value.strvalue)[var->array_length - 1] = '\0'; - return; - } - char val = 0; - val = slorp_char(val); - set_int_variable(name, val, var->modifiers); - break; - } - case VAR_STRING: { - slorp_string((char *)var->value.strvalue, strlen(var->value.strvalue)); - break; - } - default: - yyerror("Unsupported type for slorp"); - } +double slorp_double(double var) +{ + double (*fn)(double) = (double (*)(double))stdrot_lookup_symbol("slorp_double"); + return fn ? fn(var) : var; } + +#pragma GCC diagnostic pop diff --git a/stdrot.h b/stdrot.h index 041974a..24f2764 100644 --- a/stdrot.h +++ b/stdrot.h @@ -1,27 +1,44 @@ -/* stdrot.h - Standard Brainrot built-in functions */ +/* stdrot.h – Standard Brainrot library interface (main binary side) + * + * stdrot.c provides: + * • Thin varargs stubs (yapping / yappin / baka) that forward to the .so + * • AST bridge functions (execute_*_call) that evaluate arguments and call + * the raw implementations in libstdrot.so + * • A loader (stdrot_load / stdrot_unload) that uses dlopen/dlsym to wire + * the stubs and read the function registry from the .so + */ #ifndef STDROT_H #define STDROT_H #include "ast.h" +#include "stdrot/stdrot_api.h" #include +#include -/* Built-in function declarations */ -void execute_yapping_call(ArgumentList *args); -void execute_yappin_call(ArgumentList *args); -void execute_baka_call(ArgumentList *args); -void execute_ragequit_call(ArgumentList *args); -void execute_chill_call(ArgumentList *args); -void execute_slorp_call(ArgumentList *args); +/* ── Loader lifecycle ────────────────────────────────────────────────────── * + * Call stdrot_load() once at interpreter startup (interpreter_new). + * Call stdrot_unload() at interpreter shutdown (interpreter_free). + */ +void stdrot_load(void); +void stdrot_unload(void); -/* Check if a function name is a built-in function */ +/* ── Runtime query / dispatch ────────────────────────────────────────────── */ bool is_builtin_function(const char *func_name); - -/* Execute a built-in function call */ void execute_builtin_function(const char *func_name, ArgumentList *args); +void execute_func_call(const char *func_name, ArgumentList *args); -/* Built-in function names */ -extern const char* BUILTIN_FUNCTIONS[]; -extern const int BUILTIN_FUNCTION_COUNT; +/* ── Stub functions (forward declarations for use by ast.c) ──────────────── */ +void yapping(const char* format, ...); +void yappin(const char* format, ...); +void baka(const char* format, ...); +void ragequit(int exit_code); +void chill(unsigned int seconds); +char slorp_char(char chr); +char *slorp_string(char *string, size_t size); +int slorp_int(int val); +short slorp_short(short val); +float slorp_float(float var); +double slorp_double(double var); #endif /* STDROT_H */ diff --git a/stdrot/baka.c b/stdrot/baka.c new file mode 100644 index 0000000..3dc7b2a --- /dev/null +++ b/stdrot/baka.c @@ -0,0 +1,112 @@ +/* stdrot/baka.c – Error output function for libstdrot.so + * + * Exports v_baka (va_list version). + * The varargs wrapper baka() lives in stdrot.c (main binary). + */ + +#include "stdrot_api.h" +#include +#include +#include + +/* baka: print to stderr (no automatic newline, caller provides it) */ +void v_baka(const char *fmt, va_list ap) +{ + vfprintf(stderr, fmt, ap); + fflush(stderr); +} + +static void process_baka_format(const char *format, const StdrotValue *args, int arg_count) +{ + char buffer[1024]; + int buffer_offset = 0; + int arg_idx = 0; + + while (*format != '\0' && buffer_offset < (int)sizeof(buffer) - 1) { + if (*format == '%' && arg_idx < arg_count) { + const char *start = format; + format++; + + if (*format == '%') { + buffer[buffer_offset++] = '%'; + format++; + continue; + } + + while (strchr("-+ #0123456789.*", *format) != NULL) { + format++; + } + + if (*format == 'h' || *format == 'l') { + char first = *format; + format++; + if (*format == first) { + format++; + } + } else if (strchr("jztL", *format) != NULL) { + format++; + } + + char spec = *format; + if (spec == '\0') break; + + char specifier[32]; + int length = format - start + 1; + if (length >= (int)sizeof(specifier)) length = sizeof(specifier) - 1; + strncpy(specifier, start, length); + specifier[length] = '\0'; + + const StdrotValue *arg = &args[arg_idx]; + + if (spec == 'b') { + bool b = false; + if (arg->type == STDROT_BOOL) b = arg->val.b; + else if (arg->type == STDROT_INT) b = (arg->val.i != 0); + buffer_offset += snprintf(buffer + buffer_offset, sizeof(buffer) - buffer_offset, "%s", b ? "W" : "L"); + } else if (strchr("diouxX", spec)) { + if (arg->type == STDROT_INT) { + buffer_offset += snprintf(buffer + buffer_offset, sizeof(buffer) - buffer_offset, specifier, arg->val.i); + } else if (arg->type == STDROT_SHORT) { + buffer_offset += snprintf(buffer + buffer_offset, sizeof(buffer) - buffer_offset, specifier, arg->val.s); + } else if (arg->type == STDROT_BOOL) { + buffer_offset += snprintf(buffer + buffer_offset, sizeof(buffer) - buffer_offset, specifier, (int)arg->val.b); + } + } else if (strchr("fFeEgGaA", spec)) { + if (arg->type == STDROT_FLOAT) { + buffer_offset += snprintf(buffer + buffer_offset, sizeof(buffer) - buffer_offset, specifier, arg->val.f); + } else if (arg->type == STDROT_DOUBLE) { + buffer_offset += snprintf(buffer + buffer_offset, sizeof(buffer) - buffer_offset, specifier, arg->val.d); + } + } else if (spec == 'c') { + if (arg->type == STDROT_CHAR) { + buffer_offset += snprintf(buffer + buffer_offset, sizeof(buffer) - buffer_offset, "%c", arg->val.c); + } else if (arg->type == STDROT_INT) { + buffer_offset += snprintf(buffer + buffer_offset, sizeof(buffer) - buffer_offset, "%c", arg->val.i); + } + } else if (spec == 's') { + if (arg->type == STDROT_STRING && arg->val.str) { + buffer_offset += snprintf(buffer + buffer_offset, sizeof(buffer) - buffer_offset, "%s", arg->val.str); + } + } + + arg_idx++; + format++; + } else { + buffer[buffer_offset++] = *format++; + } + } + + buffer[buffer_offset] = '\0'; + fprintf(stderr, "%s", buffer); + fflush(stderr); +} + +static StdrotValue stdrot_baka(StdrotValue *args, int arg_count) +{ + if (arg_count > 0 && args[0].type == STDROT_STRING && args[0].val.str) { + process_baka_format(args[0].val.str, &args[1], arg_count - 1); + } + return (StdrotValue){STDROT_NONE, {0}}; +} + +STDROT_EXPORT("baka", stdrot_baka); diff --git a/stdrot/ragequit.c b/stdrot/ragequit.c new file mode 100644 index 0000000..2b2720b --- /dev/null +++ b/stdrot/ragequit.c @@ -0,0 +1,43 @@ +/* stdrot/ragequit.c – Process control functions for libstdrot.so */ + +#include "stdrot_api.h" +#include +#include + +/* ragequit: exit with a code. + * cleanup() is registered via atexit() in main() so it runs automatically. */ +void ragequit(int exit_code) +{ + exit(exit_code); +} + +/* chill: suspend execution for the given number of seconds */ +void chill(unsigned int seconds) +{ + sleep(seconds); +} + +static StdrotValue stdrot_ragequit(StdrotValue *args, int argc) +{ + int code = 0; + if (argc > 0) { + if (args[0].type == STDROT_INT) code = args[0].val.i; + else if (args[0].type == STDROT_SHORT) code = args[0].val.s; + } + ragequit(code); + return (StdrotValue){STDROT_NONE, {0}}; +} + +static StdrotValue stdrot_chill(StdrotValue *args, int argc) +{ + unsigned int seconds = 0; + if (argc > 0) { + if (args[0].type == STDROT_INT) seconds = (unsigned int)args[0].val.i; + else if (args[0].type == STDROT_SHORT) seconds = (unsigned int)args[0].val.s; + } + chill(seconds); + return (StdrotValue){STDROT_NONE, {0}}; +} + +STDROT_EXPORT("ragequit", stdrot_ragequit); +STDROT_EXPORT("chill", stdrot_chill); diff --git a/stdrot/registry.c b/stdrot/registry.c new file mode 100644 index 0000000..c57efbc --- /dev/null +++ b/stdrot/registry.c @@ -0,0 +1,21 @@ +/* stdrot/registry.c – Self-registration collector for libstdrot.so + * + * Uses linker section magic to auto-collect all STDROT_EXPORT() entries + * from individual source files. Each .c file self-registers via the macro + * at the end of the file. + */ + +#include "stdrot_api.h" + +/* Linker provides these symbols marking the start/end of the section */ +extern StdrotEntry __start_stdrot_exports; +extern StdrotEntry __stop_stdrot_exports; + +/* Entry point called by stdrot.c after dlopen() */ +StdrotAPI stdrot_get_api(void) +{ + StdrotAPI api; + api.functions = &__start_stdrot_exports; + api.count = (int)(&__stop_stdrot_exports - &__start_stdrot_exports); + return api; +} diff --git a/stdrot/slorp.c b/stdrot/slorp.c new file mode 100644 index 0000000..5545855 --- /dev/null +++ b/stdrot/slorp.c @@ -0,0 +1,151 @@ +/* stdrot/slorp.c – Input (read) functions for libstdrot.so + * + * All slorp_* functions call into lib/input.c which is compiled into this + * .so, so the main binary has zero direct dependency on lib/input.c. + */ + +#include "stdrot_api.h" +#include "lib/input.h" +#include +#include +#include + +char slorp_char(char chr) +{ + input_status status = input_char(&chr); + if (status == INPUT_SUCCESS) + return chr; + if (status == INPUT_INVALID_LENGTH) { + fprintf(stderr, "Error: Invalid input length.\n"); + exit(EXIT_FAILURE); + } + fprintf(stderr, "Error reading char: %d\n", status); + exit(EXIT_FAILURE); +} + +char *slorp_string(char *string, size_t size) +{ + size_t chars_read; + input_status status = input_string(string, size, &chars_read); + if (status == INPUT_SUCCESS) + return string; + if (status == INPUT_BUFFER_OVERFLOW) { + fprintf(stderr, "Error: Input exceeded buffer size.\n"); + exit(EXIT_FAILURE); + } + fprintf(stderr, "Error reading string: %d\n", status); + exit(EXIT_FAILURE); +} + +int slorp_int(int val) +{ + input_status status = input_int(&val); + if (status == INPUT_SUCCESS) + return val; + if (status == INPUT_INTEGER_OVERFLOW) { + fprintf(stderr, "Error: Integer value out of range.\n"); + exit(EXIT_FAILURE); + } + if (status == INPUT_CONVERSION_ERROR) { + fprintf(stderr, "Error: Invalid integer format.\n"); + exit(EXIT_FAILURE); + } + fprintf(stderr, "Error reading integer: %d\n", status); + exit(EXIT_FAILURE); +} + +short slorp_short(short val) +{ + input_status status = input_short(&val); + if (status == INPUT_SUCCESS) + return val; + if (status == INPUT_SHORT_OVERFLOW) { + fprintf(stderr, "Error: short value out of range.\n"); + exit(EXIT_FAILURE); + } + if (status == INPUT_CONVERSION_ERROR) { + fprintf(stderr, "Error: Invalid short integer format.\n"); + exit(EXIT_FAILURE); + } + fprintf(stderr, "Error reading short: %d\n", status); + exit(EXIT_FAILURE); +} + +float slorp_float(float var) +{ + input_status status = input_float(&var); + if (status == INPUT_SUCCESS) + return var; + if (status == INPUT_FLOAT_OVERFLOW) { + fprintf(stderr, "Error: Float value out of range.\n"); + exit(EXIT_FAILURE); + } + if (status == INPUT_CONVERSION_ERROR) { + fprintf(stderr, "Error: Invalid float format.\n"); + exit(EXIT_FAILURE); + } + fprintf(stderr, "Error reading float: %d\n", status); + exit(EXIT_FAILURE); +} + +double slorp_double(double var) +{ + input_status status = input_double(&var); + if (status == INPUT_SUCCESS) + return var; + if (status == INPUT_DOUBLE_OVERFLOW) { + fprintf(stderr, "Error: Double value out of range.\n"); + exit(EXIT_FAILURE); + } + if (status == INPUT_CONVERSION_ERROR) { + fprintf(stderr, "Error: Invalid double format.\n"); + exit(EXIT_FAILURE); + } + fprintf(stderr, "Error reading double: %d\n", status); + exit(EXIT_FAILURE); +} + +static StdrotValue stdrot_slorp(StdrotValue *args, int argc) +{ + if (argc <= 0) { + return (StdrotValue){STDROT_NONE, {0}}; + } + + StdrotValue out = {STDROT_NONE, {0}}; + switch (args[0].type) { + case STDROT_INT: + out.type = STDROT_INT; + out.val.i = slorp_int(args[0].val.i); + break; + case STDROT_FLOAT: + out.type = STDROT_FLOAT; + out.val.f = slorp_float(args[0].val.f); + break; + case STDROT_DOUBLE: + out.type = STDROT_DOUBLE; + out.val.d = slorp_double(args[0].val.d); + break; + case STDROT_SHORT: + out.type = STDROT_SHORT; + out.val.s = slorp_short(args[0].val.s); + break; + case STDROT_CHAR: + out.type = STDROT_CHAR; + out.val.c = slorp_char(args[0].val.c); + break; + case STDROT_STRING: + if (args[0].val.str) { + size_t size = strlen(args[0].val.str); + if (size == 0) size = 1024; + slorp_string(args[0].val.str, size); + out.type = STDROT_STRING; + out.val.str = args[0].val.str; + } + break; + default: + break; + } + return out; +} + +STDROT_EXPORT("slorp", stdrot_slorp); diff --git a/stdrot/stdrot_api.h b/stdrot/stdrot_api.h new file mode 100644 index 0000000..b46ed2e --- /dev/null +++ b/stdrot/stdrot_api.h @@ -0,0 +1,107 @@ +/* stdrot/stdrot_api.h – Public contract between libstdrot.so and the main binary. + * + * This is the ONLY header shared between the two compilation units. + * It contains plain C types with zero dependency on the interpreter or AST. + * + * ── HOW TO ADD A NEW STDLIB FUNCTION ──────────────────────────────────────── + * + * 1. Create stdrot/myfunc.c and implement: + * + * StdrotValue stdrot_myfunc(StdrotValue *args, int argc) { ... } + * + * 2. At the bottom of myfunc.c, add: + * + * STDROT_EXPORT("myfunc", stdrot_myfunc); + * + * 3. Recompile only the shared library: + * + * make libstdrot.so + * + * The interpreter discovers it automatically on the next run. + * No changes to stdrot.c, stdrot.h, or any other main-binary file needed. + * + * Builtins and extensions are all exposed through the same generic + * StdrotFn signature, so the host does not hardcode function names. + */ + +#ifndef STDROT_API_H +#define STDROT_API_H + +#include +#include + +/* ── Pre-evaluated argument / return value ──────────────────────────────── */ + +typedef enum { + STDROT_INT, + STDROT_FLOAT, + STDROT_DOUBLE, + STDROT_SHORT, + STDROT_BOOL, + STDROT_CHAR, + STDROT_STRING, + STDROT_NONE /* void return */ +} StdrotType; + +typedef struct { + StdrotType type; + union { + int i; + float f; + double d; + short s; + bool b; + char c; + char *str; + } val; +} StdrotValue; + +/* ── Generic extensible function signature ──────────────────────────────── * + * The main binary evaluates every AST argument into a StdrotValue before + * calling this, so the .so never needs to touch ASTNode or interpreter types. + */ +typedef StdrotValue (*StdrotFn)(StdrotValue *args, int argc); + +/* ── Function registry entry ─────────────────────────────────────────────── * + * libstdrot.so MUST export two symbols: + * + * extern StdrotEntry stdrot_exports[]; + * extern int stdrot_export_count; + * + * fn != NULL → generic function, called with pre-evaluated StdrotValue args + */ +typedef struct { + const char *name; + StdrotFn fn; +} StdrotEntry; + +/* ── Self-registration via linker section ────────────────────────────────── * + * STDROT_EXPORT(name, fn) places the function descriptor into a special + * linker section. The library startup code collects all entries automatically. + * + * Use fn == NULL for core functions (yapping, baka, slorp, etc.) that need + * AST bridge handling in stdrot.c. + */ + +#if defined(__GNUC__) || defined(__clang__) + #define STDROT_CONCAT_IMPL(x, y) x##y + #define STDROT_CONCAT(x, y) STDROT_CONCAT_IMPL(x, y) + #define STDROT_EXPORT(name_str, func_ptr) \ + __attribute__((used, section("stdrot_exports"))) \ + static const StdrotEntry STDROT_CONCAT(__stdrot_export_, __LINE__) = { name_str, func_ptr } +#else + #error "Linker sections not supported on this compiler. Add registry.c fallback." +#endif + +/* ── API discovery entrypoint ────────────────────────────────────────────── * + * libstdrot.so MUST export this function. + * Returns pointer to the function table and count. + */ +typedef struct { + StdrotEntry *functions; + int count; +} StdrotAPI; + +StdrotAPI stdrot_get_api(void); + +#endif /* STDROT_API_H */ diff --git a/stdrot/yapping.c b/stdrot/yapping.c new file mode 100644 index 0000000..2295d4c --- /dev/null +++ b/stdrot/yapping.c @@ -0,0 +1,142 @@ +/* stdrot/yapping.c – Print functions for libstdrot.so + * + * Exports v_yapping and v_yappin (va_list versions). + * The varargs wrappers yapping() / yappin() live in stdrot.c (main binary) + * as thin stubs that forward to these, avoiding duplicate symbol issues. + */ + +#include "stdrot_api.h" +#include +#include +#include + +/* yapping: print with trailing newline → stdout */ +void v_yapping(const char *fmt, va_list ap) +{ + vprintf(fmt, ap); + putchar('\n'); + fflush(stdout); +} + +/* yappin: print without trailing newline → stdout */ +void v_yappin(const char *fmt, va_list ap) +{ + vprintf(fmt, ap); + fflush(stdout); +} + +/* Format string processing for yapping with StdrotValue arguments */ +static void process_yapping_format(const char *format, const StdrotValue *args, int arg_count, int add_newline) +{ + char buffer[1024]; + int buffer_offset = 0; + int arg_idx = 0; + + while (*format != '\0' && buffer_offset < (int)sizeof(buffer) - 1) { + if (*format == '%' && arg_idx < arg_count) { + const char *start = format; + format++; + + if (*format == '%') { + buffer[buffer_offset++] = '%'; + format++; + continue; + } + + /* Skip flags, width, precision */ + while (strchr("-+ #0123456789.*", *format) != NULL) { + format++; + } + + /* Skip length modifiers (h, hh, l, ll, j, z, t, L) */ + if (*format == 'h' || *format == 'l') { + char first = *format; + format++; + if (*format == first) { + format++; + } + } else if (strchr("jztL", *format) != NULL) { + format++; + } + + /* Get the conversion specifier */ + char spec = *format; + if (spec == '\0') break; + + char specifier[32]; + int length = format - start + 1; + if (length >= (int)sizeof(specifier)) length = sizeof(specifier) - 1; + strncpy(specifier, start, length); + specifier[length] = '\0'; + + const StdrotValue *arg = &args[arg_idx]; + + if (spec == 'b') { + bool b = false; + if (arg->type == STDROT_BOOL) b = arg->val.b; + else if (arg->type == STDROT_INT) b = (arg->val.i != 0); + else if (arg->type == STDROT_SHORT) b = (arg->val.s != 0); + buffer_offset += snprintf(buffer + buffer_offset, sizeof(buffer) - buffer_offset, "%s", b ? "W" : "L"); + } else if (strchr("diouxX", spec)) { + if (arg->type == STDROT_INT) { + buffer_offset += snprintf(buffer + buffer_offset, sizeof(buffer) - buffer_offset, specifier, arg->val.i); + } else if (arg->type == STDROT_SHORT) { + buffer_offset += snprintf(buffer + buffer_offset, sizeof(buffer) - buffer_offset, specifier, arg->val.s); + } else if (arg->type == STDROT_BOOL) { + buffer_offset += snprintf(buffer + buffer_offset, sizeof(buffer) - buffer_offset, specifier, (int)arg->val.b); + } + } else if (strchr("fFeEgGaA", spec)) { + if (arg->type == STDROT_FLOAT) { + buffer_offset += snprintf(buffer + buffer_offset, sizeof(buffer) - buffer_offset, specifier, arg->val.f); + } else if (arg->type == STDROT_DOUBLE) { + buffer_offset += snprintf(buffer + buffer_offset, sizeof(buffer) - buffer_offset, specifier, arg->val.d); + } + } else if (spec == 'c') { + if (arg->type == STDROT_CHAR) { + buffer_offset += snprintf(buffer + buffer_offset, sizeof(buffer) - buffer_offset, "%c", arg->val.c); + } else if (arg->type == STDROT_INT) { + buffer_offset += snprintf(buffer + buffer_offset, sizeof(buffer) - buffer_offset, "%c", arg->val.i); + } + } else if (spec == 's') { + if (arg->type == STDROT_STRING) { + buffer_offset += snprintf(buffer + buffer_offset, sizeof(buffer) - buffer_offset, "%s", arg->val.str); + } + } + + arg_idx++; + format++; + } else { + buffer[buffer_offset++] = *format++; + } + } + + buffer[buffer_offset] = '\0'; + if (add_newline) { + printf("%s\n", buffer); + } else { + printf("%s", buffer); + } + fflush(stdout); +} + +/* StdrotValue wrapper for yapping (with format string processing) */ +static StdrotValue stdrot_yapping(StdrotValue *args, int arg_count) +{ + if (arg_count > 0 && args[0].type == STDROT_STRING) { + process_yapping_format(args[0].val.str, &args[1], arg_count - 1, 1); + } + return (StdrotValue){STDROT_NONE, {0}}; +} + +/* StdrotValue wrapper for yappin (with format string processing) */ +static StdrotValue stdrot_yappin(StdrotValue *args, int arg_count) +{ + if (arg_count > 0 && args[0].type == STDROT_STRING) { + process_yapping_format(args[0].val.str, &args[1], arg_count - 1, 0); + } + return (StdrotValue){STDROT_NONE, {0}}; +} + +STDROT_EXPORT("yapping", stdrot_yapping); +STDROT_EXPORT("yappin", stdrot_yappin); + From b04ec5e7daf4fbbd8d6939bd40e0867bc4127304 Mon Sep 17 00:00:00 2001 From: SIGMazer Date: Sun, 8 Mar 2026 04:54:03 +0200 Subject: [PATCH 2/8] chore: ignore generated libstdrot shared object --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 86e1830..25abcb7 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ lang.tab.c lang.tab.h lex.yy.c lang.gv +libstdrot.so From bbefa1207c012c6d0256b72fc845c4b7c68f282d Mon Sep 17 00:00:00 2001 From: SIGMazer Date: Sun, 8 Mar 2026 05:17:26 +0200 Subject: [PATCH 3/8] feat(stdrot): add bet assertion function with line context Signed-off-by: SIGMazer --- Makefile | 4 +-- ast.c | 10 +++++-- stdrot.c | 23 ++++++++++++++-- stdrot/bet.c | 53 ++++++++++++++++++++++++++++++++++++ stdrot/stdrot_api.h | 12 ++++++++ test_cases/bet.brainrot | 5 ++++ test_cases/bet_fail.brainrot | 5 ++++ test_cases/bet_int.brainrot | 6 ++++ tests/expected_results.json | 9 ++++-- 9 files changed, 117 insertions(+), 10 deletions(-) create mode 100644 stdrot/bet.c create mode 100644 test_cases/bet.brainrot create mode 100644 test_cases/bet_fail.brainrot create mode 100644 test_cases/bet_int.brainrot diff --git a/Makefile b/Makefile index 54de731..04c2699 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ PYTHON := python3 # Compiler and linker flags CFLAGS := -Wall -Wextra -Wpedantic -Werror -O2 -Wuninitialized -LDFLAGS := -lfl -lm -ldl +LDFLAGS := -lfl -lm -ldl -rdynamic SO_CFLAGS := -fPIC -shared # Source files and directories @@ -18,7 +18,7 @@ ALL_SRCS := $(SRCS) $(GENERATED_SRCS) # stdrot shared library STDROT_DIR := stdrot -STDROT_SRCS := $(STDROT_DIR)/yapping.c $(STDROT_DIR)/baka.c $(STDROT_DIR)/ragequit.c $(STDROT_DIR)/slorp.c $(STDROT_DIR)/registry.c $(SRC_DIR)/input.c +STDROT_SRCS := $(wildcard $(STDROT_DIR)/*.c) $(SRC_DIR)/input.c STDROT_LIB := libstdrot.so # Output files diff --git a/ast.c b/ast.c index 051551c..13b1aa3 100644 --- a/ast.c +++ b/ast.c @@ -542,7 +542,7 @@ static ASTNode *create_node(NodeType type, VarType var_type, TypeModifiers modif node->modifiers = modifiers; node->already_checked = false; node->is_valid_symbol = false; - node->line_number = 0; /* Initialize line number to avoid uninitialized value errors */ + node->line_number = yylineno; return node; } @@ -2405,7 +2405,12 @@ void execute_statement(ASTNode *node) case NODE_IDENTIFIER: evaluate_expression(node); break; - case NODE_FUNC_CALL: + case NODE_FUNC_CALL: { + // Set execution context with current line number + extern ExecutionContext g_exec_context; + g_exec_context.line_number = node->line_number; + g_exec_context.function_name = node->data.func_call.function_name; + // Use the stdrot built-in function system if (is_builtin_function(node->data.func_call.function_name)) { @@ -2418,6 +2423,7 @@ void execute_statement(ASTNode *node) node->data.func_call.arguments); } break; + } case NODE_FOR_STATEMENT: execute_for_statement(node); break; diff --git a/stdrot.c b/stdrot.c index 2acc824..10835ca 100644 --- a/stdrot.c +++ b/stdrot.c @@ -21,6 +21,9 @@ #include #include +/* ── Global execution context ────────────────────────────────────────────── */ +ExecutionContext g_exec_context = {0, NULL, NULL}; + /* ── External interpreter functions ──────────────────────────────────────── */ extern void yyerror(const char *s); extern int evaluate_expression_int(ASTNode *node); @@ -97,10 +100,17 @@ static void *stdrot_lookup_symbol(const char *symbol_name) void stdrot_load(void) { - /* Open libstdrot.so from the same directory as the binary, or LD_LIBRARY_PATH */ - lib_handle = dlopen("./libstdrot.so", RTLD_LAZY | RTLD_LOCAL); + /* First, make main binary symbols available to subsequently loaded libraries + * by loading the main program's symbols with RTLD_GLOBAL + */ + dlopen(NULL, RTLD_LAZY | RTLD_GLOBAL); + + /* Open libstdrot.so from the same directory as the binary, or LD_LIBRARY_PATH + * Use RTLD_GLOBAL so the library can access symbols from the main binary (e.g., g_exec_context) + */ + lib_handle = dlopen("./libstdrot.so", RTLD_LAZY | RTLD_GLOBAL); if (!lib_handle) { - lib_handle = dlopen("libstdrot.so", RTLD_LAZY | RTLD_LOCAL); + lib_handle = dlopen("libstdrot.so", RTLD_LAZY | RTLD_GLOBAL); } if (!lib_handle) { fprintf(stderr, "Failed to load libstdrot.so: %s\n", dlerror()); @@ -275,6 +285,13 @@ void execute_func_call(const char *func_name, ArgumentList *args) return; } + /* Set execution context - get line number from first argument node */ + g_exec_context.function_name = func_name; + g_exec_context.line_number = 0; + if (args && args->expr && args->expr->line_number > 0) { + g_exec_context.line_number = args->expr->line_number; + } + /* Generic function call - evaluate all arguments to StdrotValue */ StdrotValue arg_values[64]; int arg_count = 0; diff --git a/stdrot/bet.c b/stdrot/bet.c new file mode 100644 index 0000000..8ce5d16 --- /dev/null +++ b/stdrot/bet.c @@ -0,0 +1,53 @@ +#include "stdrot_api.h" +#include +#include + +// Raw bet function: assert that condition is true +static void bet(int condition, const char *message) { + if (!condition) { + fprintf(stderr, "Error: bet: assertion failed at line %d", g_exec_context.line_number); + + if (message) { + fprintf(stderr, ": %s", message); + } + fprintf(stderr, "\n"); + exit(1); + } +} + +// Wrapper for dynamic dispatch +StdrotValue stdrot_bet(StdrotValue *args, int argc) { + if (argc < 1) { + fprintf(stderr, "Error: bet: requires at least 1 argument\n"); + exit(1); + } + + // First argument is the condition + int condition = 0; + if (args[0].type == STDROT_INT) { + condition = args[0].val.i != 0; + } else if (args[0].type == STDROT_BOOL) { + condition = args[0].val.b; + } else if (args[0].type == STDROT_DOUBLE) { + condition = args[0].val.d != 0.0; + } else if (args[0].type == STDROT_FLOAT) { + condition = args[0].val.f != 0.0f; + } + + // Optional second argument is the message + const char *message = NULL; + if (argc > 1 && args[1].type == STDROT_STRING) { + message = args[1].val.str; + } + + bet(condition, message); + + // Return empty value (assertion succeeded) + StdrotValue result = {0}; + result.type = STDROT_INT; + result.val.i = 0; + return result; +} + +// Register with auto-export macro +STDROT_EXPORT("bet", stdrot_bet); diff --git a/stdrot/stdrot_api.h b/stdrot/stdrot_api.h index b46ed2e..53113f9 100644 --- a/stdrot/stdrot_api.h +++ b/stdrot/stdrot_api.h @@ -30,6 +30,18 @@ #include #include +/* ── Global execution context ──────────────────────────────────────────── * + * Set by the main binary before calling stdlib functions + * Allows functions to report line numbers and context + */ +typedef struct { + int line_number; + const char *function_name; + const char *condition_text; +} ExecutionContext; + +extern ExecutionContext g_exec_context; + /* ── Pre-evaluated argument / return value ──────────────────────────────── */ typedef enum { diff --git a/test_cases/bet.brainrot b/test_cases/bet.brainrot new file mode 100644 index 0000000..6f1c170 --- /dev/null +++ b/test_cases/bet.brainrot @@ -0,0 +1,5 @@ +skibidi main { + bet(W); + yapping("Assertion passed!"); + bussin 0; +} diff --git a/test_cases/bet_fail.brainrot b/test_cases/bet_fail.brainrot new file mode 100644 index 0000000..2f28890 --- /dev/null +++ b/test_cases/bet_fail.brainrot @@ -0,0 +1,5 @@ +skibidi main { + bet(L, "this assertion must fail"); + yapping("Should not reach here"); + bussin 0; +} diff --git a/test_cases/bet_int.brainrot b/test_cases/bet_int.brainrot new file mode 100644 index 0000000..2e949e1 --- /dev/null +++ b/test_cases/bet_int.brainrot @@ -0,0 +1,6 @@ +skibidi main { + rizz x = 5; + bet(x > 0); + yapping("x is positive"); + bussin 0; +} diff --git a/tests/expected_results.json b/tests/expected_results.json index 9cda433..ee41d29 100644 --- a/tests/expected_results.json +++ b/tests/expected_results.json @@ -59,12 +59,15 @@ "grid_bfs": "8", "grid_bfs_1d": "8", "rant":"hello, world!", - "semantic_error_const": "Error: Cannot modify const variable at line 1", + "semantic_error_const": "Error: Cannot modify const variable at line 4", "semantic_error_function_redef": "Error: Function redefinition at line 1", - "semantic_error_scope": "Error: Variable out of scope at line 1", + "semantic_error_scope": "Error: Variable out of scope at line 8", "giga": "8\n4\n", "salty": "1\n2\n1\n2\n", "thicc": "8\n4\n", "giga_array": "1\n2\n3\n24\n", - "thicc_array": "1\n2\n3\n24\n" + "thicc_array": "1\n2\n3\n24\n", + "bet": "Assertion passed!\n", + "bet_int": "x is positive\n", + "bet_fail": "Error: bet: assertion failed at line 2: this assertion must fail" } From 130e2ec422e3cce864cedbdeb1df8a837ae3ec06 Mon Sep 17 00:00:00 2001 From: SIGMazer Date: Sun, 8 Mar 2026 05:21:47 +0200 Subject: [PATCH 4/8] feat(docs): add bet assertion function documentation Signed-off-by: SIGMazer --- docs/brainrot-user-guide.md | 44 +++++++++++++++++++++++ docs/the-brainrot-programming-language.md | 40 +++++++++++++++++++++ 2 files changed, 84 insertions(+) diff --git a/docs/brainrot-user-guide.md b/docs/brainrot-user-guide.md index 9c5d846..b0003be 100644 --- a/docs/brainrot-user-guide.md +++ b/docs/brainrot-user-guide.md @@ -204,6 +204,7 @@ Brainrot includes some built-in functions for convenience: | **ragequit** | - | - | Terminates program execution immediately with the provided exit code. | | **chill** | - | - | Sleeps for an integer number of seconds. | | **slorp** | `stdin` | - | Reads user input. | +| **bet** | `stderr` | No | Tests conditions and terminates with error message if false. | ## 9.1. yapping @@ -347,6 +348,49 @@ skibidi main { } ``` +## 9.7. bet + +**Prototype** + +```c +void bet(int condition, const char* message); +``` + +**Key Points** + +- Tests a condition and terminates the program if it's false +- Similar to C's `assert()` macro +- When the condition fails, prints an error message with the line number and optional custom message +- Useful for catching bugs and verifying assumptions during development + +### Example + +```c +skibidi main { + rizz x = 10; + bet(x > 0, "x must be positive"); + yapping("x is positive"); + bussin 0; +} +``` + +### Failed Assertion Example + +```c +skibidi main { + bet(L, "this assertion must fail"); + yapping("This won't print"); + bussin 0; +} +``` + +Output: +``` +Error: bet: assertion failed at line 2: this assertion must fail +``` + +The program terminates immediately when a `bet` fails. + --- # 10. Example Program diff --git a/docs/the-brainrot-programming-language.md b/docs/the-brainrot-programming-language.md index 4203c36..27732d7 100644 --- a/docs/the-brainrot-programming-language.md +++ b/docs/the-brainrot-programming-language.md @@ -409,6 +409,46 @@ skibidi main { } ``` +### 8.7. `bet` + +```c +void bet(int condition, const char* message); +``` + +- Tests a condition and terminates the program if it's false. +- Similar to C's `assert()` macro, but designed for runtime checks in Brainrot. +- When the condition fails, prints an error message to `stderr` with the line number and optional custom message. +- Useful for verifying assumptions and catching bugs during development. + +**Example**: + +```c +skibidi main { + rizz x = 10; + bet(x > 0, "x must be positive"); + yapping("x is positive"); + bussin 0; +} +``` + +**What happens when the assertion fails:** + +```c +skibidi main { + bet(L, "this assertion must fail"); + yapping("This won't print"); + bussin 0; +} +``` + +Output: + +``` +Error: bet: assertion failed at line 2: this assertion must fail +``` + +The program terminates immediately when a `bet` fails, preventing further execution. + --- ## 9. Limitations From bae4e1c4ea4a1ffaa145655cc24c5a187ed3c72f Mon Sep 17 00:00:00 2001 From: SIGMazer Date: Sun, 8 Mar 2026 05:36:49 +0200 Subject: [PATCH 5/8] fix(mem): memeory leaks Signed-off-by: SIGMazer --- lang.y | 20 +++++++++++++------- stdrot.c | 7 +++++-- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/lang.y b/lang.y index edd4ad2..34fe6e6 100644 --- a/lang.y +++ b/lang.y @@ -599,6 +599,10 @@ array_access: %% int main(int argc, char *argv[]) { + /* Register cleanup function to be called on exit */ + atexit(cleanup); + atexit(stdrot_unload); + if (argc != 2) { fprintf(stderr, "Usage: %s \n", argv[0]); return 1; @@ -619,13 +623,11 @@ int main(int argc, char *argv[]) { /* Phase 1: Parse the source code to build AST */ if (yyparse() != 0) { fprintf(stderr, "Parsing failed\n"); - cleanup(); return 1; } /* Phase 2: Semantic Analysis and Type Checking */ if (!semantic_analyze(root)) { - cleanup(); return 1; } @@ -633,7 +635,6 @@ int main(int argc, char *argv[]) { global_interpreter = interpreter_new(); if (!global_interpreter) { fprintf(stderr, "Failed to create interpreter\n"); - cleanup(); return 1; } @@ -641,9 +642,7 @@ int main(int argc, char *argv[]) { interpreter_free(global_interpreter); global_interpreter = NULL; - /* Cleanup */ - cleanup(); - stdrot_unload(); + /* Note: cleanup and stdrot_unload are called via atexit */ return 0; } @@ -653,6 +652,10 @@ void yyerror(const char *s) { } void cleanup() { + static bool cleaned = false; + if (cleaned) return; // Prevent double cleanup + cleaned = true; + // Free the global interpreter if it exists if (global_interpreter) { interpreter_free(global_interpreter); @@ -669,7 +672,10 @@ void cleanup() { free_ast(); // Free the scope - free_scope(current_scope); + if (current_scope) { + free_scope(current_scope); + current_scope = NULL; + } free_function_table(); diff --git a/stdrot.c b/stdrot.c index 10835ca..f118413 100644 --- a/stdrot.c +++ b/stdrot.c @@ -334,8 +334,11 @@ void execute_func_call(const char *func_name, ArgumentList *args) case STDROT_STRING: if (var->is_array && var->array_length > 0 && result.val.str) { char *dst = (char *)var->value.array_data; - strncpy(dst, result.val.str, var->array_length - 1); - dst[var->array_length - 1] = '\0'; + /* Avoid overlapping copy */ + if (dst != result.val.str) { + strncpy(dst, result.val.str, var->array_length - 1); + dst[var->array_length - 1] = '\0'; + } } break; case STDROT_BOOL: From 3dd51af5870f953607edf621f79c88d42b7dc4b7 Mon Sep 17 00:00:00 2001 From: SIGMazer Date: Sun, 8 Mar 2026 05:44:22 +0200 Subject: [PATCH 6/8] fix(makefile): ensure stdrot lib built for runtime targets Signed-off-by: SIGMazer --- Makefile | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 04c2699..1cdcbc9 100644 --- a/Makefile +++ b/Makefile @@ -30,6 +30,14 @@ FLEX_OUTPUT := lex.yy.c .PHONY: all all: $(STDROT_LIB) $(TARGET) +# Ensure shared library exists for runtime targets +.PHONY: ensure-stdrot +ensure-stdrot: + @if [ ! -f $(STDROT_LIB) ]; then \ + echo "$(STDROT_LIB) not found. Building it now..."; \ + $(MAKE) $(STDROT_LIB); \ + fi + # Build only the standard library .PHONY: lib lib: $(STDROT_LIB) @@ -62,7 +70,7 @@ $(FLEX_OUTPUT): lang.l # Run tests .PHONY: test -test: +test: ensure-stdrot $(TARGET) $(PYTHON) -m pytest -v @echo "Tests ran bussin', no cap." @@ -75,13 +83,13 @@ clean: # Run Valgrind on all .brainrot tests .PHONY: valgrind -valgrind: +valgrind: ensure-stdrot $(TARGET) @./run_valgrind_tests.sh @echo "Valgrind check done. If anything was sus, it'll show up with a non-zero exit code. No cap." # Install target .PHONY: install -install: +install: ensure-stdrot $(TARGET) install -d /usr/local/bin install -m 755 $(TARGET) /usr/local/bin/ @echo "$(TARGET) installed successfully. You're goated with the sauce!" From b0bef69eca021ad2426d55c3f70c5bc1b6b421f5 Mon Sep 17 00:00:00 2001 From: SIGMazer Date: Sun, 8 Mar 2026 05:46:47 +0200 Subject: [PATCH 7/8] feat(ci): upload libstdrot.so as artifact Signed-off-by: SIGMazer --- .github/workflows/ci.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6579d19..c8cc705 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,7 +36,9 @@ jobs: uses: actions/upload-artifact@v4 with: name: brainrot - path: brainrot + path: | + brainrot + libstdrot.so test: runs-on: ubuntu-latest @@ -50,10 +52,15 @@ jobs: uses: actions/download-artifact@v4 with: name: brainrot + path: . - name: Grant execute permissions to Brainrot run: chmod +x brainrot + - name: Prepare shared library for tests + run: | + cp -f libstdrot.so tests/libstdrot.so + - name: Install Python and dependencies run: | sudo apt-get update @@ -66,6 +73,7 @@ jobs: - name: Run Pytest run: | source .venv/bin/activate + export LD_LIBRARY_PATH="$PWD:$LD_LIBRARY_PATH" pytest -v test_brainrot.py working-directory: tests From d361cc9764c22d33cb0ea053bc11e142d3638674 Mon Sep 17 00:00:00 2001 From: SIGMazer Date: Sun, 8 Mar 2026 06:34:57 +0200 Subject: [PATCH 8/8] refactor(semantic_analyzer): remove hardcode fn name Signed-off-by: SIGMazer --- semantic_analyzer.c | 26 +++----------------------- 1 file changed, 3 insertions(+), 23 deletions(-) diff --git a/semantic_analyzer.c b/semantic_analyzer.c index 6dd5582..007e030 100644 --- a/semantic_analyzer.c +++ b/semantic_analyzer.c @@ -410,13 +410,7 @@ void* semantic_visit_identifier(Visitor *self, ASTNode *node) { } else { Variable *var = get_variable(name); if (!var) { - if (!is_builtin_function(name) && - strcmp(name, "ragequit") != 0 && - strcmp(name, "yapping") != 0 && - strcmp(name, "yappin") != 0 && - strcmp(name, "baka") != 0 && - strcmp(name, "chill") != 0 && - strcmp(name, "slorp") != 0) { + if (!is_builtin_function(name)) { char error_msg[256]; snprintf(error_msg, sizeof(error_msg), "Undefined variable '%s'", name); add_semantic_error(analyzer, SEMANTIC_ERROR_UNDEFINED_VARIABLE, @@ -505,14 +499,7 @@ void semantic_visit_assignment(Visitor *self, ASTNode *node) { if (!symbol) { Variable *var = get_variable(var_name); if (!var) { - if (!is_builtin_function(var_name) && - strcmp(var_name, "ragequit") != 0 && - strcmp(var_name, "yapping") != 0 && - strcmp(var_name, "yappin") != 0 && - strcmp(var_name, "baka") != 0 && - strcmp(var_name, "chill") != 0 && - strcmp(var_name, "slorp") != 0 && - strcmp(var_name, "bussin") != 0) { + if (!is_builtin_function(var_name)) { char error_msg[256]; snprintf(error_msg, sizeof(error_msg), "Assignment to undefined variable '%s'", var_name); add_semantic_error(analyzer, SEMANTIC_ERROR_UNDEFINED_VARIABLE, @@ -921,14 +908,7 @@ void semantic_analyze_node(SemanticAnalyzer *analyzer, ASTNode *node) { if (!find_semantic_variable(analyzer, name, &symbol)) { /* Check if it's a built-in function or keyword */ - if (!is_builtin_function(name) && - strcmp(name, "ragequit") != 0 && - strcmp(name, "yapping") != 0 && - strcmp(name, "yappin") != 0 && - strcmp(name, "baka") != 0 && - strcmp(name, "chill") != 0 && - strcmp(name, "slorp") != 0 && - strcmp(name, "bussin") != 0) { + if (!is_builtin_function(name)){ char error_msg[256]; snprintf(error_msg, sizeof(error_msg), "Undefined variable '%s'", name); add_semantic_error(analyzer, SEMANTIC_ERROR_UNDEFINED_VARIABLE,