From edb4113a7a71d97c86a1fa6f5472549126aab0db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=B6tz=20von=20Berlichingen?= <46436462+araujo88@users.noreply.github.com> Date: Sat, 25 Jan 2025 16:08:31 -0300 Subject: [PATCH 1/2] Revert "feat: user input" --- .github/workflows/ci.yml | 6 +- Makefile | 4 +- README.md | 7 +- ast.c | 85 +------ ast.h | 5 +- docs/brainrot-user-guide.md | 41 +--- docs/the-brainrot-programming-language.md | 38 +-- lang.l | 1 - lang.y | 185 +------------- lib/input.c | 278 ---------------------- lib/input.h | 93 -------- run_valgrind_tests.sh | 2 +- tests/test_brainrot.py | 2 +- 13 files changed, 56 insertions(+), 691 deletions(-) delete mode 100644 lib/input.c delete mode 100644 lib/input.h diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0f7181e..0c0237d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,7 +30,9 @@ jobs: - name: Build Brainrot run: | - make + bison -d -Wcounterexamples lang.y -o lang.tab.c + flex lang.l + gcc -o brainrot lib/hm.c lib/mem.c lang.tab.c lex.yy.c ast.c -lfl -lm - name: Upload build artifacts uses: actions/upload-artifact@v4 @@ -73,7 +75,7 @@ jobs: run: | for f in test_cases/*.brainrot; do echo "Running Valgrind on $f..." - valgrind --leak-check=full --error-exitcode=1 ./brainrot "$f" + valgrind --leak-check=full --error-exitcode=1 ./brainrot < "$f" echo done working-directory: . diff --git a/Makefile b/Makefile index 9694cf3..6100645 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ LDFLAGS := -lfl -lm # Source files and directories SRC_DIR := lib -SRCS := $(SRC_DIR)/hm.c $(SRC_DIR)/mem.c $(SRC_DIR)/input.c ast.c +SRCS := $(SRC_DIR)/hm.c $(SRC_DIR)/mem.c ast.c GENERATED_SRCS := lang.tab.c lex.yy.c ALL_SRCS := $(SRCS) $(GENERATED_SRCS) @@ -56,7 +56,7 @@ clean: valgrind: @for f in test_cases/*.brainrot; do \ echo "Ayo, Valgrind is pulling up on $$f..."; \ - valgrind --leak-check=full --error-exitcode=1 ./$(TARGET) $$f; \ + valgrind --leak-check=full --error-exitcode=1 ./$(TARGET) < $$f; \ if [ $$? -eq 0 ]; then echo "Valgrind passed for $$f, no memory leaks. Big W!"; \ else echo "Valgrind found a memory leak in $$f, taking an L. Better grind harder."; exit 1; fi; \ echo; \ diff --git a/README.md b/README.md index dedb989..85a856e 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,7 @@ make 2. Run your Brainrot program: ```bash -./brainrot hello.brainrot +./brainrot < hello.brainrot ``` Check out the [examples](examples/README.md): @@ -117,9 +117,8 @@ Check out the [examples](examples/README.md): ## 🗪 Community Join our community on: - -- [Discord](https://discord.gg/FjHhvBHSGj) -- [Reddit](https://www.reddit.com/r/Brainrotlang/) + - [Discord](https://discord.gg/FjHhvBHSGj) + - [Reddit](https://www.reddit.com/r/Brainrotlang/) ## 📚 Language Reference diff --git a/ast.c b/ast.c index 2aea597..6207b2c 100644 --- a/ast.c +++ b/ast.c @@ -174,12 +174,6 @@ 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); -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; @@ -270,6 +264,7 @@ ASTNode *create_int_node(int value) return node; } + ASTNode *create_array_declaration_node(char *name, int length, VarType var_type) { ASTNode *node = SAFE_MALLOC(ASTNode); @@ -647,6 +642,7 @@ void *handle_binary_operation(ASTNode *node, int result_type) *(short *)right_value = evaluate_expression_short(node->data.op.right); break; + default: yyerror("Unsupported type promotion"); return NULL; @@ -1077,6 +1073,7 @@ void *handle_unary_expression(ASTNode *node, void *operand_value, int operand_ty } } + float evaluate_expression_float(ASTNode *node) { if (!node) @@ -1271,6 +1268,7 @@ double evaluate_expression_double(ASTNode *node) return result; } return 0.0L; + } default: yyerror("Invalid double expression"); @@ -1615,7 +1613,7 @@ int evaluate_expression_int(ASTNode *node) } case NODE_FUNC_CALL: { - int *res = (int *)handle_function_call(node); + int *res = (int *)handle_function_call(node); if (res != NULL) { int return_val = *res; @@ -1630,12 +1628,12 @@ int evaluate_expression_int(ASTNode *node) } } -void *handle_function_call(ASTNode *node) +void *handle_function_call(ASTNode* node) { execute_function_call( node->data.func_call.function_name, node->data.func_call.arguments); - void *return_value = NULL; + void* return_value = NULL; if (current_return_value.has_value) { switch (current_return_value.type) @@ -1671,6 +1669,7 @@ void *handle_function_call(ASTNode *node) return return_value; } + bool evaluate_expression_bool(ASTNode *node) { if (!node) @@ -1936,7 +1935,7 @@ bool is_short_expression(ASTNode *node) } } -Function *get_function(const char *name) +Function* get_function(const char *name) { Function *func = function_table; while (func != NULL) @@ -2336,10 +2335,6 @@ void execute_statement(ASTNode *node) { execute_chill_call(node->data.func_call.arguments); } - else if (strcmp(node->data.func_call.function_name, "slorp") == 0) - { - execute_slorp_call(node->data.func_call.arguments); - } break; case NODE_FOR_STATEMENT: execute_for_statement(node); @@ -2425,6 +2420,7 @@ void execute_statement(ASTNode *node) } } + void execute_statements(ASTNode *node) { if (!node) @@ -2960,64 +2956,6 @@ void execute_chill_call(ArgumentList *args) chill(formatNode->data.ivalue); } -void execute_slorp_call(ArgumentList *args) -{ - if (!args || args->expr->type != NODE_IDENTIFIER) - { - yyerror("slurp requires a variable identifier"); - return; - } - - char *name = args->expr->data.name; - Variable *var = get_variable(name); - if (!var) - { - yyerror("Undefined variable"); - return; - } - - 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: - { - char val = 0; - val = slorp_char(val); - set_int_variable(name, val, var->modifiers); - break; - } - default: - yyerror("Unsupported type for slorp"); - } -} - void bruh() { LONGJMP(); @@ -3635,7 +3573,7 @@ void handle_return_statement(ASTNode *expr) } } // skibidi main function do not have jump buffer - if (CURRENT_JUMP_BUFFER() != NULL) + if(CURRENT_JUMP_BUFFER() != NULL) LONGJMP(); } @@ -3706,3 +3644,4 @@ void free_function_table(void) } function_table = NULL; } + diff --git a/ast.h b/ast.h index cd7429c..0baff36 100644 --- a/ast.h +++ b/ast.h @@ -301,7 +301,7 @@ void enter_scope(); void free_scope(Scope *scope); void add_variable_to_scope(const char *name, Variable *var); Variable *variable_new(char *name); -Function *get_function(const char *name); +Function* get_function(const char *name); VarType get_function_return_type(const char *name); /* Node creation functions */ @@ -366,7 +366,6 @@ 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); void free_ast(ASTNode *node); void reset_modifiers(void); bool check_and_mark_identifier(ASTNode *node, const char *contextErrorMessage); @@ -374,7 +373,7 @@ void bruh(); size_t count_expression_list(ExpressionList *list); size_t handle_sizeof(ASTNode *node); size_t get_type_size(char *name); -void *handle_function_call(ASTNode *node); +void *handle_function_call(ASTNode* node); /* User-defined functions */ Function *create_function(char *name, VarType return_type, Parameter *params, ASTNode *body); diff --git a/docs/brainrot-user-guide.md b/docs/brainrot-user-guide.md index f87dcab..500fc96 100644 --- a/docs/brainrot-user-guide.md +++ b/docs/brainrot-user-guide.md @@ -70,7 +70,6 @@ count = 42; A **statement** often ends with a **semicolon** `;` unless it is a compound statement (like `{ ... }`). ### Increment and Decrement Operators (`++`, `--`) - In addition to basic arithmetic and logical expressions, you can also use **increment** (`++`) and **decrement** (`--`) operators in Brainrot. - **Pre-Increment (`++i`)**: Increments the value of `i` by 1 before it is used in an expression. @@ -80,6 +79,7 @@ In addition to basic arithmetic and logical expressions, you can also use **incr You can use these operators in expressions to simplify code and make it more concise. + Examples of valid statements: ```c @@ -135,7 +135,6 @@ flex (rizz j = 0; j < 3; j = j + 1) { yapping("j = %d", j); } ``` - - **`init_expr`**: A declaration or expression to initialize loop variables (e.g., `rizz j = 0`). - **`condition`**: Checked each iteration (e.g., `j < 3`). - **`increment`**: Executed at the end of each iteration (e.g., `j = j + 1`). @@ -202,8 +201,6 @@ Brainrot includes some built-in functions for convenience: | **yappin** | `stdout` | No | Precise control over spacing/newlines | | **baka** | `stderr` | No | Log errors or warnings, typically no extra newline | | **ragequit** | - | - | Terminates program execution immediately with the provided exit code. | -| **chill** | - | - | Sleeps for an integer number of seconds. | -| **slorp** | `stdin` | - | Reads user input. | ## 9.1. yapping @@ -323,30 +320,6 @@ skibidi main { } ``` -## 9.6. slorp - -**Prototype** - -```c -void slorp(var_type var_name); -``` - -**Key Points** - -- Reads user input (similar to C's `scanf` but safer) - -### Example - -```c -skibidi main { - rizz num; - yapping("Enter a number:"); - slorp(num); - yapping("You typed: %d", num); - bussin 0; -} -``` - --- # 10. Example Program @@ -403,3 +376,15 @@ skibidi main { - **Syntax** is otherwise quite C-like: `;` to end statements, braces `{ }` to define blocks, parentheses `( )` around conditions. - **Expressions** accept typical operators (`+`,`++`, `-`,`--`, `*`, `/`, `%`, relational, logical) plus the assignment operator `=`, matching standard precedence rules. - **Escapes in strings** (`"\n"`, `"\t"`, etc.) may require an unescape function in your lexer, so check that it’s converting them into real newlines or tabs at runtime. + +--- + +# Final Thoughts + +This language—often called **Brainrot**—provides a playful wrapper around standard C-like structures, with unique keywords and easy built-in printing: + +- **`yapping`** for automatic line-based output. +- **`yappin`** for precise printing without automatic newlines. +- **`baka`** for error messages or logs to `stderr`. + +Use them together with the custom control-flow keywords (`goon`, `flex`, etc.) and custom variable type (`rizz`) to write concise, whimsical, yet powerful scripts. Enjoy experimenting with your new language! diff --git a/docs/the-brainrot-programming-language.md b/docs/the-brainrot-programming-language.md index 4203c36..876eab6 100644 --- a/docs/the-brainrot-programming-language.md +++ b/docs/the-brainrot-programming-language.md @@ -138,7 +138,7 @@ To run your first Brainrot program: ``` 2. **Execute** it: ```bash - ./brainrot hello.brainrot + ./brainrot < hello.brainrot ``` The compiler interprets the code, prints “Hello, World!”, and ends with `bussin 0` (akin to `return 0;`). @@ -269,16 +269,14 @@ Brainrot supports common arithmetic and logical operators: - **`yappin`**: prints text **without** adding a newline. - **`baka`**: prints to `stderr`, typically used for errors/warnings. - **`ragequit`**: terminates program execution immediately with the provided exit code. -- **`chill`**: sleep for a integer number of seconds. -- **`slorp`**: reads user input, similar to `scanf` but safe. --- ### 7.7. User Defined Function -Defining a function in brainrot follows the same pattern as the C programming language: `return_type func_name(param_type param_name) {}` +To define function in brainrot it similar as C `return_type func_name(params) {}` -#### Example: +#### Example: ```c cap is_prime(rizz n) { @@ -296,19 +294,19 @@ cap is_prime(rizz n) { ``` #### BreakDown - - **Function definition**: - - `cap`: return type (bool) - - `is_prime`: function name - - `n`: parameter + - `cap`: return type (bool) + - `is_prime`: function name + - `n`: parameter -#### Usage Example: +#### Usage Example: ```c cap isPrime = is_prime(11) ``` + ## 8. Extended User Documentation ### 8.1. `yapping` @@ -389,26 +387,6 @@ void chill(unsigned int seconds); chill(2); ``` -### 8.6. `slorp` - -```c -void slorp(var_type var_name); -``` - -- Reads user input - -**Example**: - -```c -skibidi main { - rizz num; - yapping("Enter a number:"); - slorp(num); - yapping("You typed: %d", num); - bussin 0; -} -``` - --- ## 9. Limitations diff --git a/lang.l b/lang.l index a49b9b5..59bea9f 100644 --- a/lang.l +++ b/lang.l @@ -78,7 +78,6 @@ extern int yylineno; "schizo" { return VOLATILE; } "goon" { return GOON; } "baka" { return BAKA; } -"slorp" { return SLORP; } "cap" { current_var_type = VAR_BOOL; return CAP; } "==" { return EQ; } diff --git a/lang.y b/lang.y index e624ba9..2b4c75f 100644 --- a/lang.y +++ b/lang.y @@ -2,7 +2,6 @@ %{ #include "ast.h" #include "lib/mem.h" -#include "lib/input.h" #include #include #include @@ -17,19 +16,13 @@ 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); -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; extern VarType current_var_type; + extern int yylineno; -extern FILE *yyin; /* Root of the AST */ ASTNode *root = NULL; @@ -66,7 +59,6 @@ ASTNode *root = NULL; %token BOOLEAN %token FLOAT_LITERAL %token DOUBLE_LITERAL -%token SLORP /* Declare types for non-terminals */ %type type @@ -92,7 +84,7 @@ ASTNode *root = NULL; %type array_init initializer_list %type function_def %type function_def_list -%type param_list params +%type param_list parameter params %start program @@ -344,15 +336,11 @@ increment: ; function_call: - SLORP LPAREN identifier RPAREN - { - $$ = create_function_call_node("slorp", create_argument_list($3, NULL)); - } - | IDENTIFIER LPAREN arg_list RPAREN - { - $$ = create_function_call_node($1, $3); - SAFE_FREE($1); - } + IDENTIFIER LPAREN arg_list RPAREN + { + $$ = create_function_call_node($1, $3); + SAFE_FREE($1); + } ; arg_list @@ -497,29 +485,16 @@ array_access: %% -int main(int argc, char *argv[]) { - if (argc != 2) { - fprintf(stderr, "Usage: %s \n", argv[0]); - return 1; - } - - FILE *source = fopen(argv[1], "r"); - if (!source) { - perror("Cannot open source file"); - return 1; - } - - yyin = source; +int main(void) { current_scope = create_scope(NULL); - if (yyparse() == 0) { execute_statement(root); } - - fclose(source); free_ast(root); free_function_table(); free_scope(current_scope); + + // Clean up flex's internal state yylex_destroy(); return 0; @@ -561,146 +536,6 @@ void baka(const char* format, ...) { va_end(args); } -char slorp_char(char chr) { - size_t chars_read; - 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 chars_read; - input_status status; - - status = input_string(string, sizeof(string), &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"); - } - 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); - } -} - -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"); - } - 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); - } -} - -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 AST free_ast(root); diff --git a/lib/input.c b/lib/input.c deleted file mode 100644 index dd00f3b..0000000 --- a/lib/input.c +++ /dev/null @@ -1,278 +0,0 @@ -/** - * input.c - Implementation of input functions - * - * This file contains the definitions of the functions declared in input.h. - */ - -#include "input.h" - -/** - * Clears the remaining input in stdin to prevent it from affecting subsequent reads. - */ -void clear_stdin_buffer(void) -{ - int c; - while ((c = getchar()) != '\n' && c != EOF) - ; -} - -/** - * Safely reads a single character value - * - * @param value Pointer to store the character value - * @return input_status indicating success or type of error - */ -input_status input_char(char *value) -{ - if (value == NULL) - { - return INPUT_NULL_PTR; - } - - char buffer[2]; // One for the character, one for null terminator - size_t chars_read; - - input_status status = input_string(buffer, sizeof(buffer), &chars_read); - if (status != INPUT_SUCCESS) - { - return status; - } - - // Check if we got exactly one character - if (chars_read != 1) - { - return INPUT_INVALID_LENGTH; - } - - *value = buffer[0]; - return INPUT_SUCCESS; -} - -/** - * Safely reads a string with a maximum length - * - * @param buffer Pointer to the buffer where the string will be stored - * @param buffer_size Size of the buffer in bytes - * @param chars_read Pointer to store the number of characters read - * @return input_status indicating success or type of error - */ -input_status input_string(char *buffer, size_t buffer_size, size_t *chars_read) -{ - if (buffer == NULL || chars_read == NULL) - { - return INPUT_NULL_PTR; - } - - if (buffer_size == 0) - { - return INPUT_BUFFER_OVERFLOW; - } - - // Clear errno before operation - errno = 0; - - // Read input ensuring space for null terminator - if (fgets(buffer, (int)buffer_size, stdin) == NULL) - { - if (ferror(stdin)) - { - clearerr(stdin); - return INPUT_IO_ERROR; - } - // EOF reached - buffer[0] = '\0'; - *chars_read = 0; - return INPUT_SUCCESS; - } - - // Check if the input was truncated (no newline found) - size_t len = strnlen(buffer, buffer_size); - if (len == buffer_size - 1 && buffer[len - 1] != '\n') - { - // Input was truncated; clear the remaining input - clear_stdin_buffer(); - return INPUT_BUFFER_OVERFLOW; - } - - // Remove trailing newline if present - if (len > 0 && buffer[len - 1] == '\n') - { - buffer[len - 1] = '\0'; - len--; - } - - *chars_read = len; - return INPUT_SUCCESS; -} - -/** - * Safely reads an integer value - * - * @param value Pointer to store the integer value - * @return input_status indicating success or type of error - */ -input_status input_int(int *value) -{ - if (value == NULL) - { - return INPUT_NULL_PTR; - } - - char buffer[32]; // Large enough for any integer - size_t chars_read; - - input_status status = input_string(buffer, sizeof(buffer), &chars_read); - if (status != INPUT_SUCCESS) - { - return status; - } - - // Clear errno before conversion - errno = 0; - char *endptr; - long result = strtol(buffer, &endptr, 10); - - // Check for conversion errors - if (endptr == buffer || *endptr != '\0') - { - return INPUT_CONVERSION_ERROR; - } - - // Check for overflow/underflow - if (errno == ERANGE || result > INT_MAX || result < INT_MIN) - { - return INPUT_INTEGER_OVERFLOW; - } - - *value = (int)result; - return INPUT_SUCCESS; -} - -/** - * Safely reads an short value - * - * @param value Pointer to store the short value - * @return input_status indicating success or type of error - */ -input_status input_short(short *value) -{ - if (value == NULL) - { - return INPUT_NULL_PTR; - } - - char buffer[32]; // Large enough for any integer - size_t chars_read; - - input_status status = input_string(buffer, sizeof(buffer), &chars_read); - if (status != INPUT_SUCCESS) - { - return status; - } - - // Clear errno before conversion - errno = 0; - char *endptr; - long result = strtol(buffer, &endptr, 10); - - // Check for conversion errors - if (endptr == buffer || *endptr != '\0') - { - return INPUT_CONVERSION_ERROR; - } - - // Check for overflow/underflow - if (errno == ERANGE || result > SHRT_MAX || result < SHRT_MIN) - { - return INPUT_SHORT_OVERFLOW; - } - - *value = (short)result; - return INPUT_SUCCESS; -} - -/** - * Safely reads a float value - * - * @param value Pointer to store the float value - * @return input_status indicating success or type of error - */ -input_status input_float(float *value) -{ - if (value == NULL) - { - return INPUT_NULL_PTR; - } - - char buffer[32]; // Large enough for any float - size_t chars_read; - - input_status status = input_string(buffer, sizeof(buffer), &chars_read); - if (status != INPUT_SUCCESS) - { - return status; - } - - // Clear errno before conversion - errno = 0; - char *endptr; - double result = strtod(buffer, &endptr); - - // Check for conversion errors - if (endptr == buffer || *endptr != '\0') - { - return INPUT_CONVERSION_ERROR; - } - - // Check for overflow/underflow - if (errno == ERANGE) - { - return INPUT_FLOAT_OVERFLOW; - } - - *value = result; - return INPUT_SUCCESS; -} - -/** - * Safely reads a double value - * - * @param value Pointer to store the double value - * @return input_status indicating success or type of error - */ -input_status input_double(double *value) -{ - if (value == NULL) - { - return INPUT_NULL_PTR; - } - - char buffer[64]; // Large enough for any double - size_t chars_read; - - input_status status = input_string(buffer, sizeof(buffer), &chars_read); - if (status != INPUT_SUCCESS) - { - return status; - } - - // Clear errno before conversion - errno = 0; - char *endptr; - double result = strtod(buffer, &endptr); - - // Check for conversion errors - if (endptr == buffer || *endptr != '\0') - { - return INPUT_CONVERSION_ERROR; - } - - // Check for overflow/underflow - if (errno == ERANGE) - { - return INPUT_DOUBLE_OVERFLOW; - } - - *value = result; - return INPUT_SUCCESS; -} diff --git a/lib/input.h b/lib/input.h deleted file mode 100644 index b57f37a..0000000 --- a/lib/input.h +++ /dev/null @@ -1,93 +0,0 @@ -/** - * input.h - A safer implementation of scanf-like functionality - * - * This implementation focuses on safety against: - * - Buffer overflows - * - NULL pointer dereferences - * - Memory leaks - * - Integer overflows - * - Format string vulnerabilities - */ - -#ifndef INPUT_H -#define INPUT_H - -#include -#include -#include -#include -#include -#include - -// Return codes for input functions -typedef enum -{ - INPUT_SUCCESS = 0, - INPUT_NULL_PTR = -1, - INPUT_INVALID_FORMAT = -2, - INPUT_BUFFER_OVERFLOW = -3, - INPUT_CONVERSION_ERROR = -4, - INPUT_IO_ERROR = -5, - INPUT_SHORT_OVERFLOW = -6, - INPUT_INTEGER_OVERFLOW = -7, - INPUT_FLOAT_OVERFLOW = -8, - INPUT_DOUBLE_OVERFLOW = -9, - INPUT_INVALID_LENGTH = -10, -} input_status; - -/** - * Clears the remaining input in stdin to prevent it from affecting subsequent reads. - */ -void clear_stdin_buffer(void); - -/** - * Safely reads a single character value - * - * @param value Pointer to store the character value - * @return input_status indicating success or type of error - */ -input_status input_char(char *value); - -/** - * Safely reads a string with a maximum length - * - * @param buffer Pointer to the buffer where the string will be stored - * @param buffer_size Size of the buffer in bytes - * @param chars_read Pointer to store the number of characters read - * @return input_status indicating success or type of error - */ -input_status input_string(char *buffer, size_t buffer_size, size_t *chars_read); - -/** - * Safely reads an short value - * - * @param value Pointer to store the short value - * @return input_status indicating success or type of error - */ -input_status input_short(short *value); - -/** - * Safely reads an integer value - * - * @param value Pointer to store the integer value - * @return input_status indicating success or type of error - */ -input_status input_int(int *value); - -/** - * Safely reads a float value - * - * @param value Pointer to store the float value - * @return input_status indicating success or type of error - */ -input_status input_float(float *value); - -/** - * Safely reads a double value - * - * @param value Pointer to store the double value - * @return input_status indicating success or type of error - */ -input_status input_double(double *value); - -#endif // INPUT_H diff --git a/run_valgrind_tests.sh b/run_valgrind_tests.sh index 33911ce..8e8e4bc 100755 --- a/run_valgrind_tests.sh +++ b/run_valgrind_tests.sh @@ -2,6 +2,6 @@ for f in test_cases/*.brainrot; do echo "Running Valgrind on $f..." - valgrind --leak-check=full --error-exitcode=1 ./brainrot "$f" + valgrind --leak-check=full --error-exitcode=1 ./brainrot < "$f" echo done \ No newline at end of file diff --git a/tests/test_brainrot.py b/tests/test_brainrot.py index 6f9a961..efb8a2d 100644 --- a/tests/test_brainrot.py +++ b/tests/test_brainrot.py @@ -17,7 +17,7 @@ def test_brainrot_examples(example, expected_output): brainrot_path = os.path.abspath(os.path.join(script_dir, "../brainrot")) example_file_path = os.path.abspath(os.path.join(script_dir, f"../test_cases/{example}.brainrot")) - command = f"{brainrot_path} {example_file_path}" + command = f"{brainrot_path} < {example_file_path}" result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, shell=True) From 15d00e38b85f62758f59df04485bed7757a9077d Mon Sep 17 00:00:00 2001 From: Leonardo Araujo Date: Sat, 25 Jan 2025 16:11:55 -0300 Subject: [PATCH 2/2] feat: user input --- .github/workflows/ci.yml | 6 +- Makefile | 4 +- README.md | 7 +- ast.c | 85 ++++++- ast.h | 5 +- docs/brainrot-user-guide.md | 41 +++- docs/the-brainrot-programming-language.md | 38 ++- lang.l | 1 + lang.y | 185 +++++++++++++- lib/input.c | 278 ++++++++++++++++++++++ lib/input.h | 93 ++++++++ run_valgrind_tests.sh | 6 +- test_cases/slorp_char.brainrot | 6 + test_cases/slorp_double.brainrot | 6 + test_cases/slorp_float.brainrot | 6 + test_cases/slorp_int.brainrot | 6 + test_cases/slorp_short.brainrot | 6 + test_cases/slorp_string.brainrot | 6 + tests/expected_results.json | 8 +- tests/test_brainrot.py | 28 ++- 20 files changed, 756 insertions(+), 65 deletions(-) create mode 100644 lib/input.c create mode 100644 lib/input.h create mode 100644 test_cases/slorp_char.brainrot create mode 100644 test_cases/slorp_double.brainrot create mode 100644 test_cases/slorp_float.brainrot create mode 100644 test_cases/slorp_int.brainrot create mode 100644 test_cases/slorp_short.brainrot create mode 100644 test_cases/slorp_string.brainrot diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0c0237d..0f7181e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,9 +30,7 @@ jobs: - name: Build Brainrot run: | - bison -d -Wcounterexamples lang.y -o lang.tab.c - flex lang.l - gcc -o brainrot lib/hm.c lib/mem.c lang.tab.c lex.yy.c ast.c -lfl -lm + make - name: Upload build artifacts uses: actions/upload-artifact@v4 @@ -75,7 +73,7 @@ jobs: run: | for f in test_cases/*.brainrot; do echo "Running Valgrind on $f..." - valgrind --leak-check=full --error-exitcode=1 ./brainrot < "$f" + valgrind --leak-check=full --error-exitcode=1 ./brainrot "$f" echo done working-directory: . diff --git a/Makefile b/Makefile index 6100645..9694cf3 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ LDFLAGS := -lfl -lm # Source files and directories SRC_DIR := lib -SRCS := $(SRC_DIR)/hm.c $(SRC_DIR)/mem.c ast.c +SRCS := $(SRC_DIR)/hm.c $(SRC_DIR)/mem.c $(SRC_DIR)/input.c ast.c GENERATED_SRCS := lang.tab.c lex.yy.c ALL_SRCS := $(SRCS) $(GENERATED_SRCS) @@ -56,7 +56,7 @@ clean: valgrind: @for f in test_cases/*.brainrot; do \ echo "Ayo, Valgrind is pulling up on $$f..."; \ - valgrind --leak-check=full --error-exitcode=1 ./$(TARGET) < $$f; \ + valgrind --leak-check=full --error-exitcode=1 ./$(TARGET) $$f; \ if [ $$? -eq 0 ]; then echo "Valgrind passed for $$f, no memory leaks. Big W!"; \ else echo "Valgrind found a memory leak in $$f, taking an L. Better grind harder."; exit 1; fi; \ echo; \ diff --git a/README.md b/README.md index 85a856e..dedb989 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,7 @@ make 2. Run your Brainrot program: ```bash -./brainrot < hello.brainrot +./brainrot hello.brainrot ``` Check out the [examples](examples/README.md): @@ -117,8 +117,9 @@ Check out the [examples](examples/README.md): ## 🗪 Community Join our community on: - - [Discord](https://discord.gg/FjHhvBHSGj) - - [Reddit](https://www.reddit.com/r/Brainrotlang/) + +- [Discord](https://discord.gg/FjHhvBHSGj) +- [Reddit](https://www.reddit.com/r/Brainrotlang/) ## 📚 Language Reference diff --git a/ast.c b/ast.c index 6207b2c..2aea597 100644 --- a/ast.c +++ b/ast.c @@ -174,6 +174,12 @@ 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); +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; @@ -264,7 +270,6 @@ ASTNode *create_int_node(int value) return node; } - ASTNode *create_array_declaration_node(char *name, int length, VarType var_type) { ASTNode *node = SAFE_MALLOC(ASTNode); @@ -642,7 +647,6 @@ void *handle_binary_operation(ASTNode *node, int result_type) *(short *)right_value = evaluate_expression_short(node->data.op.right); break; - default: yyerror("Unsupported type promotion"); return NULL; @@ -1073,7 +1077,6 @@ void *handle_unary_expression(ASTNode *node, void *operand_value, int operand_ty } } - float evaluate_expression_float(ASTNode *node) { if (!node) @@ -1268,7 +1271,6 @@ double evaluate_expression_double(ASTNode *node) return result; } return 0.0L; - } default: yyerror("Invalid double expression"); @@ -1613,7 +1615,7 @@ int evaluate_expression_int(ASTNode *node) } case NODE_FUNC_CALL: { - int *res = (int *)handle_function_call(node); + int *res = (int *)handle_function_call(node); if (res != NULL) { int return_val = *res; @@ -1628,12 +1630,12 @@ int evaluate_expression_int(ASTNode *node) } } -void *handle_function_call(ASTNode* node) +void *handle_function_call(ASTNode *node) { execute_function_call( node->data.func_call.function_name, node->data.func_call.arguments); - void* return_value = NULL; + void *return_value = NULL; if (current_return_value.has_value) { switch (current_return_value.type) @@ -1669,7 +1671,6 @@ void *handle_function_call(ASTNode* node) return return_value; } - bool evaluate_expression_bool(ASTNode *node) { if (!node) @@ -1935,7 +1936,7 @@ bool is_short_expression(ASTNode *node) } } -Function* get_function(const char *name) +Function *get_function(const char *name) { Function *func = function_table; while (func != NULL) @@ -2335,6 +2336,10 @@ void execute_statement(ASTNode *node) { execute_chill_call(node->data.func_call.arguments); } + else if (strcmp(node->data.func_call.function_name, "slorp") == 0) + { + execute_slorp_call(node->data.func_call.arguments); + } break; case NODE_FOR_STATEMENT: execute_for_statement(node); @@ -2420,7 +2425,6 @@ void execute_statement(ASTNode *node) } } - void execute_statements(ASTNode *node) { if (!node) @@ -2956,6 +2960,64 @@ void execute_chill_call(ArgumentList *args) chill(formatNode->data.ivalue); } +void execute_slorp_call(ArgumentList *args) +{ + if (!args || args->expr->type != NODE_IDENTIFIER) + { + yyerror("slurp requires a variable identifier"); + return; + } + + char *name = args->expr->data.name; + Variable *var = get_variable(name); + if (!var) + { + yyerror("Undefined variable"); + return; + } + + 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: + { + char val = 0; + val = slorp_char(val); + set_int_variable(name, val, var->modifiers); + break; + } + default: + yyerror("Unsupported type for slorp"); + } +} + void bruh() { LONGJMP(); @@ -3573,7 +3635,7 @@ void handle_return_statement(ASTNode *expr) } } // skibidi main function do not have jump buffer - if(CURRENT_JUMP_BUFFER() != NULL) + if (CURRENT_JUMP_BUFFER() != NULL) LONGJMP(); } @@ -3644,4 +3706,3 @@ void free_function_table(void) } function_table = NULL; } - diff --git a/ast.h b/ast.h index 0baff36..cd7429c 100644 --- a/ast.h +++ b/ast.h @@ -301,7 +301,7 @@ void enter_scope(); void free_scope(Scope *scope); void add_variable_to_scope(const char *name, Variable *var); Variable *variable_new(char *name); -Function* get_function(const char *name); +Function *get_function(const char *name); VarType get_function_return_type(const char *name); /* Node creation functions */ @@ -366,6 +366,7 @@ 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); void free_ast(ASTNode *node); void reset_modifiers(void); bool check_and_mark_identifier(ASTNode *node, const char *contextErrorMessage); @@ -373,7 +374,7 @@ void bruh(); size_t count_expression_list(ExpressionList *list); size_t handle_sizeof(ASTNode *node); size_t get_type_size(char *name); -void *handle_function_call(ASTNode* node); +void *handle_function_call(ASTNode *node); /* User-defined functions */ Function *create_function(char *name, VarType return_type, Parameter *params, ASTNode *body); diff --git a/docs/brainrot-user-guide.md b/docs/brainrot-user-guide.md index 500fc96..f87dcab 100644 --- a/docs/brainrot-user-guide.md +++ b/docs/brainrot-user-guide.md @@ -70,6 +70,7 @@ count = 42; A **statement** often ends with a **semicolon** `;` unless it is a compound statement (like `{ ... }`). ### Increment and Decrement Operators (`++`, `--`) + In addition to basic arithmetic and logical expressions, you can also use **increment** (`++`) and **decrement** (`--`) operators in Brainrot. - **Pre-Increment (`++i`)**: Increments the value of `i` by 1 before it is used in an expression. @@ -79,7 +80,6 @@ In addition to basic arithmetic and logical expressions, you can also use **incr You can use these operators in expressions to simplify code and make it more concise. - Examples of valid statements: ```c @@ -135,6 +135,7 @@ flex (rizz j = 0; j < 3; j = j + 1) { yapping("j = %d", j); } ``` + - **`init_expr`**: A declaration or expression to initialize loop variables (e.g., `rizz j = 0`). - **`condition`**: Checked each iteration (e.g., `j < 3`). - **`increment`**: Executed at the end of each iteration (e.g., `j = j + 1`). @@ -201,6 +202,8 @@ Brainrot includes some built-in functions for convenience: | **yappin** | `stdout` | No | Precise control over spacing/newlines | | **baka** | `stderr` | No | Log errors or warnings, typically no extra newline | | **ragequit** | - | - | Terminates program execution immediately with the provided exit code. | +| **chill** | - | - | Sleeps for an integer number of seconds. | +| **slorp** | `stdin` | - | Reads user input. | ## 9.1. yapping @@ -320,6 +323,30 @@ skibidi main { } ``` +## 9.6. slorp + +**Prototype** + +```c +void slorp(var_type var_name); +``` + +**Key Points** + +- Reads user input (similar to C's `scanf` but safer) + +### Example + +```c +skibidi main { + rizz num; + yapping("Enter a number:"); + slorp(num); + yapping("You typed: %d", num); + bussin 0; +} +``` + --- # 10. Example Program @@ -376,15 +403,3 @@ skibidi main { - **Syntax** is otherwise quite C-like: `;` to end statements, braces `{ }` to define blocks, parentheses `( )` around conditions. - **Expressions** accept typical operators (`+`,`++`, `-`,`--`, `*`, `/`, `%`, relational, logical) plus the assignment operator `=`, matching standard precedence rules. - **Escapes in strings** (`"\n"`, `"\t"`, etc.) may require an unescape function in your lexer, so check that it’s converting them into real newlines or tabs at runtime. - ---- - -# Final Thoughts - -This language—often called **Brainrot**—provides a playful wrapper around standard C-like structures, with unique keywords and easy built-in printing: - -- **`yapping`** for automatic line-based output. -- **`yappin`** for precise printing without automatic newlines. -- **`baka`** for error messages or logs to `stderr`. - -Use them together with the custom control-flow keywords (`goon`, `flex`, etc.) and custom variable type (`rizz`) to write concise, whimsical, yet powerful scripts. Enjoy experimenting with your new language! diff --git a/docs/the-brainrot-programming-language.md b/docs/the-brainrot-programming-language.md index 876eab6..4203c36 100644 --- a/docs/the-brainrot-programming-language.md +++ b/docs/the-brainrot-programming-language.md @@ -138,7 +138,7 @@ To run your first Brainrot program: ``` 2. **Execute** it: ```bash - ./brainrot < hello.brainrot + ./brainrot hello.brainrot ``` The compiler interprets the code, prints “Hello, World!”, and ends with `bussin 0` (akin to `return 0;`). @@ -269,14 +269,16 @@ Brainrot supports common arithmetic and logical operators: - **`yappin`**: prints text **without** adding a newline. - **`baka`**: prints to `stderr`, typically used for errors/warnings. - **`ragequit`**: terminates program execution immediately with the provided exit code. +- **`chill`**: sleep for a integer number of seconds. +- **`slorp`**: reads user input, similar to `scanf` but safe. --- ### 7.7. User Defined Function -To define function in brainrot it similar as C `return_type func_name(params) {}` +Defining a function in brainrot follows the same pattern as the C programming language: `return_type func_name(param_type param_name) {}` -#### Example: +#### Example: ```c cap is_prime(rizz n) { @@ -294,19 +296,19 @@ cap is_prime(rizz n) { ``` #### BreakDown + - **Function definition**: - - `cap`: return type (bool) - - `is_prime`: function name - - `n`: parameter + - `cap`: return type (bool) + - `is_prime`: function name + - `n`: parameter -#### Usage Example: +#### Usage Example: ```c cap isPrime = is_prime(11) ``` - ## 8. Extended User Documentation ### 8.1. `yapping` @@ -387,6 +389,26 @@ void chill(unsigned int seconds); chill(2); ``` +### 8.6. `slorp` + +```c +void slorp(var_type var_name); +``` + +- Reads user input + +**Example**: + +```c +skibidi main { + rizz num; + yapping("Enter a number:"); + slorp(num); + yapping("You typed: %d", num); + bussin 0; +} +``` + --- ## 9. Limitations diff --git a/lang.l b/lang.l index 59bea9f..a49b9b5 100644 --- a/lang.l +++ b/lang.l @@ -78,6 +78,7 @@ extern int yylineno; "schizo" { return VOLATILE; } "goon" { return GOON; } "baka" { return BAKA; } +"slorp" { return SLORP; } "cap" { current_var_type = VAR_BOOL; return CAP; } "==" { return EQ; } diff --git a/lang.y b/lang.y index 2b4c75f..e624ba9 100644 --- a/lang.y +++ b/lang.y @@ -2,6 +2,7 @@ %{ #include "ast.h" #include "lib/mem.h" +#include "lib/input.h" #include #include #include @@ -16,13 +17,19 @@ 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); +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; extern VarType current_var_type; - extern int yylineno; +extern FILE *yyin; /* Root of the AST */ ASTNode *root = NULL; @@ -59,6 +66,7 @@ ASTNode *root = NULL; %token BOOLEAN %token FLOAT_LITERAL %token DOUBLE_LITERAL +%token SLORP /* Declare types for non-terminals */ %type type @@ -84,7 +92,7 @@ ASTNode *root = NULL; %type array_init initializer_list %type function_def %type function_def_list -%type param_list parameter params +%type param_list params %start program @@ -336,11 +344,15 @@ increment: ; function_call: - IDENTIFIER LPAREN arg_list RPAREN - { - $$ = create_function_call_node($1, $3); - SAFE_FREE($1); - } + SLORP LPAREN identifier RPAREN + { + $$ = create_function_call_node("slorp", create_argument_list($3, NULL)); + } + | IDENTIFIER LPAREN arg_list RPAREN + { + $$ = create_function_call_node($1, $3); + SAFE_FREE($1); + } ; arg_list @@ -485,16 +497,29 @@ array_access: %% -int main(void) { +int main(int argc, char *argv[]) { + if (argc != 2) { + fprintf(stderr, "Usage: %s \n", argv[0]); + return 1; + } + + FILE *source = fopen(argv[1], "r"); + if (!source) { + perror("Cannot open source file"); + return 1; + } + + yyin = source; current_scope = create_scope(NULL); + if (yyparse() == 0) { execute_statement(root); } + + fclose(source); free_ast(root); free_function_table(); free_scope(current_scope); - - // Clean up flex's internal state yylex_destroy(); return 0; @@ -536,6 +561,146 @@ void baka(const char* format, ...) { va_end(args); } +char slorp_char(char chr) { + size_t chars_read; + 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 chars_read; + input_status status; + + status = input_string(string, sizeof(string), &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"); + } + 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); + } +} + +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"); + } + 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); + } +} + +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 AST free_ast(root); diff --git a/lib/input.c b/lib/input.c new file mode 100644 index 0000000..dd00f3b --- /dev/null +++ b/lib/input.c @@ -0,0 +1,278 @@ +/** + * input.c - Implementation of input functions + * + * This file contains the definitions of the functions declared in input.h. + */ + +#include "input.h" + +/** + * Clears the remaining input in stdin to prevent it from affecting subsequent reads. + */ +void clear_stdin_buffer(void) +{ + int c; + while ((c = getchar()) != '\n' && c != EOF) + ; +} + +/** + * Safely reads a single character value + * + * @param value Pointer to store the character value + * @return input_status indicating success or type of error + */ +input_status input_char(char *value) +{ + if (value == NULL) + { + return INPUT_NULL_PTR; + } + + char buffer[2]; // One for the character, one for null terminator + size_t chars_read; + + input_status status = input_string(buffer, sizeof(buffer), &chars_read); + if (status != INPUT_SUCCESS) + { + return status; + } + + // Check if we got exactly one character + if (chars_read != 1) + { + return INPUT_INVALID_LENGTH; + } + + *value = buffer[0]; + return INPUT_SUCCESS; +} + +/** + * Safely reads a string with a maximum length + * + * @param buffer Pointer to the buffer where the string will be stored + * @param buffer_size Size of the buffer in bytes + * @param chars_read Pointer to store the number of characters read + * @return input_status indicating success or type of error + */ +input_status input_string(char *buffer, size_t buffer_size, size_t *chars_read) +{ + if (buffer == NULL || chars_read == NULL) + { + return INPUT_NULL_PTR; + } + + if (buffer_size == 0) + { + return INPUT_BUFFER_OVERFLOW; + } + + // Clear errno before operation + errno = 0; + + // Read input ensuring space for null terminator + if (fgets(buffer, (int)buffer_size, stdin) == NULL) + { + if (ferror(stdin)) + { + clearerr(stdin); + return INPUT_IO_ERROR; + } + // EOF reached + buffer[0] = '\0'; + *chars_read = 0; + return INPUT_SUCCESS; + } + + // Check if the input was truncated (no newline found) + size_t len = strnlen(buffer, buffer_size); + if (len == buffer_size - 1 && buffer[len - 1] != '\n') + { + // Input was truncated; clear the remaining input + clear_stdin_buffer(); + return INPUT_BUFFER_OVERFLOW; + } + + // Remove trailing newline if present + if (len > 0 && buffer[len - 1] == '\n') + { + buffer[len - 1] = '\0'; + len--; + } + + *chars_read = len; + return INPUT_SUCCESS; +} + +/** + * Safely reads an integer value + * + * @param value Pointer to store the integer value + * @return input_status indicating success or type of error + */ +input_status input_int(int *value) +{ + if (value == NULL) + { + return INPUT_NULL_PTR; + } + + char buffer[32]; // Large enough for any integer + size_t chars_read; + + input_status status = input_string(buffer, sizeof(buffer), &chars_read); + if (status != INPUT_SUCCESS) + { + return status; + } + + // Clear errno before conversion + errno = 0; + char *endptr; + long result = strtol(buffer, &endptr, 10); + + // Check for conversion errors + if (endptr == buffer || *endptr != '\0') + { + return INPUT_CONVERSION_ERROR; + } + + // Check for overflow/underflow + if (errno == ERANGE || result > INT_MAX || result < INT_MIN) + { + return INPUT_INTEGER_OVERFLOW; + } + + *value = (int)result; + return INPUT_SUCCESS; +} + +/** + * Safely reads an short value + * + * @param value Pointer to store the short value + * @return input_status indicating success or type of error + */ +input_status input_short(short *value) +{ + if (value == NULL) + { + return INPUT_NULL_PTR; + } + + char buffer[32]; // Large enough for any integer + size_t chars_read; + + input_status status = input_string(buffer, sizeof(buffer), &chars_read); + if (status != INPUT_SUCCESS) + { + return status; + } + + // Clear errno before conversion + errno = 0; + char *endptr; + long result = strtol(buffer, &endptr, 10); + + // Check for conversion errors + if (endptr == buffer || *endptr != '\0') + { + return INPUT_CONVERSION_ERROR; + } + + // Check for overflow/underflow + if (errno == ERANGE || result > SHRT_MAX || result < SHRT_MIN) + { + return INPUT_SHORT_OVERFLOW; + } + + *value = (short)result; + return INPUT_SUCCESS; +} + +/** + * Safely reads a float value + * + * @param value Pointer to store the float value + * @return input_status indicating success or type of error + */ +input_status input_float(float *value) +{ + if (value == NULL) + { + return INPUT_NULL_PTR; + } + + char buffer[32]; // Large enough for any float + size_t chars_read; + + input_status status = input_string(buffer, sizeof(buffer), &chars_read); + if (status != INPUT_SUCCESS) + { + return status; + } + + // Clear errno before conversion + errno = 0; + char *endptr; + double result = strtod(buffer, &endptr); + + // Check for conversion errors + if (endptr == buffer || *endptr != '\0') + { + return INPUT_CONVERSION_ERROR; + } + + // Check for overflow/underflow + if (errno == ERANGE) + { + return INPUT_FLOAT_OVERFLOW; + } + + *value = result; + return INPUT_SUCCESS; +} + +/** + * Safely reads a double value + * + * @param value Pointer to store the double value + * @return input_status indicating success or type of error + */ +input_status input_double(double *value) +{ + if (value == NULL) + { + return INPUT_NULL_PTR; + } + + char buffer[64]; // Large enough for any double + size_t chars_read; + + input_status status = input_string(buffer, sizeof(buffer), &chars_read); + if (status != INPUT_SUCCESS) + { + return status; + } + + // Clear errno before conversion + errno = 0; + char *endptr; + double result = strtod(buffer, &endptr); + + // Check for conversion errors + if (endptr == buffer || *endptr != '\0') + { + return INPUT_CONVERSION_ERROR; + } + + // Check for overflow/underflow + if (errno == ERANGE) + { + return INPUT_DOUBLE_OVERFLOW; + } + + *value = result; + return INPUT_SUCCESS; +} diff --git a/lib/input.h b/lib/input.h new file mode 100644 index 0000000..b57f37a --- /dev/null +++ b/lib/input.h @@ -0,0 +1,93 @@ +/** + * input.h - A safer implementation of scanf-like functionality + * + * This implementation focuses on safety against: + * - Buffer overflows + * - NULL pointer dereferences + * - Memory leaks + * - Integer overflows + * - Format string vulnerabilities + */ + +#ifndef INPUT_H +#define INPUT_H + +#include +#include +#include +#include +#include +#include + +// Return codes for input functions +typedef enum +{ + INPUT_SUCCESS = 0, + INPUT_NULL_PTR = -1, + INPUT_INVALID_FORMAT = -2, + INPUT_BUFFER_OVERFLOW = -3, + INPUT_CONVERSION_ERROR = -4, + INPUT_IO_ERROR = -5, + INPUT_SHORT_OVERFLOW = -6, + INPUT_INTEGER_OVERFLOW = -7, + INPUT_FLOAT_OVERFLOW = -8, + INPUT_DOUBLE_OVERFLOW = -9, + INPUT_INVALID_LENGTH = -10, +} input_status; + +/** + * Clears the remaining input in stdin to prevent it from affecting subsequent reads. + */ +void clear_stdin_buffer(void); + +/** + * Safely reads a single character value + * + * @param value Pointer to store the character value + * @return input_status indicating success or type of error + */ +input_status input_char(char *value); + +/** + * Safely reads a string with a maximum length + * + * @param buffer Pointer to the buffer where the string will be stored + * @param buffer_size Size of the buffer in bytes + * @param chars_read Pointer to store the number of characters read + * @return input_status indicating success or type of error + */ +input_status input_string(char *buffer, size_t buffer_size, size_t *chars_read); + +/** + * Safely reads an short value + * + * @param value Pointer to store the short value + * @return input_status indicating success or type of error + */ +input_status input_short(short *value); + +/** + * Safely reads an integer value + * + * @param value Pointer to store the integer value + * @return input_status indicating success or type of error + */ +input_status input_int(int *value); + +/** + * Safely reads a float value + * + * @param value Pointer to store the float value + * @return input_status indicating success or type of error + */ +input_status input_float(float *value); + +/** + * Safely reads a double value + * + * @param value Pointer to store the double value + * @return input_status indicating success or type of error + */ +input_status input_double(double *value); + +#endif // INPUT_H diff --git a/run_valgrind_tests.sh b/run_valgrind_tests.sh index 8e8e4bc..f68866d 100755 --- a/run_valgrind_tests.sh +++ b/run_valgrind_tests.sh @@ -2,6 +2,10 @@ for f in test_cases/*.brainrot; do echo "Running Valgrind on $f..." - valgrind --leak-check=full --error-exitcode=1 ./brainrot < "$f" + if [[ $(basename "$f") == slorp_int* ]]; then + echo "42" | valgrind --leak-check=full --error-exitcode=1 ./brainrot "$f" + else + valgrind --leak-check=full --error-exitcode=1 ./brainrot "$f" + fi echo done \ No newline at end of file diff --git a/test_cases/slorp_char.brainrot b/test_cases/slorp_char.brainrot new file mode 100644 index 0000000..bb14347 --- /dev/null +++ b/test_cases/slorp_char.brainrot @@ -0,0 +1,6 @@ +skibidi main { + yap chr; + slorp(chr); + yapping("You typed: %c", chr); + bussin 0; +} diff --git a/test_cases/slorp_double.brainrot b/test_cases/slorp_double.brainrot new file mode 100644 index 0000000..a0cf581 --- /dev/null +++ b/test_cases/slorp_double.brainrot @@ -0,0 +1,6 @@ +skibidi main { + gigachad num; + slorp(num); + yapping("You typed: %f", num); + bussin 0; +} diff --git a/test_cases/slorp_float.brainrot b/test_cases/slorp_float.brainrot new file mode 100644 index 0000000..6b26793 --- /dev/null +++ b/test_cases/slorp_float.brainrot @@ -0,0 +1,6 @@ +skibidi main { + chad num; + slorp(num); + yapping("You typed: %f", num); + bussin 0; +} diff --git a/test_cases/slorp_int.brainrot b/test_cases/slorp_int.brainrot new file mode 100644 index 0000000..304b1c2 --- /dev/null +++ b/test_cases/slorp_int.brainrot @@ -0,0 +1,6 @@ +skibidi main { + rizz num; + slorp(num); + yapping("You typed: %d", num); + bussin 0; +} diff --git a/test_cases/slorp_short.brainrot b/test_cases/slorp_short.brainrot new file mode 100644 index 0000000..6593d83 --- /dev/null +++ b/test_cases/slorp_short.brainrot @@ -0,0 +1,6 @@ +skibidi main { + smol num; + slorp(num); + yapping("You typed: %d", num); + bussin 0; +} diff --git a/test_cases/slorp_string.brainrot b/test_cases/slorp_string.brainrot new file mode 100644 index 0000000..c6fad07 --- /dev/null +++ b/test_cases/slorp_string.brainrot @@ -0,0 +1,6 @@ +skibidi main { + yap string[32]; + slorp(string); + yapping("You typed: %s", string); + bussin 0; +} diff --git a/tests/expected_results.json b/tests/expected_results.json index 31d1a6c..4bc6063 100644 --- a/tests/expected_results.json +++ b/tests/expected_results.json @@ -42,5 +42,11 @@ "add_two_numbers": "3", "mul_two_numbers": "11.400000", "max_gigachad": "5.000000", - "is_prime": "W" + "is_prime": "W", + "slorp_int": "You typed: 42", + "slorp_short": "You typed: 69", + "slorp_float": "You typed: 3.140000", + "slorp_double": "You typed: 3.141592", + "slorp_char": "You typed: c", + "slorp_string": "You typed: skibidi bop bop yes yes" } diff --git a/tests/test_brainrot.py b/tests/test_brainrot.py index efb8a2d..e4b9d67 100644 --- a/tests/test_brainrot.py +++ b/tests/test_brainrot.py @@ -17,24 +17,34 @@ def test_brainrot_examples(example, expected_output): brainrot_path = os.path.abspath(os.path.join(script_dir, "../brainrot")) example_file_path = os.path.abspath(os.path.join(script_dir, f"../test_cases/{example}.brainrot")) - command = f"{brainrot_path} < {example_file_path}" - + + if example.startswith("slorp_int"): + command = f"echo '42' | {brainrot_path} {example_file_path}" + elif example.startswith("slorp_short"): + command = f"echo '69' | {brainrot_path} {example_file_path}" + elif example.startswith("slorp_float"): + command = f"echo '3.14' | {brainrot_path} {example_file_path}" + elif example.startswith("slorp_double"): + command = f"echo '3.141592' | {brainrot_path} {example_file_path}" + elif example.startswith("slorp_char"): + command = f"echo 'c' | {brainrot_path} {example_file_path}" + elif example.startswith("slorp_string"): + command = f"echo 'skibidi bop bop yes yes' | {brainrot_path} {example_file_path}" + else: + command = f"{brainrot_path} {example_file_path}" + result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, shell=True) - - # Go back to original logic - only use stderr if stdout is empty actual_output = result.stdout.strip() if result.stdout.strip() else result.stderr.strip() - - # Special case: if output contains "Stderr:", we need both + if "Stderr:" in expected_output and result.stdout.strip(): actual_output = f"{result.stdout.strip()}\nStderr:\n{result.stderr.strip()}" - + assert actual_output == expected_output.strip(), ( f"Output for {example} did not match.\n" f"Expected:\n{expected_output}\n" f"Actual:\n{actual_output}" ) - - # Only check return code for non-error cases + if "Error:" not in expected_output: assert result.returncode == 0, ( f"Command for {example} failed with return code {result.returncode}\n"