online compiler and debugger for c/c++

code. compile. run. debug. share.
Source Code    Language
#include "log.h" #include "list.h" #include "lexing.h" #include "parsing.h" #include "running.h" #include "itype.h" int main(int argc, char ** argv) { const char * file_path = "program.[m]m"; struct List token_list = lex(file_path); if (!list_is_valid(&token_list)) { LOG_FATAL_ERROR("Failed to lex \"%s\".\n", file_path); proper_exit(EXIT_FAILURE); } struct List itype_list = parse(&token_list); enum ErrState ret_val = run(&itype_list); LOG(LOG_LVL_CONSOLE, "Final stacks:\n"); log_itype_list(LOG_LVL_CONSOLE, &itype_list); LOG(LOG_LVL_CONSOLE, "\n"); if (ret_val == ERR_FAILURE) { proper_exit(EXIT_FAILURE); } else { proper_exit(EXIT_SUCCESS); } }
IS
#include "itype.h" #include <string.h> #include "list.h" #include "settings.h" #include "stack.h" #include "mem_tools.h" void destroy_itype(struct IType * itype) { ASSERT(false, "Cannot destroy instruction types."); } void destroy_itype_void_ptr(void * itype) { destroy_itype(itype); } bool itype_in_list(const struct List * itype_list, const char * instr_name) { for (size_t i = 0; i < itype_list->length; ++i) { const struct IType * curr_itype = get_list_elem_const(itype_list, i); if (strcmp(curr_itype->name, instr_name) == 0) { return true; } } return false; } static struct IType create_itype(const char * name) { struct IType itype; itype.value = NULL; itype.name = ALLOC(char, strlen(name) + 1); strcpy(itype.name, name); return itype; } void add_itype_to_list(struct List * itype_list, const char * instr_name) { ASSERT(!itype_in_list(itype_list, instr_name), "Instruction %s already in instruction " LIST_FS ".", instr_name, LIST_FA(*itype_list)); struct IType new_itype = create_itype(instr_name); list_append(itype_list, &new_itype); } instr_id_t find_instr_id(const struct List * itype_list, const char * instr_name) { for (size_t i = 0; i < itype_list->length; ++i) { const struct IType * curr_itype = get_list_elem_const(itype_list, i); if (strcmp(curr_itype->name, instr_name) == 0) { return (instr_id_t) i; } } ASSERT(false, "Could not find instruction %s in instruction " LIST_FS ".", instr_name, LIST_FA(*itype_list)); return -1; } struct IType * id_to_itype(struct List * itype_list, instr_id_t id) { return get_list_elem(itype_list, id); } const struct IType * id_to_itype_const(const struct List * itype_list, instr_id_t id) { return get_list_elem_const(itype_list, id); } struct IType * instr_name_to_itype(struct List * itype_list, const char * instr_name) { instr_id_t id = find_instr_id(itype_list, instr_name); return id_to_itype(itype_list, id); } void log_itype_list(int log_level, const struct List * list) { if (!LOGGABLE(log_level)) { return; } for (size_t i = 0; i < list->length; ++i) { if (i != 0) { LOG(log_level, "\n"); } const struct IType * curr_itype = get_list_elem_const(list, i); LOG(log_level, "%s: ", curr_itype->name); if (curr_itype->value) { log_stack_backwards(log_level, curr_itype->value, list); } else { LOG(log_level, "(uninitialized)"); } } } bool is_builtin(instr_id_t id) { return id < 0; } instr_id_t builtin_to_id(enum Builtin builtin) { return -builtin - 1; } enum Builtin id_to_builtin(instr_id_t id) { return -id - 1; }
#ifndef INSTR_TYPE_H #define INSTR_TYPE_H #include <stdbool.h> #include "list.h" typedef int instr_id_t; enum Builtin { BUILTIN_SET, BUILTIN_UNWRAP, BUILTIN_IF, BUILTINS_COUNT }; struct IType { char * name; struct Stack * value; }; // No "create_itype" function since they're only supposed to be created // by adding them to a list using "add_itype_to_list". void destroy_itype(struct IType * itype); void destroy_itype_void_ptr(void * itype); // Returns "true" if and only if "itype_list" contains a "struct IType" named // "instr_name". bool itype_in_list(const struct List * itype_list, const char * instr_name); // Adds a new "struct IType" to "itype_list", named "instr_name". // "itype_list" cannot already contain an instruction type with that name. void add_itype_to_list(struct List * itype_list, const char * instr_name); // Return the ID of the "struct IType" in "itype_list" named "instr_name". // Can only be called if such "struct IType" exists. instr_id_t find_instr_id(const struct List * itype_list, const char * instr_name); // Returns the name of the instruction type with ID "id" in "itype_list". // Can only be called if the ID is valid within the list. struct IType * id_to_itype(struct List * itype_list, instr_id_t id); // Same as "id_to_itype", but the argument can be constant at the cost of // a constant return value. const struct IType * id_to_itype_const(const struct List * itype_list, instr_id_t id); // Returns the instruction type named "instr_name" in "itype_list". // Only legal if such instruction type exists. struct IType * instr_name_to_itype(struct List * itype_list, const char * instr_name); void log_itype_list(int log_level, const struct List * list); bool is_builtin(instr_id_t id); instr_id_t builtin_to_id(enum Builtin builtin); enum Builtin id_to_builtin(instr_id_t id); #endif
#include "stack.h" #include "mem_tools.h" #include "settings.h" #define MIN_STACK_CAPACITY 2 #define STACK_CAPACITY_MULTIPLIER 1.5 struct Stack * create_stack(void) { struct Stack * stack = ALLOC(struct Stack, 1); stack->capacity = MIN_STACK_CAPACITY; stack->contents = ALLOC(struct StackElem, stack->capacity); stack->size = 0; stack->reference_count = 1; return stack; } struct Stack * create_invalid_stack(void) { // As per the time of writing this comment, "is_stack_valid" simply // checks if the capacity is too little for the size or for // "MIN_STACK_CAPACITY". Yet, all the variables are initialized // with values as bad as possible to make sure that even if // "is_stack_valid" for some magical reason changes, it'll still // return "false" for stacks created by this function. struct Stack * stack = ALLOC(struct Stack, 1); stack->capacity = 0; stack->contents = NULL; stack->size = 1; stack->reference_count = -1; return stack; } // Do not change this function without making sure it will still return // "false" for values created using "create_invalid_stack". bool is_stack_valid(const struct Stack * stack) { return stack->capacity >= stack->size && stack->capacity >= MIN_STACK_CAPACITY; } void add_stack_reference(struct Stack * stack) { ++stack->reference_count; } void remove_stack_reference(struct Stack * stack) { --stack->reference_count; if (stack->reference_count == 0) { destroy_stack(stack); } } static void destroy_stack_elem(struct StackElem * elem) { if (elem->type == STACK_ELEM_SUBSTACK) { remove_stack_reference(elem->substack); } } void destroy_stack(struct Stack * stack) { for (size_t i = 0; i < stack->size; ++i) { destroy_stack_elem(&stack->contents[i]); } FREE(stack->contents); FREE(stack); } void destroy_stack_void_ptr(void * stack) { destroy_stack(stack); } static void resize_stack(struct Stack * stack, size_t new_size) { if (new_size < stack->size) { for (size_t i = new_size; i < stack->size; ++i) { destroy_stack_elem(&stack->contents[i]); } } while (new_size > stack->capacity) { stack->capacity *= STACK_CAPACITY_MULTIPLIER; } REALLOC(&stack->contents, struct StackElem, stack->capacity); stack->size = new_size; } void stack_push(struct Stack * stack, const struct StackElem * stack_elem) { resize_stack(stack, stack->size + 1); stack->contents[stack->size - 1] = *stack_elem; if (stack_elem->type == STACK_ELEM_SUBSTACK) { add_stack_reference(stack_elem->substack); } } void stack_pop(struct Stack * stack) { destroy_stack_elem(&stack->contents[stack->size - 1]); resize_stack(stack, stack->size - 1); } struct StackElem * stack_peek(struct Stack * stack, int idx) { return &stack->contents[stack->size - idx - 1]; } struct Stack * deepcopy_stack(const struct Stack * stack) { struct Stack * clone = ALLOC(struct Stack, 1); clone->reference_count = 1; clone->capacity = stack->capacity; clone->size = stack->size; clone->contents = ALLOC(struct StackElem, clone->capacity); COPY_MEMORY(clone->contents, stack->contents, struct StackElem, clone->capacity); // Clone all the sub-stacks. for (size_t i = 0; i < clone->size; ++i) { struct StackElem * clone_elem = &clone->contents[i]; const struct StackElem * original_elem = &stack->contents[i]; if (clone_elem->type == STACK_ELEM_SUBSTACK) { clone_elem->substack = deepcopy_stack(original_elem->substack); } } return clone; } void reverse_stack(struct Stack * stack) { int lower_idx = 0; int upper_idx = stack->size - 1; while (lower_idx < upper_idx) { struct StackElem temp = stack->contents[lower_idx]; stack->contents[lower_idx] = stack->contents[upper_idx]; stack->contents[upper_idx] = temp; ++lower_idx; --upper_idx; } } struct StackElem instr_to_stack_elem(instr_id_t instr, int indirection_level) { struct StackElem stack_elem; stack_elem.type = STACK_ELEM_INSTR; stack_elem.instr = instr; stack_elem.indirection_level = indirection_level; return stack_elem; } struct StackElem create_stack_ref(struct Stack * stack, int indirection_level) { struct StackElem stack_elem; stack_elem.type = STACK_ELEM_STACK_REF; stack_elem.indirection_level = indirection_level; stack_elem.stack_ref = stack; add_stack_reference(stack); return stack_elem; } struct StackElem create_substack(struct Stack * substack, int indirection_level) { struct StackElem stack_elem; stack_elem.type = STACK_ELEM_SUBSTACK; stack_elem.indirection_level = indirection_level; stack_elem.substack = substack; add_stack_reference(substack); return stack_elem; } struct StackElem create_invalid_stack_elem(void) { struct StackElem stack_elem; stack_elem.type = STACK_ELEM_INVALID; return stack_elem; } bool is_stack_elem_valid(const struct StackElem * stack_elem) { return stack_elem->type != STACK_ELEM_INVALID; } static void log_n_times(int log_level, char ch, int count) { for (int i = 0; i < count; ++i) { LOG(log_level, "%c", ch); } } static void log_stack_elem(int log_level, const struct StackElem * stack_elem, const struct List * itype_list) { switch (stack_elem->type) { case STACK_ELEM_INSTR: { if (is_builtin(stack_elem->instr)) { enum Builtin builtin = id_to_builtin(stack_elem->instr); LOG(log_level, "(%s)", g_builtin_names[builtin]); } else { const struct IType * itype; itype = id_to_itype_const(itype_list, stack_elem->instr); LOG(log_level, "%s", itype->name); } break; case STACK_ELEM_STACK_REF: LOG(log_level, "&"); log_stack_backwards(log_level, stack_elem->stack_ref, itype_list); break; case STACK_ELEM_SUBSTACK: log_stack_backwards(log_level, stack_elem->substack, itype_list); break; default: LOG(log_level, "(invalid stack element)"); } } log_n_times(log_level, g_indirection_ch, stack_elem->indirection_level); } void log_stack_backwards(int log_level, const struct Stack * stack, const struct List * itype_list) { if (!LOGGABLE(log_level)) { return; } LOG(log_level, "%c", g_stack_open_ch); for (int i = stack->size - 1; i >= 0; --i) { const struct StackElem * curr_elem = &stack->contents[i]; log_stack_elem(log_level, curr_elem, itype_list); if (i != 0) { LOG(log_level, " "); } } LOG(log_level, "%c", g_stack_close_ch); }
// "There should be no more than one class per file, and max 7 functions." // - someone smart #ifndef STACK_H #define STACK_H #include <stdlib.h> #include <stdbool.h> #include "itype.h" enum StackElemType { STACK_ELEM_INVALID, STACK_ELEM_INSTR, STACK_ELEM_STACK_REF, // Napping in a sack // Rapping tacky tracks // Spitting bars, spitting facts // But sud'nly happ'ning happs: // The minmod program lacks // Stack-elem-sub-stacks // So using complex hacks // I smartly add them back STACK_ELEM_SUBSTACK }; struct StackElem { enum StackElemType type; union { instr_id_t instr; struct Stack * stack_ref; // While variable stacks are passed by reference, literal stacks // are always copied by value. // It's still a pointer, though, to save some space. struct Stack * substack; }; int indirection_level; }; struct Stack { struct StackElem * contents; size_t capacity; size_t size; int reference_count; }; // Create a new stack without any elements. // Assumes one variable or stack is referencing the stack at creation. struct Stack * create_stack(void); // Create an invalid and unusable stack. struct Stack * create_invalid_stack(void); // Returns "true" if "stack" was created using "create_stack" rather than // "create_invalid_stack". bool is_stack_valid(const struct Stack * stack); void add_stack_reference(struct Stack * stack); void remove_stack_reference(struct Stack * stack); void destroy_stack(struct Stack * stack); void destroy_stack_void_ptr(void * stack); void stack_push(struct Stack * stack, const struct StackElem * stack_elem); void stack_pop(struct Stack * stack); struct StackElem * stack_peek(struct Stack * stack, int idx); // Creates a duplicate of "stack", including deeply copying the sub-stacks. // It's completely independent, in other words. Like the U. S. struct Stack * deepcopy_stack(const struct Stack * stack); // Reverses the contents of "stack". Sub-stacks won't be reversed. void reverse_stack(struct Stack * stack); // The three routines below create stack elements from different types of // data, ready to be sealed and shipped (id est, added to a stack). struct StackElem instr_to_stack_elem(instr_id_t instr, int indirection_level); struct StackElem create_stack_ref(struct Stack * stack, int indirection_level); struct StackElem create_substack(struct Stack * substack, int indirection_level); struct StackElem create_invalid_stack_elem(void); // Returns "true" if "stack_elem" wasn't created by "create_invalid_stack_elem". bool is_stack_elem_valid(const struct StackElem * stack_elem); // Logging stacks backwards is quite useful since instruction // stacks are reversed before execution void log_stack_backwards(int log_level, const struct Stack * stack, const struct List * itype_list); #endif
#include <ctype.h> #include <string.h> #include "debug.h" #include "settings.h" #include "file_operations.h" #include "list.h" #include "token.h" #include "mem_tools.h" // Not beautiful, but it gets the job done :D // Makes "*iterator" skip past the current token, incrementing "*line" // if newlines are found. // Returns the current token (the one "*iterator" skips past). static struct Token scan_token(const char ** iterator, int * line) { // Match **iterator against a bunch of different tokens ... if (**iterator == g_stack_open_ch) { ++*iterator; return create_token(TOK_STACK_OPEN, *line); } else if (**iterator == g_stack_close_ch) { ++*iterator; return create_token(TOK_STACK_CLOSE, *line); } else if (**iterator == g_indirection_ch) { int indirection_level = 0; while (**iterator == g_indirection_ch) { ++*iterator; ++indirection_level; } struct Token token = create_token(TOK_INDIRECTION, *line); token.indirection_level = indirection_level; return token; } else if (isspace(**iterator)) { while (isspace(**iterator)) { if (**iterator == '\n') { ++*line; } ++*iterator; } return create_token(TOK_WHITESPACE, *line); } else if (strncmp(*iterator, g_comment_str, strlen(g_comment_str)) == 0) { while (**iterator && **iterator != '\n') { ++*iterator; } return create_token(TOK_COMMENT, *line); } // If this point is reached, "iterator" has been matched against all // the fun tokens, and the only possibility left is plain, boring // "TOK_INSTR". ASSERT_OR_HANDLE(is_valid_instr_ch(**iterator), create_token(TOK_INVALID, -1), "Invalid character \"%c\" in line %d.", **iterator, *line); const char * instr_start = *iterator; while (is_valid_instr_ch(**iterator)) { ++*iterator; } int instr_length = *iterator - instr_start; char * instr_name = ALLOC(char, instr_length + 1); strncpy(instr_name, instr_start, instr_length); instr_name[instr_length] = '\0'; struct Token token = create_token(TOK_INSTR, *line); token.instr_name = instr_name; return token; } // Remove all tokens in "token_list" of type "type". static void remove_tokens_of_type(struct List * token_list, enum TokenType type) { LOG_INFO("Removing all %s tokens from " LIST_FS " ...\n", token_type_as_string(type), LIST_FA(*token_list)); for (size_t i = 0; i < token_list->length; ++i) { const struct Token * curr_tok = get_list_elem_const(token_list, i); if (curr_tok->type == type) { list_remove(token_list, i); --i; } } } // Lexes "string". static struct List string_to_tokens(const char * string) { LOG_INFO("Converting string to list of tokens ...\n"); struct List token_list = create_list(sizeof(struct Token), destroy_token_void_ptr); const char * iterator = string; int line = 1; while (*iterator) { struct Token curr_tok = scan_token(&iterator, &line); if (curr_tok.type == TOK_INVALID) { return create_invalid_list(); } list_append(&token_list, &curr_tok); } return token_list; }; struct List lex(const char * file_path) { LOG_INFO("Lexing \"%s\" ...\n", file_path); const char * file_ext = get_file_ext(file_path); ASSERT_OR_HANDLE(file_ext, create_invalid_list(), "Cannot lex file \"%s\", as it has no extension.", file_path); ASSERT_OR_HANDLE(strcmp(file_ext, g_minmod_file_ext) == 0, create_invalid_list(), "[min]mod files must have extension \"%s\", " "but \"%s\" has extension \"%s\".", g_minmod_file_ext, file_path, file_ext); const char * file_contents = file_to_string(file_path); LOG_DEBUG("\"%s\":\n\"%s\"\n\n", file_path, file_contents); struct List token_list = string_to_tokens(file_contents); if (!list_is_valid(&token_list)) { return create_invalid_list(); } remove_tokens_of_type(&token_list, TOK_WHITESPACE); remove_tokens_of_type(&token_list, TOK_COMMENT); LOG_DEBUG("Tokens in \"%s\":\n", file_path); log_token_list(LOG_LVL_DEBUG, &token_list); LOG_DEBUG("\n\n"); return token_list; }
#ifndef LEXING_H #define LEXING_H // Lexically analyzes the contents of "file_path", returning a list of // "struct Token"s. struct List lex(const char * file_path); #endif
#include "parsing.h" #include "itype.h" #include "token.h" #include "log.h" #include "stack.h" #include "settings.h" #include "mem_tools.h" static struct List tokens_to_itype_list(const struct List * tokens) { struct List itype_list = create_list(sizeof(struct IType), destroy_itype_void_ptr); // IS and DS must be in the list even if they're not referenced in the // program, as they're implicitly used by the built-in instructions. add_itype_to_list(&itype_list, g_instr_stack_str); add_itype_to_list(&itype_list, g_data_stack_str); for (size_t i = 0; i < tokens->length; ++i) { const struct Token * curr_tok = get_list_elem_const(tokens, i); if (curr_tok->type == TOK_INSTR && !itype_in_list(&itype_list, curr_tok->instr_name)) { add_itype_to_list(&itype_list, curr_tok->instr_name); } } return itype_list; } static struct Stack * tokens_to_stack(const struct List * tokens, const struct List * itype_list); static struct StackElem get_nested_stack(const struct List * tokens, const struct List * itype_list, size_t * iterator) { size_t stack_open_idx = *iterator; const struct Token * stack_open_tok = get_list_elem_const(tokens, stack_open_idx); int nesting_level = 0; do { ASSERT_OR_HANDLE(*iterator < tokens->length, create_invalid_stack_elem(), "Unclosed %s in line %d.", token_type_as_string(stack_open_tok->type), stack_open_tok->line); const struct Token * curr_tok = get_list_elem_const(tokens, *iterator); switch (curr_tok->type) { case TOK_STACK_OPEN: ++nesting_level; break; case TOK_STACK_CLOSE: --nesting_level; break; default: break; } ++*iterator; } while (nesting_level > 0); size_t stack_close_idx = *iterator - 1; struct List sublist = create_sublist(tokens, stack_open_idx + 1, stack_close_idx); int indirection_level = 0; if (*iterator < tokens->length) { const struct Token * tok_after_substack = get_list_elem_const(tokens, *iterator); if (tok_after_substack->type == TOK_INDIRECTION) { indirection_level = tok_after_substack->indirection_level; ++*iterator; } } struct Stack * stack = tokens_to_stack(&sublist, itype_list); struct StackElem stack_as_substack = create_substack(stack, indirection_level); return stack_as_substack; } static struct StackElem get_instr(const struct List * tokens, const struct List * itype_list, size_t * iterator) { const struct Token * instr_tok = get_list_elem_const(tokens, *iterator); ++*iterator; int indirection_level = 0; if (*iterator < tokens->length) { const struct Token * next_tok = get_list_elem_const(tokens, *iterator); if (next_tok->type == TOK_INDIRECTION) { indirection_level = next_tok->indirection_level; ++*iterator; } } instr_id_t instr = find_instr_id(itype_list, instr_tok->instr_name); struct StackElem instr_as_stack_elem = instr_to_stack_elem(instr, indirection_level); return instr_as_stack_elem; } static struct Stack * tokens_to_stack(const struct List * tokens, const struct List * itype_list) { struct Stack * stack = create_stack(); size_t idx = 0; while (idx < tokens->length) { const struct Token * curr_tok = get_list_elem_const(tokens, idx); switch (curr_tok->type) { case TOK_STACK_OPEN: { struct StackElem stack_elem = get_nested_stack(tokens, itype_list, &idx); stack_push(stack, &stack_elem); break; } case TOK_INSTR: { struct StackElem stack_elem = get_instr(tokens, itype_list, &idx); stack_push(stack, &stack_elem); break; } default: { ASSERT_OR_HANDLE(false, create_invalid_stack(), "Unexpected %s in line %d.", token_type_as_string(curr_tok->type), curr_tok->line); } } } reverse_stack(stack); return stack; } static void set_builtin_value(enum Builtin builtin, struct List * itype_list) { const char * builtin_name = g_builtin_names[builtin]; if (itype_in_list(itype_list, builtin_name)) { struct IType * itype = instr_name_to_itype(itype_list, builtin_name); itype->value = create_stack(); // Built-ins have indices -1, -2, -3 etc. instr_id_t instr = builtin_to_id(builtin); struct StackElem stack_elem = instr_to_stack_elem(instr, 0); stack_push(itype->value, &stack_elem); } } struct List parse(const struct List * tokens) { struct List itype_list = tokens_to_itype_list(tokens); LOG_DEBUG("Instruction types:\n"); log_itype_list(LOG_LVL_DEBUG, &itype_list); LOG_DEBUG("\n\n"); for (enum Builtin i = 0; i < BUILTINS_COUNT; ++i) { set_builtin_value(i, &itype_list); } struct IType * data_stack = instr_name_to_itype(&itype_list, g_data_stack_str); data_stack->value = create_stack(); struct Stack * instr_substack = tokens_to_stack(tokens, &itype_list); struct StackElem instr_substack_as_stack_elem = create_substack(instr_substack, 0); struct IType * instr_stack = instr_name_to_itype(&itype_list, g_instr_stack_str); instr_stack->value = create_stack(); stack_push(instr_stack->value, &instr_substack_as_stack_elem); LOG_DEBUG("%s (backwards for better readability):\n", g_instr_stack_str); log_stack_backwards(LOG_LVL_DEBUG, instr_stack->value, &itype_list); LOG_DEBUG("\n\n"); return itype_list; }
#ifndef PARSING_H #define PARSING_H #include "list.h" // Converts a list of tokens to a list of stacks, including the instruction // stack and data stack (which both have arbitrary indices within the list // but correct names). struct List parse(const struct List * tokens); #endif
#include "token.h" #include <stdlib.h> #include "log.h" #include "mem_tools.h" #define INVALID_INDIRECTION 0 struct Token create_token(enum TokenType type, int line) { struct Token tok; tok.type = type; tok.line = line; // Initialize data to invalid values. switch (tok.type) { case TOK_INSTR: tok.instr_name = NULL; break; case TOK_INDIRECTION: tok.indirection_level = INVALID_INDIRECTION; break; default: break; } return tok; } void destroy_token(struct Token * token) { if (token->type == TOK_INSTR && token->instr_name) { FREE(token->instr_name); } } void destroy_token_void_ptr(void * token) { destroy_token(token); } const char * token_type_as_string(enum TokenType token_type) { switch (token_type) { case TOK_WHITESPACE: return "WHITESPACE"; case TOK_COMMENT: return "COMMENT"; case TOK_INSTR: return "INSTRUCTION"; case TOK_INDIRECTION: return "INDIRECTION"; case TOK_STACK_OPEN: return "STACK OPEN"; case TOK_STACK_CLOSE: return "STACK CLOSE"; // This is not a default case because we want warnings if any enumerated // constants are missing. case TOK_INVALID: break; } return "(invalid token)"; } void log_token(int log_level, const struct Token * tok) { if (!LOGGABLE(log_level)) { return; } LOG(log_level, "%s", token_type_as_string(tok->type)); switch (tok->type) { case TOK_INSTR: if (tok->instr_name) { LOG(log_level, " (%s)", tok->instr_name); } break; case TOK_INDIRECTION: if (tok->indirection_level != INVALID_INDIRECTION) { LOG(log_level, " (%d)", tok->indirection_level); } break; default: break; } } static void log_newlines(int log_level, int start, int end) { for (int i = start; i <= end; ++i) { LOG(log_level, "\n%d\t", i); } } void log_token_list(int log_level, const struct List * list) { if (!LOGGABLE(log_level)) { return; } // Start at 0 so the first line will be logged. int line = 0; for (size_t i = 0; i < list->length; ++i) { const struct Token * curr_tok = get_list_elem_const(list, i); if (curr_tok->line > line) { log_newlines(log_level, line + 1, curr_tok->line); line = curr_tok->line; } else { LOG(log_level, ", "); } log_token(log_level, curr_tok); } }
#ifndef TOKEN_H #define TOKEN_H #include "list.h" enum TokenType { TOK_INVALID, TOK_WHITESPACE, TOK_COMMENT, TOK_INSTR, TOK_INDIRECTION, TOK_STACK_OPEN, TOK_STACK_CLOSE }; struct Token { enum TokenType type; union { char * instr_name; int indirection_level; }; int line; }; struct Token create_token(enum TokenType type, int line); void destroy_token(struct Token * token); void destroy_token_void_ptr(void * token); const char * token_type_as_string(enum TokenType token_type); void log_token(int log_level, const struct Token * tok); void log_token_list(int log_level, const struct List * list); #endif
// This file became a little uglier every time an error was fixed. // Now, I just have to pray that the errors are all gone. #include "running.h" #include <stdio.h> #include "settings.h" #include "stack.h" #include "mem_tools.h" #include "log.h" typedef enum ErrState (*builtin_func_t)(struct List * itype_list, instr_id_t data_stack_instr, instr_id_t instr_stack_instr); static struct Stack * get_stack_elem_val(struct StackElem * elem, struct List * itype_list) { switch (elem->type) { case STACK_ELEM_INVALID: break; case STACK_ELEM_INSTR: { if (is_builtin(elem->instr)) { return NULL; } struct IType * itype = get_list_elem(itype_list, elem->instr); return itype->value; } case STACK_ELEM_SUBSTACK: return elem->substack; case STACK_ELEM_STACK_REF: return elem->stack_ref; } ASSERT(false, "Invalid stack element type %d.", (int) elem->type); return NULL; } static void pop_from_instr_substack(struct Stack * instr_stack, struct Stack * instr_substack) { if (instr_substack->size == 0) { stack_pop(instr_stack); } else { stack_pop(instr_substack); } } static enum ErrState set_instr(struct List * itype_list, instr_id_t data_stack_instr, instr_id_t instr_stack_instr) { struct IType * data_stack_itype = get_list_elem(itype_list, data_stack_instr); struct Stack * data_stack = data_stack_itype->value; ASSERT_OR_HANDLE(data_stack->size >= 2, ERR_FAILURE, "%s instruction requires data stack with 2 elements or more, got %d.", g_builtin_names[0], data_stack->size); instr_id_t instr; struct StackElem * arg1 = stack_peek(data_stack, 0); ASSERT_OR_HANDLE(arg1->type == STACK_ELEM_INSTR, ERR_FAILURE, "First argument of %s instruction must be an instruction.", g_builtin_names[0]); ASSERT_OR_HANDLE(arg1->indirection_level == 0, ERR_FAILURE, "First argument of %s " "instruction cannot have any level of indirection.", g_builtin_names[0]); ASSERT_OR_HANDLE(!is_builtin(arg1->instr), ERR_FAILURE, "Cannot set a built-in"); instr = arg1->instr; struct StackElem * arg2 = stack_peek(data_stack, 1); struct Stack * new_val = get_stack_elem_val(arg2, itype_list); if (arg2->type == STACK_ELEM_SUBSTACK) { new_val = deepcopy_stack(new_val); } else { add_stack_reference(new_val); } // Must happen before a stack changes its value in case "data_stack" // changes. stack_pop(data_stack); stack_pop(data_stack); struct IType * itype = get_list_elem(itype_list, instr); if (itype->value) { remove_stack_reference(itype->value); } itype->value = new_val; return ERR_SUCCESS; } static enum ErrState unwrap_instr(struct List * itype_list, instr_id_t data_stack_instr, instr_id_t instr_stack_instr) { struct IType * data_stack_itype = get_list_elem(itype_list, data_stack_instr); struct Stack * data_stack = data_stack_itype->value; ASSERT_OR_HANDLE(data_stack->size >= 1, ERR_FAILURE, "%s instruction requires data stack with 2 elements or more, got %d.", g_builtin_names[1], data_stack->size); struct StackElem * arg = stack_peek(data_stack, 0); ASSERT_OR_HANDLE(!is_builtin(arg->instr), ERR_FAILURE, "Cannot unwrap a built-in"); struct Stack * arg_val = get_stack_elem_val(arg, itype_list); if (arg->type == STACK_ELEM_SUBSTACK) { arg_val = deepcopy_stack(arg_val); } else { add_stack_reference(arg_val); } struct StackElem new_stack_elem = create_stack_ref(arg_val, arg->indirection_level); stack_pop(data_stack); stack_push(data_stack, &new_stack_elem); return ERR_SUCCESS; } static enum ErrState if_instr(struct List * itype_list, instr_id_t data_stack_instr, instr_id_t instr_stack_instr) { struct IType * data_stack_itype = get_list_elem(itype_list, data_stack_instr); struct Stack * data_stack = data_stack_itype->value; ASSERT_OR_HANDLE(data_stack->size >= 1, ERR_FAILURE, "%s instruction requires data stack with 1 element or more, got %d.", g_builtin_names[2], data_stack->size); struct StackElem * arg = stack_peek(data_stack, 0); struct Stack * arg_val = get_stack_elem_val(arg, itype_list); ASSERT(!(arg->type == STACK_ELEM_INSTR && is_builtin(arg->instr)), ERR_FAILURE, "Cannot perform %s instruction on built-in.", g_builtin_names[2]); ASSERT(arg->type != STACK_ELEM_INSTR || arg_val, "Cannot perform %s instruction on uninitialized stack.", g_builtin_names[2]); if (arg_val->size == 0) { stack_pop(data_stack); stack_pop(data_stack); } else { stack_pop(data_stack); } return ERR_SUCCESS; } static enum ErrState step(struct List * itype_list, instr_id_t data_stack_instr, instr_id_t instr_stack_instr) { static const builtin_func_t builtin_funcs[] = { set_instr, unwrap_instr, if_instr }; struct IType * data_stack_itype = get_list_elem(itype_list, data_stack_instr); struct Stack * data_stack = data_stack_itype->value; struct IType * instr_stack_itype = get_list_elem(itype_list, instr_stack_instr); struct Stack * instr_stack = instr_stack_itype->value; if (instr_stack->size == 0) { return ERR_SUCCESS; } struct StackElem * instr_stack_top = stack_peek(instr_stack, 0); ASSERT_OR_HANDLE(instr_stack_top->type == STACK_ELEM_SUBSTACK, ERR_FAILURE, "Top value in instruction stack not a sub-stack."); struct Stack * instr_substack = instr_stack_top->substack; if (instr_substack->size == 0) { stack_pop(instr_stack); return ERR_UNFINISHED; } struct StackElem * substack_top = stack_peek(instr_substack, 0); if (substack_top->indirection_level > 0) { --substack_top->indirection_level; stack_push(data_stack, substack_top); ++substack_top->indirection_level; pop_from_instr_substack(instr_stack, instr_substack); return ERR_UNFINISHED; } if (substack_top->type == STACK_ELEM_SUBSTACK) { struct StackElem new_substack = create_substack(substack_top->substack, 0); stack_push(instr_stack, &new_substack); pop_from_instr_substack(instr_stack, instr_substack); return ERR_UNFINISHED; } if (substack_top->type == STACK_ELEM_STACK_REF) { struct StackElem stack_ref = create_stack_ref(substack_top->stack_ref, 0); stack_push(instr_stack, &stack_ref); pop_from_instr_substack(instr_stack, instr_substack); return ERR_UNFINISHED; } if (!is_builtin(substack_top->instr)) { struct IType * instr = get_list_elem(itype_list, substack_top->instr); ASSERT_OR_HANDLE(instr->value, ERR_FAILURE, "Cannot execute uninitialized instruction \"%s\".", instr->name); struct Stack * instr_val_copy = deepcopy_stack(instr->value); struct StackElem new_substack = create_substack(instr_val_copy, 0); stack_push(instr_stack, &new_substack); pop_from_instr_substack(instr_stack, instr_substack); return ERR_UNFINISHED; } enum Builtin builtin = id_to_builtin(substack_top->instr); pop_from_instr_substack(instr_stack, instr_substack); enum ErrState err_state; err_state = (builtin_funcs[builtin])(itype_list, data_stack_instr, instr_stack_instr); if (err_state == ERR_FAILURE) { return ERR_FAILURE; } else { return ERR_UNFINISHED; } } enum ErrState run(struct List * itype_list) { LOG_DEBUG("Running a program ...\n"); if (DEBUG_ON) { LOG_DEBUG("Press enter to execute a single step.\n"); } instr_id_t data_stack = find_instr_id(itype_list, g_data_stack_str); instr_id_t instr_stack = find_instr_id(itype_list, g_instr_stack_str); enum ErrState err_state; do { if (DEBUG_ON) { (void) getchar(); LOG_DEBUG("Stacks:\n"); log_itype_list(LOG_LVL_DEBUG, itype_list); LOG_DEBUG("\n\n"); } err_state = step(itype_list, data_stack, instr_stack); } while (err_state == ERR_UNFINISHED); if (DEBUG_ON) { (void) getchar(); } return err_state; }
#ifndef RUNNING_H #define RUNNING_H #include "list.h" enum ErrState { ERR_FAILURE, ERR_UNFINISHED, ERR_SUCCESS }; enum ErrState run(struct List * itype_list); #endif
#ifndef BYTE_H #define BYTE_H typedef unsigned char byte_t; #endif
#include <stdio.h> #include <stdlib.h> #include "os.h" #if OS == OS_WINDOWS #include <conio.h> #endif void proper_exit(int exit_code) { printf("\nProcess returned %d (0x%x).\n", exit_code, exit_code); #if OS == OS_WINDOWS printf("Press any key to continue ..."); (void) getch(); #else printf("Press enter to continue ..."); (void) getchar(); #endif exit(exit_code); }
// Tools for debugging and error handling, used in pcecs. #ifndef DEBUG_H #define DEBUG_H #include <stdlib.h> #include <time.h> // The compiler isn't able to correctly check for equality between // compile-time constants and enumerated constants, so these are // "#define"d. // None are 1 because then that value would correctly compare to // "DEBUG_MODE" if "DEBUG_MODE" was defined as nothing. #define DEBUG_MODE_OFF 0 #define DEBUG_MODE_ON 2 #define DEBUG_OFF (DEBUG_MODE == DEBUG_MODE_OFF) #define DEBUG_ON (DEBUG_MODE == DEBUG_MODE_ON) // Choose global debug mode here! // Each individual file might define "DEBUG_MODE" before directly // or indirectly including this file to have a different debug mode // than the default one. #ifndef DEBUG_MODE #ifdef NDEBUG #define DEBUG_MODE DEBUG_MODE_OFF #else #define DEBUG_MODE DEBUG_MODE_ON #endif #endif #if DEBUG_ON #define ASSERTIONS #define TIME_CODE(code) do { \ clock_t start = clock(); \ /* Making sure "code" is in curly brackets, because that's * a convention I use. */ \ do code while(0); \ clock_t end = clock(); \ \ double time_used = ((double) (end - start)) / CLOCKS_PER_SEC; \ LOG_DEBUG("\"" #code "\" took %lf seconds to execute.\n", time_used); \ } while (0) #define DEBUG_CODE(code) do code while(0) #elif DEBUG_OFF #define TIME(code) do code while(0) #define DEBUG_CODE(code) #else #error "Invalid debug mode." #endif #ifdef ASSERTIONS #define ASSERT(cond, msg, ...) do { \ if (!(cond)) { \ /* Print the function, line and file before printing "msg", so * that the important stuff will be printed even if the format * arguments crashes the program. */ \ LOG_FATAL_ERROR( \ "Assertion %s failed in function %s, " \ "line %d in file \"%s\": ", \ #cond, __func__, __LINE__, __FILE__); \ \ LOG_FATAL_ERROR(msg __VA_OPT__(,) __VA_ARGS__); \ LOG_FATAL_ERROR("\n"); \ \ proper_exit(EXIT_FAILURE); \ } \ } while (0) #define ASSERT_OR_HANDLE(cond, err_return_val, msg, ...) \ ASSERT(cond, msg __VA_OPT__(,) __VA_ARGS__) // My compiler does not have static_assert. #ifdef __GNUC__ // Only GNU is cool enough for __attribute((__unused__)). #define STATIC_ASSERT(cond) \ /* Trick I learned from Stack Overflow. * Now i can't find it :( * The array length is -1 (invalid) if "cond" is false. */ \ __attribute__((__unused__)) extern int x_static_assert_arr[(cond) ? 1 : -1] #else #define STATIC_ASSERT(cond) \ __attribute((__maybe_unused__)) extern int x_static_assert_arr[(cond) ? 1 : -1] #endif #else #define ASSERT(cond, msg, ...) #define ASSERT_OR_HANDLE(cond, err_return_val, msg, ...) do { \ if (!(cond)) { \ LOG_ERROR(msg __VA_OPT__(,) __VA_ARGS__); \ /* Assertions don't generally have a newline at the end, * but it's necessary when logging. */\ LOG_ERROR("\n"); \ return err_return_val; \ } \ } while(0) #define STATIC_ASSERT(cond) #endif // Make a user press a key before exiting. // When ran directly from Code::Blocks, the console prompts that by default, but // when ran as an ".exe"-file, it just exits. void proper_exit(int exit_code); // "log.h" itself relies on "debug.h". Therefore, we include log.h at the very end to // assure that all necessary info from this file is already initialized so "log.h" // can safely be initialized without any missing parts from this file. I admit this // sort of circular dependency is cumbersome, but at least it's quite loosely // coupled; this file only needs log for its assertion. #include "log.h" #endif
#include <stdio.h> #include "log.h" #include "mem_tools.h" const char * get_file_ext(const char * fname) { const char * ext = strrchr(fname, '.'); if (!ext) { LOG_ERROR("Couldn't find the file extension of \"%s\".\n", fname); return NULL; } return ext + 1; } static long get_file_length(FILE * fp) { fseek(fp, 0, SEEK_END); long flength = ftell(fp); fseek(fp, 0, SEEK_SET); return flength; } char * file_to_string(const char * fname) { LOG_INFO("Moving the contents of \"%s\" to a string ...\n", fname); FILE * fp = fopen(fname, "r"); if (!fp) { LOG_ERROR("Couldn't open \"%s\". Are you sure the file exists?\n", fname); return 0; } long flength = get_file_length(fp); // "true_flength" used in case the file contents take less space within // the program than without. char * fbuffer = ALLOC(char, flength + 1); long true_flength = fread(fbuffer, 1, flength, fp); fbuffer[true_flength] = 0; fclose(fp); char * fstring = ALLOC(char, strlen(fbuffer) + 1); strcpy(fstring, fbuffer); return fstring; }
#ifndef FILE_OPERATIONS_H #define FILE_OPERATIONS_H const char * get_file_ext(const char * fname); char * file_to_string(const char * fname); #endif
#include "list.h" #include "mem_tools.h" #define MIN_CAPACITY 2 #define CAPACITY_MULTIPLIER 1.5 // Defined as a macro so that it can be used for both const and non- // const lists, returning a const or non-const value, respectively. #define GET_ELEMENT(list, index, dest) do { \ ASSERT((index) >= 0 && (index) < (list)->length, \ "Index %d out of list range (0 - %d, inclusive).", \ index, (list)->length - 1); \ \ /* Void pointer arithmetics are apparently unportable. */ \ *(dest) = (byte_t *) (list)->contents + (index) * (list)->element_size; \ } while (0) static size_t min_valid_capacity(size_t length) { size_t capacity = MIN_CAPACITY; while (capacity < length) { capacity *= CAPACITY_MULTIPLIER; } return capacity; } struct List create_list(size_t element_size, void (*element_destructor)(void * element)) { struct List list; list.element_size = element_size; list.length = 0; list.capacity = min_valid_capacity(list.length); list.contents = ALLOC(byte_t, list.capacity * list.element_size); list.element_destructor = element_destructor; return list; } struct List create_invalid_list(void) { // An element size of 0, and possibly a capacity of 0 as well, // depending on the value of "MIN_CAPACITY"; is invalid. struct List list; list.element_size = 0; list.length = 0; list.capacity = 0; list.contents = NULL; list.element_destructor = NULL; return list; } bool list_is_valid(const struct List * list) { if (list->element_size == 0 || list->capacity < MIN_CAPACITY) { return false; } return true; } void * get_list_elem(struct List * list, size_t index) { void * element; GET_ELEMENT(list, index, &element); return element; } const void * get_list_elem_const(const struct List * list, size_t index) { const void * element; GET_ELEMENT(list, index, &element); return element; } // Returns "true" if and only if "list->capacity" isn't large enough, // or is unnecessarily large, for "new_length". static bool new_capacity_needed(size_t capacity, size_t length) { bool too_much_capacity = capacity >= length * 2 && capacity != 1; bool not_enough_capacity = capacity < length; return too_much_capacity || not_enough_capacity; } static void change_capacity(struct List * list, size_t new_length) { list->capacity = min_valid_capacity(new_length); REALLOC(&list->contents, byte_t, list->capacity * list->element_size); } static void set_length(struct List * list, size_t new_length) { if (new_capacity_needed(list->capacity, new_length)) { change_capacity(list, new_length); } list->length = new_length; } void list_append(struct List * list, const void * value) { set_length(list, list->length + 1); COPY_MEMORY((byte_t *) list->contents + (list->length - 1) * list->element_size, (byte_t *) value, byte_t, list->element_size); } void list_pop(struct List * list) { if (list->element_destructor) { void * last_elem = get_list_elem(list, list->length - 1); list->element_destructor(last_elem); } set_length(list, list->length - 1); } void list_remove(struct List * list, size_t index) { if (index == list->length - 1) { list_pop(list); return; } void * element_at_index = get_list_elem(list, index); if (list->element_destructor) { list->element_destructor(element_at_index); } void * element_after_index = get_list_elem(list, index + 1); size_t elements_to_move = list->length - index - 1; MOVE_MEMORY(element_at_index, element_after_index, byte_t, elements_to_move * list->element_size); set_length(list, list->length - 1); } void list_insert(struct List * list, size_t index, const void * value) { if (index == list->length - 1) { list_append(list, value); return; } set_length(list, list->length + 1); void * element_at_index = get_list_elem(list, index); void * element_after_index = get_list_elem(list, index + 1); size_t elements_to_move = list->length - index - 1; MOVE_MEMORY(element_after_index, element_at_index, byte_t, elements_to_move * list->element_size); COPY_MEMORY(element_at_index, value, byte_t, list->element_size); } struct List create_sublist(const struct List * list, size_t begin, size_t end) { struct List sublist = create_list(list->element_size, list->element_destructor); // Not the fastest way to do this, but it works. for (size_t i = begin; i < end; ++i) { const void * curr_elem = get_list_elem_const(list, i); list_append(&sublist, curr_elem); } return sublist; }
#ifndef LIST_H #define LIST_H #include <stdlib.h> #include "byte.h" #include "debug.h" #define LIST_FS "list (length %d)" #define LIST_FA(list) (int) (list).length struct List { size_t element_size; size_t length; size_t capacity; void * contents; void (*element_destructor)(void * element); }; struct List create_list(size_t element_size, void (*element_destructor)(void * element)); struct List create_invalid_list(void); bool list_is_valid(const struct List * list); void * get_list_elem(struct List * list, size_t index); const void * get_list_elem_const(const struct List * list, size_t index); void list_append(struct List * list, const void * value); void list_pop(struct List * list); void list_remove(struct List * list, size_t index); void list_insert(struct List * list, size_t index, const void * value); struct List create_sublist(const struct List * list, size_t begin, size_t end); #endif
// Implementation of logging tools. #include "log.h" #include <stdio.h> #include <stdarg.h> #include "debug.h" // The string printed before logging a line. static const char * log_lvl_as_str(int log_level) { switch (log_level) { case LOG_LVL_INVALID: break; case LOG_LVL_DEBUG: return "DEBUG"; case LOG_LVL_INFO: return "INFO"; case LOG_LVL_WARNING: return "WARNING"; case LOG_LVL_ERROR: return "ERROR"; case LOG_LVL_FATAL_ERROR: return "FATAL ERROR"; } // Cannot return a string containing "log_level" as an integer, // because then the string would have to be dynamically allocated // since it's not a literal, and the caller of this function would // have to free the allocation and it's just a whole lot of work. return "(invalid log level)"; } static void validate_log_level(int log_level) { switch (log_level) { case LOG_LVL_INVALID: break; case LOG_LVL_DEBUG: case LOG_LVL_INFO: case LOG_LVL_WARNING: case LOG_LVL_ERROR: case LOG_LVL_FATAL_ERROR: case LOG_LVL_CONSOLE: return; } ASSERT(false, "Invalid log level %d.", (int) log_level); } static void validate_log_mode(enum x_LogLvlVisibility log_mode) { switch (log_mode) { case LOG_VISIBILITY_INVALID: break; case LOG_VISIBILITY_SHOW_LEVEL: case LOG_VISIBILITY_HIDE_LEVEL: return; } ASSERT(false, "Invalid log mode %d.", (int) log_mode); } // Now we can output to any file we want! // To log directly to the console, make this function return "stdout". static FILE * get_output_file(void) { return stdout; // Uncomment to log to a separate file. /* static const char * fname = "log.txt"; FILE * file = fopen(fname, "w"); if (!file) { printf("Could not open \"%s\".\n", fname); } return file; */ } void x_log(int log_level, enum x_LogLvlVisibility log_mode, const char * message, ...) { static int s_last_log_level = LOG_LVL_INVALID; static FILE * output_file = NULL; if (!output_file) { output_file = get_output_file(); } if (!LOGGABLE(log_level)) { return; } if (log_level == LOG_LVL_CONSOLE) { va_list fmt_args; va_start(fmt_args, message); vprintf(message, fmt_args); va_end(fmt_args); return; } validate_log_level(log_level); validate_log_mode(log_mode); // If we're logging with the same level as before, we don't print // the level again. if (log_mode == LOG_VISIBILITY_SHOW_LEVEL && log_level != s_last_log_level) { fprintf(output_file, "%s: ", log_lvl_as_str(log_level)); // This variable should only be set if the log level is actually // logged. If we log "A" with "LOG_INFO", a newline with invisible // "LOG_DEBUG" and then "B" with "LOG_DEBUG", we want // "INFO: A // DEBUG: B" // not // "INFO: A // B" s_last_log_level = log_level; } va_list fmt_args; // Format "message" using "fmt_args" and print the result to "output_file". va_start(fmt_args, message); vfprintf(output_file, message, fmt_args); va_end(fmt_args); }
// Tools for logging, including a global minimum log level for the // limit to how unimportant messages we want to log. // "LOG_MODE" can be defined before including this file directly // or indirectly to change whether a certain file logs or not. // Same goes for "MIN_LOG_LEVEL" below. #ifndef LOG_H #define LOG_H #include <stdbool.h> #include "debug.h" // They're macros because preprocessor equality doesn't work with // enums. Stupid ANSI or Brian or Dennis or whomever. #define LOG_LVL_INVALID 0 #define LOG_LVL_DEBUG 1 #define LOG_LVL_INFO 2 #define LOG_LVL_WARNING 3 #define LOG_LVL_ERROR 4 #define LOG_LVL_FATAL_ERROR 5 #define LOG_LVL_CONSOLE 6 #define LOG_LVL_NONE 7 #ifndef MIN_LOG_LEVEL #if DEBUG_ON #define MIN_LOG_LEVEL LOG_LVL_DEBUG #else #define MIN_LOG_LEVEL LOG_LVL_WARNING #endif #endif // If this was a function and not a macro, we couldn't redefine // "LOG_MODE" and "MIN_LOG_LEVEL" in specific files, since the // same source file would do the logging anyway. #define LOG(log_level, message, ...) \ x_log(log_level, LOG_VISIBILITY_SHOW_LEVEL, message __VA_OPT__(,) __VA_ARGS__) // Log without showing the level at the beginning of the string. #define LOG_HIDE_LEVEL(log_level, message, ...) \ x_log(log_level, LOG_VISIBILITY_HIDE_LEVEL, message __VA_OPT__(,) __VA_ARGS__) #define LOGGABLE(lvl) ((lvl) >= MIN_LOG_LEVEL) // What a beauty ... #if LOGGABLE(LOG_LVL_DEBUG) #define LOG_DEBUG(...) LOG(LOG_LVL_DEBUG, __VA_ARGS__) #define LOG_DEBUG_HIDE_LEVEL(...) LOG_HIDE_LEVEL(LOG_LVL_DEBUG, __VA_ARGS__) #else #define LOG_DEBUG(...) #define LOG_DEBUG_HIDE_LEVEL(...) #endif #if LOGGABLE(LOG_LVL_INFO) #define LOG_INFO(...) LOG(LOG_LVL_INFO, __VA_ARGS__) #else #define LOG_INFO(...) #endif #if LOGGABLE(LOG_LVL_WARNING) #define LOG_WARNING(...) LOG(LOG_LVL_WARNING, __VA_ARGS__) #else #define LOG_WARNING(...) #endif #if LOGGABLE(LOG_LVL_ERROR) #define LOG_ERROR(...) LOG(LOG_LVL_ERROR, __VA_ARGS__) #else #define LOG_ERROR(...) #endif #if LOGGABLE(LOG_LVL_FATAL_ERROR) #define LOG_FATAL_ERROR(...) LOG(LOG_LVL_FATAL_ERROR, __VA_ARGS__) #else #define LOG_FATAL_ERROR(...) #endif enum x_LogLvlVisibility { LOG_VISIBILITY_INVALID, LOG_VISIBILITY_SHOW_LEVEL, LOG_VISIBILITY_HIDE_LEVEL }; void x_log(int log_level, enum x_LogLvlVisibility log_mode, const char * message, ...); #endif
// This file is quite poorly written. #include <stdlib.h> #include <string.h> #include <stdbool.h> #include "byte.h" #include "log.h" #define ALLOCATIONS_CAPACITY_MULTIPLIER 2 #define MAX_TYPE_NAME_LENGTH 25 #define MAX_FILE_NAME_LENGTH 20 // The amount of allocations logged next to each other. #define ALLOCATIONS_LOGGED_PER_LINE 3 // The maximum amount of digits assumed to be used by indices, lines and // sizes of allocations. #define MAX_ALLOC_IDX_DIGIT_COUNT 6 #define MAX_LINE_DIGIT_COUNT 6 #define MAX_ALLOCATION_SIZE_DIGITS_COUNT 6 // Information about a single allocation. struct Allocation { void * ptr; const char * file_name; int line; const char * type_name; size_t type_size; size_t count; }; // Global list of allocation structures. struct { struct Allocation * contents; size_t len; size_t capacity; } g_allocs; bool g_allocs_initialized = false; static size_t min_valid_allocations_capacity(size_t req_capacity) { size_t capacity = 1; while (capacity < req_capacity) { capacity *= ALLOCATIONS_CAPACITY_MULTIPLIER; } return capacity; } static void initialize_allocations(void) { ASSERT(!g_allocs_initialized, "Allocations already initialized."); g_allocs.len = 0; g_allocs.capacity = min_valid_allocations_capacity(0); g_allocs.contents = calloc(g_allocs.capacity, sizeof(struct Allocation)); g_allocs_initialized = true; } static struct Allocation * append_allocation(void) { ++g_allocs.len; if (g_allocs.len > g_allocs.capacity) { g_allocs.capacity = min_valid_allocations_capacity(g_allocs.len); g_allocs.contents = realloc(g_allocs.contents, sizeof(struct Allocation) * g_allocs.capacity); } return &g_allocs.contents[g_allocs.len - 1]; } static void pop_allocation_index(size_t idx) { // Move everything beyond "idx" one step back, so the allocation at // "idx" + 1 is moved to "idx", "idx" + 2 to "idx" + 1 and so on. struct Allocation * alloc_at_idx = &g_allocs.contents[idx]; size_t len_after_idx = g_allocs.len - (idx + 1); memmove(alloc_at_idx, alloc_at_idx + 1, len_after_idx * sizeof(struct Allocation)); // Now that the last part of the list is moved one step back, the // last element will have a copy in the second-last index. By // decreasing the length, we keep the second copy but get rid of // the initial one. --g_allocs.len; // Resize if capacity is far too large for length. if (g_allocs.len * ALLOCATIONS_CAPACITY_MULTIPLIER <= g_allocs.capacity) { g_allocs.capacity = min_valid_allocations_capacity(g_allocs.len); g_allocs.contents = realloc(g_allocs.contents, sizeof(struct Allocation) * g_allocs.capacity); } } static const char * file_name_without_path(const char * file_path) { // Some operating systems use slashes to split file paths, so we // look for the last slash and split it there. const char * last_slash = strrchr(file_path, '/'); if (last_slash) { // Add 1 to avoid including the slash. return last_slash + 1; } // Other operating systems used backslashes, so we try that too. const char * last_backslash = strrchr(file_path, '\\'); if (last_backslash) { // Add 1 to avoid including the backslash. return last_backslash + 1; } // If the file name doesn't have a slash or a backslash, it's safe // to assume that its path is simply its name. return file_path; } // The number of argument should generally be 3 or less, but then // this function isn't supposed to be called anyways, and if that // rule is broken the caller deserves a pita. void * x_allocate( const char * file_name, int line, const char * type_name, size_t type_size, size_t count) { if (!g_allocs_initialized) { initialize_allocations(); } void * allocated_memory = calloc(count, type_size); // When allocating 0 bytes, the result might be a NULL pointer. // Such pointers are not added to the allocation list, since // they may be generated multiple times and we'll have no idea // which one we're freeing (if any) when using FREE(NULL); if (allocated_memory == NULL && type_size * count == 0) { return allocated_memory; } ASSERT(allocated_memory, "Failed to allocate %d instances of \"%s\" in \"%s\", line %d.", (int) count, type_name, file_name, line); struct Allocation * allocation = append_allocation(); *allocation = (struct Allocation) { .ptr = allocated_memory, .file_name = file_name_without_path(file_name), .line = line, .type_name = type_name, .type_size = type_size, .count = count }; return allocated_memory; } void x_free(void * ptr, const char * file_name, int line) { // Real "free" ignores NULL pointers, so we do to. if (ptr == NULL) { return; } bool allocation_found = false; // Unused if assertions are disabled; this line prevents a warning. (void) allocation_found; for (size_t i = 0; i < g_allocs.len; ++i) { if (g_allocs.contents[i].ptr == ptr) { pop_allocation_index(i); allocation_found = true; break; } } ASSERT(allocation_found, "%p not allocated in \"%s\", line %d.", ptr, file_name, line); free(ptr); } void * x_realloc( void * ptr, const char * file_name, int line, const char * type_name, size_t type_size, size_t count) { struct Allocation * allocation = NULL; int allocation_idx = -1; for (size_t i = 0; i < g_allocs.len; ++i) { if (g_allocs.contents[i].ptr == ptr) { allocation = &g_allocs.contents[i]; allocation_idx = i; } } ASSERT(allocation || ptr == NULL, "%p not allocated in \"%s\", line %d.", ptr, file_name, line); if (allocation == NULL) { allocation = append_allocation(); allocation_idx = g_allocs.len - 1; } void * new_ptr = realloc(ptr, type_size * count); // "realloc(x, 0)" may return NULL, and in that case we don't want // the result in the allocation list since we would never know what // NULL pointer to remove (if any) when freeing NULL. if (new_ptr == NULL && type_size * count == 0) { pop_allocation_index(allocation_idx); return NULL; } ASSERT(new_ptr, "Failed to allocate %d instances of \"%s\" in \"%s\", line %d.", (int) count, type_name, file_name, line); *allocation = (struct Allocation) { .ptr = new_ptr, .file_name = file_name_without_path(file_name), .line = line, .type_name = type_name, .type_size = type_size, .count = count }; return new_ptr; } #ifdef ASSERTIONS static bool memory_overlaps(const void * mem1, const void * mem2, size_t mem_len) { const void * mem2_first = mem2; const void * mem2_last = (byte_t *) mem2 + mem_len - 1; for (size_t i = 0; i < mem_len; ++i) { void * mem1_at_idx = (byte_t *) mem1 + i; if (mem1_at_idx == mem2_first || mem1_at_idx == mem2_last) { return true; } } return false; } #endif void x_copy_memory(void * dest, const void * src, size_t len, const char * file_name, int line) { ASSERT(!memory_overlaps(dest, src, len), "Cannot copy overlapping memory in \"%s\", line %d.", file_name, line); memcpy(dest, src, len); } void x_move_memory(void * dest, const void * src, size_t len) { memmove(dest, src, len); } void x_log_allocations(void) { LOG_DEBUG("Allocations (pointer, file, line, type, count):\n"); for (size_t i = 0; i < g_allocs.len; ++i) { // If a full line of allocations has been logged, log a new // line. Otherwise, log a "|" separating allocations. if (i % ALLOCATIONS_LOGGED_PER_LINE == 0 && i > 0) { LOG_DEBUG("\n"); } else if (i > 0) { LOG_DEBUG(" | "); } LOG_DEBUG("%*d ", MAX_ALLOC_IDX_DIGIT_COUNT, i); LOG_DEBUG("%p ", g_allocs.contents[i].ptr); LOG_DEBUG("%-*.*s ", MAX_FILE_NAME_LENGTH, MAX_FILE_NAME_LENGTH, g_allocs.contents[i].file_name); LOG_DEBUG("%*d ", MAX_LINE_DIGIT_COUNT, g_allocs.contents[i].line); LOG_DEBUG("%-*.*s ", MAX_TYPE_NAME_LENGTH, MAX_TYPE_NAME_LENGTH, g_allocs.contents[i].type_name); LOG_DEBUG("%*d ", MAX_ALLOCATION_SIZE_DIGITS_COUNT, (int) g_allocs.contents[i].count); } LOG_DEBUG("\n"); } bool x_is_allocated(const void * ptr) { // NULL pointers can be freed, so they count as allocated. if (!ptr) { return true; } for (size_t i = 0; i < g_allocs.len; ++i) { if (g_allocs.contents[i].ptr == ptr) { return true; } } return false; } size_t x_mem_in_use(void) { size_t mem = 0; for (size_t i = 0; i < g_allocs.len; ++i) { mem += g_allocs.contents[i].type_size * g_allocs.contents[i].count; } return mem; }
// Tools for allocating and freeing memory, in addition to a couple // of other memory-related functions. #ifndef MEM_TOOLS_H #define MEM_TOOLS_H #include <string.h> #include "debug.h" // If DEBUG_ON, use safe but slow memory functions. Otherwise, use // the fast but dangerous ones. #if DEBUG_ON // Allocate "count" instances of type "type" #define ALLOC(type, count) \ (type *) x_allocate(__FILE__, __LINE__, #type, sizeof(type), count) // Free "ptr". Pointer must be "ALLOC"'d or equal to "NULL". #define FREE(ptr) x_free(ptr, __FILE__, __LINE__) // Assign "ptr", assumed to be allocated using "ALLOC", to // "count" instances of type "type" and free its previous // contents. A "REALLOC"'d pointer still counts as allocated // with "ALLOC". #define REALLOC(ptr, type, count) \ (void) (*(ptr) = x_realloc(*(ptr), __FILE__, __LINE__, #type, sizeof(type), count)) // Shallowly copy "count" instances of type "type" from "src" // to "dest". "src" and "dest" cannot have overlapping memory. #define COPY_MEMORY(dest, src, type, count) \ x_copy_memory(dest, src, sizeof(type) * (count), __FILE__, __LINE__) // Like "COPY_MEMORY", except it's safe to copy overlapping memory. #define MOVE_MEMORY(dest, src, type, count) \ x_move_memory(dest, src, sizeof(type) * (count)) // Log everything that's allocated using "ALLOC" and not yet // freed using "FREE". #define LOG_ALLOCATIONS() x_log_allocations() // Returns "true" if "ptr" is allocated using "ALLOC". #define IS_ALLOCATED(ptr) x_is_allocated(ptr) // Returns the number of bytes allocated using "ALLOC". #define MEM_IN_USE() x_mem_in_use() #else // Ya basic! // It's a human insult. // It's devastating. // You're devastated right now. // These macros does, in fact, use basic and really unsafe // routines. "DEBUG_MODE" is basically a choice between // a costly error checking and even more costly debugging, // which is exactly what we want since choices without // risks are not interesting choices. #define ALLOC(type, count) (type *) malloc(sizeof(type) * (count)) #define FREE(ptr) free(ptr) #define REALLOC(ptr, type, count) \ (void) (*(ptr) = realloc(*(ptr), sizeof(type) * (count))) #define COPY_MEMORY(dest, src, type, count) memcpy(dest, src, sizeof(type) * (count)) #define MOVE_MEMORY(dest, src, type, count) memmove(dest, src, sizeof(type) * (count)) #define LOG_ALLOCATIONS(log_level) // "MEM_IN_USE" and "IS_ALLOCATED" not defined since it's only accessible // when using special memory macros. #endif #define SET_MEMORY(dest, val, type, count) memset(dest, val, sizeof(type) * count) #define MEMORY_EQUALS(mem1, mem2, type, count) (memcmp(mem1, mem2, sizeof(type) * count) == 0) void * x_allocate( const char * file_name, int line, const char * type_name, size_t type_size, size_t count); void x_free(void * ptr, const char * file_name, int line); void * x_realloc( void * ptr, const char * file_name, int line, const char * type_name, size_t type_size, size_t count); void x_copy_memory(void * dest, const void * src, size_t len, const char * file_name, int line); void x_move_memory(void * dest, const void * src, size_t len); void x_log_allocations(void); bool x_is_allocated(const void * ptr); size_t x_mem_in_use(void); #endif
#ifndef OS_H #define OS_H #define OS_WINDOWS 1 #define OS_UNKNOWN 2 #ifdef WIN32 #define OS OS_WINDOWS #else #define OS OS_UNKNOWN #endif #endif
#include "settings.h" #include <ctype.h> #include "itype.h" const char * const g_minmod_file_ext = "[m]m"; const char * const g_builtin_names[BUILTINS_COUNT] = {"SET", "UNWRAP", "IF"}; const char g_stack_open_ch = '['; const char g_stack_close_ch = ']'; const char g_indirection_ch = '.'; const char * const g_comment_str = "--"; const char * g_instr_stack_str = "IS"; const char * g_data_stack_str = "DS"; bool is_valid_instr_ch(char ch) { return isalnum(ch) || ch == '_'; }
#ifndef SETTINGS_H #define SETTINGS_H #include <stdbool.h> extern const char * const g_minmod_file_ext; extern const char * const g_builtin_names[]; extern const char g_stack_open_ch; extern const char g_stack_close_ch; extern const char g_indirection_ch; extern const char * const g_comment_str; extern const char * g_instr_stack_str; extern const char * g_data_stack_str; bool is_valid_instr_ch(char ch); #endif

Compiling Program...

Command line arguments:
Standard Input: Interactive Console Text
×

                

                

Program is not being debugged. Click "Debug" button to start program in debug mode.

#FunctionFile:Line
VariableValue
RegisterValue
ExpressionValue