Create config parser and read config from file
This commit is contained in:
parent
c6d01fd8ca
commit
784e0a0f91
7
Makefile
7
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
|
||||
|
293
conf_reader.c
Normal file
293
conf_reader.c
Normal file
@ -0,0 +1,293 @@
|
||||
#include "rotagen.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#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;
|
||||
}
|
75
rotagen.c
75
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");
|
||||
|
19
rotagen.h
19
rotagen.h
@ -3,7 +3,12 @@
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#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
|
||||
|
Loading…
x
Reference in New Issue
Block a user