Warning: this is an htmlized version!
The original is here, and
the conversion rules are here.
-- repl.lua: a repl for Lua (new version).
-- This file:
--   http://angg.twu.net/dednat5/repl2.lua.html
--   http://angg.twu.net/dednat5/repl2.lua
--                    (find-dn5 "repl2.lua")
--
-- Author: Eduardo Ochs <eduardoochs@gmail.com>
-- Version: 2011dec05
-- License: GPL3
--
-- REPLs are hard to implement! As I've tried to explain in my notes in
--   http://angg.twu.net/repl.html
--             (find-TH "repl")
-- the control flow of a REPL can be daunting...
--
-- The class "Repl", defined below, is an attempt to implement all the
-- ideas mentioned in those notes, plus a few more - e.g., different
-- prefixes - without using any tricks like throw/catch, gotos, or
-- tail cails; we just use Repl object with a "status" field, plus
-- several other fields for temporary data.
--
-- The logic of a REPL is, very roughly, this:
--
--     /--------------------\
--     |                    |
--     v                    |
--     R ---> E ---> P ---> L
--
-- which means: Try to [R]ead a command, possibly spanning several
-- lines; after reading it, try to [E]val it; if the eval was
-- successful, [P]rint the results, otherwise display the error; if
-- not abort has been requested, [L]oop.
--
-- The logic of [R]ead is roughly this: read a first line of input,
-- with prompt ">"; while what we've got is an incomplete command -
-- like "if foo() then bar() else", read more input, now with prompt
-- ">>", again testing for completeness after adding each line. But
-- there are several kinds of errors that we must handle, so here is
--
-- The full pseudocode
-- ===================
-- (Note that this is to be read while you follow the real code!)
--
--   "Read()" is this:
--     Readfirst(), and while not Incomplete() do Readanother(); end;
--     if not Compilationerror() then return true end.
--     If either "Readfirst()" or "Readanother" receive a "^C", a "^D"
--     or an "eof" in its input, return nil.
--     Note that:
--       1) Compilationerror() may set the status to "compilation
--          error", and in this case Read() returns nil.
--       2) If Read() returns true this means that we have something
--          to Eval().
--       3) There are several cases in which Read() returns nil:
--            r.status = "eof"                -> means: abort the REPL
--            r.status = "^D"                 -> means: abort the REPL
--            r.status = "compilation error"  -> means: read more
--            r.status = "^C"                 -> means: read more
--       4) Incomplete() runs r.f, r.err = loadstring(r.code) and
--          tests if r.err holds certain a certain type of error (that
--          means that the code ends prematurely). The values in r.f
--          and r.err are reused by Compilationerror() and Eval().
--       5) Readfirst() runs Identify(), which detects which prefix is
--          being used and sets some variables (e.g. r.print)
--          accordingly.
--   "Eval()" is this:
--     Run r.f() with xpcall, using a simple error handler to display
--     a traceback in case of runtime errors; when a runtime error
--     occurs, set r.status to "runtime error" and return nil, and
--     when there are no runtime errors set r.fresults to a closure
--     that returns the same return values as r.f(), and return true.
--     Note that when Eval() returns true that means that we may have
--     something to print.
--   "Print()" is this:
--     Run r.print(r:fresults()). For some prefixes, like "=", r.print
--     is set to a function that prints the results; for other
--     prefixes, like "", r.print is set to nop, which do not print
--     the results.
--   "ReadEvalPrint()" is this:
--     r:Read() and r:Eval() and r:Print().
--     After running that, r.status can be one of: "eof", "^C", "^D",
--     "compilation error", "runtime error", or some other (garbage)
--     values, all meaning "ok". When the status is "eof" or "^D"
--     ReadEvalPrint() returns nil, in all other cases it returns
--     true. When ReadEvalPrint() returns true that means that we
--     should loop.
--
-- How did I develop that
-- ======================
-- I examined this sample interaction,
--
--   > r = Repl {}
--   > r:Repl()
--   L> = 2 +
--   LL>  3,     4
--        5      4
--   L> ^D
--   >
--
-- and wrote down how the fields in the Repl object "r" should be
-- changed, and in which order, and by which function:
--
--   r.line      =       "= 2 +"          <-- set by Readline
--   r.str       =       "= 2 +"          <-- set by Readfirst
--   r.prefix    =       "="              <-- set by Identify
--   r.body      =        " 2 +"          <-/
--   r.code      = "return  2 +"          <-/
--   r.print     =  <function print>      <-/
--   r.status    = "incomplete"           <-- set by Incomplete
--   r.line      =               "3, 4"   <-- set by Readline
--   r.str       =       "= 2 +\n 3, 4"   <-- set by Readanother
--   r.body      =        " 2 +\n 3, 4"   <-/
--   r.code      = "return  2 +\n 3, 4"   <-/
--   r.status    = "complete"             <-- set by Incomplete
--   r.f, r.err  = loadstring("return  2 +\n 3, 4") <-- set by Eval
--   r.out       = {true, 5, 4, n=3}                <-/
--   r.results() = 5, 4                             <-/
--   r.line      =        "^D"            <-- set by Readline
--   r.status    =        "^D"
--
-- Then, starting from the sketchy data flow diagram above, I
-- discovered how should be the control flow, and wrote the code.
--
-- A note: I'm cheap, so I decided to support "^C" and "^D" only as
-- lines holding a LITERAL caret then an uppercase "C" or "D" - I
-- don't want to have to deal with real signals right now! 8-\
--
-- (find-es "lua5" "loadstring_and_eof")
-- (find-es "lua5" "traceback")
-- (find-luamanualw3m "#pdf-xpcall")
-- (find-luamanualw3m "#pdf-unpack")
-- (find-luamanualw3m "#pdf-select")

