diff options
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | Makefile | 28 | ||||
-rw-r--r-- | README.org | 57 | ||||
-rw-r--r-- | eval.c | 29 | ||||
-rw-r--r-- | eval.h | 8 | ||||
-rw-r--r-- | lib/include.rcp | 4 | ||||
-rw-r--r-- | lib/simple_omelette.rcp | 17 | ||||
-rw-r--r-- | main.c | 14 | ||||
-rw-r--r-- | parser.c | 235 | ||||
-rw-r--r-- | parser.h | 14 | ||||
-rw-r--r-- | types.c | 178 | ||||
-rw-r--r-- | types.h | 46 | ||||
-rw-r--r-- | util.c | 54 | ||||
-rw-r--r-- | util.h | 21 |
14 files changed, 707 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8105bfd --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +hs/* +c.out
\ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..99629f0 --- /dev/null +++ b/Makefile @@ -0,0 +1,28 @@ +TARGET=c.out +SRC=*.c +CC=gcc +CFLAGS=-D_GNU_SOURCE -std=c99 -pedantic +REL_FLAGS=-O3 +DBG_FLAGS=-Wall -g3 +# LIBS=-l X11 + +make: $(SRC) + $(CC) $(REL_FLAGS) $(CFLAGS) -o $(TARGET) $^ $(LIBS) + +debug: $(SRC) + $(CC) $(DBG_FLAGS) -D_FOOD_DEBUG -fsanitize=address $(CFLAGS) -o $(TARGET) $^ $(LIBS) + +noasan: $(SRC) + $(CC) $(DBG_FLAGS) $(CFLAGS) -o $(TARGET) $^ $(LIBS) + +.PHONY: clean + +clean: + rm -f *.o + rm -f $(TARGET) + +install: $(TARGET) + @cp -v $(TARGET) /usr/local/bin/$(TARGET) + +uninstall: + @rm -v /usr/local/bin/$(TARGET) diff --git a/README.org b/README.org new file mode 100644 index 0000000..04e2c3d --- /dev/null +++ b/README.org @@ -0,0 +1,57 @@ +#+begin_src conf + # fried onion + + # `-` means preping step + # `>` means cooking step + # in a step you can add a time range in `[]` + # each step might produce some new ingredients `=>` + + onion = 2 + salt = 1tsp + sugar = 1/2tsp + butter = 3tbsp + + - chop $onion to thin stripes => sliced onion + > melt $butter in pan + > add ${sliced onion} and $salt + > cook while stirring [for 10 minutes] + > add $sugar + > continue cooking [until onion caramelises] +#+end_src + +#+begin_src fundamental + # basic seasoning + + salt = * + pepper = * +#+end_src +#+begin_src conf + # egg omellete with fried onions + + egg = 3 + !basic_seasoning + garlic paste = 1tsp + spring onion = 1 + !fried_onion + butter = 1tbsp + + - beat the ${egg}s and add the ${seasoning} + > melt $butter in pan + > cook ${egg}s [until they start to set] +#+end_src + +* cmd + + cmd [-switches] path/to/file ... + + I should be able to: + - list ingredients of recipies (to buy) + - pretty print steps for recipe (prep/cook/both) + - search for KEY in ingredients and show availiable recipes with KEY as ingredient + - should accept multiple keys and a switch for AND or OR between them + + or a custom syntax like =cmd -s"eggs & (tomatoes | potatoes)" lib/*.rcp= + + or =cmd -[A|O] -stomatoes -stomatoes -seggs lib/*rcp= (And/Or) + - *ls* + + list recipies with path + + or with title + + or both @@ -0,0 +1,29 @@ +#include "eval.h" +#include "util.h" + +static void +merge_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); + } +} + +recipe * +eval(recipe * r) +{ + if (!r) return NULL; + /* recipe * eve = new_recipe(); */ + /* /\* Copy filename, title, etc *\/ */ + /* copy_metadata(eve, r); */ + /* /\* attempt to merge items (adding qtys) *\/ */ + /* merge_items(eve, r); */ + /* /\* Resolve step type, variables, duration and step output (if any) *\/ */ + /* finalize_steps(eve, r); */ + + return r; +} + @@ -0,0 +1,8 @@ +#ifndef __EVAL_H +#define __EVAL_H + +#include "types.h" + +recipe * eval(recipe * r); + +#endif /* __EVAL_H */ diff --git a/lib/include.rcp b/lib/include.rcp new file mode 100644 index 0000000..b4b2814 --- /dev/null +++ b/lib/include.rcp @@ -0,0 +1,4 @@ +@includes + +poops = 5 +crouches = 2 diff --git a/lib/simple_omelette.rcp b/lib/simple_omelette.rcp new file mode 100644 index 0000000..37a0cb8 --- /dev/null +++ b/lib/simple_omelette.rcp @@ -0,0 +1,17 @@ +# -*- mode: fundamental -*- +@title + +!/home/gramanas/code/food_compiler/lib/include.rcp +eggs = 3 +milk = 2tbsp +salt, pepper = * +oregano = * + +--- + +- beat the $eggs and add \ +$salt and $pepper => beaten eggs +> add $milk to the ${beaten eggs} +> melt $butter in pan +> add the eggs and \ +cook [until they are almost set] @@ -0,0 +1,14 @@ +#include "util.h" +#include "parser.h" + +recipe ** cookbook; + +int +main(int argc, char * argv[]) +{ + fdebug("--- Debug mode is on ---\n"); + recipe * r = parse(argv[1], NULL); + if (r) tojson(r); + free_recipe(r); + return 0; +} diff --git a/parser.c b/parser.c new file mode 100644 index 0000000..c47096c --- /dev/null +++ b/parser.c @@ -0,0 +1,235 @@ +#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; +} diff --git a/parser.h b/parser.h new file mode 100644 index 0000000..3d704c3 --- /dev/null +++ b/parser.h @@ -0,0 +1,14 @@ +#ifndef __PARSER_H +#define __PARSER_H + +#include <stdio.h> +#include <stdlib.h> + +#include "types.h" + +#define LINE_SIZE 4096 + +recipe * +parse(char * path, const char * prev); + +#endif /* __PARSER_H */ @@ -0,0 +1,178 @@ +#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->i = NULL; + r->in = 0; + r->s = NULL; + r->sn = 0; + r->filename = NULL; + r->path = NULL; + r->title = NULL; + return r; +} + +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->filename) + free(r->filename); + if (r->path) + free(r->path); + if (r->title) + free(r->title); + + free(r); +} + +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); + 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("}\n"); +} + +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->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++) + printf("%s %s\n", r->s[i]->type == PREP ? "-" : ">", r->s[i]->inst); + } +} + +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); + } +} + @@ -0,0 +1,46 @@ +#ifndef __TYPES_H +#define __TYPES_H + +typedef struct item_t { + char *name; + char *qty; +} item; + +enum step_type { + PREP = 0, + COOK, +}; + +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; + item **i; + int in; + step **s; + int sn; +} recipe; + +recipe * new_recipe(); +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 show(recipe * r); +void tojson(recipe * r); +void tohtml(recipe * r); +void torcp(recipe * r); + +void copy_items(recipe * dst, recipe * src); + +#endif /* __TYPES_H */ @@ -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'; +} @@ -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 */ |