Files
dcfg/src/dcfg.c
Slendi 570a5fab88
Some checks failed
CMake / ubuntu-latest - shared=OFF, pthread=OFF, posix=OFF (push) Failing after 17s
CMake / ubuntu-latest - shared=ON, pthread=OFF, posix=OFF (push) Failing after 18s
CMake / ubuntu-latest - shared=OFF, pthread=OFF, posix=ON (push) Failing after 17s
CMake / ubuntu-latest - shared=ON, pthread=OFF, posix=ON (push) Failing after 17s
CMake / ubuntu-latest - shared=OFF, pthread=ON, posix=ON (push) Failing after 17s
CMake / ubuntu-latest - shared=ON, pthread=ON, posix=ON (push) Failing after 18s
Add support for emoji identifiers
Signed-off-by: Slendi <slendi@socopon.com>
2025-08-04 02:28:47 +03:00

2090 lines
48 KiB
C

/*
* Copyright 2025 Slendi <slendi@socopon.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the “Software”), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <dcfg.h>
#include <inttypes.h>
#include <stdio.h>
#include "meta.h"
#include "vendor/utf8proc.h"
#include "vendor/vec.h"
#ifdef DCFG_POSIX_SUPPORT
# ifdef __sun
// FIXME: Fix this stupid shit!
# error "realpath() is dumb and stupid on sun. sorry not sorry."
# endif
# define _POSIX_C_SOURCE 200809L
#else
# ifdef _POSIX_C_SOURCE
# undef _POSIX_C_SOURCE
# endif
# define _POSIX_C_SOURCE 0L
#endif
#ifdef DCFG_PTHREAD_SUPPORT
# ifdef _POSIX_C_SOURCE
# undef _POSIX_C_SOURCE
# endif
# define _POSIX_C_SOURCE 200809L
# include <pthread.h>
extern int pthread_mutexattr_settype(pthread_mutexattr_t *, int);
#else
# if defined __USE_POSIX199506 || defined __USE_UNIX98
# else
typedef struct {
int unused;
} pthread_mutex_t;
typedef struct {
int unused;
} pthread_mutexattr_t;
# define PTHREAD_MUTEX_RECURSIVE_NP 0
# endif
static void pthread_mutex_init(pthread_mutex_t *, void *) { }
static void pthread_mutex_destroy(pthread_mutex_t *) { }
static void pthread_mutex_lock(pthread_mutex_t *) { }
static void pthread_mutex_unlock(pthread_mutex_t *) { }
pthread_mutexattr_init(pthread_mutexattr_t *);
static void pthread_mutexattr_settype(pthread_mutexattr_t *, int) { }
#endif
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
int64_t dcfg_strtoll(const char *s, char **end, int base)
{
char const *p = s;
while (isspace((unsigned char)*p))
p++;
bool neg = false;
if (*p == '+' || *p == '-') {
neg = (*p == '-');
p++;
}
if (base == 0) {
if (*p == '0') {
if (p[1] == 'x' || p[1] == 'X') {
base = 16;
p += 2;
} else {
base = 8;
p++;
}
} else {
base = 10;
}
} else if (base == 16 && p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) {
p += 2;
}
int64_t val = 0;
char const *q = p, *last = p;
while (*q) {
char c = *q;
if (c == '_' || c == '\'') {
q++;
continue;
}
int d;
if (c >= '0' && c <= '9')
d = c - '0';
else if (c >= 'a' && c <= 'z')
d = c - 'a' + 10;
else if (c >= 'A' && c <= 'Z')
d = c - 'A' + 10;
else
break;
if (d >= base)
break;
val = val * base + d;
last = q + 1;
q++;
}
if (last == p) {
if (end)
*end = (char *)s;
return 0;
}
if (end)
*end = (char *)last;
return neg ? -val : val;
}
double dcfg_strtod(char const *s, char **end)
{
char const *p = s;
while (isspace((unsigned char)*p))
p++;
bool neg = false;
if (*p == '+' || *p == '-') {
neg = (*p == '-');
p++;
}
char const *fd = strchr(p, '.');
char const *fc = strchr(p, ',');
char dec = '.';
if (!fd && fc)
dec = ',';
else if (fd && fc && fc < fd)
dec = ',';
int64_t ip = 0;
double frac = 0.0, div = 1.0;
bool any = false, in_frac = false;
bool in_exp = false, exp_any = false;
int exp_sign = 1, exp_val = 0;
char const *last = p;
size_t i = p - s;
for (;; ++i) {
char c = s[i];
if (!c)
break;
if (!in_exp) {
if (c == '_' || c == '\'')
continue;
if (!in_frac && c == dec) {
in_frac = true;
last = s + i + 1;
continue;
}
if (!in_frac && (c == '.' || c == ','))
continue;
if (c >= '0' && c <= '9') {
any = true;
int d = c - '0';
if (!in_frac)
ip = ip * 10 + d;
else {
frac = frac * 10 + d;
div *= 10.0;
}
last = s + i + 1;
continue;
}
if ((c == 'e' || c == 'E') && any) {
in_exp = true;
last = s + i + 1;
continue;
}
break;
} else {
if ((c == '+' || c == '-') && !exp_any) {
if (c == '-')
exp_sign = -1;
last = s + i + 1;
continue;
}
if (c >= '0' && c <= '9') {
exp_any = true;
exp_val = exp_val * 10 + (c - '0');
last = s + i + 1;
continue;
}
break;
}
}
if (!any) {
if (end)
*end = (char *)s;
return 0.0;
}
double result = (double)ip + frac / div;
if (in_exp && exp_any)
result *= pow(10.0, exp_sign * exp_val);
if (neg)
result = -result;
if (end)
*end = (char *)last;
return result;
}
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;
bool key_allocated;
} ValueObjectEntry;
typedef struct {
ValueObjectEntry *entryv;
} ValueObject;
typedef struct {
Value **valuev;
} ValueArray;
typedef struct {
struct Environment *closure;
StringView *argv;
Value *body;
} ValueFunctionF;
typedef struct {
bool is_builtin;
union {
dcfg_BuiltIn bi;
ValueFunctionF f;
} v;
} ValueFunction;
typedef struct {
Value *function;
Value **argv;
} ValueFunctionCall;
typedef struct {
StringView *accessv;
} ValueMemberAccess;
struct dcfg_Value {
Instance *instance;
dcfg_ValueType type;
SourceLocation location;
union {
int64_t i;
double r;
bool b;
StringView s;
StringView p;
ValueObject o;
ValueArray a;
ValueFunction f;
ValueMemberAccess ma;
ValueFunctionCall c;
} v;
};
typedef struct Environment {
struct Environment *parent;
StringView *argv;
Value **argvv;
} Environment;
static bool environment_create(Environment *out_env, Environment *parent)
{
out_env->argv = vector_create();
out_env->argvv = vector_create();
out_env->parent = parent;
return true;
}
static bool environment_lookup(Environment *e, StringView name, Value **out)
{
for (; e; e = e->parent) {
for (size_t i = 0; i < vector_size(e->argv); i++) {
if (sv_eq(e->argv[i], name)) {
*out = e->argvv[i];
return true;
}
}
}
return false;
}
static void environment_destroy(Environment *env, bool destroy_values)
{
assert(env);
if (env->argv) {
vector_free(env->argv);
env->argv = NULL;
}
if (env->argvv) {
if (destroy_values) {
for (size_t i = 0; i < vector_size(env->argvv); i++) {
dcfg_destroy(env->argvv[i]);
}
}
vector_free(env->argvv);
env->argvv = NULL;
}
}
struct dcfg_Instance {
pthread_mutex_t mtx;
dcfg_AllocFn alloc;
dcfg_FreeFn free;
dcfg_RealpathFn realpath;
dcfg_FopenFn fopen;
dcfg_FseekFn fseek;
dcfg_FtellFn ftell;
char last_error[256];
StringView *sourcev; // Strings should be freed.
StringView *source_pathv; // Strings should be freed.
Environment *environmentv;
int *environment_referencesv;
};
#define ALLOC(sz) (instance->alloc((sz)))
#define FREE(ptr) (instance->free((ptr)))
static void *alloc(size_t size) { return calloc(1, size); }
#ifdef DCFG_POSIX_SUPPORT
static char *realpath_(char const *s) { return realpath(s, NULL); }
void *fopen_(char const *f, char const *a) { return fopen(f, a); }
int fseek_(void *f, size_t p, int o) { return fseek(f, p, o); }
long ftell_(void *f) { return ftell(f); }
#endif
dcfg_Version dcfg_get_version(void)
{
return (((uint64_t)VERSION_MAJOR & 0xFFFFULL) << 48)
| (((uint64_t)VERSION_MINOR & 0xFFFFULL) << 32)
| ((uint64_t)VERSION_PATCH & 0xFFFFFFFFULL);
}
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_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE_NP);
pthread_mutex_init(&instance->mtx, &attr);
instance->alloc = create_info->alloc;
instance->free = create_info->free;
if (!instance->alloc) {
instance->alloc = alloc;
}
if (!instance->free) {
instance->free = free;
}
#ifdef DCFG_POSIX_SUPPORT
if (!instance->realpath) {
instance->realpath = realpath_;
}
if (!instance->fopen) {
instance->fopen = fopen_;
}
if (!instance->fseek) {
instance->fseek = fseek_;
}
if (!instance->ftell) {
instance->ftell = ftell_;
}
#endif
assert(instance->alloc);
assert(instance->free);
assert(instance->realpath);
assert(instance->fopen);
assert(instance->fseek);
assert(instance->ftell);
instance->sourcev = vector_create();
instance->source_pathv = vector_create();
instance->environmentv = vector_create();
instance->environment_referencesv = vector_create();
return instance;
}
void dcfg_destroy_instance(dcfg_Instance *instance)
{
assert(instance);
for (size_t i = 0; i < vector_size(instance->environment_referencesv);
i++) {
if (instance->environment_referencesv[i] > 0) {
// environment_destroy(&instance->environmentv[i], true);
}
}
vector_free(instance->environment_referencesv);
for (size_t i = 0; i < vector_size(instance->environmentv); i++) { }
vector_free(instance->environmentv);
for (size_t i = 0; i < vector_size(instance->source_pathv); i++) {
free((void *)instance->source_pathv[i].data);
}
vector_free(instance->source_pathv);
for (size_t i = 0; i < vector_size(instance->sourcev); i++) {
free((void *)instance->sourcev[i].data);
}
vector_free(instance->sourcev);
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;
}
int32_t cp;
int bytes = utf8proc_iterate(
(uint8_t const *)src.data + pos, (int)(src.size - pos), &cp);
if (bytes < 0) {
*len = 0;
return -1;
}
*len = bytes;
return cp;
}
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)
{
utf8proc_category_t cat = utf8proc_category(cp);
return (cp <= 0x7F && isalpha(cp))
|| (cat >= UTF8PROC_CATEGORY_LU && cat <= UTF8PROC_CATEGORY_LO)
|| (cat == UTF8PROC_CATEGORY_SO);
}
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;
}
if (lx->ch == '/' && lx->next == '/') {
lex_advance(lx);
lex_advance(lx);
while (lx->ch != -1 && lx->ch != '\n')
lex_advance(lx);
continue;
}
if (lx->ch == '/' && lx->next == '*') {
lex_advance(lx);
lex_advance(lx);
while (lx->ch != -1) {
if (lx->ch == '*' && lx->next == '/') {
lex_advance(lx);
lex_advance(lx);
break;
}
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 == '_' || lx->ch == '\'')
lex_advance(lx);
if (lx->ch == '.' || lx->ch == ',') {
real = true;
lex_advance(lx);
while (isdigit_cp(lx->ch) || 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;
typedef struct {
bool is_str;
StringView s;
} ASTKey;
struct AST {
ASTKind kind;
SourceLocation location;
union {
int64_t i;
double r;
ASTKey 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);
if (parser->next.type == TokenType_Error) {
strcpy(parser->instance->last_error, "Failed to get parser token");
}
return true;
}
bool Parser_init(Parser *out_parser, Lexer *lexer, Instance *instance)
{
memset(out_parser, 0, sizeof(*out_parser));
out_parser->lexer = lexer;
out_parser->instance = instance;
out_parser->next = Lexer_next(lexer);
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, StringView, *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, StringView, 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.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.s = parser->cur.v.s;
ast->v.s.is_str = parser->cur.type == TokenType_String;
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.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, StringView, parser->cur.v.s);
if (!Parser_next(parser)) {
vector_free(ast->v.f.argv);
FREE(ast);
return NULL;
}
}
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.s, SV("on"))
|| sv_eq(ast->v.s.s, SV("true"))) {
ast->kind = ASTKind_Boolean;
ast->v.b = true;
} else if (sv_eq(ast->v.s.s, SV("off"))
|| sv_eq(ast->v.s.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, AST *, 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");
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);
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");
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);
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, ASTBlock_Entry, 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, AST *, 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)))
static Value *ensure_child_obj(Instance *inst, Value *parent, StringView key)
{
ValueObject *obj = &parent->v.o;
for (size_t i = 0; i < vector_size(obj->entryv); ++i) {
if (sv_eq(obj->entryv[i].k, key)) {
return obj->entryv[i].v;
}
}
Value *child = inst->alloc(sizeof *child);
child->instance = inst;
child->type = dcfg_ValueType_Object;
child->v.o.entryv = vector_create();
ValueObjectEntry e = { .k = key, .v = child, .key_allocated = false };
vector_add(&obj->entryv, ValueObjectEntry, e);
return child;
}
Value *ast_to_value(dcfg_Instance *instance, AST *root)
{
Value *value = ALLOC(sizeof(*value));
value->instance = instance;
if (root->kind == ASTKind_Key) {
if (root->v.s.is_str) {
value->type = dcfg_ValueType_String;
value->v.s = root->v.s.s;
} else {
value->type = dcfg_ValueType_MemberAccess;
value->v.ma.accessv = vector_create();
vector_add(value->v.ma.accessv, StringView, root->v.s.s);
}
} else if (root->kind == ASTKind_Path) {
value->type = dcfg_ValueType_Path;
value->v.p = root->v.p;
} else if (root->kind == ASTKind_Boolean) {
value->type = dcfg_ValueType_Boolean;
value->v.b = root->v.b;
} else if (root->kind == ASTKind_Integer) {
value->type = dcfg_ValueType_Integer;
value->v.i = root->v.i;
} else if (root->kind == ASTKind_Real) {
value->type = dcfg_ValueType_Real;
value->v.r = root->v.r;
} else if (root->kind == ASTKind_Block) {
value->type = dcfg_ValueType_Object;
value->v.o.entryv = vector_create();
for (size_t i = 0; i < vector_size(root->v.bl.entryv); ++i) {
ASTBlock_Entry *e = &root->v.bl.entryv[i];
Value *rhs = ast_to_value(instance, e->v);
if (!rhs) {
for (size_t j = 0; j < vector_size(value->v.o.entryv); j++) {
dcfg_destroy(value->v.o.entryv[j].v);
}
vector_free(value->v.o.entryv);
FREE(value);
return NULL;
}
Value *target = value;
StringView field;
if (e->k->kind == ASTKind_Key) {
field = e->k->v.s.s;
} else {
ASTMemberAccess *ma = &e->k->v.m;
for (size_t j = 0; j + 1 < vector_size(ma->accessv); ++j)
target = ensure_child_obj(instance, target, ma->accessv[j]);
field = ma->accessv[vector_size(ma->accessv) - 1];
}
if (target->type != dcfg_ValueType_Object) {
strcpy(instance->last_error,
"Previous declaration is not an object");
dcfg_destroy(rhs);
for (size_t j = 0; j < vector_size(value->v.o.entryv); j++)
dcfg_destroy(value->v.o.entryv[j].v);
vector_free(value->v.o.entryv);
FREE(value);
return NULL;
}
ValueObject *obj = &target->v.o;
bool replaced = false;
for (size_t j = 0; j < vector_size(obj->entryv); ++j) {
if (sv_eq(obj->entryv[j].k, field)) {
dcfg_destroy(obj->entryv[j].v);
obj->entryv[j].v = rhs;
replaced = true;
break;
}
}
if (!replaced) {
ValueObjectEntry new_e
= { .k = field, .v = rhs, .key_allocated = false };
vector_add(&obj->entryv, ValueObjectEntry, new_e);
}
}
} else if (root->kind == ASTKind_Array) {
value->type = dcfg_ValueType_Array;
value->v.a.valuev = vector_create();
for (size_t i = 0; i < vector_size(root->v.a.childv); i++) {
Value *v = ast_to_value(instance, root->v.a.childv[i]);
if (!v) {
for (size_t i = 0; i < vector_size(value->v.a.valuev); i++) {
dcfg_destroy(value->v.a.valuev[i]);
}
vector_free(value->v.a.valuev);
FREE(value);
return NULL;
}
vector_add(&value->v.a.valuev, Value *, v);
}
} else if (root->kind == ASTKind_Function) {
value->type = dcfg_ValueType_Function;
value->v.f.is_builtin = false;
value->v.f.v.f.argv = vector_create();
for (size_t i = 0; i < vector_size(root->v.f.argv); i++) {
vector_add(&value->v.f.v.f.argv, StringView, root->v.f.argv[i]);
}
Value *v = ast_to_value(instance, root->v.f.body);
if (!v) {
vector_free(value->v.f.v.f.argv);
FREE(value);
return NULL;
}
value->v.f.v.f.body = v;
} else if (root->kind == ASTKind_MemberAccess) {
value->type = dcfg_ValueType_MemberAccess;
value->v.ma.accessv = vector_create();
for (size_t i = 0; i < vector_size(root->v.m.accessv); i++)
vector_add(&value->v.ma.accessv, StringView, root->v.m.accessv[i]);
} else if (root->kind == ASTKind_FunctionCall) {
value->type = dcfg_ValueType_FunctionCall;
Value *function = ast_to_value(instance, root->v.fc.function);
if (!function) {
FREE(value);
return NULL;
}
value->v.c.function = function;
value->v.c.argv = vector_create();
for (size_t i = 0; i < vector_size(root->v.fc.argv); i++) {
Value *arg = ast_to_value(instance, root->v.fc.argv[i]);
if (!arg) {
for (size_t i = 0; i < vector_size(value->v.c.argv); i++)
dcfg_destroy(value->v.c.argv[i]);
vector_free(value->v.c.argv);
dcfg_destroy(value->v.c.function);
FREE(value);
return NULL;
}
vector_add(&value->v.c.argv, Value *, arg);
}
}
return value;
}
dcfg_Value *dcfg_parse(dcfg_Instance *instance, dcfg_StringView const file_path)
{
char *path_buf = malloc(file_path.size + 1);
memset(path_buf, 0, file_path.size + 1);
memcpy(path_buf, file_path.data, file_path.size);
char *abs = instance->realpath(path_buf);
free(path_buf);
if (!abs) {
snprintf(instance->last_error, sizeof(instance->last_error) - 1,
"realpath: %s", strerror(errno));
return NULL;
}
StringView abs_sv = SV(abs);
FILE *fp = instance->fopen(abs, "r");
if (!fp) {
FREE(abs);
snprintf(instance->last_error, sizeof(instance->last_error) - 1,
"fopen: %s", strerror(errno));
return NULL;
}
if (instance->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 (instance->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,
.data = ALLOC(size + 1),
};
if (!str.data) {
FREE(abs);
fclose(fp);
snprintf(instance->last_error, sizeof(instance->last_error) - 1,
"Failed to allocate source buffer: %s", strerror(errno));
return NULL;
}
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;
}
((char *)str.data)[str.size] = '\0';
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) {
if (!*instance->last_error) {
strcpy(instance->last_error, "Could not parse file");
}
FREE(abs);
FREE((void *)str.data);
fclose(fp);
return NULL;
}
instance->last_error[0] = '\0';
Value *v = ast_to_value(instance, ast);
AST_free_parser(ast, &parser);
if (!v) {
if (!*instance->last_error) {
strcpy(instance->last_error, "Could not get Value tree from AST");
}
dcfg_destroy(v);
FREE(abs);
FREE((void *)str.data);
fclose(fp);
return NULL;
}
instance->last_error[0] = '\0';
vector_add(&instance->sourcev, StringView, str);
vector_add(&instance->source_pathv, StringView, abs_sv);
return v;
}
void dcfg_destroy(dcfg_Value *value)
{
if (!value)
return;
switch (value->type) {
case dcfg_ValueType_Object:
for (size_t i = 0; i < vector_size(value->v.o.entryv); i++) {
dcfg_destroy(value->v.o.entryv[i].v);
if (value->v.o.entryv[i].key_allocated) {
value->instance->free((void *)value->v.o.entryv[i].k.data);
}
}
vector_free(value->v.o.entryv);
break;
case dcfg_ValueType_Array:
for (size_t i = 0; i < vector_size(value->v.a.valuev); i++) {
dcfg_destroy(value->v.a.valuev[i]);
}
vector_free(value->v.a.valuev);
break;
case dcfg_ValueType_Function:
if (!value->v.f.is_builtin) {
dcfg_destroy(value->v.f.v.f.body);
vector_free(value->v.f.v.f.argv);
}
break;
case dcfg_ValueType_FunctionCall:
dcfg_destroy(value->v.c.function);
for (size_t i = 0; i < vector_size(value->v.c.argv); i++) {
dcfg_destroy(value->v.c.argv[i]);
}
vector_free(value->v.c.argv);
break;
case dcfg_ValueType_MemberAccess:
vector_free(value->v.ma.accessv);
break;
default:
break;
}
value->instance->free(value); // https://youtu.be/RgFaK6ZQifE
}
typedef struct {
dcfg_Instance *inst;
char *buf;
size_t len, cap;
} StrBld;
static bool sb_reserve(StrBld *sb, size_t more)
{
if (sb->len + more <= sb->cap)
return true;
size_t ncap = sb->cap ? sb->cap * 2 : 128;
while (ncap < sb->len + more)
ncap *= 2;
char *nbuf = sb->inst->alloc(ncap);
if (!nbuf)
return false;
if (sb->buf) {
memcpy(nbuf, sb->buf, sb->len);
sb->inst->free(sb->buf);
}
sb->buf = nbuf;
sb->cap = ncap;
return true;
}
static bool sb_put(StrBld *sb, char const *s, size_t n)
{
if (!sb_reserve(sb, n))
return false;
memcpy(sb->buf + sb->len, s, n);
sb->len += n;
return true;
}
static bool sb_put_sv(StrBld *sb, StringView sv)
{
return sb_put(sb, sv.data, sv.size);
}
static bool sb_put_char(StrBld *sb, char c) { return sb_put(sb, &c, 1); }
static bool ser_value(dcfg_Value *v, StrBld *sb)
{
switch (v->type) {
case dcfg_ValueType_Nil:
return sb_put(sb, "nil", 3);
case dcfg_ValueType_Boolean:
return sb_put(sb, v->v.b ? "true" : "false", v->v.b ? 4 : 5);
case dcfg_ValueType_Integer: {
char tmp[64];
int n = snprintf(tmp, sizeof tmp, "%" PRId64, v->v.i);
return sb_put(sb, tmp, (size_t)n);
}
case dcfg_ValueType_Real: {
char tmp[64];
int n = snprintf(tmp, sizeof tmp, "%.17g", v->v.r);
return sb_put(sb, tmp, (size_t)n);
}
case dcfg_ValueType_String: {
if (!sb_put_char(sb, '"'))
return false;
for (size_t i = 0; i < v->v.s.size; ++i) {
char c = v->v.s.data[i];
if (c == '"' || c == '\\') {
if (!sb_put_char(sb, '\\'))
return false;
}
if (!sb_put_char(sb, c))
return false;
}
return sb_put_char(sb, '"');
}
case dcfg_ValueType_Path:
return sb_put_sv(sb, v->v.p);
case dcfg_ValueType_Array: {
if (!sb_put_char(sb, '['))
return false;
for (size_t i = 0; i < vector_size(v->v.a.valuev); ++i) {
if (i && !sb_put(sb, ", ", 2))
return false;
if (!ser_value(v->v.a.valuev[i], sb))
return false;
}
return sb_put_char(sb, ']');
}
case dcfg_ValueType_Object: {
if (!sb_put_char(sb, '{'))
return false;
for (size_t i = 0; i < vector_size(v->v.o.entryv); ++i) {
ValueObjectEntry *e = &v->v.o.entryv[i];
if (i && !sb_put(sb, ", ", 2))
return false;
if (!sb_put_sv(sb, e->k) || !sb_put(sb, " = ", 3))
return false;
if (!ser_value(e->v, sb))
return false;
}
return sb_put_char(sb, '}');
}
case dcfg_ValueType_Function:
return sb_put(sb, "<function>", 10);
case dcfg_ValueType_FunctionCall:
return sb_put(sb, "<call>", 6);
case dcfg_ValueType_MemberAccess:
return sb_put(sb, "<member>", 8);
default:
return false;
}
}
bool dcfg_serialize_value(dcfg_Value *value, dcfg_StringView *out_sv)
{
if (!value || !out_sv)
return false;
StrBld sb = { .inst = value->instance };
if (!ser_value(value, &sb)) {
if (sb.buf)
value->instance->free(sb.buf);
return false;
}
char *final = value->instance->alloc(sb.len + 1);
if (!final) {
value->instance->free(sb.buf);
return false;
}
memcpy(final, sb.buf, sb.len);
final[sb.len] = '\0';
value->instance->free(sb.buf);
out_sv->data = final;
out_sv->size = sb.len;
return true;
}
dcfg_ValueType dcfg_Value_type_ex(dcfg_Value *value, bool evaluate)
{
if (!value)
return dcfg_ValueType_Nil;
(void)evaluate;
return value->type;
}
bool dcfg_Value_get_object_field_ex(dcfg_Value *value, dcfg_StringView key,
dcfg_Value **out_value, bool evaluate)
{
if (!value || value->type != dcfg_ValueType_Object)
return false;
ValueObject *obj = &value->v.o;
for (size_t i = 0; i < vector_size(obj->entryv); ++i) {
ValueObjectEntry *entry = &obj->entryv[i];
if (sv_eq(entry->k, key)) {
*out_value = entry->v;
if (evaluate)
dcfg_Value_evaluate(*out_value, out_value);
return true;
}
}
return false;
}
bool dcfg_Value_get_array_item_ex(
dcfg_Value *value, size_t index, dcfg_Value **out_value, bool evaluate)
{
if (!value || value->type != dcfg_ValueType_Array)
return false;
ValueArray *arr = &value->v.a;
if (index >= vector_size(arr->valuev))
return false;
*out_value = arr->valuev[index];
if (evaluate)
dcfg_Value_evaluate(*out_value, out_value);
return true;
}
bool dcfg_Value_get_function_body_ex(
dcfg_Value *value, dcfg_Value **out_value, bool evaluate)
{
if (!value || value->type != dcfg_ValueType_Function
|| value->v.f.is_builtin)
return false;
if (out_value)
*out_value = value->v.f.v.f.body;
if (evaluate && out_value && *out_value)
dcfg_Value_evaluate(*out_value, out_value);
return true;
}
bool dcfg_Value_get_boolean(dcfg_Value *value, bool *out_value)
{
if (!value || value->type != dcfg_ValueType_Boolean)
return false;
if (out_value)
*out_value = value->v.b;
return true;
}
bool dcfg_Value_get_integer(dcfg_Value *value, int64_t *out_value)
{
if (!value || value->type != dcfg_ValueType_Integer)
return false;
if (out_value)
*out_value = value->v.i;
return true;
}
bool dcfg_Value_get_real(dcfg_Value *value, double *out_value)
{
if (!value || value->type != dcfg_ValueType_Real)
return false;
if (out_value)
*out_value = value->v.r;
return true;
}
bool dcfg_Value_get_string(dcfg_Value *value, dcfg_StringView *out_sv)
{
if (!value || value->type != dcfg_ValueType_String)
return false;
if (out_sv)
*out_sv = value->v.s;
return true;
}
bool dcfg_Value_get_path(dcfg_Value *value, dcfg_StringView *out_sv)
{
if (!value || value->type != dcfg_ValueType_Path)
return false;
if (out_sv)
*out_sv = value->v.p;
return true;
}
bool dcfg_Value_get_object_keys(dcfg_Value *value, size_t capacity,
size_t *out_count, dcfg_StringView *out_keys)
{
if (!value || value->type != dcfg_ValueType_Object)
return false;
ValueObject *obj = &value->v.o;
size_t count = vector_size(obj->entryv);
if (out_count)
*out_count = count;
if (out_keys) {
size_t n = capacity < count ? capacity : count;
for (size_t i = 0; i < n; ++i)
out_keys[i] = obj->entryv[i].k;
}
return true;
}
bool dcfg_Value_get_array_size(dcfg_Value *value, size_t *out_size)
{
if (!value || value->type != dcfg_ValueType_Array)
return false;
if (out_size)
*out_size = vector_size(value->v.a.valuev);
return true;
}
static bool eval_member(ValueMemberAccess *ma, Environment *env, Value **out)
{
Value *root;
if (!environment_lookup(env, ma->accessv[0], &root))
return false;
for (size_t i = 1; i < vector_size(ma->accessv); ++i) {
if (!dcfg_Value_get_object_field_ex(root, ma->accessv[i], &root, true))
return false;
}
*out = root;
return true;
}
bool dcfg_Value_evaluate_in_env(
dcfg_Value *value, Environment *frame, dcfg_Value **out_value)
{
assert(value);
assert(out_value);
bool ret = true;
value->instance->last_error[0] = '\0';
*out_value = value->instance->alloc(sizeof(**out_value));
Value *v = *out_value;
(*out_value)->instance = value->instance;
if (value->type == dcfg_ValueType_Nil) {
v->type = dcfg_ValueType_Nil;
} else if (value->type == dcfg_ValueType_Boolean) {
v->type = dcfg_ValueType_Boolean;
v->v.b = value->v.b;
} else if (value->type == dcfg_ValueType_Integer) {
v->type = dcfg_ValueType_Integer;
v->v.i = value->v.i;
} else if (value->type == dcfg_ValueType_Real) {
v->type = dcfg_ValueType_Real;
v->v.r = value->v.r;
} else if (value->type == dcfg_ValueType_String) {
v->type = dcfg_ValueType_String;
v->v.s = value->v.s;
} else if (value->type == dcfg_ValueType_Path) {
v->type = dcfg_ValueType_Path;
v->v.p = value->v.p;
} else if (value->type == dcfg_ValueType_Object) {
v->type = dcfg_ValueType_Object;
v->v.o.entryv = vector_create();
for (size_t i = 0; i < vector_size(value->v.o.entryv); i++) {
ValueObjectEntry *e = &value->v.o.entryv[i];
Value *new_v;
bool res = dcfg_Value_evaluate_in_env(e->v, frame, &new_v);
if (!res) {
ret = false;
break;
}
ValueObjectEntry ne = {
.k = e->k,
.v = new_v,
.key_allocated = true,
};
ne.k.data = value->instance->alloc(ne.k.size + 1);
memcpy((void *)ne.k.data, e->k.data, ne.k.size);
((char *)ne.k.data)[ne.k.size] = '\0';
vector_add(&v->v.o.entryv, ValueObjectEntry, ne);
}
} else if (value->type == dcfg_ValueType_Array) {
v->type = dcfg_ValueType_Array;
v->v.a.valuev = vector_create();
for (size_t i = 0; i < vector_size(value->v.a.valuev); i++) {
Value *val = NULL;
bool res
= dcfg_Value_evaluate_in_env(value->v.a.valuev[i], frame, &val);
if (!res) {
ret = false;
}
vector_add(&v->v.a.valuev, Value *, val);
}
} else if (value->type == dcfg_ValueType_Function) {
Value *out_value_prev = *out_value;
bool res = dcfg_call_function(value, NULL, 0, out_value);
if (!res) {
dcfg_destroy(out_value_prev);
ret = false;
}
} else if (value->type == dcfg_ValueType_MemberAccess) {
if (!frame) {
ret = false;
strcpy(value->instance->last_error,
"Cannot use member access outside of function");
} else {
Value *out_value_prev = *out_value;
bool ok = eval_member(&value->v.ma, frame, out_value);
if (!ok) {
ret = false;
} else {
dcfg_destroy(out_value_prev);
}
}
} else if (value->type == dcfg_ValueType_FunctionCall) {
Value *function;
bool res
= dcfg_Value_evaluate_in_env(value->v.c.function, frame, &function);
if (!res || function->type != dcfg_ValueType_Function) {
ret = false;
} else {
Value *out_value_prev = *out_value;
bool res = dcfg_call_function(function, value->v.c.argv,
vector_size(value->v.c.argv), out_value);
if (!res) {
ret = false;
} else {
dcfg_destroy(out_value_prev);
}
}
} else {
assert(0 && "Invalid value type");
}
if (!ret) {
dcfg_destroy(*out_value);
}
return ret;
}
bool dcfg_call_function(
dcfg_Value *fn, dcfg_Value **args, size_t argc, dcfg_Value **out_value)
{
pthread_mutex_lock(&fn->instance->mtx);
if (fn->v.f.is_builtin) {
*out_value = fn->v.f.v.bi(args, argc);
pthread_mutex_unlock(&fn->instance->mtx);
return *out_value != NULL;
}
Environment frame;
if (!environment_create(&frame, fn->v.f.v.f.closure)) {
pthread_mutex_unlock(&fn->instance->mtx);
return false;
}
size_t nform = vector_size(fn->v.f.v.f.argv);
if (argc != nform) {
strcpy(fn->instance->last_error, "Invalid argument count");
environment_destroy(&frame, false);
pthread_mutex_unlock(&fn->instance->mtx);
return false;
}
for (size_t i = 0; i < nform; i++) {
vector_add(&frame.argv, StringView, fn->v.f.v.f.argv[i]);
vector_add(&frame.argvv, Value *, args[i]);
}
bool ok = dcfg_Value_evaluate_in_env(fn->v.f.v.f.body, &frame, out_value);
environment_destroy(&frame, false);
pthread_mutex_unlock(&fn->instance->mtx);
return ok;
}
bool dcfg_Value_evaluate(dcfg_Value *value, dcfg_Value **out_value)
{
bool ret;
pthread_mutex_lock(&value->instance->mtx);
{
ret = dcfg_Value_evaluate_in_env(value, NULL, out_value);
}
pthread_mutex_unlock(&value->instance->mtx);
return ret;
}
bool dcfg_Value_evaluate_toplevel(dcfg_Value *top, dcfg_Value **out_value,
dcfg_StringView *function_names, dcfg_BuiltIn *functions,
size_t function_count)
{
if (!top)
return false;
if (top->type != dcfg_ValueType_Function)
return dcfg_Value_evaluate(top, out_value);
Instance *inst = top->instance;
Value *lib = inst->alloc(sizeof *lib);
if (!lib)
return false;
lib->instance = inst;
lib->type = dcfg_ValueType_Object;
lib->v.o.entryv = vector_create();
if (!lib->v.o.entryv) {
inst->free(lib);
return false;
}
for (size_t i = 0; i < function_count; ++i) {
Value *fn = inst->alloc(sizeof *fn);
if (!fn) {
dcfg_destroy(lib);
return false;
}
fn->instance = inst;
fn->type = dcfg_ValueType_Function;
fn->v.f.is_builtin = true;
fn->v.f.v.bi = functions[i];
StringView name = function_names[i];
Value *target = lib;
size_t start = 0;
for (size_t pos = 0; pos <= name.size; ++pos) {
bool end = (pos == name.size);
if (end || name.data[pos] == '.') {
StringView seg
= { .data = name.data + start, .size = pos - start };
if (end) {
ValueObject *obj = &target->v.o;
bool replaced = false;
for (size_t j = 0; j < vector_size(obj->entryv); ++j) {
if (sv_eq(obj->entryv[j].k, seg)) {
dcfg_destroy(obj->entryv[j].v);
obj->entryv[j].v = fn;
replaced = true;
break;
}
}
if (!replaced) {
ValueObjectEntry e
= { .k = seg, .v = fn, .key_allocated = false };
vector_add(&obj->entryv, ValueObjectEntry, e);
}
} else {
target = ensure_child_obj(inst, target, seg);
if (target->type != dcfg_ValueType_Object) {
dcfg_destroy(fn);
dcfg_destroy(lib);
strcpy(inst->last_error,
"Function name clashes with non-object field");
return false;
}
}
start = pos + 1;
}
}
}
Value *argv[1] = { lib };
bool ok = dcfg_call_function(top, argv, 1, out_value);
vector_free(lib->v.o.entryv);
inst->free(lib);
return ok;
}
// Libraries
#include "vendor/utf8proc.c"
#include "vendor/vec.c"