diff --git a/.gitmodules b/.gitmodules index 47029a0..24af818 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "docs/themes/hugo-book"] path = docs/themes/hugo-book url = https://github.com/alex-shpak/hugo-book +[submodule "src/libvieter"] + path = src/libvieter + url = https://git.rustybever.be/vieter-v/libvieter diff --git a/Makefile b/Makefile index 57b89a7..2f6029e 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,6 @@ # =====CONFIG===== SRC_DIR := src -SRCS_C != find '$(SRC_DIR)' -iname '*.c' -SRCS_H != find '$(SRC_DIR)' -iname '*.h' -SRCS_V != find '$(SRC_DIR)' -iname '*.v' -SOURCES := $(SRCS_C) $(SRCS_H) $(SRCS_V) +SRCS != find '$(SRC_DIR)' -iname '*.v' V_PATH ?= v V := $(V_PATH) -showcc -gc boehm -W -d use_openssl -skip-unused @@ -12,8 +9,12 @@ all: vieter # =====COMPILATION===== +.PHONY: libvieter +libvieter: + CFLAGS='-O3' make -C '$(SRC_DIR)/libvieter' + # Regular binary -vieter: $(SOURCES) +vieter: $(SOURCES) libvieter $(V) -g -o vieter $(SRC_DIR) # Debug build using gcc @@ -21,7 +22,7 @@ vieter: $(SOURCES) # multi-threaded and causes issues when running vieter inside gdb. .PHONY: debug debug: dvieter -dvieter: $(SOURCES) +dvieter: $(SOURCES) libvieter $(V_PATH) -showcc -keepc -cg -o dvieter $(SRC_DIR) # Run the debug build inside gdb @@ -32,12 +33,12 @@ gdb: dvieter # Optimised production build .PHONY: prod prod: pvieter -pvieter: $(SOURCES) +pvieter: $(SOURCES) libvieter $(V) -o pvieter -prod $(SRC_DIR) # Only generate C code .PHONY: c -c: $(SOURCES) +c: $(SOURCES) libvieter $(V) -o vieter.c $(SRC_DIR) @@ -72,32 +73,18 @@ man: vieter # =====OTHER===== # Linting .PHONY: lint -lint: lint-v lint-c - -.PHONY: lint-v -lint-v: +lint: $(V) fmt -verify $(SRC_DIR) $(V) vet -W $(SRC_DIR) $(V_PATH) missdoc -p $(SRC_DIR) @ [ $$($(V_PATH) missdoc -p $(SRC_DIR) | wc -l) = 0 ] -.PHONY: lint-c -lint-c: - clang-format --Werror -n $(SRCS_C) $(SRCS_H) - # Formatting .PHONY: fmt -fmt: fmt-v fmt-c - -.PHONY: fmt-v -fmt-v: +fmt: $(V) fmt -w $(SRC_DIR) -.PHONY: fmt-c -fmt-c: - clang-format -i $(SRCS_C) $(SRCS_H) - # Testing .PHONY: test @@ -109,6 +96,7 @@ test: .PHONY: clean clean: rm -rf 'data' 'vieter' 'dvieter' 'pvieter' 'vieter.c' 'pkg' 'src/vieter' *.pkg.tar.zst 'suvieter' 'afvieter' '$(SRC_DIR)/_docs' 'docs/public' + make -C '$(SRC_DIR)/libvieter' clean # =====EXPERIMENTAL===== diff --git a/src/cron/c/expression.c b/src/cron/c/expression.c deleted file mode 100644 index 7d27be6..0000000 --- a/src/cron/c/expression.c +++ /dev/null @@ -1,118 +0,0 @@ -#include "expression.h" -#include - -const uint8_t month_days[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; - -cron_expression *ce_init() { return malloc(sizeof(cron_expression)); } - -void ce_free(cron_expression *ce) { - free(ce->months); - free(ce->days); - free(ce->hours); - free(ce->minutes); - free(ce); -} - -void ce_next(cron_simple_time *out, cron_expression *ce, - cron_simple_time *ref) { - // For all of these values, the rule is the following: if their value is - // the length of their respective array in the CronExpression object, that - // means we've looped back around. This means that the "bigger" value has - // to be incremented by one. For example, if the minutes have looped - // around, that means that the hour has to be incremented as well. - uint8_t month_index = 0; - uint8_t day_index = 0; - uint8_t hour_index = 0; - uint8_t minute_index = 0; - - // This chain is the same logic multiple times, namely that if a "bigger" - // value loops around, then the smaller value will always reset as well. - // For example, if we're going to a new day, the hour & minute will always - // be their smallest value again. - while (month_index < ce->month_count && - ref->month > ce->months[month_index]) { - month_index++; - } - - if (month_index < ce->month_count && - ref->month == ce->months[month_index]) { - while (day_index < ce->day_count && ref->day > ce->days[day_index]) { - day_index++; - } - - if (day_index < ce->day_count && ref->day == ce->days[day_index]) { - while (hour_index < ce->hour_count && - ref->hour > ce->hours[hour_index]) { - hour_index++; - } - - if (hour_index < ce->hour_count && - ref->hour == ce->hours[hour_index]) { - // Minute is the only value where we explicitely make sure we - // can't match sref's value exactly. This is to ensure we only - // return values in the future. - while (minute_index < ce->minute_count && - ref->minute >= ce->minutes[minute_index]) { - minute_index++; - } - } - } - } - - // Here, we increment the "bigger" values by one if the smaller ones loop - // around. The order is important, as it allows a sort-of waterfall effect - // to occur which updates all values if required. - if (minute_index == ce->minute_count && hour_index < ce->hour_count) { - hour_index++; - } - - if (hour_index == ce->hour_count && day_index < ce->day_count) { - day_index++; - } - - if (day_index == ce->day_count && month_index < ce->month_count) { - month_index++; - } - - out->minute = ce->minutes[minute_index % ce->minute_count]; - out->hour = ce->hours[hour_index % ce->hour_count]; - out->day = ce->days[day_index % ce->day_count]; - - // Sometimes, we end up with a day that does not exist within the selected - // month, e.g. day 30 in February. When this occurs, we reset day back to - // the smallest value & loop over to the next month that does have this - // day. - if (out->day > month_days[ce->months[month_index % ce->month_count] - 1]) { - out->day = ce->days[0]; - month_index++; - - while (out->day > - month_days[ce->months[month_index % ce->month_count] - 1]) { - month_index++; - } - } - - out->month = ce->months[month_index % ce->month_count]; - - if (month_index >= ce->month_count) { - out->year = ref->year + 1; - } else { - out->year = ref->year; - } -} - -void ce_next_from_now(cron_simple_time *out, cron_expression *ce) { - time_t t = time(NULL); - struct tm gm; - gmtime_r(&t, &gm); - - cron_simple_time ref = {// tm_year contains years since 1900 - .year = 1900 + gm.tm_year, - // tm_mon goes from 0 to 11 - .month = gm.tm_mon + 1, - .day = gm.tm_mday, - .hour = gm.tm_hour, - .minute = gm.tm_min}; - - ce_next(out, ce, &ref); -} diff --git a/src/cron/c/expression.h b/src/cron/c/expression.h deleted file mode 100644 index 9d378bd..0000000 --- a/src/cron/c/expression.h +++ /dev/null @@ -1,50 +0,0 @@ -#ifndef VIETER_CRON -#define VIETER_CRON - -#include -#include -#include -#include -#include - -typedef enum cron_parse_error { - cron_parse_ok = 0, - cron_parse_invalid_expression = 1, - cron_parse_invalid_number = 2, - cron_parse_out_of_range = 3, - cron_parse_too_many_parts = 4, - cron_parse_not_enough_parts = 5 -} cron_parse_error; - -typedef struct cron_expression { - uint8_t *minutes; - uint8_t *hours; - uint8_t *days; - uint8_t *months; - uint8_t minute_count; - uint8_t hour_count; - uint8_t day_count; - uint8_t month_count; -} cron_expression; - -typedef struct cron_simple_time { - int year; - int month; - int day; - int hour; - int minute; -} cron_simple_time; - -cron_expression *ce_init(); - -void cron_ce_free(cron_expression *ce); - -void cron_ce_next(cron_simple_time *out, cron_expression *ce, - cron_simple_time *ref); - -void cron_ce_next_from_now(cron_simple_time *out, cron_expression *ce); - -enum cron_parse_error cron_ce_parse_expression(cron_expression *out, - const char *expression); - -#endif diff --git a/src/cron/c/parse.c b/src/cron/c/parse.c deleted file mode 100644 index 6a8bdc1..0000000 --- a/src/cron/c/parse.c +++ /dev/null @@ -1,335 +0,0 @@ -#include "expression.h" - -// This prefix is needed to properly compile -const uint8_t parse_month_days[] = {31, 28, 31, 30, 31, 30, - 31, 31, 30, 31, 30, 31}; - -// Allowed value ranges for the minute, hour, day and month field -const uint8_t min[4] = {0, 0, 1, 1}; -const uint8_t max[4] = {59, 23, 31, 12}; - -const uint8_t min_parts = 2; -const uint8_t max_parts = 4; - -// Convert a string into a uint8_t value by parsing it using atoi and checking -// whether it's contained within the given range -#define SAFE_ATOI(v, s, min, max) \ - int _##v = atoi(s); \ - if ((_##v) == 0 && strcmp((s), "0") != 0) { \ - return cron_parse_invalid_number; \ - } \ - if (((_##v) < (min)) || ((_##v) > (max))) { \ - return cron_parse_out_of_range; \ - } \ - v = (uint8_t)(_##v); - -#define MAX(x, y) (((x) > (y)) ? (x) : (y)) - -/** - * Given a range expression, produce a bit field defining what numbers in the - * min-max range the expression represents. Bit 0 (starting from the - * right) corresponds to min, the bit max - min to max. All trailing bits - * after this should be ignored. The given bitfield is modified in-place, so - * multiple calls of this function can be performed on the same value to create - * the effect of ORing their values. - * - * A range expression has one of the following forms: - * - * - * - * - a - * - a-b - * - a/c - * - a-b/c - */ -cron_parse_error ce_parse_range(uint64_t *out, char *s, uint8_t min, - uint8_t max) { - size_t slash_index = 0, dash_index = 0; - size_t s_index = 0; - char cur_char; - bool is_valid_character; - - while ((cur_char = s[s_index]) != '\0') { - is_valid_character = cur_char == '/' || cur_char == '-' || - cur_char == '*' || - (cur_char >= '0' && cur_char <= '9'); - - if (!is_valid_character) { - return cron_parse_invalid_expression; - } - - if (cur_char == '/') { - if (s_index == 0 || slash_index != 0) { - return cron_parse_invalid_expression; - } - - slash_index = s_index; - - s[s_index] = '\0'; - } else if (cur_char == '-') { - // At most one dash is allowed, and it must be before the slash - if (s_index == 0 || dash_index != 0 || slash_index != 0) { - return cron_parse_invalid_expression; - } - - dash_index = s_index; - - s[s_index] = '\0'; - } - - s_index++; - } - - uint8_t start; - uint8_t end = max; - uint8_t interval = 0; - - if (s[0] == '*') { - if (s[1] != '\0' || dash_index != 0) { - return cron_parse_invalid_expression; - } - - start = min; - interval = 1; - } else { - SAFE_ATOI(start, s, min, max); - - if (dash_index > 0) { - SAFE_ATOI(end, &s[dash_index + 1], min, max); - interval = 1; - } - } - - if (slash_index > 0) { - SAFE_ATOI(interval, &s[slash_index + 1], 1, max - min); - } - - if (interval == 0) { - *out |= ((uint64_t)1) << (start - min); - } else { - while (start <= end) { - *out |= ((uint64_t)1) << (start - min); - start += interval; - } - } - - return cron_parse_ok; -} - -/* - * Given an expression part, produce a bitfield defining what numbers in the - * min-max range the part represents. A part consists of one or more range - * expressions, separated by commas. - */ -cron_parse_error ce_parse_part(uint64_t *out, char *s, uint8_t min, - uint8_t max) { - *out = 0; - - char *next; - cron_parse_error res; - - while ((next = strchr(s, ',')) != NULL) { - next[0] = '\0'; - - res = ce_parse_range(out, s, min, max); - - if (res != cron_parse_ok) { - return res; - } - - s = next + 1; - } - - // Make sure to parse the final range as well - return ce_parse_range(out, s, min, max); -} - -/* - * Return how many bits are set in the bitfield, better known as popcount. I - * added my own implementation (taken from my algorithms course) as I don't want - * to be dependent on GCC-specific extensions. - */ -uint8_t uint64_t_popcount(uint64_t n) { - uint8_t set_bits = 0; - - while (n != 0) { - // This sets the least significant bit to zero (very cool) - n &= n - 1; - - set_bits++; - } - - return set_bits; -} - -/* - * Convert a bitfield into an array containing the numbers in the min-max range - * it represents. - */ -uint8_t bf_to_nums(uint8_t **out, uint64_t bf, uint8_t min, uint8_t max) { - // Each bit field only has `max - min + 1` meaningful bits. All other bits - // should be ignored, and can be any value. By shifting the bit field back - // and forth, we set these excessive bits to zero, ensuring popcount returns - // the correct value. - uint8_t excess_bits = 64 - (max - min + 1); - bf = (bf << excess_bits) >> excess_bits; - - uint8_t size = uint64_t_popcount(bf); - uint8_t *buf = malloc(size * sizeof(uint8_t)); - - uint8_t bit_index = 0, buf_index = 0; - - while (buf_index < size && bit_index <= max - min) { - if (((uint64_t)1 << bit_index) & bf) { - // Resize buffer if needed - buf[buf_index] = min + bit_index; - buf_index++; - } - - bit_index++; - } - - *out = buf; - - return size; -} - -/* - * Parse a cron expression string into a cron_expression struct. - */ -cron_parse_error ce_parse_expression(cron_expression *out, - const char *expression) { - // The parsing functions modify the input string in-place - char *s = strdup(expression); - char *orig_s = s; - - cron_parse_error res = cron_parse_ok; - - // First we divide the input string into its parts, divided by spaces. - // Each part is delimited by a NULL byte. - uint8_t part_count = 0; - char *parts[max_parts]; - char *next_space; - - // Skip leading spaces - size_t offset = 0; - - while (s[offset] == ' ') { - offset++; - } - - s += offset; - - while (part_count < max_parts && ((next_space = strchr(s, ' ')) != NULL)) { - next_space[0] = '\0'; - - parts[part_count] = s; - part_count++; - - // Skip multiple spaces - offset = 1; - while (next_space[offset] == ' ') { - offset++; - } - s = next_space + offset; - } - - // Each iteration of the loop skips all trailing spaces. This means that, if - // s[0] isn't '\0', there's still another part before the end of the string. - if (s[0] != '\0') { - if (part_count == max_parts) { - res = cron_parse_too_many_parts; - goto end; - } - - parts[part_count] = s; - part_count++; - } - - if (part_count < min_parts) { - res = cron_parse_not_enough_parts; - goto end; - } - - // We now parse the parts in reverse. This is because the month part - // determines the maximum value of the day part. - - uint64_t bit_field = 0; - - // Months - if (part_count >= 4) { - res = ce_parse_part(&bit_field, parts[3], min[3], max[3]); - - if (res != cron_parse_ok) { - goto end; - } - - out->month_count = bf_to_nums(&out->months, bit_field, min[3], max[3]); - } - // If months aren't provided, they're replaced with a * - else { - out->month_count = bf_to_nums(&out->months, ~0, min[3], max[3]); - } - - // Determine what the largest allowed day value is, given the months - uint8_t max_day_value = 0; - - for (uint8_t i = 0; i < out->month_count; i++) { - max_day_value = - MAX(max_day_value, parse_month_days[out->months[i] - 1]); - } - - // Days - if (part_count >= 3) { - bit_field = 0; - - res = ce_parse_part(&bit_field, parts[2], min[2], max_day_value); - - if (res != cron_parse_ok) { - free(out->months); - - goto end; - } - - out->day_count = - bf_to_nums(&out->days, bit_field, min[2], max_day_value); - } - // If days aren't provided, they're replaced with a * - else { - out->day_count = bf_to_nums(&out->days, ~0, min[2], max_day_value); - } - - // Hours - bit_field = 0; - - res = ce_parse_part(&bit_field, parts[1], min[1], max[1]); - - if (res != cron_parse_ok) { - free(out->months); - free(out->days); - - goto end; - } - - out->hour_count = bf_to_nums(&out->hours, bit_field, min[1], max[1]); - - // Minutes - bit_field = 0; - - res = ce_parse_part(&bit_field, parts[0], min[0], max[0]); - - if (res != cron_parse_ok) { - free(out->months); - free(out->days); - free(out->hours); - - goto end; - } - - out->minute_count = bf_to_nums(&out->minutes, bit_field, min[0], max[0]); - -end: - // s is cloned - free(orig_s); - - return res; -} diff --git a/src/cron/expression.c.v b/src/cron/expression.c.v index 217b687..8c574c7 100644 --- a/src/cron/expression.c.v +++ b/src/cron/expression.c.v @@ -1,11 +1,11 @@ module cron -#flag -I @VMODROOT/c -#flag @VMODROOT/c/parse.o -#flag @VMODROOT/c/expression.o -#include "expression.h" +#flag -I @VMODROOT/libvieter/include +#flag -L @VMODROOT/libvieter/build +#flag -lvieter +#include "vieter_cron.h" -pub struct C.cron_expression { +pub struct C.vieter_cron_expression { minutes &u8 hours &u8 days &u8 @@ -16,7 +16,7 @@ pub struct C.cron_expression { month_count u8 } -pub type Expression = C.cron_expression +pub type Expression = C.vieter_cron_expression // == returns whether the two expressions are equal by value. fn (ce1 Expression) == (ce2 Expression) bool { @@ -57,7 +57,7 @@ fn (ce1 Expression) == (ce2 Expression) bool { return true } -struct C.cron_simple_time { +struct C.vieter_cron_simple_time { year int month int day int @@ -65,7 +65,7 @@ struct C.cron_simple_time { minute int } -type SimpleTime = C.cron_simple_time +type SimpleTime = C.vieter_cron_simple_time enum ParseError as u8 { ok = 0 @@ -88,12 +88,12 @@ fn (e ParseError) str() string { } } -fn C.ce_init() &C.cron_expression +fn C.vieter_cron_expr_init() &C.vieter_cron_expression -fn C.ce_free(ce &C.cron_expression) +fn C.vieter_cron_expr_free(ce &C.vieter_cron_expression) -fn C.ce_next(out &C.cron_simple_time, ce &C.cron_expression, ref &C.cron_simple_time) +fn C.vieter_cron_expr_next(out &C.vieter_cron_simple_time, ce &C.vieter_cron_expression, ref &C.vieter_cron_simple_time) -fn C.ce_next_from_now(out &C.cron_simple_time, ce &C.cron_expression) +fn C.vieter_cron_expr_next_from_now(out &C.vieter_cron_simple_time, ce &C.vieter_cron_expression) -fn C.ce_parse_expression(out &C.cron_expression, s &char) ParseError +fn C.vieter_cron_expr_parse(out &C.vieter_cron_expression, s &char) ParseError diff --git a/src/cron/expression.v b/src/cron/expression.v index c463d06..62692fa 100644 --- a/src/cron/expression.v +++ b/src/cron/expression.v @@ -5,13 +5,13 @@ import time // free the memory associated with the Expression. [unsafe] pub fn (ce &Expression) free() { - C.ce_free(ce) + C.vieter_cron_expr_free(ce) } // parse_expression parses a string into an Expression. pub fn parse_expression(exp string) !&Expression { - out := C.ce_init() - res := C.ce_parse_expression(out, exp.str) + out := C.vieter_cron_expr_init() + res := C.vieter_cron_expr_parse(out, exp.str) if res != .ok { return error(res.str()) @@ -32,7 +32,7 @@ pub fn (ce &Expression) next(ref time.Time) time.Time { } out := SimpleTime{} - C.ce_next(&out, ce, &st) + C.vieter_cron_expr_next(&out, ce, &st) return time.new_time(time.Time{ year: out.year @@ -47,7 +47,7 @@ pub fn (ce &Expression) next(ref time.Time) time.Time { // current time as reference. pub fn (ce &Expression) next_from_now() time.Time { out := SimpleTime{} - C.ce_next_from_now(&out, ce) + C.vieter_cron_expr_next_from_now(&out, ce) return time.new_time(time.Time{ year: out.year diff --git a/src/cron/v.mod b/src/cron/v.mod deleted file mode 100644 index e69de29..0000000 diff --git a/src/libvieter b/src/libvieter new file mode 160000 index 0000000..11709cc --- /dev/null +++ b/src/libvieter @@ -0,0 +1 @@ +Subproject commit 11709cc611c02a4e9140409a0e81d639522c06f1