Files
ibuild/ibuild.c
0x221E a89589a634 Refactor: Fragment to linear memory style.
Remove: Allocator from parser, lexer, configuration
2026-01-13 23:32:03 +01:00

528 lines
10 KiB
C

#include "deps/utils/src/arena_alloc.h"
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <stdarg.h>
#define CONFIG_FILE "IBUILD"
#define MAX_STATEMENTS 50
#define MAX_TOKENS 200
#define DIE(fmt, ...) die_t(__func__, __LINE__, fmt, ##__VA_ARGS__)
#define LOG_ERROR(fmt, ...) log_write(__func__, fmt, ## __VA_ARGS__)
#define LOG_USER(fmt, ...) log_write(NULL, fmt, ## __VA_ARGS__)
/*** allocated memory ***/
// There shall never be another heap allocation outside of thy block.
// Each system and/or type of allocation shall have its own memory.
// There shall be no arena managed in this state, if the allocated memory is redundant and will not affect performance.
// Arenas shall never be arena_free'd during program execution, only before exit. Use arena_reset if you wish to achieve
// the same functionality without free() calls.
typedef struct
{
Arena file_contents;
Arena configuration;
Arena strings;
Arena parser;
} Memory;
static Memory memory;
void memory_free()
{
arena_free(&memory.file_contents);
arena_free(&memory.configuration);
arena_free(&memory.strings);
arena_free(&memory.parser);
}
/*** logging ***/
// Developer-reporting error interface, along with steps to reproduce the error.
// Include this log in your issues
void die_t(const char* func, int line, const char* fmt, ...)
{
va_list args;
va_start(args, fmt);
printf("ibuild exception(f:%s-l:%d): ", func, line);
vprintf(fmt, args);
printf("\n");
printf("******Last syscall error: %s\n", strerror(errno));
exit(1);
}
// User-reporting interface
// func_name: MSG
void log_write(const char* func, const char* fmt, ...)
{
if(fmt == NULL) DIE("fmt must not be null!");
va_list args;
va_start(args, fmt);
if(func != NULL)
printf("%s: ", func);
vprintf(fmt, args);
printf("\n");
va_end(args);
}
/*** file management ***/
long file_get_size(FILE* fd)
{
if(fd == NULL) DIE("fd cannot be NULL.");
if(fseek(fd, 0, SEEK_END) != 0) DIE("file exists, fseek SEEK_END fail.");
long size = ftell(fd);
if(fseek(fd, 0, SEEK_SET) != 0) DIE("file exists, fseek SEEK_SET fail.");
return size;
}
char* file_get_content(Arena* a, const char* fp)
{
if(fp == NULL) DIE("fp cannot be NULL");
FILE* fd = fopen(fp, "r");
long size = file_get_size(fd);
char* buf = (char*)arena_alloc(a, size + 1);
size_t ret = fread(buf, size, 1, fd);
if (ret != 1) DIE("fread returned %d", ret);
buf[size] = '\0';
return buf;
}
bool file_has_r_access(const char* f)
{
if(access(f, F_OK | R_OK) == 0)
return true;
return false;
}
/*** compilation process ***/
typedef struct
{
char* compiler_path;
char* build_dir;
char* target_exec;
char* src_dir;
char* version;
char* dep_searcher;
char* dep_searcher_flags;
} Configuration;
void launch_compile(Configuration* co)
{
if(co == NULL) DIE("co has to be valid");
if(co->compiler_path == NULL) DIE("requires a valid compiler path");
pid_t p = fork();
if(p<0)
{
DIE("fork error");
}
else if (p==0)
{
char* args[] = {co->compiler_path, "ibuild.c", "-o", co->target_exec, NULL};
printf("SET COMPILER: %s\n", co->compiler_path);
if((execvp(co->compiler_path, args)) == -1)
DIE("launch_compile() execvp error");
}
}
/*** configuration lexer ***/
typedef struct
{
char *src;
} Tokenizer;
typedef enum
{
T_INVALID = -1,
T_IDENTIFIER = 0,
T_STRING = 1,
T_IS = 2,
T_EOF = 3,
} TokenType;
typedef struct
{
TokenType type;
char* value;
int line;
} Token;
#define TOKEN_CONST {T_INVALID, NULL, 0}
bool is_alpha_uppercase(char s)
{
return (s >= 'A' && s <= 'Z');
}
bool is_alpha_lowercase(char s)
{
return (s >= 'a' && s <= 'z');
}
bool is_whitespace(char s)
{
return s == ' ' || s == '\t' || s == '\r' || s == '\n';
}
bool is_part_of_key(char s)
{
return is_alpha_uppercase(s) || s == '_';
}
size_t skip_group(Tokenizer* t, bool (*func)(char))
{
if(func == NULL) DIE("func invalid!");
size_t len = 0;
while(func(*t->src))
{
len++;
t->src += 1;
}
return len;
}
Token tokenize_identifier(Tokenizer *t)
{
char* temp = t->src;
size_t len = skip_group(t, &is_part_of_key);
char* buf = (char*)arena_alloc(&memory.strings, sizeof(char) * len + 1);
memcpy(buf, temp, len);
buf[len] = '\0';
return (Token){T_IDENTIFIER, buf};
}
Token tokenize_string(Tokenizer *t)
{
t->src += 1;
char* temp = t->src;
size_t len = 0;
while(*t->src != '"' && *t->src != '\0')
{
len++;
t->src += 1;
}
if(*t->src == '"') t->src++;
char* buf = (char*)arena_alloc(&memory.strings, (sizeof(char) * len) + 1);
memcpy(buf, temp, len);
buf[len] = '\0';
return (Token){T_STRING, buf};
}
Token tokenizer_next(Tokenizer* t)
{
if(is_whitespace(*t->src)) skip_group(t, &is_whitespace);
if(is_alpha_uppercase(*t->src))
{
return tokenize_identifier(t);
}
switch(*t->src)
{
case '"':
return tokenize_string(t);
case '=':
t->src++;
return (Token){T_IS, NULL};
}
if(*t->src == '\0') return (Token){T_EOF, NULL};
t->src++;
return (Token){T_INVALID, NULL};
}
/*** configuration parser ***/
typedef enum
{
K_NA = -1,
K_UNKNOWN = 0,
K_COMPILER_PATH = 1,
K_SRC_DIR = 2,
K_SRC_FILES = 3,
K_BUILD_DIR = 4,
K_TARGET_EXEC = 5,
K_DEP_SEARCHER = 6,
K_DEP_SEARCHER_FLAGS = 7,
K_VERSION = 8,
} Key;
typedef enum
{
N_STRING = 0,
N_ARRAY = 1,
N_PAIR = 2,
} NodeType;
typedef struct
{
NodeType type;
Key key;
void* value;
} Node;
typedef struct
{
Token* tokens;
size_t loc;
size_t cap;
} Parser;
typedef struct
{
Key key;
const char* value;
} KeyMap;
static KeyMap keyword_mappings[] = {
{K_COMPILER_PATH, "COMPILER_PATH"},
{K_SRC_DIR, "SRC_DIR"},
{K_SRC_FILES, "SRC_FILES"},
{K_BUILD_DIR, "BUILD_DIR"},
{K_TARGET_EXEC, "TARGET_EXEC"},
{K_DEP_SEARCHER, "DEP_SEARCHER"},
{K_DEP_SEARCHER_FLAGS, "DEP_SEARCHER_FLAGS"},
{K_UNKNOWN, NULL},
};
Key key_lookup(char* s)
{
if(s == NULL) return K_UNKNOWN;
for(const KeyMap* ptr = keyword_mappings; ptr->value != NULL; ptr++)
{
if(strcmp(ptr->value, s) == 0)
{
return ptr->key;
}
}
return K_UNKNOWN;
}
Token parser_peek(Parser* p, size_t o)
{
if (p->loc + o >= p->cap)
{
return p->tokens[p->loc - 1];
}
Token t = p->tokens[p->loc + o];
return t;
}
Token parser_previous(Parser* p)
{
if(p->loc - 1 < 0)
{
DIE("parser logic error.");
}
return p->tokens[p->loc - 1];
}
Token parser_advance(Parser* p)
{
if(p->loc + 1 >= p->cap)
{
return p->tokens[p->loc];
}
p->loc++;
return p->tokens[p->loc];
}
Token parser_expect(Parser* p, TokenType tt, char* s)
{
if(p == NULL) DIE("p must not be NULL");
if(p == NULL) DIE("s must not be NULL");
if(parser_peek(p, 1).type != tt)
DIE(s);
return parser_advance(p);
}
Node* parser_expression(Parser* p)
{
Token t = parser_advance(p);
if(t.type == T_STRING)
{
Node* node = (Node*)arena_alloc(&memory.parser, sizeof(Node));
node->key = K_NA;
node->value = (void*)t.value;
node->type = N_STRING;
parser_advance(p);
return node;
}
DIE("Syntax error: Invalid expression.");
}
Node* parser_statement(Parser* p)
{
Token t = parser_peek(p, 0);
if(t.type == T_IDENTIFIER)
{
Key k = key_lookup(t.value);
if(k == K_UNKNOWN) DIE("Syntax error: Unexpected identifer encountered");
parser_expect(p, T_IS, "Syntax error: Expected '=' after identifier");
Node* expression = parser_expression(p);
Node* node = (Node*)arena_alloc(&memory.parser, sizeof(Node));
node->key = k;
node->value = (void*)expression;
node->type = N_PAIR;
return node;
}
else
{
DIE("Syntax error: expected an identifier, got: %d", t.type);
}
}
Node* parser_parse_next(Parser* p)
{
return parser_statement(p);
}
#ifdef DEBUG
void debug_parser(Node* n, int indent)
{
for(int i = 0; i < indent; i++)
{
printf(" ");
}
switch(n->type)
{
case N_PAIR:
printf("PAIR STATEMENT");
printf(" Key: %10d\n", n->key);
if(n->value != NULL)
debug_parser((Node*)n->value, 2);
break;
case N_STRING:
printf("STRING EXPRESSION");
printf(" Value: %s\n", (char*)n->value);
break;
}
}
#endif
/*** configuration process ***/
void set_configuration_option(Configuration* co, Key k, char* val)
{
switch(k)
{
case K_COMPILER_PATH: co->compiler_path = val; break;
case K_BUILD_DIR: co->build_dir = val; break;
case K_TARGET_EXEC: co->target_exec = val; break;
case K_DEP_SEARCHER: co->dep_searcher = val; break;
case K_DEP_SEARCHER_FLAGS: co->dep_searcher_flags = val; break;
case K_VERSION: co->version = val; break;
default:
DIE("Invalid compiler option was supplied!");
}
}
void populate_configuration_node(Configuration* co, Node* n)
{
switch(n->type)
{
case N_PAIR:
Key k = n->key;
set_configuration_option(co, k, (char*)((Node*)n->value)->value);
break;
}
}
Configuration setup_configuration(int len, Node** st)
{
Configuration config;
config.compiler_path = "/usr/bin/gcc";
config.build_dir = ".";
config.src_dir = ".";
config.target_exec = "iexec";
config.dep_searcher = "/usr/bin/gcc";
config.dep_searcher_flags = "-MM";
for (size_t i = 0; i < len; i++)
populate_configuration_node(&config, st[i]);
LOG_USER("Configured IBUILD options.");
return config;
}
/*** configuration file management ***/
Configuration process_config()
{
if(!file_has_r_access(CONFIG_FILE))
{
return setup_configuration(0, NULL);
}
LOG_USER("IBUILD Configuration file detected.\n");
char* config_mem = file_get_content(&memory.file_contents, CONFIG_FILE);
size_t t_count = 0;
Token tokens[MAX_TOKENS];
Tokenizer t = { .src = config_mem };
while(1)
{
if(t_count > MAX_TOKENS) DIE("Maximum number of tokens reached! \n Could not read configuration file.");
Token token = tokenizer_next(&t);
if(token.type == T_INVALID) DIE("illegal token!");
tokens[t_count] = token;
t_count++;
if(token.type == T_EOF) break;
}
Parser parser = {.tokens = tokens, .loc = 0, .cap = t_count };
Node* statements[MAX_STATEMENTS];
size_t p_count = 0;
while(p_count < MAX_STATEMENTS && parser.loc < parser.cap - 1)
{
statements[p_count] = parser_parse_next(&parser);
p_count++;
}
#ifdef DEBUG
for (size_t i = 0; i < p_count; i++)
debug_parser(statements[i], 0);
#endif
return setup_configuration(p_count, statements);
}
int main(int argc, char** argv)
{
LOG_USER("Build version: %s", VERSION);
Node** ast = NULL;
Configuration c = process_config();
launch_compile(&c);
memory_free();
return 0;
}