vessel/web/path-builtin.c
2025-06-24 13:17:46 +03:00

1656 lines
44 KiB
C

#include "include/conf.h"
#include "include/path-builtin.h"
#include <vessel/def.h>
#include <vessel/switch.h>
#include "include/path.h"
#include <ctype.h>
typedef struct {
char *pat;
size_t len;
} vw_DateTimeState;
static inline int
vw_DateTime_matchn_parse_digits(const char *str, size_t *pos, size_t max, size_t count) {
int val = 0;
for (size_t idx = 0; idx < count; ++idx) {
if (*pos >= max || str[*pos] < '0' || str[*pos] > '9') {
return -1;
}
val = val * 10 + (str[(*pos)++] - '0');
}
return val;
}
static inline bool vw_DateTime_matchn_is_leap_year(const uint16_t year) {
return (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0));
}
static inline uint16_t vw_find_century(const uint16_t year) {
return (uint16_t)((year % 100 != 0) + (year / 100));
}
bool vw_DateTime_print(const vw_DateTime *dt) {
if (!dt) {
return false;
}
static const char *weekdays[] = { "Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday" };
if (dt->set & VW_DATE_TIME_SET_YEAR) {
printf("%04u", dt->year);
if (dt->set & VW_DATE_TIME_SET_MONTH) {
printf("-%02u", dt->month);
if (dt->set & VW_DATE_TIME_SET_DAY) {
printf("-%02u", dt->day);
}
}
}
if (dt->set & VW_DATE_TIME_SET_HOUR) {
printf(" %02u", dt->hour);
if (dt->set & VW_DATE_TIME_SET_MINUTE) {
printf(":%02u", dt->minute);
if (dt->set & VW_DATE_TIME_SET_SECOND) {
printf(":%02u", dt->second);
}
}
}
if (dt->set & VW_DATE_TIME_SET_WEEKDAY) {
printf(", %s", weekdays[dt->weekday]);
if (dt->set & VW_DATE_TIME_SET_YEARWEEK) {
printf(" (week %u)", dt->yearweek);
}
if (dt->set & VW_DATE_TIME_SET_YEARDAY) {
printf(", day %u of the year", dt->yearday);
}
}
if (dt->set & VW_DATE_TIME_SET_OFFSET) {
printf(" UTC");
if (dt->offset >= 0) {
printf("+");
}
printf("%.2f", dt->offset);
}
putchar('\n');
return true;
}
size_t vw_DateTime_matchn(
vw_DateTime *dt, const char *pat, size_t pat_len, const char *src, size_t src_len) {
if (!dt || !pat || !*pat || pat_len == 0 || !src || !*src || src_len == 0) {
return 0;
}
size_t p = 0;
size_t s = 0;
size_t matched = 0;
time_t seconds = time(NULL);
struct tm *current_time = localtime(&seconds);
dt->year = (uint16_t)(current_time->tm_year > 1900 ? (current_time->tm_year + 1900) : 0);
dt->set = VW_DATE_TIME_SET_NONE;
while (p < pat_len && s < src_len) {
/* Static match */
if (pat[p] != '%') {
if (src[s++] != pat[p++]) {
return 0;
}
matched = s;
continue;
}
/* Skip past % */
if (++p >= pat_len) {
return 0;
}
/* Parse fmt */
switch (pat[p++]) {
case 'Y': {
const int year = vw_DateTime_matchn_parse_digits(src, &s, src_len, 4);
if (year < 0) {
return 0;
}
dt->year = (uint16_t)year;
dt->set |= VW_DATE_TIME_SET_YEAR;
break;
}
case 'y': {
const int yy = vw_DateTime_matchn_parse_digits(src, &s, src_len, 2);
if (yy < 0) {
return 0;
}
dt->year = (uint16_t)(vw_find_century(dt->year) + yy);
dt->set |= VW_DATE_TIME_SET_YEAR;
break;
}
case 'm': {
const int month = vw_DateTime_matchn_parse_digits(src, &s, src_len, 2);
if (month < 1 || month > 12) {
return 0;
}
dt->month = (uint8_t)month;
dt->set |= VW_DATE_TIME_SET_MONTH;
break;
}
case 'b': {
if (s + 2 >= src_len) {
return 0;
}
const uint64_t hash = vs_switch_hashn(&src[s], 3);
switch (hash) {
case 4939817477974586218ULL /* Jan */:
dt->month = 1;
break;
case 15709302032656021367ULL /* Feb */:
dt->month = 2;
break;
case 13369147897303923553ULL /* Mar */:
dt->month = 3;
break;
case 1455433102601615117ULL /* Apr */:
dt->month = 4;
break;
case 214815404126853896ULL /* May */:
dt->month = 5;
break;
case 3581390445273919101ULL /* Jun */:
dt->month = 6;
break;
case 15353982735725600812ULL /* Jul */:
dt->month = 7;
break;
case 15401104073652460215ULL /* Aug */:
dt->month = 8;
break;
case 3369370446682186658ULL /* Sep */:
dt->month = 9;
break;
case 1911355218825294899ULL /* Oct */:
dt->month = 10;
break;
case 9172265116556117536ULL /* Nov */:
dt->month = 11;
break;
case 5696553264589341849ULL /* Dec */:
dt->month = 12;
break;
default:
return 0;
}
s += 3;
dt->set |= VW_DATE_TIME_SET_MONTH;
break;
}
case 'B': {
const size_t start = s;
if (!(src[s] >= 'A' && src[s] <= 'Z')) {
return false;
}
++s;
while (src[s] >= 'a' && src[s] <= 'z') {
++s;
}
const size_t len = s - start;
if (len < 3) {
return false;
}
const uint64_t hash = vs_switch_hashn(&src[start], len);
switch (hash) {
case 17964601829990767649ULL /* January */:
dt->month = 1;
break;
case 7255334670142472140ULL /* February */:
dt->month = 2;
break;
case 9305720913207313840ULL /* March */:
dt->month = 3;
break;
case 11074245164197147555ULL /* April */:
dt->month = 4;
break;
case 214815404126853896ULL /* May */:
dt->month = 5;
break;
case 15085057628823179632ULL /* June */:
dt->month = 6;
break;
case 3727642979271583586ULL /* July */:
dt->month = 7;
break;
case 5444754080439213179ULL /* August */:
dt->month = 8;
break;
case 7373379154950709813ULL /* September */:
dt->month = 9;
break;
case 7166752048728274669ULL /* October */:
dt->month = 10;
break;
case 12975793898007606520ULL /* November */:
dt->month = 11;
break;
case 17027227474939830354ULL /* December */:
dt->month = 12;
break;
default:
return 0;
}
s += len - 1;
dt->set |= VW_DATE_TIME_SET_MONTH;
break;
}
case 'd': {
const int day = vw_DateTime_matchn_parse_digits(src, &s, src_len, 2);
if (day < 1 || day > 31) {
return 0;
}
dt->day = (uint8_t)day;
dt->set |= VW_DATE_TIME_SET_DAY;
break;
}
case 'j': {
const int yearday = vw_DateTime_matchn_parse_digits(src, &s, src_len, 3);
if (yearday < 1 || yearday > 366) {
return 0;
}
dt->yearday = (uint16_t)yearday;
dt->set |= VW_DATE_TIME_SET_YEARDAY;
break;
}
case 'H': {
const int hour = vw_DateTime_matchn_parse_digits(src, &s, src_len, 2);
if (hour < 0 || hour > 23) {
return 0;
}
dt->hour = (uint8_t)hour;
dt->set |= VW_DATE_TIME_SET_HOUR;
break;
}
case 'I': {
const int hour = vw_DateTime_matchn_parse_digits(src, &s, src_len, 2);
if (hour < 1 || hour > 12) {
return 0;
}
dt->hour = (uint8_t)hour;
dt->set |= VW_DATE_TIME_SET_HOUR;
break;
}
case 'M': {
const int mins = vw_DateTime_matchn_parse_digits(src, &s, src_len, 2);
if (mins < 0 || mins > 59) {
return 0;
}
dt->minute = (uint8_t)mins;
dt->set |= VW_DATE_TIME_SET_MINUTE;
break;
}
case 'S': {
const int sec = vw_DateTime_matchn_parse_digits(src, &s, src_len, 2);
if (sec < 0 || sec > 59) {
return 0;
}
dt->second = (uint8_t)sec;
dt->set |= VW_DATE_TIME_SET_SECOND;
break;
}
case 'p': {
if (s + 1 >= src_len) {
return 0;
}
if ((src[s] == 'P' || src[s] == 'p') && (src[s + 1] == 'M' || src[s + 1] == 'm')) {
if (dt->hour < 12) {
dt->hour += 12;
}
dt->set |= VW_DATE_TIME_SET_HOUR;
s += 2;
} else if ((src[s] == 'A' || src[s] == 'a') &&
(src[s + 1] == 'M' || src[s + 1] == 'm')) {
if (dt->hour == 12) {
dt->hour = 0;
}
dt->set |= VW_DATE_TIME_SET_HOUR;
s += 2;
} else {
return 0;
}
break;
}
case 'A': {
const size_t start = s;
if (!(src[s] >= 'A' && src[s] <= 'Z')) {
return false;
}
++s;
while (src[s] >= 'a' && src[s] <= 'z') {
++s;
}
const size_t len = s - start;
if (len < 3) {
return false;
}
const uint64_t hash = vs_switch_hashn(&src[start], len);
switch (hash) {
case 1499209936175797929ULL /* Sunday */:
dt->weekday = 0;
break;
case 15343497047085022806ULL /* Monday */:
dt->weekday = 1;
break;
case 682670563137842932ULL /* Tuesday */:
dt->weekday = 2;
break;
case 12516549914688717708ULL /* Wednesday */:
dt->weekday = 3;
break;
case 13714863946278208848ULL /* Thursday */:
dt->weekday = 4;
break;
case 18143703299807957456ULL /* Friday */:
dt->weekday = 5;
break;
case 15689643484499948403ULL /* Saturday */:
dt->weekday = 6;
break;
default:
return 0;
}
dt->set |= VW_DATE_TIME_SET_WEEKDAY;
break;
}
case 'a': {
if (s + 2 >= src_len) {
return 0;
}
const uint64_t hash = vs_switch_hashn(&src[s], 3);
switch (hash) {
case 16849390285903832232ULL /* Sun */:
dt->weekday = 0;
break;
case 10205521733537287787ULL /* Mon */:
dt->weekday = 1;
break;
case 6157980093028800402ULL /* Tue */:
dt->weekday = 2;
break;
case 14244903640496851306ULL /* Wed */:
dt->weekday = 3;
break;
case 2309337100958552554ULL /* Thu */:
dt->weekday = 4;
break;
case 9175942417219430151ULL /* Fri */:
dt->weekday = 5;
break;
case 14299747937899091273ULL /* Sat */:
dt->weekday = 6;
break;
default:
return 0;
}
dt->set |= VW_DATE_TIME_SET_WEEKDAY;
s += 3;
break;
}
case 'w': {
const int weekday = vw_DateTime_matchn_parse_digits(src, &s, src_len, 1);
if (weekday < 0 || weekday > 6) {
return 0;
}
dt->weekday = (uint8_t)weekday;
dt->set |= VW_DATE_TIME_SET_WEEKDAY;
break;
}
case 'W': {
const int yearweek = vw_DateTime_matchn_parse_digits(src, &s, src_len, 2);
if (yearweek < 0 || yearweek > 53) {
return 0;
}
dt->yearweek = (uint8_t)yearweek;
dt->set |= VW_DATE_TIME_SET_YEARWEEK;
break;
}
case 'Z': {
if (s >= src_len) {
return 0;
}
const size_t start = s;
while (s - start < 4 && src[s] >= 'A' && src[s] <= 'Z') {
++s;
}
const size_t len = s - start;
const char *tz = &src[s - len];
const uint64_t hash = vs_switch_hashn(tz, len);
switch (hash) {
case 5400647078792755728ULL /* ACDT */:
dt->offset = 10.5;
break;
case 4766677935472667375ULL /* ACST */:
dt->offset = 9.5;
break;
case 2103978029288782776ULL /* ACT */:
dt->offset = -5.0;
break;
case 7370160732139184342ULL /* AEDT */:
dt->offset = 11.0;
break;
case 9967661272838146170ULL /* AEST */:
dt->offset = 10.0;
break;
case 10106460371877471795ULL /* AKDT */:
dt->offset = -8.0;
break;
case 2628000161117006295ULL /* AKST */:
dt->offset = -9.0;
break;
case 13964713882942732942ULL /* ART */:
dt->offset = -3.0;
break;
case 6186762422267593169ULL /* AST */:
dt->offset = +3.0;
break;
case 18265226512131121940ULL /* AWST */:
dt->offset = 8.0;
break;
case 777586831009781693ULL /* BST */:
dt->offset = 1.0;
break;
case 11147673027240901128ULL /* CDT */:
dt->offset = -5.0;
break;
case 10256117318433937095ULL /* CET */:
dt->offset = 1.0;
break;
case 4107056610611744234ULL /* CEST */:
dt->offset = 2.0;
break;
case 13777264060303488607ULL /* CST */:
dt->offset = -6.0;
break;
case 6856334708055307450ULL /* EET */:
dt->offset = 2.0;
break;
case 18305502196204186102ULL /* EST */:
dt->offset = -5.0;
break;
case 5811218158441837705ULL /* EEST */:
dt->offset = +3.0;
break;
case 17094731644321730344ULL /* GMT */:
dt->offset = 0.0;
break;
case 13438807085359815380ULL /* HKT */:
dt->offset = 8.0;
break;
case 13009103050995326270ULL /* IST */:
dt->offset = 5.5;
break;
case 6807014158321330431ULL /* JST */:
dt->offset = 9.0;
break;
case 9256148920970304684ULL /* MDT */:
dt->offset = -6.0;
break;
case 685932710232085380ULL /* MST */:
dt->offset = 8.0;
break;
case 4660469000854508473ULL /* PDT */:
dt->offset = -7.0;
break;
case 10135139478756120134ULL /* PST */:
dt->offset = -8.0;
break;
case 12852326507233345576ULL /* SGT */:
dt->offset = 8.0;
break;
case 12512111609108849081ULL /* WET */:
dt->offset = 0.0;
break;
default:
return 0;
}
dt->set |= VW_DATE_TIME_SET_OFFSET;
break;
}
case 'z': {
if (s >= src_len) {
return 0;
}
const char sign = src[s++];
if (sign != '+' && sign != '-') {
return 0;
}
const int hrs = vw_DateTime_matchn_parse_digits(src, &s, src_len, 2);
const int mins = vw_DateTime_matchn_parse_digits(src, &s, src_len, 2);
if (hrs < 0 || hrs > 23 || mins < 0 || hrs > 59) {
return 0;
}
dt->offset = (sign == '+' ? 1 : -1) * ((float)(hrs + mins) / 60.0F);
dt->set |= VW_DATE_TIME_SET_OFFSET;
break;
}
case '%': {
if (src[s++] != '%') {
return 0;
}
break;
}
default:
return 0;
}
matched = s;
}
if ((dt->set | VW_DATE_TIME_SET_YEARDAY) && dt->yearday == 366 &&
(dt->set | VW_DATE_TIME_SET_YEAR) && !vw_DateTime_matchn_is_leap_year(dt->year)) {
return false;
}
return (p == pat_len) ? matched : 0;
}
static inline uint8_t vw_DateTime_adjust_offset_days_in_month(const uint8_t month,
const uint16_t year) {
static const uint8_t days[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
if (month < 1 || month > 12) {
return 0;
}
return (uint8_t)(days[month - 1] + (month == 2 && vw_DateTime_matchn_is_leap_year(year)));
}
static inline void vw_DateTime_adjust_offset_adjust_date(vw_DateTime *dt, ssize_t days) {
if (days == 0) {
return;
}
ssize_t dir = days > 0 ? 1 : -1;
days = days > 0 ? days : -days;
while (days--) {
if (dir == 1) {
if (++dt->day > vw_DateTime_adjust_offset_days_in_month(dt->month, dt->year)) {
dt->day = 1;
if (++dt->month > 12) {
dt->month = 1;
dt->year++;
}
}
} else {
if (--dt->day < 1) {
if (--dt->month < 1) {
dt->month = 12;
dt->year--;
}
dt->day = vw_DateTime_adjust_offset_days_in_month(dt->month, dt->year);
}
}
}
}
bool vw_DateTime_adjust_offset(vw_DateTime *dt, const float new_offset) {
if (new_offset < -14.0F || new_offset > 14.0F) {
return false;
}
const float delta = new_offset - dt->offset;
const ssize_t delta_seconds = (ssize_t)(delta * 3600.0F);
ssize_t total_sec = (dt->hour * 3600) + (dt->minute * 60) + dt->second;
total_sec += delta_seconds;
ssize_t days_delta = total_sec / 86400;
ssize_t remaining = total_sec % 86400;
if (remaining < 0) {
remaining += 86400;
--days_delta;
}
dt->second = (uint8_t)(remaining % 60);
remaining /= 60;
dt->minute = (uint8_t)(remaining % 60);
dt->hour = (uint8_t)(remaining / 60);
if (days_delta != 0) {
vw_DateTime_adjust_offset_adjust_date(dt, days_delta);
}
dt->offset = new_offset;
return true;
}
static inline bool
vw_IntRange_parse_int_only(vw_IntRange *out, const char *end, const char **pptr) {
const char *ptr = *pptr;
if (!*ptr) {
return false;
}
int8_t sign = 1;
if (*ptr == '-') {
sign = -1;
++ptr;
} else if (*ptr == '+') {
++ptr;
}
intmax_t value = 0;
bool has_digits = false;
while (ptr < end && *ptr >= '0' && *ptr <= '9') {
value = value * 10 + (*ptr - '0');
++ptr;
has_digits = true;
}
if (!has_digits) {
return false; /* Invalid number */
}
value *= sign;
out->a = out->b = value;
out->set |= VW_INT_RANGE_A | VW_INT_RANGE_B;
*pptr = ptr;
return ptr == end; /* We should be done */
}
static inline bool vw_IntRange_parse_step(vw_IntRange *out, const char *end, const char **pptr) {
const char *ptr = *pptr;
if (*ptr != '/') {
return true;
}
++ptr;
if (ptr >= end) {
return false; /* Invalid step */
}
/* Spaces after / */
if (!*ptr) {
return false;
}
if (*ptr == '+') {
++ptr;
}
if (ptr >= end) {
return false; /* Invalid number */
}
intmax_t step = 0;
bool has_digits = false;
while (ptr < end && *ptr >= '0' && *ptr <= '9') {
step = step * 10 + (*ptr - '0');
++ptr;
has_digits = true;
}
if (!has_digits || step == 0) {
return false; /* Invalid step */
}
out->step = step;
out->set |= VW_INT_RANGE_STEP;
*pptr = ptr;
return ptr <= end;
}
static inline bool vw_IntRange_parse_a(vw_IntRange *out, const char *end, const char **pptr) {
const char *ptr = *pptr;
/* Check if not :b */
if (*ptr == ':') {
return true;
}
int8_t sign = 1;
if (*ptr == '-') {
sign = -1;
++ptr;
} else if (*ptr == '+') {
++ptr;
}
if (ptr >= end) {
return false; /* Invalid number */
}
intmax_t a = 0;
bool has_digits = false;
while (ptr < end && *ptr >= '0' && *ptr <= '9') {
a = a * 10 + (*ptr - '0');
++ptr;
has_digits = true;
}
if (!has_digits) {
return false; /* Invalid a */
}
out->a = sign * a;
out->set |= VW_INT_RANGE_A;
*pptr = ptr;
return ptr <= end;
}
static inline bool vw_IntRange_parse_b(vw_IntRange *out, const char *end, const char **pptr) {
const char *ptr = *pptr;
if (ptr > end || !*ptr || *ptr == '/') {
return true;
}
int8_t sign = 1;
if (*ptr == '-') {
sign = -1;
++ptr;
} else if (*ptr == '+') {
++ptr;
}
if (ptr >= end) {
return false; /* Invalid number */
}
intmax_t b = 0;
bool has_digits = false;
while (ptr < end && *ptr >= '0' && *ptr <= '9') {
b = b * 10 + (*ptr - '0');
++ptr;
has_digits = true;
}
if (!has_digits) {
return false; /* Invalid b */
}
if (out->set & VW_INT_RANGE_A) {
if (out->a > b) {
return false; /* Invalid range: a cannot be larger than b */
}
if ((out->set & VW_INT_RANGE_STEP) && out->a % out->step != 0) {
const intmax_t n = (out->a + (out->step - (out->a % out->step)));
if (n > b) {
return false; /* Invalid range: No such multiple in range */
}
}
}
out->b = sign * b;
out->set |= VW_INT_RANGE_B;
*pptr = ptr;
return ptr <= end;
}
bool vw_IntRange_parse(vw_IntRange *out, const char *range, size_t len) {
if (!range || !out) {
return false;
}
/* Values for : */
out->a = 0;
out->b = 0;
out->step = 1;
out->set = VW_INT_RANGE_NONE;
if (!*range || len == 0) {
return true;
}
const char *ptr = range;
const char *end = ptr + len;
if (ptr >= end) {
return true;
}
/* Check if the string is just an integer */
if (vs_strnintonly(ptr, (size_t)(end - ptr)) && !vw_IntRange_parse_int_only(out, end, &ptr)) {
return false;
}
if (ptr >= end) {
return true;
}
/* /step (only) */
if (ptr + 1 < end && *ptr == '/' && vs_strnintonly(ptr + 1, (size_t)(end - ptr - 1)) &&
!vw_IntRange_parse_step(out, end, &ptr)) {
return false;
}
if (ptr >= end) {
return true;
}
/* a */
if (!vw_IntRange_parse_a(out, end, &ptr)) {
return false;
}
if (ptr >= end) {
return true;
}
/* : */
if (*ptr == ':') {
ptr += 1;
} else {
return false; /* Invalid format */
}
if (ptr >= end) {
return true;
}
/* b */
if (!vw_IntRange_parse_b(out, end, &ptr)) {
return false;
}
if (ptr >= end) {
return true;
}
/* /step (after a:b) */
if (ptr + 1 < end && vs_strnintonly(ptr + 1, (size_t)(end - ptr - 1)) &&
!vw_IntRange_parse_step(out, end, &ptr)) {
return false;
}
/* We should be done... */
return ptr == end;
}
static inline void vw_IntRange_print_raw(const vw_IntRange *range) {
const bool a_set = range->set & VW_INT_RANGE_A;
const bool b_set = range->set & VW_INT_RANGE_B;
const bool step_set = range->set & VW_INT_RANGE_STEP;
/* Handle special case for "" */
if (!a_set && !b_set && !step_set) {
return;
}
/* Handle special case for "/step" (instead of ":/step") */
if (step_set && !a_set && !b_set) {
printf("/%ju", (uintmax_t)range->step);
return;
}
/* Print a/b components */
if (a_set && b_set) {
if (range->a == range->b) {
printf("%jd", range->a);
} else {
printf("%jd:%jd", range->a, range->b);
}
} else if (a_set) {
printf("%jd:", range->a);
} else if (b_set) {
printf(":%jd", range->b);
} else {
fputs(":", stdout);
}
/* Add step if present */
if (step_set) {
printf("/%ju", (uintmax_t)range->step);
}
}
bool vw_IntRange_print(const vw_IntRange *range) {
if (!range) {
return false;
}
vw_IntRange_print_raw(range);
putchar('\n');
return true;
}
bool vw_IntRange_inrange(const vw_IntRange *range, const intmax_t n) {
if (!range) {
return false;
}
/* a: (from a) */
if ((range->set & VW_INT_RANGE_A) && n < range->a) {
return false;
}
/* :b (up to b) */
if ((range->set & VW_INT_RANGE_B) && n > range->b) {
return false;
}
/* /step (on step) */
if ((range->set & VW_INT_RANGE_STEP)) {
if ((range->set & VW_INT_RANGE_A)) {
/* Ensure n is of the form a + k * step */
if ((n - range->a) % range->step != 0) {
return false;
}
} else {
/* If no start is defined, just check divisibility */
if (n % range->step != 0) {
return false;
}
}
}
return true;
}
bool vw_IntRange_indrange(const vw_IntRange *range, const double n) {
if (!range) {
return false;
}
/* a: (from a) */
if ((range->set & VW_INT_RANGE_A) && n < (double)range->a) {
return false;
}
/* :b (up to b) */
if ((range->set & VW_INT_RANGE_B) && n > (double)range->b) {
return false;
}
/* /step (on step) */
if ((range->set & VW_INT_RANGE_STEP)) {
if ((range->set & VW_INT_RANGE_A)) {
/* Ensure n is of the form a + k * step */
if (vs_double_mod((double)(n - (double)range->a), (double)range->step) != 0) {
return false;
}
} else {
/* If no start is defined, just check divisibility */
if (vs_double_mod((double)n, (double)range->step) != 0) {
return false;
}
}
}
return true;
}
bool vw_IntRange_expand(const vw_IntRange *range, intmax_t *min, intmax_t *max, intmax_t *step) {
if (!range) {
return false;
}
if (min) {
*min = (range->set & VW_INT_RANGE_A) ? range->a : INTMAX_MIN;
}
if (max) {
*max = (range->set & VW_INT_RANGE_B) ? range->b : INTMAX_MAX;
}
if (step) {
*step = (range->set & VW_INT_RANGE_STEP) ? range->step : 1;
}
return true;
}
bool vw_BoolList_init(vw_BoolList *list) {
if (!list) {
return false;
}
return vs_HMap_init_unsafe(&list->bools);
}
static inline bool vw_BoolList_valid_char(const char c) {
/* a-z, 0-9, -, _, @ */
return (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '@';
}
bool vw_BoolList_parse(vw_BoolList *out, const char *list, size_t len) {
if (!out || !list || !*list || len == 0) {
return false;
}
bool seen_slash = false;
bool is_truthy = true;
bool in_token = false;
size_t token_start = 0;
size_t token_len = 0;
size_t idx = 0;
for (idx = 0; idx <= len; ++idx) {
const char c = list[idx];
if (!(c == ' ' || c == '/' || c == '\0') && !vw_BoolList_valid_char(c)) {
return false;
}
if (c == ' ' || c == '/' || c == '\0') {
if (in_token) {
if (vs_HMap_findn(&out->bools, &list[token_start], token_len) != NULL) {
return false;
}
vs_HMap_insertn(
&out->bools, &list[token_start], token_len, is_truthy ? (void *)1 : (void *)0);
in_token = false;
}
if (c == '/') {
if (seen_slash) {
return false;
}
seen_slash = true;
is_truthy = false;
}
} else {
if (!in_token) {
token_start = idx;
token_len = 0;
in_token = true;
}
++token_len;
}
if (c == '\0') {
break;
}
}
return idx == len;
}
vw_BoolStatus vw_BoolList_find(const vw_BoolList *list, const char *str, size_t len) {
if (!list || !str || !*str || len == 0) {
return vw_BoolStatus_not_bool;
}
const vs_HMapEntry *b = vs_HMap_findn_unsafe(&list->bools, str, len);
if (!b) {
return vw_BoolStatus_not_bool;
}
switch ((uintptr_t)b->value) {
case 0:
return vw_BoolStatus_false;
case 1:
return vw_BoolStatus_true;
default:
return vw_BoolStatus_not_bool;
}
}
bool vw_BoolList_destroy(vw_BoolList *list) { return list && vs_HMap_destroy(&list->bools); }
static inline void vw_BoolList_print_raw(const vw_BoolList *list) {
bool slash_printed = false;
for (size_t idx = 0; idx < list->bools.size; ++idx) {
const vs_HMapEntry *b = list->bools.occupied_ents[idx];
if (!slash_printed && (uintptr_t)b->value == 0) {
fputs("/ ", stdout);
slash_printed = true;
}
if (idx + 1 == list->bools.size) {
printf("%s", (const char *)b->key);
} else {
printf("%s ", (const char *)b->key);
}
}
}
bool vw_BoolList_print(const vw_BoolList *list) {
if (!list) {
return false;
}
vw_BoolList_print_raw(list);
putchar('\n');
return true;
}
bool vw_PathType_builtin_int_consume(const vw_PathSource *src, size_t *size, size_t *reqb) {
size_t idx = 0;
while (idx < src->len && src->src[idx] >= '0' && src->src[idx] <= '9') {
++idx;
}
*size = idx;
*reqb = sizeof(intmax_t);
if (src->full && idx != src->len) {
return false;
}
if (src->seg->compiled) {
const intmax_t n = vs_strn2max(src->src, idx);
if (!vw_IntRange_inrange(src->seg->compiled, n)) {
return false;
}
}
return true;
}
bool vw_PathType_builtin_int_convert(const vw_PathSource *src, size_t reqb, void *out) {
if (reqb != sizeof(intmax_t)) {
return false;
}
intmax_t *final = (intmax_t *)out;
*final = vs_strn2max(src->src, src->len);
return true;
}
bool vw_PathType_builtin_int_compile(const vw_ArgSource *arg, void **comp) {
vw_IntRange *range = VS_MALLOC(sizeof(*range));
if (!range) {
return false;
}
if (!vw_IntRange_parse(range, arg->src, arg->len)) {
VS_FREE(range);
return false;
}
*comp = range;
return true;
}
bool vw_PathType_builtin_int_cleanup(void *comp) {
VS_FREE(comp);
return true;
}
bool vw_PathType_builtin_int_print(const void *comp) {
vw_IntRange_print_raw((const vw_IntRange *)comp);
return true;
}
bool vw_PathType_builtin_str_compile(const vw_ArgSource *arg, void **comp) {
vw_IntRange *range = VS_MALLOC(sizeof(*range));
if (!range) {
return false;
}
if (!vw_IntRange_parse(range, arg->src, arg->len) || vw_IntRange_inrange(range, 0)) {
VS_FREE(range);
return false;
}
*comp = range;
return true;
}
bool vw_PathType_builtin_str_consume(const vw_PathSource *src, size_t *size, size_t *reqb) {
size_t idx = 0;
while (idx < src->len && src->src[idx] != '\0' && src->src[idx] != '/') {
++idx;
}
*size = idx;
*reqb = idx;
if (src->full && idx != src->len) {
return false;
}
if (src->seg->compiled) {
if (!vw_IntRange_inrange(src->seg->compiled, (intmax_t)idx)) {
return false;
}
}
return true;
}
bool vw_PathType_builtin_str_cleanup(void *comp) {
VS_FREE(comp);
return true;
}
bool vw_PathType_builtin_str_print(const void *comp) {
vw_IntRange_print_raw((const vw_IntRange *)comp);
return true;
}
bool vw_PathType_builtin_path_consume(const vw_PathSource *src, size_t *size, size_t *reqb) {
const vw_IntRange *length = src->seg->compiled;
*size = src->len;
*reqb = src->len;
if (length && !vw_IntRange_inrange(length, (intmax_t)src->len)) {
return false;
}
return true;
}
bool vw_PathType_builtin_float_consume(const vw_PathSource *src, size_t *size, size_t *reqb) {
size_t idx = 0;
while (idx < src->len && src->src[idx] >= '0' && src->src[idx] <= '9') {
++idx;
}
if (idx < src->len && src->src[idx] == '.') {
++idx;
}
while (idx < src->len && src->src[idx] >= '0' && src->src[idx] <= '9') {
++idx;
}
*size = idx;
*reqb = sizeof(double);
if (src->full && idx != src->len) {
return false;
}
if (src->seg->compiled) {
const double n = vs_strn2double(src->src, idx, false);
if (!vw_IntRange_indrange(src->seg->compiled, n)) {
return false;
}
}
return true;
}
bool vw_PathType_builtin_float_convert(const vw_PathSource *src, size_t reqb, void *out) {
if (reqb != sizeof(double)) {
return false;
}
const double final = vs_strn2double(src->src, src->len, false);
if (final < VS_DNULL && *src->src != '0') {
return false;
}
*(double *)out = final;
return true;
}
bool vw_PathType_builtin_double_consume(const vw_PathSource *src, size_t *size, size_t *reqb) {
size_t idx = 0;
while (idx < src->len && src->src[idx] >= '0' && src->src[idx] <= '9') {
++idx;
}
if (idx >= src->len || src->src[idx] != '.') {
*size = idx;
return false;
}
++idx;
while (idx < src->len && src->src[idx] >= '0' && src->src[idx] <= '9') {
++idx;
}
*size = idx;
*reqb = sizeof(double);
if (src->full && idx != src->len) {
return false;
}
if (src->seg->compiled) {
const double n = vs_strn2double(src->src, idx, true);
if (!vw_IntRange_indrange(src->seg->compiled, n)) {
return false;
}
}
return true;
}
bool vw_PathType_builtin_double_convert(const vw_PathSource *src, size_t reqb, void *out) {
if (reqb != sizeof(double)) {
return false;
}
const double final = vs_strn2double(src->src, src->len, true);
if (final < VS_DNULL && *src->src != '0') {
return false;
}
*(double *)out = final;
return true;
}
bool vw_PathType_builtin_bool_compile(const vw_ArgSource *arg, void **comp) {
vw_BoolList *bl = VS_CALLOC(1, sizeof(*bl));
if (!bl) {
return false;
}
if (!vw_BoolList_init(bl)) {
VS_FREE(bl);
return false;
}
if (arg->src && arg->len) {
if (!vw_BoolList_parse(bl, arg->src, arg->len)) {
vw_BoolList_destroy(bl);
VS_FREE(bl);
return false;
}
} else {
static const char default_bools[] = "true 1 yes up / false 0 no down";
if (!vw_BoolList_parse(bl, default_bools, VS_ARRLEN(default_bools) - 1)) {
vw_BoolList_destroy(bl);
VS_FREE(bl);
return false;
}
}
*comp = bl;
return true;
}
bool vw_PathType_builtin_bool_consume(const vw_PathSource *src, size_t *size, size_t *reqb) {
size_t idx = 0;
while (idx < src->len && src->src[idx] != '\0' && src->src[idx] != '/' &&
vw_BoolList_valid_char(src->src[idx])) {
++idx;
}
*size = idx;
*reqb = sizeof(bool);
if (src->full && idx != src->len) {
return false;
}
return vw_BoolList_find(src->seg->compiled, src->src, idx) != vw_BoolStatus_not_bool;
}
bool vw_PathType_builtin_bool_convert(const vw_PathSource *src, size_t reqb, void *out) {
if (reqb != sizeof(bool)) {
return false;
}
*(bool *)out = (vw_BoolList_find(src->seg->compiled, src->src, src->len) == vw_BoolStatus_true);
return true;
}
bool vw_PathType_builtin_bool_cleanup(void *comp) {
vw_BoolList_destroy(comp);
VS_FREE(comp);
return false;
}
bool vw_PathType_builtin_bool_print(const void *comp) {
vw_BoolList_print_raw(comp);
return true;
}
bool vw_PathType_builtin_date_consume(const vw_PathSource *src, size_t *size, size_t *reqb) {
vw_DateTime dt = { 0 };
const vw_DateTimeState *dts = src->seg->compiled;
const size_t matched = vw_DateTime_matchn(&dt, dts->pat, dts->len, src->src, src->len);
*size = matched;
*reqb = sizeof(vw_DateTime);
if (src->full && matched != src->len) {
return false;
}
return true;
}
bool vw_PathType_builtin_date_convert(const vw_PathSource *src, size_t reqb, void *out) {
if (reqb != sizeof(vw_DateTime)) {
return false;
}
vw_DateTimeState *dts = src->seg->compiled;
return vw_DateTime_matchn(out, dts->pat, dts->len, src->src, src->len) == src->len;
}
bool vw_PathType_builtin_date_compile(const vw_ArgSource *arg, void **comp) {
vw_DateTimeState *dts = VS_CALLOC(1, sizeof(*dts));
if (!dts) {
return false;
}
if (arg->len && arg->src) {
dts->pat = VS_CALLOC(1, arg->len + 1);
if (!dts->pat) {
VS_FREE(dts);
return false;
}
dts->len = arg->len;
strncpy(dts->pat, arg->src, arg->len);
} else {
static const char default_date[] = "%Y-%m-%d";
dts->len = VS_ARRLEN(default_date) - 1;
dts->pat = VS_CALLOC(1, dts->len + 1);
if (!dts->pat) {
VS_FREE(dts);
return false;
}
strncpy(dts->pat, default_date, dts->len);
}
*comp = dts;
return true;
}
bool vw_PathType_builtin_date_cleanup(void *comp) {
vw_DateTimeState *dts = (vw_DateTimeState *)comp;
VS_FREE(dts->pat);
VS_FREE(dts);
return true;
}
bool vw_PathType_builtin_date_print(const void *comp) {
const vw_DateTimeState *dts = (const vw_DateTimeState *)comp;
printf("%*s", (unsigned)dts->len, dts->pat);
return true;
}
bool vw_PathType_builtin_uuid_consume(const vw_PathSource *src, size_t *size, size_t *reqb) {
static const uint8_t uuid_segment_lengths[5] = { 8, 4, 4, 4, 12 };
size_t idx = 0;
size_t segment_idx = 0;
size_t segment_length = uuid_segment_lengths[segment_idx];
if (src->full && src->len != 36) {
return false;
}
while (idx < src->len && src->src[idx] != '\0' && src->src[idx] != '/') {
if (segment_length == 0) {
if (src->src[idx] != '-') {
return false;
}
segment_length = uuid_segment_lengths[++segment_idx];
} else {
if (!((src->src[idx] >= '0' && src->src[idx] <= '9') ||
(src->src[idx] >= 'a' && src->src[idx] <= 'f') ||
(src->src[idx] >= 'A' && src->src[idx] <= 'F'))) {
return false;
}
--segment_length;
}
++idx;
}
*size = idx;
*reqb = idx;
if (src->full && idx != src->len) {
return false;
}
if (segment_idx != 4 || segment_length != 0 || idx != 36) {
return false;
}
if (src->seg->compiled) {
const char v = (char)(uintptr_t)src->seg->compiled;
if (src->src[14] != v) {
return false;
}
}
return true;
}
bool vw_PathType_builtin_uuid_compile(const vw_ArgSource *arg, void **comp) {
if (arg->src && arg->len <= 2) {
char c = 0;
if (arg->len == 1) {
c = arg->src[0];
} else if (*arg->src == 'v' && arg->len == 2) {
c = arg->src[1];
} else {
return false;
}
if (c < '1' || c > '8') {
return false;
}
*comp = VS_MALLOC(sizeof(char));
if (!*comp) {
return false;
}
*comp = (void *)(uintptr_t)c;
if (c) {
return true;
}
}
return false;
}
bool vw_PathType_builtin_uuid_print(const void *comp) {
putchar((char)(uintptr_t)comp);
return true;
}
bool vw_PathType_builtin_hex_consume(const vw_PathSource *src, size_t *size, size_t *reqb) {
size_t idx = 0;
while (idx < src->len && src->src[idx] != '\0' && isxdigit(src->src[idx])) {
++idx;
}
*size = idx;
*reqb = idx;
if (src->full && idx != src->len) {
return false;
}
if (src->seg->compiled) {
if (!vw_IntRange_inrange(src->seg->compiled, (intmax_t)idx)) {
return false;
}
}
return true;
}