On this page:
Introduction
Input Language
Part 1:   Parsing
Part 2:   Unit Configuration Files
Part 3:   Semantics
What to turn in
Academic Integrity
6.1.1.7

Project 2: Unit Calculator

Due: February 28, 2015 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 you 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 calc intended to be used as follows:

Input Language

The input to your calculator will be a list of expressions 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], m2 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:

{

  "m": [100, "cm"],

  "cm": [10, "mm"],

  "m^2": [10000, "cm^2"],

  "g": [0.001, "kg"],

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

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

}

Configurations describe derived units. The first, m, is a derived unit made by decreasing some value with the unit cm 100x. The second describes cm, also a derived unit from mm. The last, newton, is derived from a unit product. The product should be match in any order because multiplication commutes.

You should be able to handle products, exponents on either side of the configuration. You should not handle product/exponent search; we can convert from cm^2 to m^2 in the above, but not mm^2 to cm^2, nor m g to cm g.

The conversions are in a single direction, from right to left. The only units we can convert back and forth in the above are newtons and s-2 g m.

The conversions are transitive. You are able to convert from mm to m with a single in expression.

It doesn’t particularly matter whether the units are SI units or not. Any well implemented solution that works for SI units will work for all units.

Configuration files are in JSON format; you can use Yojson to parse these files. The _tags file supplied with the project includes a line to load the Yojson package, so you won’t be able to build unless Yojson is installed. You can install Yojson using opam:

> opam install yojson

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.

For this part of the project, you must implement the functions:

Main.read_config_file : string -> Config.t option

Config.parse          : string -> Config.t option

The type Config.t is a functional map from keys of type string to values of type float * unit_t.

This function takes the name of a configuration file as input, and parses the file, creating a Config.t that contains the definitions from the file. Your configuration file parser should return None if any of the input string is ill-formed, i.e., it does not have the format described above.

Hint: If you’re unsure how to use Yojson, it’s probably easiest to experiment with it at the ocaml toplevel utop. 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.

Hint: For more information on file manipulation, start here and here.

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. You can either upload your solution via the web interface, or you can run java -jar submit.jar in the project directory.

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