(* simple.ml Interpeter for a Simple IMperative Programming LanguagE. *) (* * * Recall the BNF grammar for this SIMPLE language: * * ::= * | // variables * | + // addition * | * // multiplication * | < // less-than * | // literal * | () * * ::= * | skip * | = * | ifNZ { } else { } * | whileNZ { } * | ; * *) (* * OCaml datatypes that we use to represent SIMPLE abstract syntax. * * This is called _abstract syntax_ because it uses the labeled * tree structure rather than concrete keywords, punctuation marks, etc., * to represent the program. * * For example, the concrete syntax for the following program: * (3 + X) * 2 * is the tree: * Mul(Add(Lit 3, Var "X"), Lit 2) *) (* AST for the concrete syntax: X = 6; ANS = 1; whileNZ (x) { ANS = ANS * X; X = X + -1; } *) (* let factorial : cmd = Seq(Assn("X", Lit 6), Seq(Assn("ANS", Lit 1), WhileNZ(Var "X", Seq(Assn("ANS", Mul(Var "ANS", Var "X")), Assn("X", Add(Var "X", Lit (-1))) )) )) *) (* interpreters and state --------------------------------------------------- *) (* We can "interpret" a SIMPLE program by giving it a meaning in terms of * OCaml operations. One key question is how to represent the _state_ of * the SIMPLE program. Out intuition tells us that it should be a map * that sends variables to their (current) values. There are many ways that * we could represent such a state. Here, we use OCaml's functions. *) (* The initial state maps every variable to 0 *) (* We can update an old state [s] to one that maps `x` to `v` but is otherwise * unchanged by building a new function like this: *) (* Looking up the value of a variable in a state is easy: *) (* To interpret an expression in a given state, we recursively compute the * values of subexpressions and then combine them according to the operation. * * One wrinkle: we have chosen to use only `int` as the domain of values, * so the result of a less-than comparison encodes "true" as 1 and "false" as 0. *) (* To interpret a command, we write an OCaml program that manipulates that * state as appropriate. The result of running a command is a final state. * * Note that `WhileNZ` "unfolds" the loop into a conditional that either * runs the loop body once and the coninues as another `WhileNZ`, or just * Skip. * * Note that the SIMPLE sequence of two commands is interpreted by the * sequencing of OCaml's `let` binding construct. *) (* We can write a program that runs the SIMPLE factorial program like this: *) (* let main () = let s_ans : state = interpret_cmd init_state factorial in let ans : var = "ANS" in Printf.printf "ANS = %d\n" (lookup s_ans ans) ;; main () *)