From 53ab9ae05a579a19c626e8be0c1e2cf9244bf863 Mon Sep 17 00:00:00 2001
From: gramanas <anastasis.gramm2@gmail.com>
Date: Fri, 28 Jan 2022 13:45:30 +0200
Subject: sha1 sum and others

---
 .gitignore                     |   3 +-
 Makefile                       |  11 ++-
 README.org                     |  10 +++
 cookbook.c                     | 162 +++++++++++++++++++++++++++++++++++++++++
 emacs/recipe-mode.el           |  67 +++++++++++++++++
 food.c                         |  64 +++++++++++-----
 lib/minced-meat.rcp            |  32 ++++++++
 lib/pasta-red-sauce.rcp        |   5 +-
 lib/pasta-with-minced-meat.rcp |   4 +
 lib/sandwich-base.rcp          |   2 +-
 lib/sandwich-sour.rcp          |  13 ++++
 src/eval.c                     |  23 ++++++
 src/lib.c                      |  48 ++++++++++++
 src/lib.h                      |   7 ++
 src/parser.h                   |   6 ++
 src/pbg.c                      |   1 +
 src/pbg.h                      |   1 +
 src/search.c                   | 118 ++++++++++++++++++++++++++++++
 src/search.h                   |   2 +
 src/search_stuff.c             | 126 ++++++++++++++++++++++++++++++++
 src/teeny-sha1.c               |   1 +
 src/types.c                    |  64 +++++++++++++++-
 src/types.h                    |   7 ++
 23 files changed, 745 insertions(+), 32 deletions(-)
 create mode 100644 cookbook.c
 create mode 100644 emacs/recipe-mode.el
 create mode 100644 lib/minced-meat.rcp
 create mode 100644 lib/pasta-with-minced-meat.rcp
 create mode 100644 lib/sandwich-sour.rcp
 create mode 100644 src/lib.c
 create mode 100644 src/lib.h
 create mode 120000 src/pbg.c
 create mode 120000 src/pbg.h
 create mode 100644 src/search_stuff.c
 create mode 120000 src/teeny-sha1.c

