CMSC 430, Fall 2015
Due Wed, Sep 30, 2015 @ 11:59:59pm
main.native
is built rather than calc.
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 main.naitve intended to be used as follows:
./main.native -config config-file input-file loads the unit
configuration (see below) specified by config-file and then
reads each expression from input-file, evaluating it and
printing the result to standard out.
./main.native -config config-file -parse-config-only loads the
unit configuration file and prints it to standard out
./main.native -parse-only input-file reads each expression from
input-file, printing it (but not evaluating it) to standard
out.
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.
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:
2 is a valid floating point number for this project.
+, -, * and / are left
associative.
^ binds most tightly, then * and /,
which have equal precedence, and then + and -, which
have equal precedence. in has the lowest precedence.
4 + 3 s means "add the dimensionless number 4 to 3
seconds" (which will be an error under our semantics).
^ binds more tightly than juxtaposition,
so 1 m^2 s^3 parses as 1 (m^2) (s^3).
3
smoots is valid, even if we don't have conversions for unit
smoots.
4 + 3 s, even though attempting to
evaluate that expression will yield an error.
s s should become ["s", 2],
and m s m^-1 should become ["s", 1]. (Note here we
needed to remove m from the list entirely, rather than leave
it in with exponent 0.) The order of base units in unit_t is
arbitrary. In this stage, non-sensical units such as m ft
and g kg are allowed, and should yield ASTs.
;;
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.
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;
add /afs/glue.umd.edu/class/fall2014/cmsc/430/0101/public/bin
to your path. 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.tThis 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:
"m",
"g", and "s".
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.
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:
1 ft + 2 ft | ok, returns 3 ft |
1 ft + 1 s | error |
1 ft s + 1 g s | error |
1 ft * 1 lb | ok, returns 1 ft lb |
(2 ft^2)/(1 ft) | ok, returns 2 ft |
(2 acres)/(4 acres) | ok, returns 0.5 |
in must make sense, i.e., they must
map into and out of the same base units. For example, 1 ft in
m is valid, but 1 ft in s is not.
g, m, and
s, which always exist.
1 ft
lb is the same as 1 lb ft as far as grading is concerned.
1 + 2s.
Put all your code in the code skeleton we have supplied, and upload your solution to the submit server.
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 honesty 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.