--[[ Imperative Core Language (Prefi), Abstract Syntax Module CSci 450: Organization of Programming Languages, Fall 2016 H. Conrad Cunningham, Professor Computer and Information Science University of Mississippi 1234567890123456789012345678901234567890123456789012345678901234567890 2016-09-22: Extended Expression Language 1 for new ImpCore operations 2016-09-24: Code for "for", "read" for HW 2 2016-09-25: Redact "for", "read" code 2016-10-03: Fix type in isGt function 2016-10-20: Added Script level to language This module encapsulates the representation of Expressions behind constructors and accessors. Other parts of the program access the data structures through these public functions. This design allows the representation to change with few effects on the other parts of the interpreter. This module represents the abstract syntax for Expression Language using Lua array tables as indicated below. Field 1 is a type tag. Other fields are the arguments of the operations. Script == {SCRIPT, Expr, ... Expr} -- script, expression sequence Expr == {NOP, string} -- no operation {INT, number} -- integer value {VAR, string} -- variable name {NEG, Expr} -- negation {ADD, Expr, Expr} -- addition {SUB, Expr, Expr} -- subtraction {MUL, Expr, Expr} -- multiplcation {DIV, Expr, Expr} -- division {EQ, Expr, Expr} -- equality comparison {NE, Expr, Expr} -- inequality comparison {LT, Expr, Expr} -- less than comparion {GT, Expr, Expr} -- greater than comparion {LE, Expr, Expr} -- less or equal comparison {GE, Expr, Expr} -- greater or equal comparison {IF, Expr, Expr, Expr} -- if-then-else expression -- (returns e2 value if e1 true -- otherwise, returns e3 value) -- Added for ImpCore {SET, string, Expr} -- assigns value of Expr to variable {WHILE, Expr, Expr} -- while expression, evaluates 2nd expr -- while 1st expr is true {WRITE, Expr } -- write expression {BLOCK, Expr, ... Expr} -- block of 0 or more expressions -- evaluated in order Public constructor and type query functions: Script, isScript -- sequence of one or more expressions Nop, isNop -- no operation Int, isInt -- integer number constants Var, isVar -- variable names Neg, isNeg -- negation Add, isAdd -- addition Sub, isSub -- subtraction Mul, isMul -- multiplication UNIMPLEMENTED HW1 Div, isDiv -- division UNIMPLEMENTED HW1 Eq, isEQ -- equality comparison Ne, isNe -- inequality comparison UNIMPLEMENTED HW1 Lt, isLt -- less than comparison UNIMPLEMENTED HW1 Gt, isGt -- greater than comparison UNIMPLEMENTED HW1 Le, isLe -- less or equal comparison UNIMPLEMENTED HW1 Ge, isGe -- greater/equal comparison UNIMPLEMENTED HW1 If, isIf -- if-then-else expression -- added for ImpCore Set, isSet While, isWhile Write, isWrite Block, isBlock Public operand accessor function: getArg, numArgs Public string conversion function: exprToString Internal: isExpr, newBinOp, EXPRFIELDS, all type tag constants TODO: - Reconsider the representation and evaluation scripts. Should the be expressions or arrays of expressions as currently implemented? --]] --[[ Module names varialbes now global local UTILITIES = "utilities" --]] -- Import Utilities module local util = require(UTILITIES) local show_data, treeConcat = util.show_data, util.treeConcat -- printTree not currently used -- Internal singleton constants to represent Expression type tags local NOP, INT, VAR, NEG, ADD, SUB, MUL, DIV = "Nop", "Int", "Var", "Neg", "Add", "Sub", "Mul", "Div" local EQ, NE, LT, GT, LE, GE, IF = "Eq", "Ne", "Lt", "Gt", "Le", "Ge", "If" -- Added for ImpCore local SET, WHILE, WRITE, BLOCK = "Set", "While", "Write", "Block" local SCRIPT = "Script" -- Internal constant mapping from Expression type tags to -- required number of arguments. -- For ImpCore, allow negative value to indicate a variable number -- of fields. This changes here to EXPRFIELDS, numArgs, getArgs -- should still work for Expression Language 1. -- HW1 required Min and Max -- Added Set, While, Write, Block for ImpCore local EXPRFIELDS = { ["Int"] = 1, ["Var"] = 1, ["Neg"] = 1, ["Add"] = 2, ["Sub"] = 2, ["Mul"] = 2, ["Div"] = 2, ["Eq"] = 2, ["Ne"] = 2, ["Lt"] = 2, ["Gt"] = 2, ["Le"] = 2, ["Ge"] = 2, ["If"] = 3, ["Nop"] = 1, ["Set"] = 2, ["While"] = 2, ["Write"] = 1, ["Block"] = -1 } -- Internal query function "isExpr" checks whether the first level of -- the data structure "e" has a valid Expression tag and number of -- arguments. It does not recursively check arguments that are -- Expressions. -- For ImpCore, added negative entry in EXPRFIELDS to mean a -- variable number of arguments, modified isExpr and getArgs, added -- numArgs. Together these should be backwardly compatible for use by -- Expression Language 1. local function isExpr(e, t) return type(e) =="table" and (EXPRFIELDS[e[1]] == #e-1 or EXPRFIELDS[e[1]] < 0) and (t == nil or e[1] == t) end -- Public accessor function "numArgs" returns the number of arguments -- occurring in Expression "e". -- Added for ImpCore. local function numArgs(e) if isExpr(e) then return #e-1 else error("numArgs called with unsupported expression: " .. show_data(e)) end end -- Public accessor function "getArg" returns argument "n" of -- Expression "e". For ImpCore, allow negative entries in EXPRFIELDS -- to mean a variable number of arguments. -- For ImpCore, add feature for variable number of fields and use -- of numArgs. Expression Language 1 code might not fail gracefully -- if called with non-expression e. local function getArg(e, n) if 1 <= n and n <= numArgs(e) then return e[n+1] else error("Expression argument index " .. n .. " out of range for expression " .. show_data(e)) end end -- Internal constructor function "newBinOp" builds Expressions for -- operators with two Expression arguments. It is used by other -- constructors. local function newBinOp(tag, e1, e2) if EXPRFIELDS[tag] == 2 and isExpr(e1) and isExpr(e2) then return {tag, e1, e2} else error("Cannot construct binary operator " .. tag .. " with arguments:" .. show_data(e1) .. " and " .. show_data(e2)) end end -- Public constructor and type check query functions for Expression -- types. local function Nop(s) return {NOP,s} end local function isNop(e) return isExpr(e, NOP) end local function Int(n) if type(n) == "number" then return {INT, math.floor(n)} -- coerce to integer else error("Cannot construct Int with nonnumeric argument: " .. show_data(n)) end end local function isInt(e) return isExpr(e, INT) end local function Var(n) if type(n) == "string" then -- check valid form of name? return {VAR, n} else error("Cannot construct Var with argument: " .. show_data(n)) end end local function isVar(e) return isExpr(e, VAR) end local function Neg(e) if isExpr(e) then return {NEG, e} else error("Cannot construct unary operator Neg with argument: " .. show_data(e)) end end local function isNeg(e) return isExpr(e, NEG) end local function Add(e1, e2) return newBinOp(ADD, e1, e2) end local function isAdd(e) return isExpr(e, ADD) end local function Sub(e1, e2) return newBinOp(SUB, e1, e2) end local function isSub(e) return isExpr(e, SUB) end local function Mul(e1, e2) return newBinOp(MUL, e1, e2) -- implement for HW1 end local function isMul(e) return isExpr(e, MUL) -- implement for HW1 end local function Div(e1, e2) return newBinOp(DIV, e1, e2) -- implement for HW1 end local function isDiv(e) return isExpr(e, DIV) -- implement for HW1 end local function Eq(e1, e2) return newBinOp(EQ, e1, e2) end local function isEq(e) return isExpr(e, EQ) end local function Ne(e1, e2) return newBinOp(NE, e1, e2) -- implement for HW1 end local function isNe(e) return isExpr(e, NE) -- implement for HW1 end local function Lt(e1, e2) return newBinOp(LT, e1, e2) -- implement for HW1 end local function isLt(e) return isExpr(e, LT) -- implement for HW1 end local function Gt(e1, e2) return newBinOp(GT, e1, e2) -- implement for HW1 end local function isGt(e) return isExpr(e, GT) -- implement for HW1 end local function Le(e1, e2) return newBinOp(LE, e1, e2) -- implement for HW1 end local function isLe(e) return isExpr(e, LE) -- implement for HW1 end local function Ge(e1, e2) return newBinOp(GE, e1, e2) -- implement for HW1 end local function isGe(e) return isExpr(e, GE) -- implement for HW1 end local function If(e1, e2, e3) if isExpr(e1) and isExpr(e2) and isExpr(e3) then return {IF, e1, e2, e3} else error("Cannot construct ternary operator If with arguments: " .. show_data(e1) .. " ; " .. show_data(e2) .. " ; " .. show_data(e3)) end end local function isIf(e) return isExpr(e, IF) end -- Set added for ImpCore local function Set(v,e) if type(v) == "string" and isExpr(e) then return {SET, v, e} else error("Cannot construct operator Set for target variable " .. show_data(v) .. " and expression " .. show_data(e)) end end local function isSet(e) return isExpr(e, SET) end -- While added for ImpCore local function While(e1, e2) return newBinOp(WHILE, e1, e2) end local function isWhile(e) return isExpr(e, WHILE) end -- Write added for ImpCore local function Write(e) if isExpr(e) then return {WRITE, e} else error("Cannot construct unary operator Write with argument: " .. show_data(e)) end end local function isWrite(e) return isExpr(e, WRITE) end -- Block added for ImpCore local function Block(...) local args = {...} for i = 1, #args do if not isExpr(args[i]) then error("Cannot construct Block from invalid expressions: " .. show_data(args)) end end return {BLOCK, ...} end local function isBlock(e) return isExpr(e, BLOCK) end -- Script added to ImpCore late local function Script(...) local args = {...} for i = 1, #args do if not isExpr(args[i]) then error("Cannot construct Script from invalid expressions: " .. show_data(args)) end end return {SCRIPT, ...} end local function isScript(t) return type(t) == "table" and t[1] == SCRIPT end -- Public accessor function "exprToString" converts Expression "e" to -- a readable string format. For ImpCore, added isSet branch and -- modified isExpr branch to work with variable numbers of arguments. local function exprToString(e) if isScript(e) then -- scripts are not expressions local strb = {} for i = 2, #e do strb[#strb+1] = exprToString(e[i]) end return "begin script\n" .. table.concat(strb, ",\n") .. "end script" elseif isInt(e) then return "Int(" .. tostring(e[2]) .. ")" elseif isVar(e) then return "Var(" .. tostring(e[2]) .. ")" elseif isNop(e) then return "Nop(" .. tostring(e[2]) .. ")" -- Set added for ImpCore elseif isSet(e) then return "Set(" .. tostring(e[2]) .. ", " .. exprToString(e[3]) .. ")" elseif isExpr(e) then -- only has Expression arguments local strb = {} for i = 2, #e do strb[#strb+1] = exprToString(e[i]) end return e[1] .. "( " .. table.concat(strb, ", ") .. " )" else error("Not a valid Expression in exprToString: " .. show_data(e)) end end -- MODULE EXPORT LIST -- not exported: isExpr, EXPRFIELDS, type tag constants, newBinOp return { exprToString = exprToString, numArgs = numArgs, -- added for ImpCore getArg = getArg, Int = Int, isInt = isInt, Var = Var, isVar = isVar, Neg = Neg, isNeg = isNeg, Add = Add, isAdd = isAdd, Sub = Sub, isSub = isSub, Mul = Mul, isMul = isMul, -- unimplemented HW1 Div = Div, isDiv = isDiv, -- unimplemented HW1 Eq = Eq, isEq = isEq, Ne = Ne, isNe = isNe, -- unimplemented HW1 Lt = Lt, isLt = isLt, -- unimplemented HW1 Gt = Gt, isGt = isGt, -- unimplemented HW1 Le = Le, isLe = isLe, -- unimplemented HW1 Ge = Ge, isGe = isGe, -- unimplemented HW1 If = If, isIf = isIf, Nop = Nop, isNop = isNop, Set = Set, isSet = isSet, -- added for ImpCore While = While, isWhile = isWhile, -- added for ImpCore Write = Write, isWrite = isWrite, -- added for ImpCore Block = Block, isBlock = isBlock, -- added for ImpCore Script = Script, isScript = isScript }