diff --git a/.gitignore b/.gitignore
index 3b0f025..85db1bd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
 hs/*
-food
\ No newline at end of file
+food
+cookbook
\ No newline at end of file
diff --git a/Makefile b/Makefile
index 3b011df..28066bd 100644
--- a/Makefile
+++ b/Makefile
@@ -1,13 +1,13 @@
 CC=gcc
 
-CFLAGS=-D_GNU_SOURCE -std=c99 -pedantic -O3
-DBG_FLAGS=-Wall -g3 -D_FOOD_DEBUG -fsanitize=address -Og
+CFLAGS=-D_GNU_SOURCE -std=c99 -pedantic 
+DBG_FLAGS=-Wall -g -D_FOOD_DEBUG -fsanitize=address -O0
 
 SRC=src/*.c
 
 .PHONY: all debug clean
 
-all: food #eval
+all: food cookbook #eval
 
 food: $(SRC) food.c
 	$(CC) $(CFLAGS) -o food $^ $(LIBS)
@@ -15,12 +15,15 @@ food: $(SRC) food.c
 eval: $(SRC) eval.c
 	$(CC) $(CFLAGS) -o eval $^ $(LIBS)
 
+cookbook: $(SRC) cookbook.c
+	$(CC) $(CFLAGS) -o cookbook $^ $(LIBS)
+
 debug: CFLAGS += $(DBG_FLAGS)
 debug: all
 
 clean:
 	rm -f *.o
-	rm -f food eval
+	rm -f food eval cookbook
 
 # tests:
 # 	$(MAKE) -C test
diff --git a/README.org b/README.org
index 5e0f020..8d4f973 100644
--- a/README.org
+++ b/README.org
@@ -81,3 +81,13 @@
      }
    #+end_src
 
+
+* Food compiler
+
+  recipes are text files defined above
+
+  ingredients and results from steps are variables linking back to the quantity or the step that produced them
+
+  the food compiler parses these files into internal C structures, the errors reported are syntax ones
+
+  then they can be eval'd which merges any subrecipe ingredients and steps into one plain recipe
diff --git a/cookbook.c b/cookbook.c
new file mode 100644
index 0000000..ca435ec
--- /dev/null
+++ b/cookbook.c
@@ -0,0 +1,162 @@
+#include <getopt.h>
+
+#include "src/util.h"
+#include "src/parser.h"
+#include "src/search.h"
+#include "src/eval.h"
+#include "src/lib.h"
+
+recipe ** cookbook;
+
+static struct opts {
+  int json;
+  int html;
+  int rcp;
+  char query[2048];
+  int eval;
+  int list;
+  int hash;
+  int search;
+  int search_strict;
+  int help;
+} opt = {
+  .json = 0,
+  .html = 0,
+  .rcp = 0,
+  .query = "",
+  .eval = 1,
+  .list = 0,
+  .hash = 0,
+  .search = 0,
+  .search_strict = 0,
+  .help = 0,
+};
+
+void
+print_help(char * argv0) {
+  printf("%s [OPTION] FILE ...\n", argv0);
+  printf("OPTIONS:\n");
+  printf("--format json,html,rcp\n");
+  printf("--to-{json,html,rcp}\n");
+  printf("-j json\n");
+  printf("-w html\n");
+  printf("-r rcp\n");
+  printf("-l, --list-ingredients\n");
+  printf("-s TERMS, --search TERMS\n");
+  printf("-S TERMS, --strict TERMS\n");
+  printf("-n, --no-eval\n");
+}
+
+int
+main(int argc, char * argv[])
+{
+  fdebug("--- Debug mode is on ---\n");
+
+  int c;
+
+  while (1) {
+    static struct option long_options[] =
+      {
+        /* <span class="roman">These options set a flag.</span> */
+        //        {"verbose", no_argument,       &verbose_flag, 1},
+        //        {"brief",   no_argument,       &verbose_flag, 0},
+        {"help",              no_argument,       0, 'h'},
+        {"no-eval",           no_argument,       0, 'n'},
+        {"to-json",           no_argument,       0, 'j'},
+        {"to-html",           no_argument,       0, 'w'},
+        {"to-rcp",            no_argument,       0, 'r'},
+        {"format",            required_argument, 0, 'f'},
+        {"search",            required_argument, 0, 's'},
+        {"strict",            required_argument, 0, 'S'},
+        {"hash",              required_argument, 0, 'H'},
+        {"list-ingredients",  no_argument,       0, 'l'},
+        //          {"to-rcp",  required_argument, 0, 'r'},
+        {0, 0, 0, 0}
+      };
+
+    int option_index = 0;
+
+    c = getopt_long (argc, argv, "jnlhrwf:s:S:H:",
+                     long_options, &option_index);
+
+    if (c == -1)
+      break;
+
+    switch (c) {
+    case 0:
+      if (long_options[option_index].flag != 0)
+        break;
+      printf ("option %s", long_options[option_index].name);
+      if (optarg)
+        printf (" with arg %s", optarg);
+      printf ("\n");
+      break;
+    case 's':
+      opt.search = 1;
+      strcpy(opt.query, optarg);
+      break;
+    case 'S':
+      opt.search = 1;
+      opt.search_strict = 1;
+      strcpy(opt.query, optarg);
+      break;
+    case 'H':
+      opt.hash = 1;
+      strcpy(opt.query, optarg);
+      break;
+    case 'j':
+      opt.json = 1;
+      break;
+    case 'w':
+      opt.html = 1;
+      break;
+    case 'r':
+      opt.rcp = 1;
+      break;
+    case 'l':
+      opt.list = 1;
+      break;
+    case 'h':
+      opt.help = 1;
+      break;
+    case 'n':
+      opt.eval = 0;
+      break;
+    case '?':
+      return -1;
+      break;
+    default:
+      abort ();
+    }
+  }
+
+  if (opt.help) {
+    print_help(argv[0]);
+    return 0;
+  }
+
+  char ** lib = NULL;
+  int n = collect_library(&lib, argv, argc, optind);
+
+  cookbook = (recipe **)malloc(sizeof(recipe *) * n);
+  for (int i = 0; i < n; i++) {
+    cookbook[i] = parse(lib[i], NULL);
+    if (opt.eval) {
+      recipe * _r = eval(cookbook[i]);
+      free_recipe(cookbook[i]);
+      cookbook[i] = _r;
+    }
+  }
+  free_library(lib, n);
+  
+  for (int i = 0; i < n; i++) {
+    listing(cookbook[i]);
+  }
+
+  for (int i = 0; i < n; i++) {
+    free_recipe(cookbook[i]);
+  }
+  free(cookbook);
+
+  return 0;
+}
diff --git a/emacs/recipe-mode.el b/emacs/recipe-mode.el
new file mode 100644
index 0000000..c38008a
--- /dev/null
+++ b/emacs/recipe-mode.el
@@ -0,0 +1,67 @@
+;;; recipe-mode.el --- major mode for foodtool's recipe files  -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2021  
+
+;; Author:  <gramanas@EYE>
+;; Keywords: files, data
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; 
+
+;;; Code:
+
+(eval-when-compile
+  (require 'rx))
+
+(setq recipe-highlights
+      '(
+        ;; comment
+        ("^ *#+.*$" . 'font-lock-comment-delimiter-face)
+        ;; title
+        ("^ *@\\(.*\\)$" . (1 'font-lock-preprocessor-face))
+        ;; duration
+        ("\\[\\(.+?\\)\\]" . (1 'font-lock-builtin-face))
+        ;; quantity measurements
+        ("= *[[:digit:]/]+\\([ [:graph:]]+?\\)$" . (1 'font-lock-builtin-face))
+        ;; variable in assignment
+        ("^\\(.+?\\)=.+?$" . (1 'font-lock-variable-name-face))
+        ;; variable in ${}
+        ("\\${\\(.+?\\)}" . (1 'font-lock-variable-name-face))
+        ;; single word variable
+        ("\\$\\([^ {\n]+\\)" . (1 'font-lock-variable-name-face))
+        ;; step result
+        ("=>\\(.*\\)" . (1 'font-lock-variable-name-face))
+        ;; include
+        ("^[ [:digit:]]*!" . 'font-lock-constant-face)
+        ;; symbols
+        ("@\\|-\\|>\\|+\\|=>" . 'font-lock-constant-face)
+        ))
+
+(define-derived-mode recipe-mode prog-mode "rcp"
+  "Mode for rcp files
+\\{recipe-mode-map}"
+
+  ;;(setq-local font-lock-defaults '(conf-font-lock-keywords nil t nil nil))
+  (setq-local font-lock-defaults '(recipe-highlights))
+  (setq-local comment-use-syntax nil)
+  (setq-local comment-start "#")
+  (setq-local parse-sexp-ignore-comments t))
+
+(add-to-list 'auto-mode-alist '("\\.rcp\\'"     . recipe-mode))
+
+(provide 'recipe-mode)
+;;; recipe-mode.el ends here
diff --git a/food.c b/food.c
index d0466a3..f7ddbc1 100644
--- a/food.c
+++ b/food.c
@@ -4,6 +4,7 @@
 #include "src/parser.h"
 #include "src/search.h"
 #include "src/eval.h"
+#include "src/lib.h"
 
 recipe ** cookbook;
 
@@ -14,6 +15,7 @@ static struct opts {
   char query[2048];
   int eval;
   int list;
+  int hash;
   int search;
   int search_strict;
   int help;
@@ -24,6 +26,7 @@ static struct opts {
   .query = "",
   .eval = 1,
   .list = 0,
+  .hash = 0,
   .search = 0,
   .search_strict = 0,
   .help = 0,
@@ -65,6 +68,7 @@ main(int argc, char * argv[])
         {"format",            required_argument, 0, 'f'},
         {"search",            required_argument, 0, 's'},
         {"strict",            required_argument, 0, 'S'},
+        {"hash",              required_argument, 0, 'H'},
         {"list-ingredients",  no_argument,       0, 'l'},
         //          {"to-rcp",  required_argument, 0, 'r'},
         {0, 0, 0, 0}
@@ -72,7 +76,7 @@ main(int argc, char * argv[])
 
     int option_index = 0;
 
-    c = getopt_long (argc, argv, "jnlhrwf:s:S:",
+    c = getopt_long (argc, argv, "jnlhrwf:s:S:H:",
                      long_options, &option_index);
 
     if (c == -1)
@@ -106,6 +110,10 @@ main(int argc, char * argv[])
       opt.search_strict = 1;
       strcpy(opt.query, optarg);
       break;
+    case 'H':
+      opt.hash = 1;
+      strcpy(opt.query, optarg);
+      break;
     case 'j':
       opt.json = 1;
       break;
@@ -137,33 +145,49 @@ main(int argc, char * argv[])
     return 0;
   }
 
-  if (optind < argc) {
-    while (optind < argc) {
-      recipe * r = parse(argv[optind++], NULL);
-      if (r) {
-        if (opt.search) {
-          if (!query_for_items(r, opt.query, opt.search_strict))
-            continue;
-        }
-        if (opt.eval) {
-          recipe * r_eval = eval(r);
+  char ** lib = NULL;
+  int n = collect_library(&lib, argv, argc, optind);
+  
+  for (int i = 0; i < n; i++) {
+    recipe * r = parse(lib[i], NULL);
+    if (r) {
+      if (opt.eval) {
+        recipe * r_eval = eval(r);
+        free_recipe(r);
+        r = r_eval;
+      }
+      if (opt.hash) {
+        if (!check_hash(r, opt.query)) {
           free_recipe(r);
-          r = r_eval;
+          continue;
         }
-        if (opt.list) {
-          pprint_items(r);
+      }
+      if (opt.search) {
+        int c;
+        if (!(c = query_for_items_pbn(r, opt.query, opt.search_strict))) {
+          free_recipe(r);
+          continue;
         }
+        if (c < 0)
+          exit(1);
+      }
+
+      if (opt.list) {
+        pprint_items(r);
+      }
+      else {
+        if (opt.json) tojson(r);
+        else if (opt.html) tohtml(r);
+        else if (opt.rcp) torcp(r);
         else {
-          if (opt.json) tojson(r);
-          if (opt.html) tohtml(r);
-          if (opt.rcp) torcp(r);
+          listing(r);
         }
-        free_recipe(r);
       }
+      free_recipe(r);
     }
-  } else {
-    fprintf(stderr, "Specify filenames\n");
   }
 
+  free_library(lib, n);
+
   return 0;
 }
diff --git a/lib/minced-meat.rcp b/lib/minced-meat.rcp
new file mode 100644
index 0000000..1b25998
--- /dev/null
+++ b/lib/minced-meat.rcp
@@ -0,0 +1,32 @@
+@minced meat
+
+minced veal = 400g
+minced pork = 100g
+
+salt, pepper = *
+kayen pepper = 1/2 tbsp
+cinamon = 1/2 tbsp
+cumin = 1/2 tbsp
+bay leaf = 2
+
+sun dried tomato = 6 pieces
+
+white onion = 1
+garlic = 2 cloves
+carrot = 1 small
+
+white wine = 1/2 glass
+tomato sauce = 2 cans
+olive oil = 3 tbsp
+
+---
+
+- chop the ${white onion} and $garlic, slice the $carrot
+> heat ${olive oil} in a pan and add ${white onion} and $garlic and $salt and [brown for 3 minutes]
+> add the $carrot and [stir until well browned]
+> add the ${minced pork} and ${minced veal}
+> add the $pepper ${kayen pepper} $cinamon $cumin ${bay leaf}
+> stir [until the meat is brown and starts to stick on the pan]
+> deglaze with ${white wine} and stir [until most wine has evaporated]
+> add the ${tomato sauce} and stir, if too thick add some water
+> [cook for 30 minutesd] 
diff --git a/lib/pasta-red-sauce.rcp b/lib/pasta-red-sauce.rcp
index 2b56894..a57f30c 100644
--- a/lib/pasta-red-sauce.rcp
+++ b/lib/pasta-red-sauce.rcp
@@ -1,4 +1,4 @@
-@pasta with red sauce 3
+@ pasta with red sauce 3
 
 3!pasta.rcp
 salt = *
@@ -14,8 +14,7 @@ olive oil = *
 - prepare $pasta
 > heat some ${olive oil} in the pan
 > fry $garlic [until light brown]
-> add $onion and some $salt, fry [until \
-onion is translucent]
+> add $onion and some $salt, fry [until onion is translucent]
 > deglaze with the $wine and cook [until it reduces in half]
 > add the ${tomato sauce}, and some warm water to rinse the can
 > add $pasta to the sauce, turn the heat off, and cook [for 1-2 minutes]
diff --git a/lib/pasta-with-minced-meat.rcp b/lib/pasta-with-minced-meat.rcp
new file mode 100644
index 0000000..3a4a6bc
--- /dev/null
+++ b/lib/pasta-with-minced-meat.rcp
@@ -0,0 +1,4 @@
+@ pasta with minced meat
+
+!pasta.rcp
+!minced-meat.rcp
diff --git a/lib/sandwich-base.rcp b/lib/sandwich-base.rcp
index 98568a7..7a485b2 100644
--- a/lib/sandwich-base.rcp
+++ b/lib/sandwich-base.rcp
@@ -5,5 +5,5 @@ butter or mayo = *
 
 ---
 
-- toast the $bread => toasted bread
+- toast the $bread [for 3 minutes] => toasted bread
 - spead the ${butter or mayo} on the ${toasted bread}
diff --git a/lib/sandwich-sour.rcp b/lib/sandwich-sour.rcp
new file mode 100644
index 0000000..de06b3d
--- /dev/null
+++ b/lib/sandwich-sour.rcp
@@ -0,0 +1,13 @@
+@ sandwich with sun dried tomatoes
+
+!sandwich-base.rcp
+
+sun dried tomates = 3
+turkey = 2pcs
+cheddar = 2pcs
+
+---
+
+> chop the ${sun dried tomatoes} into litle pieces
+> slice the $turkey and ${cheddar} in half
++ assembly order: bread mayo tomatoes cheddar turkey bread
diff --git a/src/eval.c b/src/eval.c
index 8bc886a..57bfbb0 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -1,5 +1,25 @@
 #include "eval.h"
 #include "util.h"
+#include <stdint.h>
+
+extern int
+sha1digest(uint8_t *digest, char *hexdigest, const uint8_t *data, size_t databytes);
+
+void
+create_hash(recipe * r)
+{
+  char data[65536] = "\0";
+  
+  for (int i = 0; i < r->in; i++) {
+    strcat(data, r->i[i]->name);
+    strcat(data, r->i[i]->qty);
+  }
+  if (r->sn) {
+    for (int i = 0; i < r->sn; i++)
+      strcat(data, r->s[i]->inst);
+  }
+  sha1digest(NULL, r->sha1, (uint8_t *)data, strlen(data));
+}
 
 recipe *
 eval(recipe * r)
@@ -10,7 +30,10 @@ eval(recipe * r)
   merge_items(_r, r);
   merge_steps(_r, r);
   copy_metadata(_r, r);
+  copy_subrecipes(_r, r);
 
+  create_hash(_r);
+  
   return _r;
 }
 
diff --git a/src/lib.c b/src/lib.c
new file mode 100644
index 0000000..f43bdc2
--- /dev/null
+++ b/src/lib.c
@@ -0,0 +1,48 @@
+#include "lib.h"
+#include "util.h"
+
+int
+collect_library(char *** dst, char * argv[], int argc, int optind)
+{
+  int n = 0;
+  FILE *fp;
+  char path[1035];
+
+  char ** lib = NULL;
+
+  for (int i = optind; i < argc; i++) {
+    lib = realloc(lib, sizeof(char **) * (n + 1));
+    lib[n] = strdup(argv[i]);
+    fdebug("%d: %s\n", n, lib[n]);
+    n = n + 1;
+  }
+
+  fp = popen("/bin/find /home/gramanas/code/foodtools/lib/ -name '*.rcp'", "r");
+  if (fp == NULL) {
+    fprintf(stderr, "Couldn't run /bin/find\n");
+    exit(1);
+  }
+
+  /* Read the output a line at a time */
+  while (fgets(path, sizeof(path), fp) != NULL) {
+    lib = realloc(lib, sizeof(char **) * (n + 1));
+    trim(path);
+    lib[n] = strdup(path);
+    fdebug("%d: %s\n", n, lib[n]);
+    n = n + 1;
+  }
+
+  /* close */
+  pclose(fp);
+
+  *dst = lib;
+  return n; // no of items
+}
+
+void
+free_library(char ** lib, int n) {
+  for (int i = 0; i < n; i++) {
+    free(lib[i]);
+  }
+  free(lib);
+}
diff --git a/src/lib.h b/src/lib.h
new file mode 100644
index 0000000..005a330
--- /dev/null
+++ b/src/lib.h
@@ -0,0 +1,7 @@
+#ifndef __LIB_H
+#define __LIB_H
+
+int collect_library(char *** lib, char * argv[], int argc, int optind);
+void free_library(char ** lib, int n);
+
+#endif /* __LIB_H */
diff --git a/src/parser.h b/src/parser.h
index 2680da5..d47bad5 100644
--- a/src/parser.h
+++ b/src/parser.h
@@ -5,6 +5,12 @@
 
 #define LINE_SIZE 4096
 
+/**
+* Return a recipe struct after parsing file in path.
+*
+* Path can be relative or full, ~ will be resolved as $HOME, and - will accept
+* input from stdin
+*/
 recipe *
 parse(char * path, const char * prev);
 
diff --git a/src/pbg.c b/src/pbg.c
new file mode 120000
index 0000000..c707a6b
--- /dev/null
+++ b/src/pbg.c
@@ -0,0 +1 @@
+../pbg/pbg.c
\ No newline at end of file
diff --git a/src/pbg.h b/src/pbg.h
new file mode 120000
index 0000000..36a1da6
--- /dev/null
+++ b/src/pbg.h
@@ -0,0 +1 @@
+../pbg/pbg.h
\ No newline at end of file
diff --git a/src/search.c b/src/search.c
index cf2eab9..a038cfb 100644
--- a/src/search.c
+++ b/src/search.c
@@ -1,5 +1,115 @@
 #include "util.h"
 #include "search.h"
+#include "pbg.h"
+
+static char *
+form_string(const recipe * r, const char * s, int strict)
+{
+  char _s[4048] = "";
+  int l = 0;
+  char buf[256] = "";
+
+  for (int i = 0; i < strlen(s); i++) {
+    if (s[i] == '(' ||
+        s[i] == ')' ||
+        s[i] == ' ') {
+      if (strlen(buf)) {
+        if (!strcmp(buf, "not") ||
+            !strcmp(buf, "NOT") ||
+            !strcmp(buf, "!")) {
+          _s[l++] = '!';
+        }
+        else if (!strcmp(buf, "and") ||
+                 !strcmp(buf, "AND") ||
+                 !strcmp(buf, "&&")  ||
+                 !strcmp(buf, "&")) {
+          _s[l++] = '&';
+        }
+        else if (!strcmp(buf, "or") ||
+                 !strcmp(buf, "OR") ||
+                 !strcmp(buf, "||") ||
+                 !strcmp(buf, "|")) {
+          _s[l++] = '|';
+        }
+        else {
+          fdebug("querying %s for %s\n", r->title, buf);
+          if (query_for_items(r, buf, strict)) {
+            strcat(_s, "TRUE");
+            l+=4;
+          }
+          else {
+            strcat(_s, "FALSE");
+            l+=5;
+          }
+        }
+        strcpy(buf, "");
+      }
+      _s[l++] = s[i];
+    }
+    else {
+      int k = strlen(buf);
+      buf[k] = s[i];
+      buf[k+1] = '\0';
+    }
+  }
+  _s[l] = '\0';
+
+  return strdup(_s);
+}
+
+int
+query_for_items_pbn(const recipe * r, const char * s, int strict)
+{
+  if (!strlen(s)) {
+    return 0;
+  }
+
+  char * query = NULL;
+
+  if (s[0] != '(') {
+    if (strstr(s, "and ") ||
+        strstr(s, "or ")  ||
+        strstr(s, "not ")) {
+      query = (char *)malloc(sizeof(char) * strlen(s) + 3);
+      strcpy(query, "(");
+      strcat(query, s);
+      strcat(query, ")");
+    } else {
+      return query_for_items(r, s, strict);
+    }
+  } else {
+    query = strdup(s);
+  }
+
+  pbg_error err;
+  pbg_expr e;
+  int result;
+	
+  fdebug("PBG Query string: %s\n", query);
+  char* str = form_string(r, query, strict);
+  fdebug("PBG Query eval'd string: %s\n", str);
+
+  /* Parse the expression string and check if 
+   * there were any compilation errors. */
+  pbg_parse(&e, &err, str);
+  if(pbg_iserror(&err)) {
+    fprintf(stderr, "Parsing error: %s => %s\n", s, str);
+    free(query); free(str); free(err._data);
+    return -1;
+  }
+	
+  /* Evaluate the expression string and check if 
+   * there were any runtime errors. */
+  result = pbg_evaluate(&e, &err, NULL);
+  if(pbg_iserror(&err)) {
+    fprintf(stderr, "Eval error: %s => %s\n", s, str);
+    free(query); free(str); pbg_free(&e); free(err._data);
+    return -1;
+  }
+	
+  free(query); free(str); pbg_free(&e);
+  return (result == PBG_TRUE) ? 1 : 0;
+}
 
 /**
  * Query recipe `r` for input `s` and return 1 if found 0 otherwise
@@ -17,3 +127,11 @@ query_for_items(const recipe * r, const char * s, int strict)
     }
   return 0;
 }
+
+int
+check_hash(const recipe * r, const char * s)
+{
+  char _sha1[9];
+  sprintf(_sha1, "%.*s", 8, r->sha1);
+  return (!strcmp(s, _sha1) || !strcmp(s, r->sha1)) ? 1 : 0;
+}
diff --git a/src/search.h b/src/search.h
index 1295cd4..1c7c61f 100644
--- a/src/search.h
+++ b/src/search.h
@@ -3,6 +3,8 @@
 
 #include "types.h"
 
+int check_hash(const recipe * r, const char * s);
 int query_for_items(const recipe * r, const char * s, int strict);
+int query_for_items_pbn(const recipe * r, const char * s, int strict);
 
 #endif
diff --git a/src/search_stuff.c b/src/search_stuff.c
new file mode 100644
index 0000000..5d4cb6c
--- /dev/null
+++ b/src/search_stuff.c
@@ -0,0 +1,126 @@
+#include "util.h"
+#include "search.h"
+
+char stk[4048];
+int p = 0;
+
+/*
+ * expr : (expr) | expr op expr | not expr | term
+ * op : and | or
+ * term : <string>
+ * 
+ */
+
+void
+reverse_str(char ** s)
+{
+  int l = strlen(*s);
+  char * rev_s = strdup(*s);
+  int j = 0;
+  for (int i = l - 1; i >= 0; i--, j++) {
+    rev_s[j] = (*s)[i];
+  }
+  rev_s[j] = '\0';
+  free(*s);
+  *s = rev_s;
+}
+
+
+/*
+ * Implementing https://www.javatpoint.com/convert-infix-to-prefix-notation
+ *
+ * ascii values:
+ *   & -> 38
+ *   | -> 124
+ *   ( -> 40
+ *   ) -> 41
+ *   a-z -> 97-122
+ *   A-Z -> 64-90
+ *
+ * precidence:
+ *   & > |
+ * (http://www.cs.ucc.ie/~gavin/cs1001/Notes/chap40/ch40_16.html)
+ */
+int
+precedence(char c)
+{
+  switch (c) {
+  case '&':
+    return 100;
+  case '|':
+    return 50;
+  default:
+    return 0;
+  }
+}
+
+void
+infix_to_prefix(char ** s)
+{
+  char * prefixed = malloc(sizeof(char) * strlen(*s) * 2);
+  prefixed[0] = '\0';
+  char * rev_s = strdup(*s);
+  reverse_str(&rev_s);
+
+  for (int i = 0; i < strlen(rev_s); i++) {
+    if (rev_s[i] == '&' ||
+        rev_s[i] == '|') {
+      if (p == 0) {
+        stk[p++] = rev_s[i];
+      } else {
+        if (precedence(rev_s[i] > precedence(stk[p - 1]))) {
+          stk[p++] = rev_s[i];
+        } else {
+          int k = strlen(prefixed);
+          for (int j = p; j > 0 || precedence(rev_s[i]) < precedence(stk[p - 1]); j--) {
+            prefixed[k++] = stk[j - 1];
+            p--;
+          }
+          prefixed[k] = '\0';
+          stk[p++] = rev_s[i];
+        }
+      }
+    }
+    else if (rev_s[i] == '(') {
+      stk[p++] = rev_s[i];
+    }
+    else if (rev_s[i] == ')') {
+      int k = strlen(prefixed);
+      prefixed[k++] = rev_s[i];
+      for (int j = p; j > 0 && stk[j] != ')'; j--) {
+        prefixed[k++] = stk[j - 1];
+        p--;
+      }
+      prefixed[k] = '\0';
+    }
+    else {
+      int k = strlen(prefixed);
+      prefixed[k] = rev_s[i];
+      prefixed[k+1] = '\0';
+    }
+  }
+
+  int k = strlen(prefixed);
+  prefixed[k++] = ' '; // add space after the first operator
+  for (int j = p; j > 0; j--) {
+    prefixed[k++] = stk[j - 1];
+    p--;
+  }
+  prefixed[k] = '\0';
+
+  printf("rev %s\n", prefixed);
+  for (int i = 0; i < strlen(prefixed); i++) {
+    if (prefixed[i] == '(' &&
+        i < strlen(prefixed) &&
+        (prefixed[i+1] == '&' || prefixed[i+1] == '|')) {
+      prefixed[i] = prefixed[i+1];
+      prefixed[i+1] = '(';
+    }
+  }
+
+  printf("rev fixed %s\n", prefixed);
+  reverse_str(&prefixed);
+  free(rev_s);
+  free(*s);
+  *s = prefixed;
+}
diff --git a/src/teeny-sha1.c b/src/teeny-sha1.c
new file mode 120000
index 0000000..4208eb3
--- /dev/null
+++ b/src/teeny-sha1.c
@@ -0,0 +1 @@
+../teeny-sha1/teeny-sha1.c
\ No newline at end of file
diff --git a/src/types.c b/src/types.c
index dbc7458..3233323 100644
--- a/src/types.c
+++ b/src/types.c
@@ -21,6 +21,7 @@ new_recipe()
   r->filename = NULL;
   r->path = NULL;
   r->title = NULL;
+  r->sha1[0] = '\0';
   return r;
 }
 
@@ -136,6 +137,12 @@ pprint_items(recipe * r)
   printf("\n");
 }
 
+void
+listing(recipe * r)
+{
+  printf("%.*s  %d:%d\t%d\t%s\n", 8, (strcmp(r->sha1, "") ? r->sha1 : "neval"), r->in, r->sn, r->rn, r->title);
+}
+
 void
 show(recipe * r)
 {
@@ -158,7 +165,9 @@ tojson(recipe * r)
   printf("{\"filename\":\"%s\",", r->filename);
   printf("\"dirname\":\"%s\",", r->path);
   printf("\"title\":\"%s\",", r->title);
-  printf("\"n\":\"%d\"", r->n);
+  printf("\"n\":\"%d\",", r->n);
+  printf("\"sha1_short\":\"%.*s\",", 8, r->sha1);
+  printf("\"sha1\":\"%s\"", r->sha1);
   if (r->rn) {
     printf(",\"subrecipes\":[");
     int i = 0;
@@ -189,16 +198,51 @@ tojson(recipe * r)
 void
 tohtml(recipe * r)
 {
-  printf("todo\n");
+  printf("<div class=\"recipe\">\n");
+  printf("<!-- %s/%s -->\n", r->path, r->filename);
+  printf("<h1>%s</h1>\n\n", r->title);
+  if (r->rn) {
+    printf("<ul class=\"list items subrecipes\">\n");
+    for (int i = 0; i < r->rn; i++) {
+      printf("<li>!%s/%s</li>\n", r->r[i]->path, r->r[i]->filename);
+    }
+    printf("</ul>\n");
+  }
+  if (r->in) {
+    printf("<ul class=\"list items\">\n");
+    for (int i = 0; i < r->in; i++) {
+      printf("<li>%s = %s</li>\n", r->i[i]->name, r->i[i]->qty);
+    }
+    printf("</ul>\n");
+  }
+  if (r->sn) {
+    printf("<br><p>---</p><br>\n");
+    printf("<ul class=\"list steps\">\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("<li>%c %s</li>\n", c, r->s[i]->inst);
+    }
+    printf("</ul>\n");
+  }
+  printf("<hr>\n</div>\n");
 }
 
 void
 torcp(recipe * r)
 {
   printf("# %s/%s\n\n", r->path, r->filename);
+  if (r->sha1[0] != '\0') {
+    printf("# %s\n\n", r->sha1);
+  }
   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);
+    printf("%s!%s/%s\n", (r->sha1[0] != '\0') ? "# ": "", 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);
@@ -217,6 +261,20 @@ torcp(recipe * r)
   }
 }
 
+void
+copy_subrecipes(recipe * dst, recipe * src)
+{
+  if (!dst || !src) return;
+  for (int i = 0; i < src->rn; i++) {
+    recipe * r = new_recipe();
+    r->title = strdup(src->r[i]->title);
+    r->path = strdup(src->r[i]->path);
+    r->filename = strdup(src->r[i]->filename);
+    r->n = src->r[i]->n;
+    new_subrecipe(dst, r);
+  }
+}
+
 void
 copy_metadata(recipe * dst, recipe * src)
 {
diff --git a/src/types.h b/src/types.h
index 2c58537..867368b 100644
--- a/src/types.h
+++ b/src/types.h
@@ -30,6 +30,7 @@ typedef struct recipe_t {
   int sn;
   struct recipe_t **r;
   int rn;
+  char sha1[41];
 } recipe;
 
 recipe * new_recipe();
@@ -42,6 +43,7 @@ void free_item(item * i);
 void free_step(step * s);
 
 void pprint_items(recipe * r);
+void listing(recipe * r);
 void show(recipe * r);
 void tojson(recipe * r);
 void tohtml(recipe * r);
@@ -49,6 +51,11 @@ void torcp(recipe * r);
 
 /* Operations */
 
+/**
+ * Copy metadata from `src` to `dst`
+ */
+void copy_subrecipes(recipe * dst, recipe * src);
+
 /**
  * Copy metadata from `src` to `dst`
  */
-- 
cgit v1.2.3