Assignment 1
Table of Contents
This assignment is due on Thursday 1/29 by 8:00PM. You should put
all of your solutions in a file called assign1/lib/assign1.ml. See
the file test/test_assign1.ml for example behavior of each function.
Setting-up your system. Before starting this assignment, follow the instructions from Lab 1 for setting up OCaml and your private course repository.
1. Programming
1.1. Integer Square Root
Implement the function
val sqrt : int -> int
so that sqrt n is the smallest integer k such that n <= k * k.
The behavior of the function is undefined if n is negative.
1.2. Integer Exponentiation
Implement the function
val pow : int -> int -> int
so that pow m n is m to the power of n. The behavior of the
function is undefined if m to the power of n is not an integer.
1.3. Split by Whitespace
The primary objective of this assignment is to build an interpreter for arithmetic in reverse Polish notation (RPN) (i.e., a calculator). This means we need to split an expression─provided as a string─into its whitespace-separated parts, e.g., the following assertions should hold.
let _ = assert (split_on_ws "1 23 + -94 *" = ["1"; "23"; "+"; "-94"; "*"]) let _ = assert (split_on_ws " foo bar \n\n baz " = ["foo"; "bar"; "baz"])
To make this easier, you can working with strings as lists of characters, e.g., the following assertions should hold.
let _ = assert (split_on_ws_helper ['1'; ' '; '2'; '3'; ' '; '+'; ' '; '-'; '9'; '4'; ' '; ' '; '*'] = [['1']; ['2'; '3']; ['+']; ['-'; '9'; '4']; ['*']]) let _ = assert (split_on_ws_helper ['f'; 'o'; 'o'; ' '; '\n'; 'b'; 'a'; 'z'; ' '; ' '] = [['f'; 'o'; 'o']; ['b'; 'a'; 'z']])
Implement the function
val split_on_ws : string -> string list
So that split_on_ws s is the list of contiguous non-whitespace
substrings of s, in the order in which they appear, as in the above
examples. The starter code includes a small skeleton based on the
above discussion, using the functions explode and implode to
convert between strings and character lists. Note: We will not test
split_on_ws_helper directly.
Challenge worth nothing: Despite the above, it's bad practice to
work with strings as lists of characters. Implement split_on_ws
without explode and implode, instead by making use of the function
String.sub.
1.4. RPN calculator
One of the simplest programming languages is arithmetic. There are several syntaxes for arithmetic; in this assignment we'll be using reverse Polish notation (RPN). In reverse Polish notation, arithmetic operators appear after their arguments. This has the benefit of not requiring parentheses, which makes RPN expressions easier to parse and evaluate. We further require that all numbers and operators that appear in an RPN expression are separated by whitespace. The following table contains translations of arithmetic expressions in standard infix notation and in RPN.
(1 + 2) + 3 |
1 2 + 3 + |
-1 + (2 + 3) |
-1 2 3 + + |
sqrt 2 - (3 mod (4 * 5)) |
2 sqrt 3 4 5 * mod - |
1 / 2 * (3 ^ 4) |
1 2 / 3 4 ^ * |
Formally, a RPN expression is a sequence of symbols (which we will
take to be strings in this assignment) that can be any of the
following:
- the string representation of an integer;
- an operator of the form
+,-,*,/,mod,sqrt, or^.
The type system of arithmetic is simple and requires no
implementation: everything is an int. All operators operate int's
and evaluate to int values.
The semantics of arithmetic in RPN is most easily described in terms
of a stack (i.e., an int list). Given an RPN arithmetic expression
\(e\), and a stack \(S\), the following operations are done from left to
right:
- the string representation of an integer \(n\) in \(e\) pushes \(n\) onto
\(S\) (the function
int_of_stringmay be useful); - an operator─its standard interpretation─is applied to the top one or two elements of \(S\), depending on how many arguments the operator expects.
This is best understood by example. The following is a walk-through of how the stack is updated as each part of an RPN expression is processed during evaluation.
Stack Expression ───── ────────── [] -1 2 3 - + 4 * (-1) :: [] 2 3 - + 4 * 2 :: (-1) :: [] 3 - + 4 * 3 :: 2 :: (-1) :: [] - + 4 * (-1) :: (-1) :: [] + 4 * (-2) :: [] 4 * 4 :: (-2) :: [] * -8 :: []
Implement the function
val eval : int list -> string list -> int list
so that eval stack expr is the result of evaluating expr with the
starting stack according to the above description, e.g., the
following assertions hold.
let _ = assert (eval [1; 2; 3] ["-1"] = [-1; 1; 2; 3]) let _ = assert (eval [1; 2; 3] ["-1"; "+"] = [0; 2; 3]) let _ = assert (eval [] ["-1"; "2"; "3"; "-"; "+"; "4"; "*"] = [-8]) let _ = assert (eval [1; 2; 3] ["-1"; "2"; "3"; "-"; "+"; "4"; "*"] = [-8; 1; 2; 3])
You should use your implementations of sqrt and pow for the
operators sqrt and ^ respectively. The behavior of the function
is undefined if expr is not well-formed or if it is not possible to
evaluate with respect to stack. This means you don't need to worry
about handling syntax errors (e.g., you won't be given the expression
["asdf"; "2"; "3"] or evaluation errors (e.g., you won't be given
the expression ["-1"; "+"] and the stack []).
The function interp puts all the pieces of this assignment together.
If you've done everything correctly, you should be able to run dune
exec assign1 to open a REPL for your implementation of arithmetic in
RPN. You should also be able to put an expression in a file and pass
it as a command line argument the interpreter to run it, e.g., dune
exec assign1 example.rpn.