Assignment 2: Parsing Recipes

In this assignment, you are going to write programs to gather information about recipes. There are several web pages with catalogues of recipes, for example Unfortunately, (most?) of these pages are in Dutch, but there certainly are English pages with recipes in the WWW as well. This is a (slightly simplified, please don’t try at home!) example recipe:
Bossche bollen

Ingredients
500 g    flour
100 g    butter
3        eggs
1.5 dl   water
2.5 dl   whipped cream
2        spoonfuls cocoa
125 g    powdered sugar
         salt

Preparation
5 min.    flour, butter, 1 dl water -> mix -> pastry
          pastry, eggs -> add eggs one by one -> pastry
          pastry -> divide into small towers -> towers
20 min.   towers -> bake until brown -> towers
          cocoa, 0.5 dl water, powderes sugar -> mix -> cocoa mix
          towers, cocoa mix -> coat towers with cocoa mix -> Bossche bollen

In this assignment you will define a language for recipes, write a parser and an unparser for recipes, and define several functions that compute information from recipes. Such functions could, for example, compute the calories of one portion of a certain dish, or the total time necessary to prepare the meal, or, given the number of persons, compute the quantities of the ingredients needed. With such functions, you can already do better than the Albert Heijn recipes page: if you search there for recipes which use bread (brood in Dutch), and specify two persons first, later four persons, then you will get completely different recipes! The assignment consists of the design and implementation of several sub-tasks, distributed over the two parts of this practical:

For this, you have to follow the instructions that are detailed below. A sceleton for the program to be written is already available. You can find it on the course’s webpage. The relevant files are:
  1. Recipes.hs: the skeleton file where you can modify and add code to carry out the assignments;
  2. recipes-ex: some sample recipes that can be used to test your programs. Note that you only have to change Recipes.hs.
  3. Calories.hs : the energetic value (calories) of various food products (per 100 g or dl)
  4. ParseLib.hs: a file containing parser combinators
There will be no precise definition of the language that your program should recognize. We rather give a number of examples, in the file recipes-ex. The idea is that, given these examples, you can give the definition of the language yourself. One of the goals of this assignment is that you learn how to design a language yourself. But some tricky points about the language of recipes are discussed below.
  1. The preparation section of a recipe consists of a number of actions, one per line. Only the first action has a time associated with it. You can assume that the following actions can be performed in the same time frame. For instance, in the following preparation
    5 min. flour, butter, 1 dl water -> mix -> pastry
    pastry, eggs -> add eggs one by one -> pastry
    pastry -> divide into small towers -> towers
    20 min. towers -> bake until brown -> towers
    cocoa, 0.5 dl water, powderes sugar -> mix -> cocoa mix
    towers, cocoa mix -> coat towers with cocoa mix -> bossche bollen
    
    the first three actions can be performed in a total of 5 minutes.
  2. An ingredient can consist of more than one word, such as fresh fish or red onion.
  3. In the example sentences there are many blank spaces. They are used, for example, to align ingredients. In you explanation of the concrete syntax you do not need to deal with whitespace, but you should take care of newlines ("\n").

Concrete Syntax

We will call the input language of our recipe page Recipe. A problem is that we do not have a precise definition of this language yet. However, we do have a number of sentences of this language, from which we can infer the grammar of the concrete syntax.

Abstract Syntax

To be able to manipulate the input for our weg pages, we have to find a suitable way to represent the input in our program. In Haskell, we can use a (data)type for this purpose. The datatype is then an abstract syntax for the recipe language.

Unparsing

Unparsing is the inverse operation of parsing. For that, a function is needed that gets an abstract syntax tree as input and transforms it into a string. We want to transform an abstract syntax tree of type Recipe into a String.

Parsing

The parser has to be written using parsing combinators, which are described in Chapter 3 of the college notes. The Haskell sourcecode of the functions discussed in Chapter 3 is contained in the file ParseLib.hs.

Functions on recipes