(* F# examples seen in class Adapted from "Programming Language Concepts" by P. Sestoft, Springer 2012 *) (* Evaluation, checking, and compilation of object language expressions *) (* Stack machines for expression evaluation *) (* Object language expressions with variables and let binders *) type expr = | CstI of int | Var of string | Prim of string * expr * expr | Let of string * expr * expr // new (* Some expressions: *) // let z = 17 in z + z let e1 = Let("z", CstI 17, Prim("+", Var "z", Var "z")) // let z = 17 in (let z = 22 in 100 * z) + z let e2 = Let("z", CstI 17, Prim("+", Let("z", CstI 22, Prim("*", CstI 100, Var "z")), Var "z")) // let z = 5 - 4 in 100 * z let e3 = Let("z", Prim("-", CstI 5, CstI 4), Prim("*", CstI 100, Var "z")) // (20 + (let z = 17 in z + 2)) + 30 let e4 = Prim("+", Prim("+", CstI 20, Let("z", CstI 17, Prim("+", Var "z", CstI 2))), CstI 30) // 2 * (let x = 3 in x + 4) let e5 = Prim("*", CstI 2, Let("x", CstI 3, Prim("+", Var "x", CstI 4))) (* ---------------------------------------------------------------------- *) (* Compilation to target expressions with numerical indexes instead of symbolic variable names. *) (* Why compilation? * Better correctness and safety guarantees. The compiler can: – check that all names are defined: classes, methods, fields, variables, types, functions, ... – check that the names have the correct type – check that it is legal to refer to them (not private, etc.) – improve the code, e.g. inline calls to private methods * Better performance – The compiler checks are performed once, but the machine code gets executed again and again * Why not compilation? – Compilation reduces flexibility by imposing static types checks and static name binding – Web programming often requires more flexibility – ... hence PHP, Python, Ruby, JavaScript, VB.NET, ... *) (* target expressions *) type texpr = | TCstI of int | TVar of int // index into runtime environment | TLet of texpr * texpr // rhs and body | TPrim of string * texpr * texpr // replacing variable names with indexes // (let x = 5 in x + (let y = 3 in x + y)) // (let x = 5 in x + (let y = 3 in x + y )) // (let 5 in v_0 + (let 3 in v_1 + v_0)) // (let x = 5 in // x + (let y = 3 in x + y ) + (let z = 4 in x + z )) // // (let 5 in // v_0 + (let 3 in v_1 + v_0) + (let 4 in v_1 + v_0)) (* Map variable name to variable index at compile-time *) let rec getIndex vars x = match vars with | [] -> failwith "Variable not found" | y :: _ when x = y -> 0 | y :: vars' -> 1 + (getIndex vars' x) (* Compiling from expr to texpr *) let rec tcomp (e : expr) (cenv : string list) : texpr = match e with | CstI i -> TCstI i | Var x -> TVar (getIndex cenv x) // retrieve binding depth at compile-time | Prim(op, e1, e2) -> TPrim (op, tcomp e1 cenv, tcomp e2 cenv) | Let(x, e1, e2) -> TLet (tcomp e1 cenv, tcomp e2 (x :: cenv)) // let z = 17 in z + z e1 tcomp e1 [] // let x = 5 in x * (let y = 3 in x + y) let e6 = Let ("x", CstI 5, Prim ("*", Var "x", Let ("y", CstI 3, Prim ("+", Var "x", Var "y")))) // TLet (TCstI 5, TPrim ("*", TVar 0, TLet (TCstI 3, TPrim ("+", TVar 1, TVar 0)))) tcomp e6 [] // let x = 5 in // x + ((let y = 3 in x + y) - (let z = 4 in x + z)) let e7 = Let ("x", CstI 5, Prim ("+", Var "x", Prim ("-", Let ("y", CstI 3, Prim ("+", Var "x", Var "y")), Let ("z", CstI 4, Prim ("+", Var "x", Var "z"))))) tcomp e7 [] (* Evaluation of target expressions with variable indexes. *) // The run-time environment renv is a list of variable values (ints) let rec teval (e : texpr) (renv : int list) : int = match e with | TCstI i -> i | TVar n -> List.item n renv // binding depth to access value at run-time | TPrim ("+", e1, e2) -> (teval e1 renv) + (teval e2 renv) | TPrim ("*", e1, e2) -> (teval e1 renv) * (teval e2 renv) | TPrim ("-", e1, e2) -> (teval e1 renv) - (teval e2 renv) | TPrim _ -> failwith "unknown primitive" | TLet (e1, e2) -> let v = teval e1 renv let renv' = v :: renv teval e2 renv' (* Correctness: eval e [] equals teval (tcomp e []) [] *)