(* abstract syntax tree of arithmetic expressions *) datatype expr = IConst of int | Plus of expr * expr | Minus of expr * expr; (* an integer constant expression *) val e1 = IConst 17; (* the expression 3 - 4, whose AST is - / \ 3 4 *) val e2 = Minus(IConst 3, IConst 4); (* the expression - / \ + 6 / \ 23 5 *) val e3 = Minus(Plus(IConst 23, IConst 5), IConst 6); (* Evaluating arithmetic expressions *) fun eval (IConst i) = i | eval (Plus(e1, e2)) = (eval e1) + (eval e2) | eval (Minus(e1, e2)) = (eval e1) - (eval e2); e1; eval e1; e2; eval e2; e3; eval e3; (* Evaluating arithmetic expressions with a "cutoff subraction", i.e. one that only produces non-negative results *) fun eval2 (IConst i) = i | eval2 (Plus(e1, e2)) = (eval2 e1) + (eval2 e2) | eval2 (Minus(e1, e2)) = let val res = (eval2 e1) - (eval2 e2) in if res < 0 then 0 else res end; e1; eval2 e1; e2; eval2 e2; e3; eval2 e3; (* Note that expr + eval2 is a DIFFERENT language than expr + eval. Even though their ASTs are the same their semantics differ. *) (* We can further extend our expression language to encompass Boolean expressions and conditionals *) datatype bexpr = BConst of bool | Not of bexpr | And of bexpr * bexpr | Or of bexpr * bexpr; val b1 = BConst false; val b2 = Not b1; (* an evaluator for Boolean expressions *) fun evalb (BConst b) = b | evalb (Not b) = not (evalb b) | evalb (And(b1, b2)) = (evalb b1) andalso (evalb b2) | evalb (Or(b1, b2)) = (evalb b1) orelse (evalb b2); evalb b1; evalb b2; (* abstract syntax tree for (conditional) arithmetic expressions *) datatype aexpr = IConst of int | Plus of aexpr * aexpr | Minus of aexpr * aexpr | Ite of bexpr * aexpr * aexpr; val e1 = IConst 17; val e2 = Plus(e1, Minus(IConst 10, IConst 30)); val e3 = Ite(b1, e1, e2); val e4 = Ite(b2, e1, e2); (* an evaluator for (conditiotional) arithmetic expressions *) fun evala (IConst i) = i | evala (Plus(e1, e2)) = (evala e1) + (evala e2) | evala (Minus(e1, e2)) = (evala e1) - (evala e2) | evala (Ite(c, t, e)) = if (evalb c) then (evala t) else (evala e); e1; evala e1; e2; evala e2; e3; evala e3; e4; evala e4; (* We can further expand our language to include _variables_. *) datatype aexpr = IConst of int | Plus of aexpr * aexpr | Minus of aexpr * aexpr | Ite of bexpr * aexpr * aexpr | Var of string; val e1 = Var "x"; val e2 = Plus(IConst 17, e1); (* To evaluate expressions with variables we need to be able to associate them with values. To this we will need a notion of _state_, where by means of an _association lists_, i.e. - lists of type ('a * 'b) list which can be seen as maps/dictionaries with keys of type 'a and values of type 'b. By building such lists from variable identifiers (strings) to values (IConst i) we will be able to map variables to values and thus evaluate expressions with variables. *) val state = [("x", IConst 3), ("y", IConst 78), ("z", IConst 676)]; (* We define a helper function to look for values in an associotion list *) fun lookup [] id = raise Match | lookup ((k:string, v)::t) id = if k = id then v else lookup t id; lookup state "x"; lookup state "z"; lookup state "w"; (* Evaluation now takes an expression and a state *) fun eval (IConst i) _ = i | eval (Var x) state = eval (lookup state x) state | eval (Plus(e1, e2)) state = (eval e1 state) + (eval e2 state) | eval (Minus(e1, e2)) state = (eval e1 state) - (eval e2 state) | eval (Ite(c, t, e)) state = if (evalb c) then (eval t state) else (eval e state); e1; e2; eval e1 state; eval e2 state; (* How to extend the language to allow bulding a state _during_ evaluation? *)