On this page:
Introduction
Input language
Part 1:   Parsing
Part 2:   Unit configuration files
Part 3: Semantics
What to turn in
Academic Integrity
6.4

Project 2: Unit Calculator

Due: February 25, 2016 11:59:59pm

Introduction

In this project, you will develop a unit calculator that implements functionality similar to the unit conversions available in Google’s calculator. For example, if your calculator is asked to compute 1 pound in grams it should return 453.59237 grams. As another example, if your calculator is asked to compute 60 miles hour^-1 in m s^-1, it should return 26.8224 m s^-1.

To get you started, we’ve provided a code skeleton for your unit calculator. After unpacking this directory, make should build an executable main.naitive intended to be used as follows:

Input language

The input to your calculator will be a list of expressions es given by the following grammar:

es ::= ε                       Empty list

    |   e ;; es                List of expressions

e  ::= fp                      Dimensionless quantity

    |   fp u                   Dimensional quantity

    |   e + e                  Addition

    |   e - e                  Subtraction

    |   e * e                  Multiplication

    |   e / e                  Division

    |   e in u                 Unit conversion

    |   ( e )                  Grouping

u  ::= ft | lbs | m | s | ...  Base units

    |   u ^ n                  Unit Exponentiation

    |   u u                    Unit multiplication

    |   ( u )                  Grouping

Here fp is a floating point number (whose syntax follows the OCaml convention, except the decimal point should not be required) and n is an integer (which may be negative, and may contain leading zeroes). The expression language is straightforward: the base quantities that can be calculated with may either be dimensionless or have dimensions. Standard arithmetic operations are permitted, along with a special form e in u, which converts the expression e from its current units into the units specified by u. Note that this conversion must be sensible; for example, 3 miles in lbs is an error.

Units u can either be base units such as ft, lbs, etc, or can be hybrid units composed from base units, e.g., s, m^2 s^-1, ft lbs, etc. Base units are sequences of upper or lower-case letters. Notice that multiplication of units is indicated by juxtaposition, and division by negative exponentiation; this avoids some annoying parsing conflicts.

Note that the input file is a list of expressions, each of which must be terminated by ;;. You should ignore whitespace; this is already baked into the code skeleton we gave you.

Part 1: Parsing

The first step in building your calculator is to develop a parser for the input language. Your parser will accept strings produced from e in the grammar above, and from them should produce an abstract syntax tree (AST) in the form of an instance of the expr type, defined as:

type unit_t = (string * int) list

 

type value = float * unit_t

 

type expr =

  | Val of value

  | Plus of expr * expr

  | Minus of expr * expr

  | Mult of expr * expr

  | Div of expr * expr

  | In of expr * unit_t

Here units are represented by unit_t, which is a (possibly empty) list of dimensions and their exponents (either positive or negative). For example, s is represented by ["s",1], m^2 s^-1 is represented by ["m",2; "s",-1], and ft lbs is represented by ["ft",1; "lbs",1]. Quantities (constructed with Val) are paired with a unit. For example, 42 is represented as Val (42, []), and 3 ft s^-2 is represented as Val (3, ["ft", 1; "s", -2]).

Your parser should obey the following conventions:

You must implement your parser using ocamllex and ocamlyacc. We’ve provided you with skeleton code to add your parser to; you should modify the files lexer.mll and parser.mly for this part of the project.

Part 2: Unit configuration files

In order to perform unit conversions, your calculator will need to know some basic ratios among units. Rather than hard-code this into your calculator, we’ll use a configuration file instead. For example, the following configuration file gives conversions for feet, pounds, and kilograms in terms of meters, grams, and kilograms, respectively:

{

 "ft": [0.3048, "m"],

 "lb": [453.59237, "g"],

 "kg": [1000, "g"],

 "acres": [4046.85642, "m^2"],

 "newton": [1000, "m g s^-2"]

}

The first line says that 1 ft is equal to 0.3048 m, the second and third lines give conversions for pounds and kilograms, the fourth line gives a conversion for acres in terms of square meters, and the last line gives a conversion for newtons (force) in terms of base units. We will refer to units defined in this way as derived units.

Configuration files are in JSON format; you can use Yojson to parse these files. The Makefile supplied with the project includes a line to load the Yojson package, so you won’t be able to build unless Yojson is installed. (Yojson is installed on GRACE. If you are using your own machine, you should be able to install it with opam. You may need to do an "opam update" to get the latest package info.) A valid configuration file consists of a single object representing a mapping from unit abbreviations to an array of [ratio, target-unit], where target-unit is produced by u from the grammar from part 1.

To keep things a bit simpler, target units for this project will always be the SI units for meters, grams, or seconds.

For this part of the project, you should write a function parse_config : string -> config -> unit where config is defined as

type config = (string, float * unit_t) Hashtbl.t

This function takes the name of a configuration file and a configuration hash table as input, and parses the file, adding the configuration definitions to the hash table. You may assume the hash table is empty when your parsing function is called. Your configuration file parser should raise an exception (any exception) if any of the following hold:

Hint: If you’re unsure how to use yojson, it’s probably easiest to experiment with it at the ocaml toplevel. You can load yojson there by doing #use "topfind";; to load findlib, and then #require "yojson";; to load yojson. From the top level you can use a function like Yojson.Safe.from_string to produce a json instance.

Part 3: Semantics

Finally, write a function eval : config -> expr -> value that calculates the value of an expession under the given configuration. In some cases the best result to give is obvious, but in other cases there are many reasonable answers. For this part of the project, your evaluation function must follow the following rules:

What to turn in

Put all your code in the code skeleton we have supplied, and upload your solution to the submit server.

Academic Integrity

The Campus Senate has adopted a policy asking students to include the following statement on each assignment in every course: “I pledge on my honor that I have not given or received any unauthorized assistance on this assignment.” Consequently your program is requested to contain this pledge in a comment near the top.

Please carefully read the Academic Integrity section of the course syllabus. Any evidence of impermissible cooperation on projects, use of disallowed materials or resources, or unauthorized use of computer accounts, will be submitted to the Student Honor Council, which could result in an XF for the course, or suspension or expulsion from the University. Be sure you understand what you are and what you are not permitted to do in regards to academic integrity when it comes to project assignments. These policies apply to all students, and the Student Honor Council does not consider lack of knowledge of the policies to be a defense for violating them. Full information is found in the course syllabus—please review it at this time.

 

Web Accessibility