--[[ REPL Module Read-Evaluate-Print Loop command line interface 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-21: REPL adapted from KILT REPL from CSci 658 in Fall 2013 This module implements a simple, text-based, interactive user interface to the interpreter, commonly called a REPL. It repeatedly prompts for an expression, READs and parses the user's input expression, EVALUATEs it, and then PRINTs the result. It calls the Parser and Evaluator. Before processing, the REPL removes any leadings spaces and end-of-line comment from each line. A comment is any text beginning with a semicolon ";" and continuing until the end of the line. This REPL originally creatd for parenthesized prefix (Lisp-like) expression syntax. If it detects unbalanced parentheses on a line, it prompts the user for the continuation of that line until the parentheses balance. The REPL continues to read expressions until the user enters the command "quit". TODO: - Parser returns nil for illegal syntax but does not fail. Check out the pcall-wrapped parse call --]] --[[ Module names varialbes now global local UTILITIES = "utilities" local ENVIRONMENT = "environment" local PARSER = "parser_infix_01" local EVALUATOR = "evaluator_01" local VALUES = "values_01" --]] -- Import Utilities Module local util = require(UTILITIES) local treeConcat, printTree, show_data = util.treeConcat, util.printTree, util.show_data -- Import Environment Module local environment = require(ENVIRONMENT) local globalEnv = environment.newEnv() -- Import Values Module local values = require(VALUES) local valToString = values.valToString -- Import Parser Module local parser = require(PARSER) local parse = parser.parse -- Import Evaluator Module local evaluator = require(EVALUATOR) local eval = evaluator.eval -- Import LPEG module local lpeg = require "lpeg" local C, Ct, P, R, S, V = lpeg.C, lpeg.Ct, lpeg.P, lpeg.R, lpeg.S, lpeg.V -- MOVE this to Parser module? -- Command processing--could have more than just "quit" local command = {} local QUIT = "quit" local KQuit = P(QUIT) command[QUIT] = true local Command = KQuit local Space = S(" \n\t")^0 local ICommand = Space * C(Command) local KComment = P(";") local Comment = C((1 - KComment)^0) local mainPrompt, contPrompt = "> ", ">>" -- 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 -- 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 -- 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 -- REPL functions -- The function "readExpression" interactively reads an expression -- entered at the command line. local function readExpr() local line, ch local input = {} local bal = 0 -- #left parentheses - #right parentheses local fullinput = false -- loop until a complete expression is entered while not fullinput do if bal == 0 then io.write(mainPrompt) elseif bal > 0 then io.write(contPrompt) end -- bal < 0 means a syntax error, caught below line = io.read("*l") -- trim leading spaces and end-of-line comment line = trimSpaces(line) line = trimComment(line) -- check for balanced parentheses, need for additional input for i = 1, #line do ch = string.sub(line,i,i) if ch == "(" then bal = bal + 1 elseif ch == ")" then bal = bal - 1 end if bal < 0 then -- found ) before matching ( break end end input[#input+1] = line if bal < 0 then -- too many )'s, syntax error, restart input print( "Syntax error. Input ignored: " .. table.concat(input,"\n") ) input, bal = {}, 0 elseif line ~= "" and bal == 0 then -- complete input expression fullinput = true end -- bal > 0, continue gathering input lines end return table.concat(input,"\n") end -- Procedure "readEvalPrint" drives the Read-Evaluate-Print-Loop -- command line interface. It repeatedly reads an expression, parses -- it, evaluates it, and prints the resulting value until the command -- "quit" is entered. local function readEvalPrint() local input, expr, result local quitting = false while not quitting do input = readExpr() -- READ (& parse) local cmd = getCommand(input) quitting = (cmd == QUIT) if not quitting then expr = nil local parseok, parsemsg = pcall(function() expr = parse(input) print("DEBUG: after parse in repl -> " .. treeConcat(expr)) end) if parseok and expr then -- why is parseok true for invalid input? local evalok, evalmsg = pcall(function() result = eval(expr,globalEnv) -- EVALUATE end) if evalok then local output = valToString(result) print(output) -- PRINT else print(evalmsg) print("Evaluation failed for " .. input) print("Input ignored.") end else print(parmsg) print("Parsing failed for " .. input) print("Input ignored.") end end input = "" end end -- MODULE EXPORT LIST return { readEvalPrint = readEvalPrint }