--[[ REPL Module -- with modification for ImpCore 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 2016-09-24: Command lines parsing functions moved to Parser module 2016-09-25: Added startREPL to run scripts before starting REPL; Factored out parseEval Allow Environment arguments with globalEnv default 2016-09-27: Handle errors for missing files in startREPL 2016-10-03: Fix bug in startREPL loop to process lines 2016-10-20: Added Script level to language 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. Beforrepe 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". Public function readEvalPrint starts the REPL described abolve. Public startREPL first processes given in command line arguments before calling readEvalPrint. 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_core" local EVALUATOR = "evaluator_core" 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) -- Import Values Module local values = require(VALUES) local valToString = values.valToString -- Import Parser Module local parser = require(PARSER) local parse = parser.parse local trimSpaces = parser.trimSpaces local trimComment = parser.trimComment local getCommand = parser.getCommand local isQuit = parser.isQuit local command = parser.command -- Import Evaluator Module local evaluator = require(EVALUATOR) local eval, evalScript = evaluator.eval, evaluator.evalScript -- Command line prompts local mainPrompt, contPrompt = "> ", ">>" -- Default Global Environment local globalEnv = environment.newEnv() -- REPL functions -- Private function "readExpr" interactively reads an expression -- entered at the command line and returns it. 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 -- Internal function "parseEval" takes a string "input", parses -- it using the Parser module, evaluates it in environment "env" -- using the Evaluator module, and then returns the array of results. local function parseEval(input,env) env = env or globalEnv local script, result = nil, nil local parseok, parsemsg = pcall(function() script = parse(input) end) if parseok and script then -- why parseok true for invalid? local evalok, evalmsg = pcall(function() result = evalScript(script,env) -- EVALUATE end) if evalok then return result, nil else print("Evaluation failed for " .. input) print(evalmsg) return nil, "Evaluation foilure" end else print("Parsing failed for " .. input) print(parsemsg) return nil, "Parsing failure" end end -- Procedure "readEvalPrint" drives the Read-Evaluate-Print-Loop -- command line interface. It repeatedly reads an expression, -- parses it, evaluates it in "env", and prints the resulting value -- until the command "quit" is entered. local function readEvalPrint(env) env = env or globalEnv local quitting = false while not quitting do local input = readExpr() -- READ local cmd = getCommand(input) quitting = isQuit(cmd) if not quitting then local result = parseEval(input,env) -- EVALUATE if result then print(valToString(result[1])) -- PRINT else print("Input ignored.") end end end end -- Public procedure "startREPL" first reads, parses, and evaluates -- any script files given as command line arguments to Lua. It uses -- "env" as the initial Environment; this defaults to "globalEnv". -- It then starts the interactive REPL in the resulting environment. local function startREPL(env) env = env or globalEnv for s = 1, #arg do print("Loading script file " .. tostring(arg[s])) local f, err, errno = io.open(arg[s], "r") if f then local strb = {} for line in f:lines() do line = trimSpaces(trimComment(line)) if line ~= "" then strb[#strb+1] = line end end local result = parseEval(table.concat(strb,"\n"), env) if result then print(valToString(result[#result])) -- PRINT else print("Input ignored.") end else print("Cannot open file " .. arg[s], err, errno) end end print("Starting interactive REPL. Type 'quit' to exit.") readEvalPrint(env) end -- MODULE EXPORT LIST return { readEvalPrint = readEvalPrint, startREPL = startREPL }