summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/eval.c21
-rw-r--r--src/eval.h8
-rw-r--r--src/parser.c263
-rw-r--r--src/parser.h11
-rw-r--r--src/types.c299
-rw-r--r--src/types.h73
-rw-r--r--src/util.c54
-rw-r--r--src/util.h21
8 files changed, 750 insertions, 0 deletions
diff --git a/src/eval.c b/src/eval.c
new file mode 100644
index 0000000..a26cc21
--- /dev/null
+++ b/src/eval.c
@@ -0,0 +1,21 @@
+#include "eval.h"
+#include "util.h"
+
+recipe *
+eval(recipe * r)
+{
+ if (!r) return NULL;
+ recipe * r1 = new_recipe();
+ recipe * r2 = new_recipe();
+ /* attempt to merge items (adding qtys) */
+ merge_items(r1, r);
+ distinct_sum_items(r2, r1);
+ free_recipe(r1);
+
+ copy_metadata(r2, r);
+ /* /\* Resolve step type, variables, duration and step output (if any) *\/ */
+ /* finalize_steps(eve, r); */
+
+ return r2;
+}
+
diff --git a/src/eval.h b/src/eval.h
new file mode 100644
index 0000000..2d4f6a1
--- /dev/null
+++ b/src/eval.h
@@ -0,0 +1,8 @@
+#ifndef __EVAL_H
+#define __EVAL_H
+
+#include "types.h"
+
+recipe * eval(recipe * r);
+
+#endif /* __EVAL_H */
diff --git a/src/parser.c b/src/parser.c
new file mode 100644
index 0000000..df8d80b
--- /dev/null
+++ b/src/parser.c
@@ -0,0 +1,263 @@
+#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");
+ int rc = 0;
+
+ 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);
+ rc = 1;
+ }
+ *type = ITEMS;
+ return rc;
+}
+
+static const int
+parse_item(const char * s, recipe * r, pt * type, char * error)
+{
+ fdebug("^ item\n");
+ if (!strcmp(s, "---")) {
+ *type = STEPS;
+ return 0;
+ }
+
+ /* 1 - 9 in ascii */
+ if ((s[0] > 48 && s[0] < 58) || s[0] == '!') {
+ fdebug("INCLUDING: %s\n", s);
+
+ char tmp[LINE_SIZE] = "";
+ char path[LINE_SIZE] = "";
+
+ char *endptr;
+ int n = (int)strtol(s, &endptr, 10);
+ if (endptr[0] != '!') {
+ sprintf(error, "malformed include: %s", s);
+ return 1;
+ }
+
+ strcpy(tmp, ++endptr);
+ trim(tmp);
+
+ if (!strlen(tmp)) {
+ sprintf(error, "malformed include: %s", s);
+ return 1;
+ }
+
+ if (tmp[0] == '/' || 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;
+ }
+ rr->n = n;
+ //copy_items(r, rr);
+ new_subrecipe(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 (!itemc) {
+ sprintf(error, "malformed ingderient: %s", buf);
+ return 1;
+ }
+
+ 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] == '-' ||
+ s[0] == '+') && strlen(s+1)) {
+ new_step(r);
+ r->s[r->sn - 1]->type = s[0] == '>' ? COOK : s[0] == '-' ? PREP : SERVE;
+ 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 {
+ char tmp[LINE_SIZE] = "";
+ if (path[0] == '~') {
+ strcpy(tmp, getenv("HOME"));
+ strcat(tmp, path + 1);
+ } else {
+ strcpy(tmp, path);
+ }
+ f = fopen(tmp, "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;
+}
diff --git a/src/parser.h b/src/parser.h
new file mode 100644
index 0000000..2680da5
--- /dev/null
+++ b/src/parser.h
@@ -0,0 +1,11 @@
+#ifndef __PARSER_H
+#define __PARSER_H
+
+#include "types.h"
+
+#define LINE_SIZE 4096
+
+recipe *
+parse(char * path, const char * prev);
+
+#endif /* __PARSER_H */
diff --git a/src/types.c b/src/types.c
new file mode 100644
index 0000000..d9253af
--- /dev/null
+++ b/src/types.c
@@ -0,0 +1,299 @@
+#include <stdlib.h>
+
+#include "types.h"
+#include "util.h"
+
+recipe *
+new_recipe()
+{
+ recipe * r;
+ r = (recipe *) malloc(sizeof(recipe));
+ if (!r)
+ die("Couldn't allocate memory for recipe");
+
+ r->n = 1;
+ r->i = NULL;
+ r->in = 0;
+ r->s = NULL;
+ r->sn = 0;
+ r->r = NULL;
+ r->rn = 0;
+ r->filename = NULL;
+ r->path = NULL;
+ r->title = NULL;
+ return r;
+}
+
+void
+new_subrecipe(recipe * r, recipe * src)
+{
+ r->r = (recipe **)realloc(r->r, (r->rn + 1) * sizeof(recipe *));
+ if (!r->r)
+ die("Couldn't allocate memory for subricepie");
+
+ r->r[r->rn] = src;
+ r->rn++;
+}
+
+void
+new_item(recipe * r)
+{
+ r->i = (item **)realloc(r->i, (r->in + 1) * sizeof(item *));
+ if (!r->i)
+ die("Couldn't allocate memory for item");
+
+ r->i[r->in] = (item *)malloc(sizeof(item));
+ if (!r->i[r->in])
+ die("Couldn't allocate memory for item");
+ r->i[r->in]->name = NULL;
+ r->i[r->in]->qty = NULL;
+ r->in++;
+}
+
+void
+free_item(item * i)
+{
+ if (!i)
+ return;
+ if (i->name)
+ free(i->name);
+ if (i->qty)
+ free(i->qty);
+ free(i);
+}
+
+void
+new_step(recipe * r)
+{
+ r->s = (step **)realloc(r->s, (r->sn + 1) * sizeof(step *));
+ if (!r->s)
+ die("Couldn't allocate memory for step");
+
+ r->s[r->sn] = (step *)malloc(sizeof(step));
+ if (!r->s[r->sn])
+ die("Couldn't allocate memory for item");
+ r->s[r->sn]->inst = NULL;
+ r->s[r->sn]->duration = NULL;
+ r->s[r->sn]->result = NULL;
+ r->s[r->sn]->type = 0;
+ r->sn++;
+}
+
+void
+free_step(step * s)
+{
+ if (!s)
+ return;
+ if (s->inst)
+ free(s->inst);
+ if (s->duration)
+ free(s->duration);
+ if (s->result)
+ free(s->result);
+ free(s);
+}
+
+void
+free_recipe(recipe * r)
+{
+ if (!r)
+ return;
+
+ if (r->i) {
+ for (int i = 0; i < r->in; i++)
+ free_item(r->i[i]);
+ free(r->i);
+ }
+
+ if (r->s) {
+ for (int i = 0; i < r->sn; i++)
+ free_step(r->s[i]);
+ free(r->s);
+ }
+
+ if (r->r) {
+ for (int i = 0; i < r->rn; i++)
+ free_recipe(r->r[i]);
+ free(r->r);
+ }
+
+ if (r->filename)
+ free(r->filename);
+ if (r->path)
+ free(r->path);
+ if (r->title)
+ free(r->title);
+
+ free(r);
+}
+
+void
+pprint_items(recipe * r)
+{
+ printf("Ingredients for %s:\n", r->title);
+ for (int i = 0; i < r->in; i++)
+ printf("%s: %s\n", r->i[i]->name, r->i[i]->qty);
+ printf("\n");
+}
+
+void
+show(recipe * r)
+{
+ printf("Filename\t%s\n", r->filename);
+ printf("Dirname\t\t%s\n", r->path);
+ printf("Title\t\t%s\n\n", r->title);
+
+ for (int i = 0; i < r->in; i++)
+ printf("I:%s\t%s\n", r->i[i]->name, r->i[i]->qty);
+ if (r->sn) {
+ printf("\nSteps:\n~~~~~~\n\n");
+ for (int i = 0; i < r->sn; i++)
+ printf("%s: %s\n", r->s[i]->type == PREP ? "PREP" : "COOK", r->s[i]->inst);
+ }
+}
+
+void
+tojson(recipe * r)
+{
+ printf("{\"filename\":\"%s\",", r->filename);
+ printf("\"dirname\":\"%s\",", r->path);
+ printf("\"title\":\"%s\",", r->title);
+ printf("\"n\":\"%d\"", r->n);
+ if (r->rn) {
+ printf(",\"subrecipes\":[");
+ int i = 0;
+ for (; i < r->rn - 1; i++) {
+ tojson(r->r[i]);
+ printf(",");
+ }
+ tojson(r->r[i]);
+ printf("]");
+ }
+ if (r->in) {
+ printf(",\"ingredients\":{");
+ int i = 0;
+ for (; i < r->in - 1; i++)
+ printf("\"%s\":\"%s\",", r->i[i]->name, r->i[i]->qty);
+ printf("\"%s\":\"%s\"}", r->i[i]->name, r->i[i]->qty);
+ }
+ if (r->sn) {
+ printf(",\"steps\":[");
+ int i = 0;
+ for (; i < r->sn - 1; i++)
+ printf("\"%s\",", r->s[i]->inst);
+ printf("\"%s\"]", r->s[i]->inst);
+ }
+ printf("}");
+}
+
+void
+tohtml(recipe * r)
+{
+ printf("todo\n");
+}
+
+void
+torcp(recipe * r)
+{
+ printf("# %s/%s\n\n", r->path, r->filename);
+ printf("@%s\n\n", r->title);
+ for (int i = 0; i < r->rn; i++) {
+ printf("!%s/%s\n", r->r[i]->path, r->r[i]->filename);
+ }
+ for (int i = 0; i < r->in; i++)
+ printf("%s = %s\n", r->i[i]->name, r->i[i]->qty);
+ if (r->sn) {
+ printf("\n---\n\n");
+ for (int i = 0; i < r->sn; i++) {
+ char c;
+ if (r->s[i]->type == PREP)
+ c = '-';
+ else if (r->s[i]->type == COOK)
+ c = '>';
+ else
+ c = '+';
+ printf("%c %s\n", c, r->s[i]->inst);
+ }
+ }
+}
+
+void
+copy_metadata(recipe * dst, recipe * src)
+{
+ if (!dst || !src) return;
+ dst->filename = strdup(src->filename);
+ dst->path = strdup(src->path);
+ dst->title = strdup(src->title);
+}
+
+void
+copy_items(recipe * dst, recipe * src)
+{
+ if (!dst || !src) return;
+ for (int i = 0; i < src->in; i++) {
+ new_item(dst);
+ dst->i[dst->in - 1]->name = strdup(src->i[i]->name);
+ dst->i[dst->in - 1]->qty = strdup(src->i[i]->qty);
+ }
+}
+
+static void
+join_subrecipe_items(recipe * dst, recipe * src)
+{
+ if (!src || !dst) return;
+ for (int i = 0; i < src->rn; i++) {
+ join_subrecipe_items(dst, src->r[i]);
+ for (int j = 0; j < src->r[i]->in; j++) {
+ new_item(dst);
+ dst->i[dst->in - 1]->name = strdup(src->r[i]->i[j]->name);
+ if (src->r[i]->n > 1) {
+ char tmp[2048] = "";
+ sprintf(tmp, "%d X ( %s )", src->r[i]->n, src->r[i]->i[j]->qty);
+ dst->i[dst->in - 1]->qty = strdup(tmp);
+ }
+ else {
+ dst->i[dst->in - 1]->qty = strdup(src->r[i]->i[j]->qty);
+ }
+ }
+ }
+}
+
+void
+merge_items(recipe * dst, recipe * src)
+{
+ /* Join all items in src's subrecipes to dst */
+ join_subrecipe_items(dst, src);
+ /* Copy src items as well to dst */
+ copy_items(dst, src);
+}
+
+static int
+item_exists(const char * name, const recipe * r)
+{
+ for (int i = 0; i < r->in; i++)
+ if (!strcmp(r->i[i]->name, name))
+ return i;
+ return -1;
+}
+
+void
+distinct_sum_items(recipe * dst, recipe * src)
+{
+ if (!dst || !src) return;
+ for (int i = 0; i < src->in; i++) {
+ int n = item_exists(src->i[i]->name, dst);
+ if (n != -1) {
+ char tmp[2048] = "";
+ strcat(tmp, dst->i[n]->qty);
+ strcat(tmp, " + ");
+ strcat(tmp, src->i[i]->qty);
+ free(dst->i[n]->qty);
+ dst->i[n]->qty = strdup(tmp);
+ }
+ else {
+ new_item(dst);
+ dst->i[dst->in - 1]->name = strdup(src->i[i]->name);
+ dst->i[dst->in - 1]->qty = strdup(src->i[i]->qty);
+ }
+ }
+}
diff --git a/src/types.h b/src/types.h
new file mode 100644
index 0000000..0d870cf
--- /dev/null
+++ b/src/types.h
@@ -0,0 +1,73 @@
+#ifndef __TYPES_H
+#define __TYPES_H
+
+typedef struct item_t {
+ char *name;
+ char *qty;
+} item;
+
+enum step_type {
+ PREP = 0,
+ COOK,
+ SERVE,
+};
+
+typedef struct step_t {
+ char *inst;
+ char *duration;
+ char *result;
+ enum step_type type;
+} step;
+
+typedef struct recipe_t {
+ char * filename;
+ char * path;
+ char * title;
+ int n;
+ item **i;
+ int in;
+ step **s;
+ int sn;
+ struct recipe_t **r;
+ int rn;
+} recipe;
+
+recipe * new_recipe();
+void new_subrecipe(recipe * r, recipe * src);
+void new_item(recipe * r);
+void new_step(recipe * r);
+
+void free_recipe(recipe * r);
+void free_item(item * i);
+void free_step(step * s);
+
+void pprint_items(recipe * r);
+void show(recipe * r);
+void tojson(recipe * r);
+void tohtml(recipe * r);
+void torcp(recipe * r);
+
+/* Operations */
+
+/**
+ * Copy metadata from `src` to `dst`
+ */
+void copy_metadata(recipe * dst, recipe * src);
+
+/**
+ * Copy top level items from `src` to `dst`
+ */
+void copy_items(recipe * dst, recipe * src);
+
+/**
+ * Merge all items from `src` and add them to `dst`
+ */
+void merge_items(recipe * dst, recipe * src);
+
+/**
+ * Sum all top level item qtys in `src` and add them to `dst`
+ * making sure each item name exists only once.
+ */
+void distinct_sum_items(recipe * dst, recipe * src);
+
+#endif /* __TYPES_H */
diff --git a/src/util.c b/src/util.c
new file mode 100644
index 0000000..6eda622
--- /dev/null
+++ b/src/util.c
@@ -0,0 +1,54 @@
+#include "util.h"
+
+void
+fdebug(const char *fmt, ...)
+{
+#ifdef _FOOD_DEBUG
+ va_list ap;
+
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+#else
+ return;
+#endif
+}
+
+void
+die(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+
+ if (fmt[0] && fmt[strlen(fmt)-1] == ':') {
+ fputc(' ', stderr);
+ perror(NULL);
+ } else {
+ fputc('\n', stderr);
+ }
+
+ exit(1);
+}
+
+void
+trim(char * str)
+{
+ int i;
+ int begin = 0;
+ int end = strlen(str) - 1;
+
+ while (isspace((unsigned char) str[begin]))
+ begin++;
+
+ while (isspace((unsigned char) str[end]) && (end >= begin))
+ end--;
+
+ /* Shift all characters back to the start of the string array. */
+ for (i = begin; i <= end; i++)
+ str[i - begin] = str[i];
+
+ str[i - begin] = '\0';
+}
diff --git a/src/util.h b/src/util.h
new file mode 100644
index 0000000..fe09145
--- /dev/null
+++ b/src/util.h
@@ -0,0 +1,21 @@
+#ifndef __UTIL_H
+#define __UTIL_H
+
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+#define MAX(A, B) ((A) > (B) ? (A) : (B))
+
+void
+fdebug(const char *fmt, ...);
+
+void
+die(const char *fmt, ...);
+
+void
+trim(char * str);
+
+#endif /* __UTIL_H */