diff --git a/Makefile b/Makefile index 2cf23b3..2f92bcf 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,10 @@ .POSIX: CFLAGS += -Wall -Wextra -pedantic -std=c99 - -#CFLAGS += -ggdb CFLAGS += -flto -O2 -rotagen: rotagen.o Makefile - $(CC) $(CFLAGS) $(LDFLAGS) -o rotagen rotagen.o +rotagen: rotagen.o conf_reader.o Makefile + $(CC) $(CFLAGS) $(LDFLAGS) -o rotagen rotagen.o conf_reader.o rotagen.o: rotagen.c rotagen.h Makefile +conf_reader.o: conf_reader.c rotagen.h Makefile diff --git a/conf_reader.c b/conf_reader.c new file mode 100644 index 0000000..ee297ce --- /dev/null +++ b/conf_reader.c @@ -0,0 +1,293 @@ +#include "rotagen.h" + +#include +#include +#include +#include + +#define MAX_LINE_LENGTH 64 + +enum conf_section { + CONF_SECTION_PEOPLE, + CONF_SECTION_JOBS, + CONF_SECTION_SLOTS, + CONF_SECTION_CONSTRAINTS, + + CONF_SECTION_COUNT, +}; + +static const char *section_names[CONF_SECTION_COUNT] = { + [CONF_SECTION_PEOPLE] = "people", + [CONF_SECTION_JOBS] = "jobs", + [CONF_SECTION_SLOTS] = "slots", + [CONF_SECTION_CONSTRAINTS] = "constraints", +}; + +static FILE *stream; +static int line_len; +static char line[MAX_LINE_LENGTH]; +static bool section_read[CONF_SECTION_COUNT]; + +static void next_line(void); +static bool line_is_heading(void); +static enum conf_section read_heading(void); +static bool all_sections(void); +static void read_people(struct config *conf_out); +static void read_jobs(struct config *conf_out); +static void read_slots(struct config *conf_out); +static void read_constraints(struct config *conf_out); +static bool parse_constraint(const struct config *conf, + char *input, + int len, + struct constraint *out); + +void read_config(const char *path, struct config *config_out) +{ + stream = fopen(path, "r"); + if (stream == NULL) { + fprintf(stderr, "Fatal error: can't open config file %s\n", path); + exit(1); + } + + memset(section_read, 0, sizeof(section_read)); + + do + next_line(); + while (!line_is_heading()); + do { + const enum conf_section section = read_heading(); + switch (section) { + case CONF_SECTION_PEOPLE: + read_people(config_out); + break; + case CONF_SECTION_JOBS: + read_jobs(config_out); + break; + case CONF_SECTION_SLOTS: + read_slots(config_out); + break; + case CONF_SECTION_CONSTRAINTS: + read_constraints(config_out); + break; + + default: + fprintf(stderr, "Warning: unhandled conf section %d\n", section); + continue; + } + section_read[section] = true; + } while (!all_sections()); + + fclose(stream); +} + +static void next_line(void) +{ + line_len = 0; + int c; + while (1) { + if (line_len >= MAX_LINE_LENGTH) { + fprintf(stderr, + "Warning: line length limit reached, line truncated\n"); + return; + } + c = fgetc(stream); + if (c == '\n' || c == EOF) { + line[line_len] = '\0'; + return; + } + line[line_len++] = c; + } +} + +static bool line_is_heading(void) +{ + return line[0] == '[' && line[line_len - 1] == ']'; +} + +static enum conf_section read_heading(void) +{ + for (int i = 0; i < CONF_SECTION_COUNT; ++i) { + if (strncasecmp(section_names[i], &line[1], line_len - 2) == 0) + return (enum conf_section)i; + } + fprintf(stderr, "Fatal error: unexpected section %s\n", line); + exit(1); +} + +static bool all_sections(void) +{ + for (int i = 0; i < CONF_SECTION_COUNT; ++i) { + if (!section_read[i]) + return false; + } + return true; +} + +static void read_people(struct config *conf_out) +{ + while (1) { + next_line(); + if (line_is_heading()) { + return; + } else if (line_len == 0) { + if (feof(stream)) + return; + else + continue; + } + strcpy(conf_out->people[conf_out->num_people++], line); + } +} + +static void read_jobs(struct config *conf_out) +{ + while (1) { + next_line(); + if (line_is_heading()) { + return; + } else if (line_len == 0) { + if (feof(stream)) + return; + else + continue; + } + strcpy(conf_out->jobs[conf_out->num_jobs++], line); + } +} + +static void read_slots(struct config *conf_out) +{ + while (1) { + next_line(); + if (line_is_heading()) { + return; + } else if (line_len == 0) { + if (feof(stream)) + return; + else + continue; + } + strcpy(conf_out->slots[conf_out->num_slots++], line); + } +} + +static void read_constraints(struct config *conf_out) +{ + while (1) { + next_line(); + if (line_is_heading()) { + return; + } else if (line_len == 0) { + if (feof(stream)) + return; + else + continue; + } + + struct constraint *out + = &conf_out->constraints[conf_out->num_constraints]; + if (parse_constraint(conf_out, line, line_len, out)) + ++conf_out->num_constraints; + else + fprintf( + stderr, + "Warning: failed to parse constraint \"%s\"; will be ignored\n", + line); + } +} + +static bool parse_constraint(const struct config *conf, + char *input, + int len, + struct constraint *out) +{ + bool match = false; + for (int i = 0; i < conf->num_people; ++i) { + const int person_len = strlen(conf->people[i]); + if (strncasecmp(input, conf->people[i], person_len) == 0) { + out->person = i; + input += person_len + 1; + len -= person_len + 1; + match = true; + } + } + if (!match) { + fprintf(stderr, "Unknown person in constraint\n"); + return false; + } + + const char *filler = "can't do "; + const int filler_len = strlen(filler); + if (strncasecmp(input, filler, filler_len) == 0) { + input += filler_len; + len -= filler_len; + } else { + fprintf(stderr, "Invalid constraint syntax\n"); + return false; + } + + const char *job_str = "job ", *slot_str = "slot "; + const int job_str_len = strlen(job_str), slot_str_len = strlen(slot_str); + if (strncasecmp(input, job_str, job_str_len) == 0) { + out->type = JOB_EXEMPTION_CONSTRAINT; + input += job_str_len; + len -= job_str_len; + } else if (strncasecmp(input, slot_str, slot_str_len) == 0) { + out->type = SLOT_EXEMPTION_CONSTRAINT; + input += slot_str_len; + len -= slot_str_len; + } else { + fprintf(stderr, "Unknown constraint type\n"); + return false; + } + + if (input[0] == '"' && input[len - 1] == '"') { + ++input; + len -= 2; + } else { + fprintf(stderr, "Missing quotes in constraint\n"); + return false; + } + + switch (out->type) { + case SLOT_EXEMPTION_CONSTRAINT: + match = false; + for (int i = 0; i < conf->num_slots; ++i) { + const int slot_len = strlen(conf->slots[i]); + if (strncasecmp(input, conf->slots[i], slot_len) == 0) { + out->object.slot = i; + input += slot_len + 1; + len -= slot_len + 1; + match = true; + } + } + if (!match) { + fprintf(stderr, "Unknown slot in constraint\n"); + return false; + } + break; + + case JOB_EXEMPTION_CONSTRAINT: + match = false; + for (int i = 0; i < conf->num_jobs; ++i) { + const int job_len = strlen(conf->jobs[i]); + if (strncasecmp(input, conf->jobs[i], job_len) == 0) { + out->object.job = i; + input += job_len + 1; + len -= job_len + 1; + match = true; + } + } + if (!match) { + fprintf(stderr, "Unknown job in constraint\n"); + return false; + } + break; + + default: + fprintf(stderr, "Warning: unhandled constraint type in %s()", __func__); + break; + } + + return true; +} diff --git a/rotagen.c b/rotagen.c index b67daf4..c2ed791 100644 --- a/rotagen.c +++ b/rotagen.c @@ -9,35 +9,7 @@ #define POOL_SIZE 0x1000 #define MAX_PEOPLE 32 -/* Hard-coded test data. */ -static const char *people[] = { - "alice", "bob", "charlie", "dave", "eve", "fred", "george", -}; -static const int num_people = sizeof(people) / sizeof(people[0]); -static const char *jobs[] = { - "washing dishes", - "putting away", - "wiping surfaces", -}; -static const int num_jobs = sizeof(jobs) / sizeof(jobs[0]); -static const char *slots[] = { - "mon", "tue", "wed", "thu", "fri", "sat", "sun", -}; -static const int num_slots = sizeof(slots) / sizeof(slots[0]); -static const struct constraint constraints[] = { - { - .person = 0, - .type = JOB_EXEMPTION_CONSTRAINT, - .object = { .job = 2 }, - }, - { - .person = 1, - .type = SLOT_EXEMPTION_CONSTRAINT, - .object = { .slot = 4 }, - }, -}; -static const int num_constraints = sizeof(constraints) / sizeof(constraints[0]); - +static struct config conf; static uint8_t pool[POOL_SIZE]; static uint8_t *free_ptr; @@ -66,8 +38,11 @@ int main(void) } srand(seed); + memset(&conf, 0, sizeof(conf)); + read_config("rotagen.conf", &conf); + struct slot_result *rota - = pool_alloc(num_slots * sizeof(struct slot_result)); + = pool_alloc(conf.num_slots * sizeof(struct slot_result)); if (rota == NULL) { fprintf(stderr, "Fatal error: memory pool exhausted in %s().\n", @@ -84,46 +59,46 @@ int main(void) void generate_rota(struct slot_result *rota_out) { int prev[MAX_JOBS]; - for (int job = 0; job < num_jobs; ++job) - prev[job] = num_people; + for (int job = 0; job < conf.num_jobs; ++job) + prev[job] = conf.num_people; - for (int slot = 0; slot < num_slots; ++slot) { + for (int slot = 0; slot < conf.num_slots; ++slot) { do { - for (int job = 0; job < num_jobs; ++job) { + for (int job = 0; job < conf.num_jobs; ++job) { struct assignment *assignment = &rota_out[slot].assignments[job]; do { assignment->slot = slot; assignment->job = job; - assignment->person = rand() % num_people; + assignment->person = rand() % conf.num_people; } while (!satisfies_assignment_constraints(assignment) || previously_allocated(prev, assignment->person)); } } while (!satisfies_slot_constraints(&rota_out[slot])); - for (int job = 0; job < num_jobs; ++job) + for (int job = 0; job < conf.num_jobs; ++job) prev[job] = rota_out[slot].assignments[job].person; } } bool satisfies_assignment_constraints(const struct assignment *assignment) { - for (int i = 0; i < num_constraints; ++i) { - if (assignment->person != constraints[i].person) + for (int i = 0; i < conf.num_constraints; ++i) { + if (assignment->person != conf.constraints[i].person) continue; - switch (constraints[i].type) { + switch (conf.constraints[i].type) { case JOB_EXEMPTION_CONSTRAINT: - if (assignment->job == constraints[i].object.job) + if (assignment->job == conf.constraints[i].object.job) return false; break; case SLOT_EXEMPTION_CONSTRAINT: - if (assignment->slot == constraints[i].object.slot) + if (assignment->slot == conf.constraints[i].object.slot) return false; break; default: fprintf(stderr, "Warning: unhandled constraint type %d in %s()", - constraints[i].type, + conf.constraints[i].type, __func__); break; } @@ -133,7 +108,7 @@ bool satisfies_assignment_constraints(const struct assignment *assignment) bool previously_allocated(int prev[MAX_JOBS], int person) { - for (int job = 0; job < num_jobs; ++job) { + for (int job = 0; job < conf.num_jobs; ++job) { if (prev[job] == person) return true; } @@ -142,8 +117,8 @@ bool previously_allocated(int prev[MAX_JOBS], int person) bool satisfies_slot_constraints(const struct slot_result *result) { - for (int i = 0; i < num_jobs; ++i) { - for (int j = i + 1; j < num_jobs; ++j) { + for (int i = 0; i < conf.num_jobs; ++i) { + for (int j = i + 1; j < conf.num_jobs; ++j) { if (result->assignments[i].person == result->assignments[j].person) return false; } @@ -153,13 +128,13 @@ bool satisfies_slot_constraints(const struct slot_result *result) void print_rota(const struct slot_result *rota) { - for (int slot = 0; slot < num_slots; ++slot) { + for (int slot = 0; slot < conf.num_slots; ++slot) { printf("----------------------------------------\n"); - for (int job = 0; job < num_jobs; ++job) { + for (int job = 0; job < conf.num_jobs; ++job) { printf("%s\t%s\t\t%s\n", - job == 0 ? slots[slot] : " ", - jobs[job], - people[rota[slot].assignments[job].person]); + job == 0 ? conf.slots[slot] : " ", + conf.jobs[job], + conf.people[rota[slot].assignments[job].person]); } } printf("----------------------------------------\n"); diff --git a/rotagen.h b/rotagen.h index 582a022..1c3b23f 100644 --- a/rotagen.h +++ b/rotagen.h @@ -3,7 +3,12 @@ #include -#define MAX_JOBS 8 +#define MAX_PEOPLE 32 +#define MAX_JOBS 32 +#define MAX_SLOTS 32 +#define MAX_CONSTRAINTS 32 + +#define MAX_STRING_LEN 32 enum constraint_type { JOB_EXEMPTION_CONSTRAINT, @@ -29,7 +34,18 @@ struct slot_result { struct assignment assignments[MAX_JOBS]; }; +typedef char string[MAX_STRING_LEN]; +struct config { + string people[MAX_PEOPLE]; + int num_people; + string jobs[MAX_JOBS]; + int num_jobs; + string slots[MAX_SLOTS]; + int num_slots; + struct constraint constraints[MAX_CONSTRAINTS]; + int num_constraints; +}; void generate_rota(struct slot_result *rota_out); void generate_assignment(int slot, int job, struct assignment *assignment_out); @@ -37,5 +53,6 @@ bool satisfies_assignment_constraints(const struct assignment *assignment); bool previously_allocated(int previous_assignments[MAX_JOBS], int person); bool satisfies_slot_constraints(const struct slot_result *slot); void print_rota(const struct slot_result *rota); +void read_config(const char *path, struct config *config_out); #endif