require "common"     -- (find-dn5 "common.lua")
require "eoo"        -- (find-dn5 "eoo.lua")

Repl = Class {
  type    = "Repl",
  __index = {
    Readline = function (r, prompt)
        io.write(prompt)
        r.line = io.read()
	if r.line == nil  then r.status = "eof"; return end
        if r.line == "^C" then r.status = "^C"; return end
        if r.line == "^D" then r.status = "^D"; return end
        return true
      end,
    Identify = function (r)
        local prefix, body = r.line:match("^(==?)(.*)$")
	r.str = r.line
        if prefix then
          r.prefix = prefix
          r.body   = body
          r.code   = "return "..body
	  r.print  = print
	  r.print  = PP
        else
          r.prefix = ""
          r.body   = r.line
          r.code   = r.line
	  r.print  = function (...) end
        end
      end,
    Incomplete = function (r)
        local pat = "<eof>.$"
        r.f, r.err = loadstring(r.code)
        if r.err and r.err:match(pat) then
          r.status = "incomplete"
          return true
        end
      end,
    Compilationerror = function (r)
        if r.err then
          r.status = "compilation error"
	  print(r.err)
          return true
        end
      end,
    Readfirst = function (r)
        if r:Readline("L> ") then
          r:Identify()
          return true
        end
      end,
    Readanother = function (r)
        if r:Readline("LL> ") then
          r.str  = r.str .."\n"..r.line
          r.body = r.body.."\n"..r.line
          r.code = r.code.."\n"..r.line
          return true
        end
      end,
    Read = function (r)
        if not r:Readfirst() then return end
        while r:Incomplete() do
          if not r:Readanother() then return end
        end
        if r:Compilationerror() then return end
        return true
      end,
    Eval = function (r)
        local handler    = function () print(debug.traceback()) end
        local out        = pack(xpcall(r.f, handler))
        if not out[1] then r.status = "runtime error"; return end
        r.fresults = function () return unpack(out, 2, out.n) end
	return true
      end,
    Print = function (r)
        r.print(r:fresults())
      end,
    ReadEvalPrint = function (r)
        if r:Read() and r:Eval() then r:Print() end
	if r.status == "^D" or r.status == "eof" then return nil end
        return true
      end,
    Repl = function (r)
        while r:ReadEvalPrint() do end
      end,
  },
}

repl = function () Repl{}:Repl() end

-- dump-to: tests
--[==[
* (eepitch-lua51)
* (eepitch-kill)
* (eepitch-lua51)
-- dofile "eoo.lua"
dofile "repl2.lua"
repl()
= 2, 3, 4
= 2, 3 +
 4
= !!!
= (2 +
   3 +
   4)
= (2 +
   3
   + 4)
foo(bar)
print(2, 3)
print(2,
^C
^C
^D

--]==]

-- Local Variables:
-- coding:             raw-text-unix
-- ee-anchor-format:   "«%s»"
-- End: