970
src/dcfg.c
Normal file
970
src/dcfg.c
Normal file
@@ -0,0 +1,970 @@
|
||||
#include <dcfg.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "vendor/utf8proc.h"
|
||||
#include "vendor/vec.h"
|
||||
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
#include <assert.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
int64_t dcfg_strtoll(const char *s, char **end, int base) {
|
||||
size_t n = strlen(s);
|
||||
char *clean = malloc(n + 1);
|
||||
if (!clean) {
|
||||
errno = ENOMEM;
|
||||
if (end)
|
||||
*end = (char *)s;
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t w = 0;
|
||||
for (size_t r = 0; r < n && s[r]; ++r)
|
||||
if (s[r] != '_')
|
||||
clean[w++] = s[r];
|
||||
clean[w] = '\0';
|
||||
|
||||
char *tmp_end;
|
||||
int64_t v = strtoll(clean, &tmp_end, base);
|
||||
|
||||
if (end) {
|
||||
size_t consumed = (size_t)(tmp_end - clean);
|
||||
size_t i = 0, c = 0;
|
||||
while (i < n && c < consumed) {
|
||||
if (s[i] != '_')
|
||||
++c;
|
||||
++i;
|
||||
}
|
||||
*end = (char *)s + i;
|
||||
}
|
||||
free(clean);
|
||||
return v;
|
||||
}
|
||||
|
||||
double dcfg_strtod(const char *s, char **end) {
|
||||
size_t n = strlen(s);
|
||||
char *clean = malloc(n + 1);
|
||||
if (!clean) {
|
||||
errno = ENOMEM;
|
||||
if (end)
|
||||
*end = (char *)s;
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
size_t w = 0;
|
||||
for (size_t r = 0; r < n && s[r]; ++r)
|
||||
if (s[r] != '_')
|
||||
clean[w++] = s[r];
|
||||
clean[w] = '\0';
|
||||
|
||||
char *tmp_end;
|
||||
double v = strtod(clean, &tmp_end);
|
||||
|
||||
if (end) {
|
||||
size_t consumed = (size_t)(tmp_end - clean);
|
||||
size_t i = 0, c = 0;
|
||||
while (i < n && c < consumed) {
|
||||
if (s[i] != '_')
|
||||
++c;
|
||||
++i;
|
||||
}
|
||||
*end = (char *)s + i;
|
||||
}
|
||||
free(clean);
|
||||
return v;
|
||||
}
|
||||
|
||||
typedef dcfg_Value Value;
|
||||
typedef dcfg_Instance Instance;
|
||||
typedef dcfg_StringView StringView;
|
||||
|
||||
#define SV(cstr) ((StringView){.data = cstr, .size = strlen(cstr)})
|
||||
|
||||
static inline bool sv_eq(StringView a, StringView b) {
|
||||
if (a.size != b.size) {
|
||||
return false;
|
||||
}
|
||||
return memcmp(a.data, b.data, a.size) == 0;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
int row, col;
|
||||
} Location;
|
||||
|
||||
typedef struct {
|
||||
Location begin, end;
|
||||
} LocationRange;
|
||||
|
||||
typedef struct {
|
||||
StringView fp;
|
||||
LocationRange range;
|
||||
} SourceLocation;
|
||||
|
||||
typedef struct {
|
||||
StringView k;
|
||||
Value *v;
|
||||
} ValueObjectEntry;
|
||||
|
||||
typedef struct {
|
||||
ValueObjectEntry *entryv;
|
||||
size_t entryc;
|
||||
} ValueObject;
|
||||
|
||||
typedef struct {
|
||||
Value *valuev;
|
||||
size_t valuec;
|
||||
} ValueArray;
|
||||
|
||||
typedef struct {
|
||||
bool is_builtin;
|
||||
union {
|
||||
Value *(*bi)(Value **argv, size_t argc);
|
||||
Value *v;
|
||||
} v;
|
||||
} ValueFunction;
|
||||
|
||||
typedef struct {
|
||||
Value *function;
|
||||
Value **argv;
|
||||
size_t argc;
|
||||
} ValueFunctionCall;
|
||||
|
||||
struct dcfg_Value {
|
||||
dcfg_ValueType type;
|
||||
SourceLocation location;
|
||||
|
||||
union {
|
||||
int64_t i;
|
||||
double r;
|
||||
StringView s;
|
||||
StringView p;
|
||||
ValueObject o;
|
||||
ValueArray a;
|
||||
ValueFunction f;
|
||||
ValueFunctionCall c;
|
||||
} v;
|
||||
};
|
||||
|
||||
struct dcfg_Instance {
|
||||
pthread_mutex_t mtx;
|
||||
|
||||
dcfg_AllocFn alloc;
|
||||
dcfg_FreeFn free;
|
||||
|
||||
char last_error[256];
|
||||
};
|
||||
|
||||
#define ALLOC(sz) (instance->alloc((sz)))
|
||||
#define FREE(ptr) (instance->free((ptr)))
|
||||
|
||||
static void *alloc(size_t size) { return calloc(1, size); }
|
||||
|
||||
dcfg_Instance *dcfg_make_instance(dcfg_InstanceCreateInfo const *create_info) {
|
||||
assert(create_info);
|
||||
|
||||
dcfg_Instance *instance = calloc(1, sizeof(*instance));
|
||||
if (!instance) {
|
||||
return NULL;
|
||||
}
|
||||
pthread_mutex_init(&instance->mtx, NULL);
|
||||
|
||||
instance->alloc = create_info->alloc;
|
||||
instance->free = create_info->free;
|
||||
if (!instance->alloc) {
|
||||
instance->alloc = alloc;
|
||||
}
|
||||
if (!instance->free) {
|
||||
instance->free = free;
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
void dcfg_destroy_instance(dcfg_Instance *instance) {
|
||||
assert(instance);
|
||||
|
||||
pthread_mutex_lock(&instance->mtx);
|
||||
{ // De-init other instance things
|
||||
}
|
||||
pthread_mutex_unlock(&instance->mtx);
|
||||
|
||||
pthread_mutex_destroy(&instance->mtx);
|
||||
|
||||
free(instance);
|
||||
}
|
||||
|
||||
char const *dcfg_last_error(dcfg_Instance *instance) {
|
||||
assert(instance);
|
||||
|
||||
char const *ret = NULL;
|
||||
|
||||
pthread_mutex_lock(&instance->mtx);
|
||||
{
|
||||
if (!instance->last_error[0]) {
|
||||
pthread_mutex_unlock(&instance->mtx);
|
||||
return NULL;
|
||||
}
|
||||
ret = instance->last_error;
|
||||
}
|
||||
pthread_mutex_unlock(&instance->mtx);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
typedef enum {
|
||||
TokenType_Identifier,
|
||||
TokenType_Integer,
|
||||
TokenType_Real,
|
||||
TokenType_String,
|
||||
TokenType_Path,
|
||||
TokenType_LBracket,
|
||||
TokenType_RBracket,
|
||||
TokenType_LParen,
|
||||
TokenType_RParen,
|
||||
TokenType_LSquirly,
|
||||
TokenType_RSquirly,
|
||||
TokenType_Set,
|
||||
TokenType_Dot,
|
||||
TokenType_End,
|
||||
TokenType_Error,
|
||||
} TokenType;
|
||||
|
||||
typedef struct {
|
||||
TokenType type;
|
||||
SourceLocation location;
|
||||
|
||||
union {
|
||||
StringView id;
|
||||
int64_t i;
|
||||
double r;
|
||||
StringView s;
|
||||
StringView p;
|
||||
} v;
|
||||
} Token;
|
||||
|
||||
typedef struct {
|
||||
StringView source;
|
||||
StringView fp;
|
||||
|
||||
int32_t ch, next;
|
||||
int ch_len, next_len;
|
||||
|
||||
Location cursor;
|
||||
int offset;
|
||||
} Lexer;
|
||||
|
||||
static inline int32_t decode_cp(StringView src, int pos, int *len) {
|
||||
if (pos >= (int)src.size) {
|
||||
*len = 0;
|
||||
return -1;
|
||||
}
|
||||
return (int32_t)utf8proc_iterate((const uint8_t *)src.data + pos,
|
||||
(int)(src.size - pos), len);
|
||||
}
|
||||
|
||||
static inline bool is_space_cp(int32_t cp) {
|
||||
return (cp <= 0x7F &&
|
||||
(cp == ' ' || cp == '\t' || cp == '\r' || cp == '\n')) ||
|
||||
utf8proc_category(cp) == UTF8PROC_CATEGORY_ZS;
|
||||
}
|
||||
static inline bool is_alpha_cp(int32_t cp) {
|
||||
return (cp <= 0x7F && isalpha(cp)) ||
|
||||
(utf8proc_category(cp) >= UTF8PROC_CATEGORY_LU &&
|
||||
utf8proc_category(cp) <= UTF8PROC_CATEGORY_LO);
|
||||
}
|
||||
static inline bool is_digit_cp(int32_t cp) {
|
||||
return (cp <= 0x7F && isdigit(cp)) ||
|
||||
utf8proc_category(cp) == UTF8PROC_CATEGORY_ND;
|
||||
}
|
||||
static inline bool is_alnum_cp(int32_t cp) {
|
||||
return is_alpha_cp(cp) || is_digit_cp(cp);
|
||||
}
|
||||
static inline bool is_path_ch_cp(int32_t cp) {
|
||||
return cp == '_' || cp == '.' || cp == '/' || cp == '-' || cp == ':' ||
|
||||
is_alnum_cp(cp);
|
||||
}
|
||||
|
||||
#define is_space is_space_cp
|
||||
#define is_path_ch is_path_ch_cp
|
||||
#define isalpha_cp is_alpha_cp
|
||||
#define isdigit_cp is_digit_cp
|
||||
#define isalnum_cp is_alnum_cp
|
||||
|
||||
static void lex_advance(Lexer *lx) {
|
||||
if (lx->next == -1) {
|
||||
lx->ch = -1;
|
||||
return;
|
||||
}
|
||||
if (lx->ch == '\n') {
|
||||
++lx->cursor.row;
|
||||
lx->cursor.col = 1;
|
||||
} else {
|
||||
++lx->cursor.col;
|
||||
}
|
||||
|
||||
lx->offset += lx->ch_len;
|
||||
lx->ch = lx->next;
|
||||
lx->ch_len = lx->next_len;
|
||||
|
||||
lx->next = decode_cp(lx->source, lx->offset + lx->ch_len, &lx->next_len);
|
||||
}
|
||||
|
||||
static StringView lex_slice(Lexer *lx, int start, int end) {
|
||||
StringView sv;
|
||||
sv.data = lx->source.data + start;
|
||||
sv.size = (size_t)(end - start);
|
||||
return sv;
|
||||
}
|
||||
|
||||
static void skip_ws_and_comments(Lexer *lx) {
|
||||
for (;;) {
|
||||
while (is_space(lx->ch))
|
||||
lex_advance(lx);
|
||||
if (lx->ch == '#') {
|
||||
while (lx->ch != -1 && lx->ch != '\n')
|
||||
lex_advance(lx);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static Token make_token(TokenType t, Lexer *lx, int start, int end) {
|
||||
Token tk = {0};
|
||||
tk.type = t;
|
||||
tk.location.fp = lx->fp;
|
||||
tk.location.range.begin = lx->cursor;
|
||||
tk.location.range.end = lx->cursor;
|
||||
|
||||
switch (t) {
|
||||
case TokenType_Identifier:
|
||||
tk.v.id = lex_slice(lx, start, end);
|
||||
break;
|
||||
case TokenType_Integer:
|
||||
tk.v.i = dcfg_strtoll(lex_slice(lx, start, end).data, NULL, 10);
|
||||
break;
|
||||
case TokenType_Real:
|
||||
tk.v.r = dcfg_strtod(lex_slice(lx, start, end).data, NULL);
|
||||
break;
|
||||
case TokenType_String:
|
||||
tk.v.s = lex_slice(lx, start, end);
|
||||
break;
|
||||
case TokenType_Path:
|
||||
tk.v.p = lex_slice(lx, start, end);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return tk;
|
||||
}
|
||||
|
||||
static Token lex_number(Lexer *lx) {
|
||||
int start = lx->offset;
|
||||
bool real = false;
|
||||
|
||||
while (isdigit_cp(lx->ch) || lx->ch == '_')
|
||||
lex_advance(lx);
|
||||
if (lx->ch == '.') {
|
||||
real = true;
|
||||
lex_advance(lx);
|
||||
while (isdigit_cp(lx->ch) || lx->ch == '_')
|
||||
lex_advance(lx);
|
||||
}
|
||||
return make_token(real ? TokenType_Real : TokenType_Integer, lx, start,
|
||||
lx->offset);
|
||||
}
|
||||
|
||||
static Token lex_identifier(Lexer *lx) {
|
||||
int start = lx->offset;
|
||||
while (isalnum_cp(lx->ch) || lx->ch == '_')
|
||||
lex_advance(lx);
|
||||
return make_token(TokenType_Identifier, lx, start, lx->offset);
|
||||
}
|
||||
|
||||
static Token lex_string(Lexer *lx) {
|
||||
int quote = lx->ch;
|
||||
lex_advance(lx); // skip opening quote
|
||||
int start = lx->offset;
|
||||
while (lx->ch != quote && lx->ch != -1) {
|
||||
if (lx->ch == '\\' && lx->next != -1)
|
||||
lex_advance(lx); // simple escapes
|
||||
lex_advance(lx);
|
||||
}
|
||||
int end = lx->offset;
|
||||
if (lx->ch == quote)
|
||||
lex_advance(lx); // skip closing quote
|
||||
return make_token(TokenType_String, lx, start, end);
|
||||
}
|
||||
|
||||
static Token lex_path(Lexer *lx) {
|
||||
int start = lx->offset;
|
||||
for (;;) {
|
||||
if (lx->ch == '\\') {
|
||||
lex_advance(lx);
|
||||
if (lx->ch != -1)
|
||||
lex_advance(lx);
|
||||
} else if (is_path_ch(lx->ch)) {
|
||||
lex_advance(lx);
|
||||
} else
|
||||
break;
|
||||
}
|
||||
return make_token(TokenType_Path, lx, start, lx->offset);
|
||||
}
|
||||
|
||||
bool Lexer_init(Lexer *lx, StringView src, StringView fp) {
|
||||
memset(lx, 0, sizeof *lx);
|
||||
lx->source = src;
|
||||
lx->fp = fp;
|
||||
lx->cursor = (Location){1, 1};
|
||||
|
||||
lx->ch = decode_cp(src, 0, &lx->ch_len);
|
||||
lx->next = decode_cp(src, lx->ch_len, &lx->next_len);
|
||||
return true;
|
||||
}
|
||||
|
||||
Token Lexer_next(Lexer *lx) {
|
||||
skip_ws_and_comments(lx);
|
||||
int start_off = lx->offset;
|
||||
|
||||
if (lx->ch == -1) {
|
||||
Token tk = {.type = TokenType_End};
|
||||
tk.location.fp = lx->fp;
|
||||
return tk;
|
||||
}
|
||||
|
||||
if (lx->ch == '/' // "/foo"
|
||||
|| (lx->ch == '.' && lx->next == '/') // "./foo"
|
||||
|| (lx->ch == '.' && lx->offset + 2 < (int)lx->source.size &&
|
||||
lx->source.data[lx->offset + 1] == '.' &&
|
||||
lx->source.data[lx->offset + 2] == '/') // "../foo"
|
||||
|| (isalpha_cp(lx->ch) && lx->next == ':')) { // "C:/foo"
|
||||
return lex_path(lx);
|
||||
}
|
||||
|
||||
switch (lx->ch) {
|
||||
case '[':
|
||||
lex_advance(lx);
|
||||
return make_token(TokenType_LBracket, lx, start_off, lx->offset);
|
||||
case ']':
|
||||
lex_advance(lx);
|
||||
return make_token(TokenType_RBracket, lx, start_off, lx->offset);
|
||||
case '(':
|
||||
lex_advance(lx);
|
||||
return make_token(TokenType_LParen, lx, start_off, lx->offset);
|
||||
case ')':
|
||||
lex_advance(lx);
|
||||
return make_token(TokenType_RParen, lx, start_off, lx->offset);
|
||||
case '{':
|
||||
lex_advance(lx);
|
||||
return make_token(TokenType_LSquirly, lx, start_off, lx->offset);
|
||||
case '}':
|
||||
lex_advance(lx);
|
||||
return make_token(TokenType_RSquirly, lx, start_off, lx->offset);
|
||||
case '=':
|
||||
lex_advance(lx);
|
||||
return make_token(TokenType_Set, lx, start_off, lx->offset);
|
||||
case '.':
|
||||
lex_advance(lx);
|
||||
return make_token(TokenType_Dot, lx, start_off, lx->offset);
|
||||
case '"':
|
||||
case '\'':
|
||||
return lex_string(lx);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (isalpha_cp(lx->ch) || lx->ch == '_')
|
||||
return lex_identifier(lx);
|
||||
if (isdigit_cp(lx->ch))
|
||||
return lex_number(lx);
|
||||
|
||||
Token err = {.type = TokenType_Error};
|
||||
err.location.fp = lx->fp;
|
||||
lex_advance(lx);
|
||||
return err;
|
||||
}
|
||||
|
||||
typedef enum {
|
||||
ASTKind_Key, // Identifier + string combo
|
||||
ASTKind_Path,
|
||||
ASTKind_Boolean,
|
||||
ASTKind_Integer,
|
||||
ASTKind_Real,
|
||||
ASTKind_Block,
|
||||
ASTKind_Array,
|
||||
ASTKind_Function,
|
||||
ASTKind_MemberAccess,
|
||||
ASTKind_FunctionCall,
|
||||
} ASTKind;
|
||||
|
||||
typedef struct AST AST;
|
||||
|
||||
typedef struct {
|
||||
StringView *argv;
|
||||
AST *body;
|
||||
} ASTFunction;
|
||||
|
||||
typedef struct {
|
||||
AST **childv;
|
||||
} ASTArray;
|
||||
|
||||
typedef struct {
|
||||
AST *k; // Either MemberAccess or Key
|
||||
AST *v;
|
||||
} ASTBlock_Entry;
|
||||
|
||||
typedef struct {
|
||||
ASTBlock_Entry *entryv;
|
||||
} ASTBlock;
|
||||
|
||||
typedef struct {
|
||||
StringView *accessv;
|
||||
} ASTMemberAccess;
|
||||
|
||||
typedef struct {
|
||||
AST *function;
|
||||
AST **argv;
|
||||
} ASTFunctionCall;
|
||||
|
||||
struct AST {
|
||||
ASTKind kind;
|
||||
SourceLocation location;
|
||||
union {
|
||||
int64_t i;
|
||||
double r;
|
||||
StringView s;
|
||||
StringView p;
|
||||
bool b;
|
||||
ASTFunction f;
|
||||
ASTArray a;
|
||||
ASTBlock bl;
|
||||
ASTMemberAccess m;
|
||||
ASTFunctionCall fc;
|
||||
} v;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
Lexer *lexer;
|
||||
Instance *instance;
|
||||
|
||||
Token cur, next;
|
||||
} Parser;
|
||||
|
||||
bool Parser_next(Parser *parser) {
|
||||
parser->cur = parser->next;
|
||||
parser->next = Lexer_next(parser->lexer);
|
||||
bool ret = parser->next.type != TokenType_Error;
|
||||
if (!ret) {
|
||||
strcpy(parser->instance->last_error, "Failed to get parser token");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool Parser_init(Parser *out_parser, Lexer *lexer, Instance *instance) {
|
||||
out_parser->lexer = lexer;
|
||||
out_parser->instance = instance;
|
||||
if (!Parser_next(out_parser)) {
|
||||
return false;
|
||||
}
|
||||
if (!Parser_next(out_parser)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Parser_accept(Parser *parser, TokenType type,
|
||||
SourceLocation *out_location) {
|
||||
if (parser->cur.type == type) {
|
||||
if (out_location) {
|
||||
*out_location = parser->cur.location;
|
||||
}
|
||||
if (!Parser_next(parser)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#undef ALLOC
|
||||
#undef FREE
|
||||
#define ALLOC(sz) (parser->instance->alloc((sz)))
|
||||
#define FREE(ptr) (parser->instance->free((ptr)))
|
||||
|
||||
void AST_free_parser(AST *ast, Parser *parser) {
|
||||
assert(parser);
|
||||
if (!ast) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (ast->kind) {
|
||||
case ASTKind_Function:
|
||||
AST_free_parser(ast->v.f.body, parser);
|
||||
vector_free(ast->v.f.argv);
|
||||
break;
|
||||
case ASTKind_Array:
|
||||
for (size_t i = 0; i < vector_size(ast->v.a.childv); i++)
|
||||
AST_free_parser(ast->v.a.childv[i], parser);
|
||||
vector_free(ast->v.a.childv);
|
||||
break;
|
||||
case ASTKind_Block:
|
||||
for (size_t i = 0; i < vector_size(ast->v.bl.entryv); i++) {
|
||||
AST_free_parser(ast->v.bl.entryv[i].k, parser);
|
||||
AST_free_parser(ast->v.bl.entryv[i].v, parser);
|
||||
}
|
||||
vector_free(ast->v.bl.entryv);
|
||||
break;
|
||||
case ASTKind_MemberAccess:
|
||||
vector_free(ast->v.m.accessv);
|
||||
break;
|
||||
case ASTKind_FunctionCall:
|
||||
AST_free_parser(ast->v.fc.function, parser);
|
||||
for (size_t i = 0; i < vector_size(ast->v.fc.argv); i++)
|
||||
AST_free_parser(ast->v.fc.argv[i], parser);
|
||||
vector_free(ast->v.fc.argv);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
FREE(ast);
|
||||
}
|
||||
|
||||
AST *parser_parse_dot_access(Parser *parser, StringView *current) {
|
||||
assert(parser);
|
||||
assert(parser->cur.type == TokenType_Identifier ||
|
||||
parser->cur.type == TokenType_String);
|
||||
|
||||
StringView *accessv = vector_create();
|
||||
if (!accessv) {
|
||||
strcpy(parser->instance->last_error, "Failed to allocate vector");
|
||||
return NULL;
|
||||
}
|
||||
if (current) {
|
||||
vector_add(&accessv, *current);
|
||||
}
|
||||
|
||||
SourceLocation loc = parser->cur.location;
|
||||
SourceLocation last_loc = parser->cur.location;
|
||||
while (true) {
|
||||
if (parser->cur.type != TokenType_Identifier &&
|
||||
parser->cur.type != TokenType_String) {
|
||||
break;
|
||||
}
|
||||
|
||||
last_loc = parser->cur.location;
|
||||
vector_add(&accessv, parser->cur.v.s);
|
||||
|
||||
if (!Parser_next(parser)) {
|
||||
strcpy(parser->instance->last_error,
|
||||
"Failed to get next token in dot access parsing");
|
||||
vector_free(accessv);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!Parser_accept(parser, TokenType_Dot, NULL)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
assert(vector_size(accessv) != 0);
|
||||
|
||||
loc.range.end = last_loc.range.end;
|
||||
|
||||
AST *ast = ALLOC(sizeof(*ast));
|
||||
ast->kind = ASTKind_MemberAccess;
|
||||
ast->v.m.accessv = accessv;
|
||||
ast->location = loc;
|
||||
|
||||
if (vector_size(accessv) == 1) {
|
||||
ast->kind = ASTKind_Key;
|
||||
ast->v.s = accessv[0];
|
||||
vector_free(accessv);
|
||||
}
|
||||
return ast;
|
||||
}
|
||||
|
||||
AST *parser_parse_value(Parser *parser) {
|
||||
AST *ast = ALLOC(sizeof(*ast));
|
||||
ast->location = parser->cur.location;
|
||||
if (parser->cur.type == TokenType_Integer) {
|
||||
ast->kind = ASTKind_Integer;
|
||||
ast->v.i = parser->cur.v.i;
|
||||
} else if (parser->cur.type == TokenType_Real) {
|
||||
ast->kind = ASTKind_Real;
|
||||
ast->v.r = parser->cur.v.r;
|
||||
} else if (parser->cur.type == TokenType_String ||
|
||||
parser->cur.type == TokenType_Identifier) {
|
||||
ast->kind = ASTKind_Key;
|
||||
ast->v.s = parser->cur.v.s;
|
||||
|
||||
if (parser->next.type == TokenType_Dot) {
|
||||
FREE(ast);
|
||||
return parser_parse_dot_access(parser, NULL);
|
||||
} else if (parser->cur.type == TokenType_Identifier) {
|
||||
if (sv_eq(ast->v.s, SV("fn"))) {
|
||||
if (!Parser_next(parser)) {
|
||||
strcpy(parser->instance->last_error, "Failed to advance fn keyword");
|
||||
FREE(ast);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ast->kind = ASTKind_Function;
|
||||
ast->v.f.argv = vector_create();
|
||||
if (!ast->v.f.argv) {
|
||||
strcpy(parser->instance->last_error, "Failed to allocate vector");
|
||||
FREE(ast);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
if (Parser_accept(parser, TokenType_Set, NULL)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (parser->cur.type != TokenType_Identifier) {
|
||||
strcpy(parser->instance->last_error,
|
||||
"Expected identifier for function argument");
|
||||
vector_free(ast->v.f.argv);
|
||||
FREE(ast);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
vector_add(&ast->v.f.argv, parser->cur.v.s);
|
||||
}
|
||||
|
||||
ast->v.f.body = parser_parse_value(parser);
|
||||
if (!ast->v.f.body) {
|
||||
vector_free(ast->v.f.argv);
|
||||
FREE(ast);
|
||||
return NULL;
|
||||
}
|
||||
ast->location.range.end = ast->v.f.body->location.range.end;
|
||||
return ast;
|
||||
} else if (sv_eq(ast->v.s, SV("on")) || sv_eq(ast->v.s, SV("true"))) {
|
||||
ast->kind = ASTKind_Boolean;
|
||||
ast->v.b = true;
|
||||
} else if (sv_eq(ast->v.s, SV("off")) || sv_eq(ast->v.s, SV("false"))) {
|
||||
ast->kind = ASTKind_Boolean;
|
||||
ast->v.b = false;
|
||||
}
|
||||
}
|
||||
} else if (parser->cur.type == TokenType_Path) {
|
||||
ast->kind = ASTKind_Path;
|
||||
ast->v.p = parser->cur.v.p;
|
||||
} else if (Parser_accept(parser, TokenType_LBracket, NULL)) {
|
||||
ast->kind = ASTKind_Array;
|
||||
ast->v.a.childv = vector_create();
|
||||
if (!ast->v.a.childv) {
|
||||
strcpy(parser->instance->last_error, "Failed to allocate vector");
|
||||
FREE(ast);
|
||||
return NULL;
|
||||
}
|
||||
SourceLocation rbracket_loc;
|
||||
while (true) {
|
||||
if (Parser_accept(parser, TokenType_RBracket, &rbracket_loc)) {
|
||||
break;
|
||||
}
|
||||
|
||||
AST *value = parser_parse_value(parser);
|
||||
if (!value) {
|
||||
for (size_t i = 0; i < vector_size(ast->v.a.childv); i++) {
|
||||
AST_free_parser(ast->v.a.childv[i], parser);
|
||||
}
|
||||
vector_free(ast->v.a.childv);
|
||||
FREE(ast);
|
||||
return NULL;
|
||||
}
|
||||
vector_add(&ast->v.a.childv, value);
|
||||
}
|
||||
ast->location.range.end = rbracket_loc.range.end;
|
||||
return ast;
|
||||
} else if (Parser_accept(parser, TokenType_LSquirly, NULL)) {
|
||||
ast->kind = ASTKind_Block;
|
||||
ast->v.bl.entryv = vector_create();
|
||||
if (!ast->v.bl.entryv) {
|
||||
strcpy(parser->instance->last_error, "Failed to allocate vector");
|
||||
FREE(ast);
|
||||
return NULL;
|
||||
}
|
||||
SourceLocation rsquirly_loc;
|
||||
while (true) {
|
||||
if (Parser_accept(parser, TokenType_RSquirly, &rsquirly_loc)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (parser->cur.type != TokenType_Identifier &&
|
||||
parser->cur.type != TokenType_String) {
|
||||
strcpy(parser->instance->last_error,
|
||||
"Expected identifier or string for object key");
|
||||
vector_free(ast->v.bl.entryv);
|
||||
FREE(ast);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
AST *key = parser_parse_dot_access(parser, NULL);
|
||||
if (!Parser_accept(parser, TokenType_Set, NULL)) {
|
||||
strcpy(parser->instance->last_error, "Expected = after object key");
|
||||
vector_free(ast->v.bl.entryv);
|
||||
FREE(ast);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
AST *value = parser_parse_value(parser);
|
||||
if (!value) {
|
||||
AST_free_parser(key, parser);
|
||||
vector_free(ast->v.bl.entryv);
|
||||
FREE(ast);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
assert(key->kind == ASTKind_MemberAccess || key->kind == ASTKind_Key);
|
||||
ASTBlock_Entry entry = {
|
||||
.k = key,
|
||||
.v = value,
|
||||
};
|
||||
vector_add(&ast->v.bl.entryv, entry);
|
||||
}
|
||||
return ast;
|
||||
} else if (Parser_accept(parser, TokenType_LParen, NULL)) {
|
||||
ast->kind = ASTKind_FunctionCall;
|
||||
ast->v.fc.function = parser_parse_value(parser);
|
||||
ast->v.fc.argv = vector_create();
|
||||
if (!ast->v.fc.argv) {
|
||||
strcpy(parser->instance->last_error, "Failed to allocate vector");
|
||||
FREE(ast);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SourceLocation rparen_loc;
|
||||
while (true) {
|
||||
if (Parser_accept(parser, TokenType_RParen, &rparen_loc)) {
|
||||
break;
|
||||
}
|
||||
|
||||
AST *value = parser_parse_value(parser);
|
||||
if (!value) {
|
||||
for (size_t i = 0; i < vector_size(ast->v.fc.argv); i++) {
|
||||
AST_free_parser(ast->v.fc.argv[i], parser);
|
||||
}
|
||||
vector_free(ast->v.fc.argv);
|
||||
FREE(ast);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
vector_add(&ast->v.fc.argv, value);
|
||||
}
|
||||
|
||||
ast->location.range.end = rparen_loc.range.end;
|
||||
|
||||
return ast;
|
||||
} else {
|
||||
if (parser->cur.type == TokenType_End) {
|
||||
strcpy(parser->instance->last_error, "Expected value, got end of file");
|
||||
} else {
|
||||
strcpy(parser->instance->last_error, "Unexpected token for value");
|
||||
}
|
||||
FREE(ast);
|
||||
ast = NULL;
|
||||
}
|
||||
if (!Parser_next(parser)) {
|
||||
if (ast) {
|
||||
FREE(ast);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
return ast;
|
||||
}
|
||||
|
||||
// TODO: Print SourceLocation in last error.
|
||||
AST *Parser_parse(Parser *parser) { return parser_parse_value(parser); }
|
||||
|
||||
#undef ALLOC
|
||||
#undef FREE
|
||||
#define ALLOC(sz) (instance->alloc((sz)))
|
||||
#define FREE(ptr) (instance->free((ptr)))
|
||||
|
||||
dcfg_Value *dcfg_parse(dcfg_Instance *instance,
|
||||
dcfg_StringView const file_path) {
|
||||
char path_buf[file_path.size + 1] = {};
|
||||
memcpy(path_buf, file_path.data, file_path.size);
|
||||
|
||||
char *abs = realpath(path_buf, NULL);
|
||||
if (!abs) {
|
||||
snprintf(instance->last_error, sizeof(instance->last_error) - 1,
|
||||
"realpath: %s", strerror(errno));
|
||||
}
|
||||
StringView abs_sv = SV(abs);
|
||||
|
||||
FILE *fp = fopen(abs, "r");
|
||||
if (!fp) {
|
||||
FREE(abs);
|
||||
snprintf(instance->last_error, sizeof(instance->last_error) - 1,
|
||||
"fopen: %s", strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (fseek(fp, 0, SEEK_END)) {
|
||||
FREE(abs);
|
||||
fclose(fp);
|
||||
snprintf(instance->last_error, sizeof(instance->last_error) - 1,
|
||||
"fseek: %s", strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
long const size = ftell(fp);
|
||||
|
||||
if (fseek(fp, 0, SEEK_SET)) {
|
||||
FREE(abs);
|
||||
fclose(fp);
|
||||
snprintf(instance->last_error, sizeof(instance->last_error) - 1,
|
||||
"fseek: %s", strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
StringView str = {
|
||||
.size = size,
|
||||
};
|
||||
str.data = ALLOC(size);
|
||||
if (!fread((void *)str.data, str.size, 1, fp)) {
|
||||
FREE(abs);
|
||||
FREE((void *)str.data);
|
||||
fclose(fp);
|
||||
snprintf(instance->last_error, sizeof(instance->last_error) - 1,
|
||||
"fread: %s", strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Lexer lexer;
|
||||
if (!Lexer_init(&lexer, str, abs_sv)) {
|
||||
FREE(abs);
|
||||
FREE((void *)str.data);
|
||||
fclose(fp);
|
||||
snprintf(instance->last_error, sizeof(instance->last_error) - 1,
|
||||
"Could not init lexer");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Parser parser;
|
||||
Parser_init(&parser, &lexer, instance);
|
||||
|
||||
AST *ast = Parser_parse(&parser);
|
||||
if (!ast) {
|
||||
FREE(abs);
|
||||
FREE((void *)str.data);
|
||||
fclose(fp);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// FIXME: Get Value * from AST
|
||||
}
|
||||
|
||||
void dcfg_destroy(dcfg_Value *value) {}
|
||||
|
||||
// Libraries
|
||||
#include "vendor/utf8proc.c"
|
||||
#include "vendor/vec.c"
|
Reference in New Issue
Block a user