lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


Hi!

Am 25.04.2013 05:29 schröbte Philipp Janda:

My suggestion would be to make a proper interactive interpreter that
respects the local environment. I have some ideas, but it is late, so
maybe they are utter nonsense ...:

Proof of concept:

Lua 5.1.3  Copyright (C) 1994-2008 Lua.org, PUC-Rio
> local a = 1
> =a
1
> local function inc()
>> a = a + 1
>> print( a )
>> end
> local function dec()
>> a = a - 1
>> print( a )
>> end
> inc()
2
> inc()
3
> inc()
4
> =a
4
> dec()
3
> dec()
2
> =a
2
> =_G.a
nil
>

What you need: Lua 5.2 (yes, really, even if it says 5.1.3 above, I just didn't change the version string), lbci for Lua 5.2 (the preliminary version posted yesterday is sufficient), David Manura's lua.lua from here[1], and the attached patch.

  [1]:  http://lua-users.org/wiki/LuaInterpreterInLua

"Features" of this prototype:
- I needed to introduce another magic upvalue (currently named ___MAGIC___), which you must not use in your interactive code or all hell might break loose. - A top-level return will close all locals. This was a) a lot easier to implement, and b) helps avoid hitting the upvalue limit if you start to paste lots of code into the interactive interpreter. That means, that if you want to paste your module code, you must omit the final return statement if you want to access the locals in the module later on. The special "=<expr>" syntax should work as expected, though. - The code needs to compile each chunk multiple times and slice it using string.*, lbci.*, and debug.* functions -- I hope you didn't expect the interactive interpreter to be fast ...
-   I haven't tested much, *especially* anything _ENV-related.

David Manura's original lua.lua is MIT-licensed, my modifications of it are hereby placed in the public domain.

Have fun!

Philipp


diff -Naurd old/lua.lua new/lua.lua
--- old/lua.lua	2013-04-26 02:32:47.709924914 +0200
+++ new/lua.lua	2013-04-26 02:37:24.317916797 +0200
@@ -27,13 +27,13 @@
 local assert = assert
 local collectgarbage = collectgarbage
 local loadfile = loadfile
-local loadstring = loadstring
+local loadstring = load
 local pcall = pcall
 local rawget = rawget
 local select = select
 local tostring = tostring
 local type = type
-local unpack = unpack
+local unpack = table.unpack
 local xpcall = xpcall
 local io_stderr = io.stderr
 local io_stdout = io.stdout
@@ -42,6 +42,15 @@
 local string_sub = string.sub
 local os_getenv = os.getenv
 local os_exit = os.exit
+local require = require
+local bci = require( "bci" )
+local bci_getheader = bci.getheader
+local bci_getlocal = bci.getlocal
+local bci_getupvalue = bci.getupvalue
+local debug = require( "debug" )
+local db_getupvalue = debug.getupvalue
+local db_upvaluejoin = debug.upvaluejoin
+local tconcat = table.concat
 
 
 local progname = LUA_PROGNAME
@@ -161,7 +170,7 @@
 
 local function incomplete (msg)
   if msg then
