#include <libgen.h>
#include "parser.h"
#include "util.h"
typedef enum parser_type {
TITLE = 0,
ITEMS,
STEPS,
} pt;
static const int
parse_title(const char * s, recipe * r, pt * type)
{
fdebug("^ title\n");
if (s[0] == '@' && strlen(s) > 1) {
char tmp[LINE_SIZE] = "";
strcpy(tmp, ++s);
trim(tmp);
r->title = strdup(tmp);
} else {
r->title = strdup(r->filename);
}
*type = ITEMS;
return 0;
}
static const int
parse_item(const char * s, recipe * r, pt * type, char * error)
{
fdebug("^ item\n");
if (!strcmp(s, "---")) {
*type = STEPS;
return 0;
}
if (s[0] == '!') {
fdebug("INCLUDING: %s\n", s);
char tmp[LINE_SIZE] = "";
char path[LINE_SIZE] = "";
strcpy(tmp, ++s);
trim(tmp);
if (!strlen(tmp)) {
sprintf(error, "malformed include: %s", s);
return 1;
}
if (tmp[0] == '/') {
strcpy(path, tmp);
} else {
strcpy(path, r->path);
strcat(path, "/");
strcat(path, tmp);
}
recipe * rr = parse(path, r->filename);
if (!rr) {
sprintf(error, "Couldn't include %s", path);
return 1;
}
copy_items(r, rr);
free_recipe(rr);
return 0;
}
int l = strlen(s);
int val = 1; /* key vs value flag */
int itemc = 0;
int c = 0;
char buf[LINE_SIZE] = "";
for (int i = 0; i < l; i++) {
if (val) {
if (s[i] == ',' || s[i] == '=') {
buf[c++] = '\0';
if (!strlen(buf)) {
sprintf(error, "malformed ingredient: %s", s);
return 1;
}
trim(buf);
if (strlen(buf)) {
new_item(r);
r->i[r->in - 1]->name = strdup(buf);
itemc++;
buf[0] = '\0';
c = 0;
}
if (s[i] == '=')
val = 0;
continue;
}
}
buf[c++] = s[i];
}
buf[c] = '\0';
trim(buf);
if (!strlen(buf)) {
sprintf(error, "empty ingredient quantity: %s", s);
return 1;
}
for (int i = 0; i < itemc; i++) {
r->i[r->in - 1 - i]->qty = strdup(buf);
}
return 0;
}
static const int
parse_step(const char * s, recipe * r, char * error)
{
fdebug("^ step\n");
if ((s[0] == '>' || s[0] == '-') && strlen(s+1)) {
new_step(r);
r->s[r->sn - 1]->type = s[0] == '>' ? COOK : PREP;
char *tmp = strdup(s+1);
trim(tmp);
r->s[r->sn - 1]->inst = tmp;
return 0;
}
sprintf(error, "malformed step: %s", s);
return 1;
}
static const int
handle_line(const char * s, recipe * r, char * error, pt * type, const char * prev)
{
fdebug("Handling line: %s\n", s);
if (strlen(s) == 0) return 0;
if (s[0] == '#') return 0;
switch (*type) {
case TITLE:
if (!parse_title(s, r, type))
if (parse_item(s, r, type, error))
return 1;
break;
case ITEMS:
if (parse_item(s, r, type, error))
return 1;
break;
case STEPS:
if (parse_step(s, r, error))
return 1;
}
return 0;
}
static const int
next_escaped_line(FILE *f, char * line, int * lino, char * error) {
if (!fgets(line, LINE_SIZE, f))
return 1;
(*lino)++;
if (line[strlen(line) - 1] != '\n' && !feof(f)) {
sprintf(error, "line exceeds %d characters", LINE_SIZE);
return -1;
}
trim(line);
int l = strlen(line);
/* Last char is `\n` check if */
/* (last - 1) is `\` */
while (l > 0 && line[l - 1] == '\\') {
char tmp[LINE_SIZE] = "";
if (!fgets(tmp, LINE_SIZE, f))
/* return line on EOF */
break;
(*lino)++;
trim(tmp);
if (strlen(tmp) + l >= LINE_SIZE) {
sprintf(error, "line exceeds %d characters", LINE_SIZE);
return -1;
}
/* remove `\` */
line[l - 1] = '\0';
strcat(line, tmp);
l = strlen(line);
}
return 0;
}
recipe *
parse(char * path, const char * prev)
{
fdebug("Parsing: %s\n", path);
FILE *f;
if (!strcmp(path, "-")) {
f = stdin;
} else {
f = fopen(path, "r");
if (!f) return NULL;
}
recipe * r = new_recipe();
char line[LINE_SIZE] = "";
char *dirc, *bname;
dirc = strdup(path);
bname = strdup(path);
r->path = strdup(dirname(dirc));
r->filename = strdup(basename(bname));
free(dirc); free(bname);
/* (re)init px */
char error[LINE_SIZE] = "";
int lino = 0;
enum parser_type type = TITLE;
while (!next_escaped_line(f, line, &lino, error)) {
if (handle_line(line, r, error, &type, prev))
break;
}
if (strlen(error) > 0) {
fprintf(stderr, "%s:%d: %s\n", path, lino, error);
free_recipe(r);
return NULL;
}
return r;
}