#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