-    local ender = LUA_QL("<eof>")
+    local ender = "<eof>"
     if string_sub(msg, -#ender) == ender then
       return true
     end
@@ -170,26 +179,105 @@
 end
 
 
+local get_magic
+do
+  local ___MAGIC___ = {}
+  function get_magic ()
+    return ___MAGIC___
+  end
+end
+
+local function funclocals (chunk)
+  local names = {}
+  local header = bci_getheader(chunk)
+  for i = 1, header.upvalues do
+    local name = bci_getupvalue(chunk, i)
+    if not names[ name ] then
+      names[ #names+1 ] = name
+      names[ name ] = #names
+    end
+  end
+  for i = 1, header.locals do
+    local name = bci_getlocal(chunk, i)
+    if name:sub( 1, 1 ) ~= "(" and not names[ name ] then
+      names[ #names+1 ] = name
+      names[ name ] = #names
+    end
+  end
+  return names
+end
+
+local function make_locals_header (f)
+  local names = funclocals(f)
+  local h = tconcat(names, ", ")
+  return h
+end
+
+local function find_upvalue (f, name)
+  local i = 1
+  repeat
+    local up_name = db_getupvalue(f, i)
+    if up_name == name then
+      return i
+    end
+    i = i + 1
+  until up_name == nil
+end
+
+
 local function pushline (firstline)
   local prmt = get_prompt(firstline)
   io_stdout:write(prmt)
   io_stdout:flush()
   local b = io_stdin:read'*l'
   if not b then return end -- no input
-  if firstline and string_sub(b, 1, 1) == '=' then
-    return "return " .. string_sub(b, 2)  -- change '=' to `return'
-  else
-    return b
+  return b, firstline and string_sub(b, 1, 1) == "="
+end
+
+
+local function eq_repl (s, has_eq)
+  if has_eq then
+    s = "return "..string_sub(s, 2)
   end
+  return s
 end
 
+local function eq_repl2 (s, has_eq, newheader)
+  if has_eq then
+    s = "return ___MAGIC___, function() return "..newheader.." end, "..
+        string_sub(s, 2)
+  end
+  return s
+end
 
-local function loadline ()
-  local b = pushline(true)
+
+local function loadline (lastlocals)
+  local b, replace = pushline(true)
   if not b then return -1 end  -- no input
+  local header = make_locals_header(lastlocals)
   local f, msg
   while true do  -- repeat until gets a complete line
-    f, msg = loadstring(b, "=stdin")
+    f, msg = loadstring("local "..header.."\n"..eq_repl(b,replace), "=stdin")
+    if f then
+      local newheader = make_locals_header(f)
+      local common = "return (function("..header..") return function() "..
+                     eq_repl2(b, replace, newheader)
+      f = assert(loadstring(common..
+                 "\n;return ___MAGIC___, function() return "..
+                 newheader.." end end end)()", b ) or
+                 loadstring(common.."\nend end)()", b))()
+      local i = 1
+      repeat
+        local name, val = db_getupvalue(lastlocals, i)
+        if name then
+          local n = find_upvalue(f, name)
+          if n then
+            db_upvaluejoin(f, n, lastlocals, i)
+          end
+          i = i + 1
+        end
+      until name == nil
+    end
     if not incomplete(msg) then break end  -- cannot try to add lines?
     local b2 = pushline(false)
     if not b2 then -- no more input?
@@ -198,7 +286,7 @@
     b = b .. "\n" .. b2 -- join them
   end
 
-  saveline(b)
+  saveline(eq_repl(b, replace))
 
   return f, msg
 end
@@ -207,21 +295,29 @@
 local function dotty ()
   local oldprogname = progname
   progname = nil
+  local lastlocals = get_magic
   while true do
     local result
-    local status, msg = loadline()
+    local status, msg = loadline(lastlocals)
     if status == -1 then break end
     if status then
       result = tuple(docall(status))
       status, msg = result[1], result[2]
     end
     report(status, msg)
+    lastlocals = get_magic
     if status and result.n > 1 then  -- any result to print?
-      status, msg = pcall(_G.print, unpack(result, 2, result.n))
-      if not status then
-        l_message(progname, string_format(
-            "error calling %s (%s)",
-            LUA_QL("print"), msg))
+      local off = 2
+      if result[2] == get_magic() then
+        off, lastlocals = 4, result[3]
+      end
+      if off == 2 or result.n > 3 then
+        status, msg = pcall(_G.print, unpack(result, off, result.n))
+        if not status then
+          l_message(progname, string_format(
+              "error calling %s (%s)",
+              LUA_QL("print"), msg))
+        end
       end
     end
   end
@@ -354,3 +450,4 @@
   else dofile(nil)  -- executes stdin as a file
   end
 end
+