[Date Prev][Date Next][Thread Prev][Thread Next]
[Date Index]
[Thread Index]
- Subject: Re: In praise of globals
- From: Philipp Janda <siffiejoe@...>
- Date: Fri, 26 Apr 2013 03:15:57 +0200
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
+