mirror of
https://github.com/0x221E/ibuild.git
synced 2026-01-18 02:42:20 +00:00
Refactor: Fragment to linear memory style.
Remove: Allocator from parser, lexer, configuration
This commit is contained in:
311
ibuild.c
311
ibuild.c
@@ -9,7 +9,39 @@
|
||||
#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
|
||||
@@ -17,25 +49,60 @@ 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);
|
||||
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 error interface
|
||||
// func_name(): ERRMSG
|
||||
void log_error(const char* func, const char* fmt, ...)
|
||||
// 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;
|
||||
@@ -43,20 +110,20 @@ typedef struct
|
||||
char* target_exec;
|
||||
char* src_dir;
|
||||
char* version;
|
||||
char** src_files;
|
||||
} CompileOptions;
|
||||
char* dep_searcher;
|
||||
char* dep_searcher_flags;
|
||||
} Configuration;
|
||||
|
||||
void launch_compile(CompileOptions* co)
|
||||
void launch_compile(Configuration* co)
|
||||
{
|
||||
if(co == NULL) DIE("launch_compile() co has to be valid");
|
||||
|
||||
if(co->compiler_path == NULL) DIE("launch_compile() requires a valid compiler path");
|
||||
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("launch_compile() fork error");
|
||||
DIE("fork error");
|
||||
}
|
||||
else if (p==0)
|
||||
{
|
||||
@@ -67,19 +134,11 @@ void launch_compile(CompileOptions* co)
|
||||
}
|
||||
}
|
||||
|
||||
bool detect_config_file()
|
||||
{
|
||||
if(access(CONFIG_FILE, F_OK | R_OK) == 0)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/*** configuration lexer ***/
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char *src;
|
||||
Arena alloc;
|
||||
} Tokenizer;
|
||||
|
||||
typedef enum
|
||||
@@ -122,7 +181,7 @@ bool is_part_of_key(char s)
|
||||
|
||||
size_t skip_group(Tokenizer* t, bool (*func)(char))
|
||||
{
|
||||
if(func == NULL) DIE("skip_group() func invalid!");
|
||||
if(func == NULL) DIE("func invalid!");
|
||||
size_t len = 0;
|
||||
while(func(*t->src))
|
||||
{
|
||||
@@ -137,7 +196,7 @@ Token tokenize_identifier(Tokenizer *t)
|
||||
{
|
||||
char* temp = t->src;
|
||||
size_t len = skip_group(t, &is_part_of_key);
|
||||
char* buf = (char*)arena_alloc(&t->alloc, sizeof(char) * len + 1);
|
||||
char* buf = (char*)arena_alloc(&memory.strings, sizeof(char) * len + 1);
|
||||
memcpy(buf, temp, len);
|
||||
buf[len] = '\0';
|
||||
return (Token){T_IDENTIFIER, buf};
|
||||
@@ -157,7 +216,7 @@ Token tokenize_string(Tokenizer *t)
|
||||
|
||||
if(*t->src == '"') t->src++;
|
||||
|
||||
char* buf = (char*)arena_alloc(&t->alloc, (sizeof(char) * len) + 1);
|
||||
char* buf = (char*)arena_alloc(&memory.strings, (sizeof(char) * len) + 1);
|
||||
memcpy(buf, temp, len);
|
||||
buf[len] = '\0';
|
||||
return (Token){T_STRING, buf};
|
||||
@@ -198,6 +257,9 @@ typedef enum
|
||||
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
|
||||
@@ -216,10 +278,9 @@ typedef struct
|
||||
|
||||
typedef struct
|
||||
{
|
||||
Arena* tokens_alloc;
|
||||
Token* tokens;
|
||||
size_t loc;
|
||||
size_t cap;
|
||||
Arena alloc;
|
||||
} Parser;
|
||||
|
||||
typedef struct
|
||||
@@ -234,6 +295,8 @@ static KeyMap keyword_mappings[] = {
|
||||
{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},
|
||||
};
|
||||
|
||||
@@ -255,9 +318,10 @@ Token parser_peek(Parser* p, size_t o)
|
||||
{
|
||||
if (p->loc + o >= p->cap)
|
||||
{
|
||||
return ((Token*)p->tokens_alloc->start)[p->loc - 1];
|
||||
return p->tokens[p->loc - 1];
|
||||
}
|
||||
return ((Token*)p->tokens_alloc->start)[p->loc + o];
|
||||
Token t = p->tokens[p->loc + o];
|
||||
return t;
|
||||
}
|
||||
|
||||
Token parser_previous(Parser* p)
|
||||
@@ -267,23 +331,23 @@ Token parser_previous(Parser* p)
|
||||
DIE("parser logic error.");
|
||||
}
|
||||
|
||||
return ((Token*)p->tokens_alloc->start)[p->loc - 1];
|
||||
return p->tokens[p->loc - 1];
|
||||
}
|
||||
|
||||
Token parser_advance(Parser* p)
|
||||
{
|
||||
if(p->loc + 1 >= p->cap)
|
||||
{
|
||||
return ((Token*)p->tokens_alloc->start)[p->loc];
|
||||
return p->tokens[p->loc];
|
||||
}
|
||||
p->loc++;
|
||||
return ((Token*)p->tokens_alloc->start)[p->loc];
|
||||
return p->tokens[p->loc];
|
||||
}
|
||||
|
||||
Token parser_expect(Parser* p, TokenType tt, char* s)
|
||||
{
|
||||
if(p == NULL) DIE("parser_expect() p must not be NULL");
|
||||
if(p == NULL) DIE("parser_expect() s must not be NULL");
|
||||
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);
|
||||
@@ -296,7 +360,7 @@ Node* parser_expression(Parser* p)
|
||||
Token t = parser_advance(p);
|
||||
if(t.type == T_STRING)
|
||||
{
|
||||
Node* node = (Node*)arena_alloc(&p->alloc, sizeof(Node));
|
||||
Node* node = (Node*)arena_alloc(&memory.parser, sizeof(Node));
|
||||
node->key = K_NA;
|
||||
node->value = (void*)t.value;
|
||||
node->type = N_STRING;
|
||||
@@ -316,34 +380,24 @@ Node* parser_statement(Parser* p)
|
||||
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(&p->alloc, sizeof(Node));
|
||||
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");
|
||||
DIE("Syntax error: expected an identifier, got: %d", t.type);
|
||||
}
|
||||
}
|
||||
|
||||
size_t parser_parse(Parser* p, Node*** out)
|
||||
Node* parser_parse_next(Parser* p)
|
||||
{
|
||||
Arena statements_alloc = ARENA_CONST;
|
||||
|
||||
size_t size = 0;
|
||||
|
||||
while(p->loc < p->cap - 1)
|
||||
{
|
||||
Node** temp = (Node**)arena_alloc(&statements_alloc, sizeof(Node*));
|
||||
*temp = parser_statement(p);
|
||||
size++;
|
||||
return parser_statement(p);
|
||||
}
|
||||
|
||||
*out = (Node**)statements_alloc.start;
|
||||
|
||||
return size;
|
||||
}
|
||||
#ifdef DEBUG
|
||||
|
||||
void debug_parser(Node* n, int indent)
|
||||
{
|
||||
@@ -367,126 +421,107 @@ void debug_parser(Node* n, int indent)
|
||||
}
|
||||
}
|
||||
|
||||
/*** configuration file management ***/
|
||||
#endif
|
||||
|
||||
long get_file_size(FILE* fd)
|
||||
{
|
||||
if(fd == NULL) DIE("get_file_size() fd cannot be NULL.");
|
||||
if(fseek(fd, 0, SEEK_END) != 0) DIE("process_config() file exists, fseek SEEK_END fail.");
|
||||
long size = ftell(fd);
|
||||
if(fseek(fd, 0, SEEK_SET) != 0) DIE("process_config() file exists, fseek SEEK_SET fail.");
|
||||
return size;
|
||||
}
|
||||
/*** configuration process ***/
|
||||
|
||||
typedef struct
|
||||
{
|
||||
Arena tokens;
|
||||
} ConfigMemory;
|
||||
|
||||
int process_config(Node*** ast)
|
||||
{
|
||||
if(!detect_config_file())
|
||||
return 1;
|
||||
|
||||
printf("IBUILD Configuration file detected.\n");
|
||||
|
||||
ConfigMemory cm = {ARENA_CONST};
|
||||
|
||||
FILE* fd = fopen("IBUILD", "r");
|
||||
|
||||
if(fd == NULL) DIE("process_config() file exists however fd is NULL");
|
||||
|
||||
long fs = get_file_size(fd);
|
||||
|
||||
printf("IBUILD file of size: %d\n", fs);
|
||||
|
||||
if(fs < 2) return 0;
|
||||
|
||||
char* config_mem = malloc(fs + 1);
|
||||
|
||||
fread(config_mem, sizeof(char), fs, fd);
|
||||
config_mem[fs] = '\0';
|
||||
|
||||
printf("Config file:\n%s\n", config_mem);
|
||||
|
||||
size_t t_count = 0;
|
||||
|
||||
Tokenizer t = { .src = config_mem, .alloc = ARENA_CONST};
|
||||
while(1)
|
||||
{
|
||||
Token token = tokenizer_next(&t);
|
||||
if(token.type == T_INVALID) DIE("illegal token!");
|
||||
|
||||
Token* tm = arena_alloc(&cm.tokens, sizeof(Token));
|
||||
*tm = token;
|
||||
|
||||
t_count++;
|
||||
if(token.type == T_EOF) break;
|
||||
}
|
||||
|
||||
Parser parser = {.tokens_alloc = &cm.tokens, .loc = 0, .cap = t_count, .alloc = ARENA_CONST};
|
||||
|
||||
Node** statements = NULL;
|
||||
size_t statements_len = parser_parse(&parser, &statements);
|
||||
|
||||
if(statements == NULL) DIE("parser return invalid results");
|
||||
|
||||
for (size_t i = 0; i < statements_len; i++)
|
||||
debug_parser(statements[i], 0);
|
||||
|
||||
free(config_mem);
|
||||
arena_free(&cm.tokens);
|
||||
*ast = statements;
|
||||
return statements_len;
|
||||
}
|
||||
|
||||
/*** build configuration process ***/
|
||||
|
||||
void set_compiler_option(CompileOptions* co, Key k, char* val)
|
||||
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_compile_options(CompileOptions* co, Node* n)
|
||||
void populate_configuration_node(Configuration* co, Node* n)
|
||||
{
|
||||
switch(n->type)
|
||||
{
|
||||
case N_PAIR:
|
||||
Key k = n->key;
|
||||
set_compiler_option(co, k, (char*)((Node*)n->value)->value);
|
||||
set_configuration_option(co, k, (char*)((Node*)n->value)->value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void build(int len, Node** st)
|
||||
Configuration setup_configuration(int len, Node** st)
|
||||
{
|
||||
CompileOptions co;
|
||||
co.compiler_path = "/usr/bin/gcc";
|
||||
co.build_dir = ".";
|
||||
co.src_dir = ".";
|
||||
co.target_exec = "iexec";
|
||||
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_compile_options(&co, st[i]);
|
||||
populate_configuration_node(&config, st[i]);
|
||||
|
||||
launch_compile(&co);
|
||||
printf("Compilation finished.\n");
|
||||
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)
|
||||
{
|
||||
printf("Build version: 0.0.1\n");
|
||||
LOG_USER("Build version: %s", VERSION);
|
||||
Node** ast = NULL;
|
||||
int len_ast = process_config(&ast);
|
||||
|
||||
build(len_ast, ast);
|
||||
|
||||
Configuration c = process_config();
|
||||
launch_compile(&c);
|
||||
memory_free();
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user