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
Signed-off-by: Slendi <slendi@socopon.com>
2090 lines
48 KiB
C
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"
|