--[[ Imperative Core Language, Prefix Parser Module (LPEG) H. Conrad Cunningham, Professor Computer and Information Science University of Mississippi Developed for CSci 450/503, Organization of Programming Languaages, Fall 2016 1234567890123456789012345678901234567890123456789012345678901234567890 2016-09-22: Imperative Core Language prefix parser adapted from Expression Language 1 prefix parser 2016-09-24: Changed parser "If" to "IfP" because of name conflict Changed definition of Id so identifier "ifx" recognized Moved trimSpaces, trimComment, getCommand, etc. from REPL Added code for HW2 ("for", "read", etc.) 2016-09-25: Redacted semantic action code for HW 2 but left hooks 2016-10-05: Added \r, \v, and \f to Space parser 2016-10-07: Add workaround for lack of table.unpack in Lua 5.1 This program uses the Lua Parsing Expression Grammar (LPEG) library to parse the following grammar for the following simple language and to capture significant strings during parsing. cd Exp ::= Value | (Op1 Exp) | (Op2 Exp Exp) | ( := Variable Exp ) | (if Exp Exp Exp) | (while Exp Exp) | (write Exp) | (block Exp^* ) Value ::= Number | Variable Op1 ::= neg Op2 ::= + | - | * | / | == | ~= | < | > | <= | >= Variable ::= Leading alpabetic or underscore followed by zero or more alphabetic, numeric, or underscore characters Number ::= One or more decimal digits with optional leading - TODO: Study the suppor for REPL commands (e.g., command table) --]] --[[ local UTILITIES = "utilities" local ABS_SYNTAX = "abs_syn_code" local ENVIRONMENT = "environment" local VALUES = "values_01" --]] -- Import Utilities module local util = require(UTILITIES) local treeConcat, printTree, show_data = util.treeConcat, util.printTree, util.show_data -- PARSER -- Import LPEG module local lpeg = require("lpeg") -- For convenience, define local names for LPEG functions local C, Ct, P, R, S, V = lpeg.C, lpeg.Ct, lpeg.P, lpeg.R, lpeg.S, lpeg.V -- Import Abstract Syntax module local absyn = require(ABS_SYNTAX) -- For convenience, define local names for constructors local Nop = absyn.Nop local Int = absyn.Int local Var = absyn.Var local Neg = absyn.Neg local Add = absyn.Add local Sub = absyn.Sub local Mul = absyn.Mul local Div = absyn.Div local Eq = absyn.Eq local Ne = absyn.Ne local Lt = absyn.Lt local Gt = absyn.Gt local Le = absyn.Le local Ge = absyn.Ge local If = absyn.If -- Added for ImpCore local Set = absyn.Set local While = absyn.While local Write = absyn.Write local Block = absyn.Block -- SEMANTIC ACTIONS -- Table to map from binary operator symbols in concrete syntax -- to constructor functions in abstract syntax module. local BinOpCons = {} -- Trap any attempts to use invalid operator setmetatable(BinOpCons,BinOpCons) BinOpCons.__index = function(_,key) error("Attempt to reference invalid operator '" .. treeConcat(key) .."'",2) end BinOpCons["+"] = Add BinOpCons["-"] = Sub BinOpCons["*"] = Mul BinOpCons["/"] = Div BinOpCons["=="] = Eq BinOpCons["~="] = Ne BinOpCons["<"] = Lt BinOpCons["<="] = Le BinOpCons[">"] = Gt BinOpCons[">="] = Ge -- The following functions are called by the LPEG parsing machine -- to process the captures. They call the appropriare abstract -- syntax constructors. -- Argument s = integer as string of numerals local function xNum(s) return Int(tonumber(s)) end -- Argument s = variable name as string local function xVar(s) return Var(s) end -- Argument t = { operator, expression } local function xOp1(t) if t[1] == "neg" then return Neg(t[2]) else error("unsupported unary operator " .. treeConcat(t)) end end -- Argument t = { operator, expr1, expr2 } local function xOp2(t) return BinOpCons[t[1]]( t[2], t[3] ) end -- Argument t = { ":=", var, expr } local function xAssign(t) return Set(t[2],t[3]) end -- Argument t = { "if", expr1, expr2, expr3 } local function xIf(t) if #t ~= 4 then error("syntax error for IF expression: " .. treeConcat(t)) end return If(t[2],t[3],t[4]) end -- Argument t = { "while", expr1, expr2 } local function xWhile(t) return While(t[2],t[3]) end -- Argument t = { "write", expr } local function xWrite(t) return Write(t[2]) end -- Argument t = { "block", expr, ... } local function xBlock(t) local my_unpack = table.unpack or unpack return Block(my_unpack(t,2,#t)) end -- Argument t = { "for", var, expr, expr, expr } local function xFor(t) return Nop("for " .. tostring(t[2])) end -- Argument s = "read" local function xRead(s) return Nop("Read") end -- Lexical Elements -- keyword (reserved word) patterns local Kif = P("if") local Kneg = P("neg") local Kquit = P("quit") local Kwhile = P("while") -- added for ImpCore local Kwrite = P("write") -- added for ImpCore local Kblock = P("block") -- added for ImpCore local Kfor = P("for") -- added for HW2 local Kread = P("read") -- added for HW2 local Keyword = Kif + Kneg + Kquit + Kwhile + Kwrite + Kblock + Kfor + Kread -- operator symbol patterns local Assign = P(":=") -- added Assign for ImpCore local Plus = P("+") local Minus = P("-") local Times = P("*") local Divide = P("/") local Equals = P("==") local NotEq = P("~=") local Less = P("<") local LessEq = P("<=") local Greater = P(">") local GreaterEq = P(">=") -- identifier and value patterns local Space = S(" \r\n\t\v\f")^0 local Alpha = R("az") + R("AZ") + P("_") local Num = R("09") local AlphaNum = Alpha + Num local Id = -(Keyword * (-AlphaNum)) * (Alpha * AlphaNum^0) -- lexical parsers, most with string captures local Open = P("(") * Space local Close = P(")") * Space local Variable = C(Id) * Space -- capture string local Number = C(Minus^-1 * Num^1) * Space -- capture string local Op1 = C(Kneg) * Space -- capture string local Op2 = C(Plus + Minus + Times + Divide + Equals + NotEq + LessEq + Less + GreaterEq + Greater) * Space -- capture string -- above requires LessEq before Less, GreaterEq before Greater local IfP = C(Kif) * Space -- capture string -- Added AssignP, WhileP, WriteP, and BlockP for ImpCore local AssignP = C(Assign) * Space -- capture string local WhileP = C(Kwhile) * Space -- capture string local WriteP = C(Kwrite) * Space -- capture string local BlockP = C(Kblock) * Space -- capture string -- Added for HW2 local ForP = C(Kfor) * Space -- capture string local ReadP = C(Kread) * Space -- capture string -- Grammar -- nonterminals local Exp, Value = V("Exp"), V("Value") -- rules local G = P { -- Added assign, while, write, block rules for ImpCore Exp; -- Added for, read rules for HW 2 Exp = Ct(Open * Op1 * Exp * Close) / xOp1 + Ct(Open * Op2 * Exp * Exp * Close) / xOp2 + Ct(Open * AssignP * Variable * Exp * Close) / xAssign + Ct(Open * IfP * Exp * Exp * Exp * Close) / xIf + Ct(Open * WhileP * Exp * Exp * Close) / xWhile + Ct(Open * WriteP * Exp * Close) / xWrite + Ct(Open * BlockP * Exp^0 * Close) / xBlock + Ct(Open * ForP * Variable * Exp * Exp * Exp * Close) / xFor + ReadP / xRead + Value ; Value = Variable / xVar + Number / xNum ; } -- Allow leading spaces and disallow any trailing non-space characters G = Space * G * -1 -- Public function "parse" takes a string "s" and returns the -- abstract syntax tree. local function parse(s) assert(type(s) == "string", "parse requires string argument, but has " .. treeConcat(s)) return lpeg.match(G, s) end -- MOVED: The following code was in the REPL for Expression Language 1 -- Public function "trimSpaces" removes all spaces from the -- beginning of string "line" and returns the resulting string. local function trimSpaces(line) local nonspace = lpeg.match(Space,line) return string.sub(line,nonspace,-1) end -- Command processing -- "quit" is a command. Pattern Kquit is defined above as a -- Keyword and, hence, removed as possible Id. But "quite" and -- "quit_19" are valid Id's. -- Note: Not sure command table is useful yet local command = { ["quit"] = true } local QuitP = Kquit * (-AlphaNum) local Command = QuitP local ICommand = Space * C(Command) -- Public function "getCommand" retrieves a REPL command from -- the beginning of the string "input" and returns the command. local function getCommand(input) return lpeg.match(ICommand,input) end -- Pubiic function "isQuit" determines whether the given string is -- the quit command. local function isQuit(s) return s == "quit" end -- End-of-line comment processing local Kcomment = P(";") local Comment = C((1 - Kcomment)^0) -- Function "trimComment" removes the comment from the end of string -- "line and returns the line. local function trimComment(line) return lpeg.match(Comment,line) end -- Export module table return { parse = parse, trimSpaces = trimSpaces, getCommand = getCommand, isQuit = isQuit, command = command, -- ? usefulness? trimComment = trimComment }