refactor(cron): make code a bit more expressive
ci/woodpecker/pr/docs Pipeline was successful Details
ci/woodpecker/pr/lint Pipeline failed Details
ci/woodpecker/pr/build Pipeline was successful Details
ci/woodpecker/pr/docker Pipeline was successful Details
ci/woodpecker/pr/man Pipeline was successful Details
ci/woodpecker/pr/test Pipeline was successful Details

Jef Roosens 2023-01-16 15:36:02 +01:00
parent 786787cf1f
commit 83b0451d3c
3 changed files with 81 additions and 101 deletions

View File

@ -1,6 +1,7 @@
#ifndef VIETER_CRON #ifndef VIETER_CRON
#define VIETER_CRON #define VIETER_CRON
#include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@ -43,6 +44,7 @@ void cron_ce_next(cron_simple_time *out, cron_expression *ce,
void cron_ce_next_from_now(cron_simple_time *out, cron_expression *ce); void cron_ce_next_from_now(cron_simple_time *out, cron_expression *ce);
enum cron_parse_error cron_ce_parse_expression(cron_expression *out, char *s); enum cron_parse_error cron_ce_parse_expression(cron_expression *out,
const char *expression);
#endif #endif

View File

@ -43,52 +43,53 @@ const uint8_t max_parts = 4;
*/ */
cron_parse_error ce_parse_range(uint64_t *out, char *s, uint8_t min, cron_parse_error ce_parse_range(uint64_t *out, char *s, uint8_t min,
uint8_t max) { uint8_t max) {
size_t slash_index = 0; size_t slash_index = 0, dash_index = 0;
size_t dash_index = 0; size_t s_index = 0;
size_t i = 0; char cur_char;
bool is_valid_character;
// We first iterate over the string to determine whether it contains a slash while ((cur_char = s[s_index]) != '\0') {
// and/or a dash. We know the dash can only be valid if it appears before is_valid_character = cur_char == '/' || cur_char == '-' ||
// the slash. cur_char == '*' ||
while (s[i] != '\0') { (cur_char >= '0' && cur_char <= '9');
if (s[i] == '/') {
// At most one slash is allowed
if (i == 0 || slash_index != 0) {
return cron_parse_invalid_expression;
}
slash_index = i; if (!is_valid_character) {
s[i] = '\0';
} else if (s[i] == '-') {
// At most one dash is allowed, and it must be before the slash
if (i == 0 || dash_index != 0 || slash_index != 0) {
return cron_parse_invalid_expression;
}
dash_index = i;
s[i] = '\0';
} else if (s[i] != '*' && (s[i] < '0' || s[i] > '9')) {
return cron_parse_invalid_expression; return cron_parse_invalid_expression;
} }
i++; 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++;
} }
// Parse the three possible numbers in the pattern
uint8_t start; uint8_t start;
uint8_t end = max; uint8_t end = max;
uint8_t interval = 0; uint8_t interval = 0;
if (s[0] == '*') { if (s[0] == '*') {
// A star character is only allowed on its own if (s[1] != '\0' || dash_index != 0) {
if (s[1] == '\0' && dash_index == 0) {
start = min;
interval = 1;
} else {
return cron_parse_invalid_expression; return cron_parse_invalid_expression;
} }
start = min;
interval = 1;
} else { } else {
SAFE_ATOI(start, s, min, max); SAFE_ATOI(start, s, min, max);
@ -128,6 +129,7 @@ cron_parse_error ce_parse_part(uint64_t *out, char *s, uint8_t min,
while ((next = strchr(s, ',')) != NULL) { while ((next = strchr(s, ',')) != NULL) {
next[0] = '\0'; next[0] = '\0';
res = ce_parse_range(out, s, min, max); res = ce_parse_range(out, s, min, max);
if (res != cron_parse_ok) { if (res != cron_parse_ok) {
@ -147,15 +149,16 @@ cron_parse_error ce_parse_part(uint64_t *out, char *s, uint8_t min,
* to be dependent on GCC-specific extensions. * to be dependent on GCC-specific extensions.
*/ */
uint8_t uint64_t_popcount(uint64_t n) { uint8_t uint64_t_popcount(uint64_t n) {
uint8_t c = 0; uint8_t set_bits = 0;
while (n != 0) { while (n != 0) {
// This sets the least significant bit to zero (very cool) // This sets the least significant bit to zero (very cool)
n &= n - 1; n &= n - 1;
c++;
set_bits++;
} }
return c; return set_bits;
} }
/* /*
@ -169,19 +172,20 @@ uint8_t bf_to_nums(uint8_t **out, uint64_t bf, uint8_t min, uint8_t max) {
// the correct value. // the correct value.
uint8_t excess_bits = 64 - (max - min + 1); uint8_t excess_bits = 64 - (max - min + 1);
bf = (bf << excess_bits) >> excess_bits; bf = (bf << excess_bits) >> excess_bits;
uint8_t size = uint64_t_popcount(bf); uint8_t size = uint64_t_popcount(bf);
uint8_t *buf = malloc(size * sizeof(uint8_t)); uint8_t *buf = malloc(size * sizeof(uint8_t));
uint8_t i = 0, j = 0; uint8_t bit_index = 0, buf_index = 0;
while (j < size && i <= max - min) { while (buf_index < size && bit_index <= max - min) {
if (((uint64_t)1 << i) & bf) { if (((uint64_t)1 << bit_index) & bf) {
// Resize buffer if needed // Resize buffer if needed
buf[j] = min + i; buf[buf_index] = min + bit_index;
j++; buf_index++;
} }
i++; bit_index++;
} }
*out = buf; *out = buf;
@ -192,9 +196,10 @@ uint8_t bf_to_nums(uint8_t **out, uint64_t bf, uint8_t min, uint8_t max) {
/* /*
* Parse a cron expression string into a cron_expression struct. * Parse a cron expression string into a cron_expression struct.
*/ */
cron_parse_error ce_parse_expression(cron_expression *out, char *s) { cron_parse_error ce_parse_expression(cron_expression *out,
const char *expression) {
// The parsing functions modify the input string in-place // The parsing functions modify the input string in-place
s = strdup(s); char *s = strdup(expression);
char *orig_s = s; char *orig_s = s;
cron_parse_error res = cron_parse_ok; cron_parse_error res = cron_parse_ok;
@ -203,7 +208,7 @@ cron_parse_error ce_parse_expression(cron_expression *out, char *s) {
// Each part is delimited by a NULL byte. // Each part is delimited by a NULL byte.
uint8_t part_count = 0; uint8_t part_count = 0;
char *parts[max_parts]; char *parts[max_parts];
char *next; char *next_space;
// Skip leading spaces // Skip leading spaces
size_t offset = 0; size_t offset = 0;
@ -214,18 +219,18 @@ cron_parse_error ce_parse_expression(cron_expression *out, char *s) {
s += offset; s += offset;
while (part_count < max_parts && ((next = strchr(s, ' ')) != NULL)) { while (part_count < max_parts && ((next_space = strchr(s, ' ')) != NULL)) {
next[0] = '\0'; next_space[0] = '\0';
parts[part_count] = s;
parts[part_count] = s;
part_count++; part_count++;
// Skip multiple spaces // Skip multiple spaces
offset = 1; offset = 1;
while (next[offset] == ' ') { while (next_space[offset] == ' ') {
offset++; offset++;
} }
s = next + offset; s = next_space + offset;
} }
// Each iteration of the loop skips all trailing spaces. This means that, if // Each iteration of the loop skips all trailing spaces. This means that, if

View File

@ -1,59 +1,32 @@
module cron module cron
fn test_not_allowed() { fn test_not_allowed() {
illegal_expressions := [
'4 *-7',
'4 *-7/4',
'4 7/*',
'0 0 30 2',
'0 /5',
'0 ',
'0',
' 0',
' 0 ',
'1 2 3 4~9',
'1 1-3-5',
'0 5/2-5',
'',
'1 1/2/3',
'*5 8',
'x 8',
]
mut res := false mut res := false
parse_expression('4 *-7') or { res = true }
assert res
res = false for exp in illegal_expressions {
parse_expression('4 *-7/4') or { res = true } res = false
assert res parse_expression(exp) or { res = true }
assert res, "'$exp' should produce an error"
res = false }
parse_expression('4 7/*') or { res = true }
assert res
res = false
parse_expression('0 0 30 2') or { res = true }
assert res
res = false
parse_expression('0 0 30 2 0') or { res = true }
assert res
res = false
parse_expression('0 /5') or { res = true }
assert res
res = false
parse_expression('0 ') or { res = true }
assert res
res = false
parse_expression('0') or { res = true }
assert res
res = false
parse_expression('1 2 3 4~9') or { res = true }
assert res
res = false
parse_expression('1 1-3-5') or { res = true }
assert res
res = false
parse_expression('0 5/2-5') or { res = true }
assert res
}
fn test_leading_star() {
mut x := false
parse_expression('*5 8') or { x = true }
assert x
x = false
parse_expression('x 8') or { x = true }
assert x
} }
fn test_auto_extend() ! { fn test_auto_extend() ! {