This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
Tinyboss's profile picture

Loadstring has some issues with variable scope

Started by Tinyboss, 11 January 2013 - 01:21 PM
Tinyboss #1
Posted 11 January 2013 - 02:21 PM
Edit: See my second post (third in the thread). The real problem is variable scope, not anything to do with tables.


Hi, I'm trying to write my own Lua REPL (read-eval-print loop), for two reasons: one, I'd like a better one, and two, I want to run it in parallel with other things. My plan is to use loadstring inside a loop to execute the Lua code input by the user, but I'm running into a problem. The following works under regular Lua 5.1 (on my computer, no Minecraft involved):



Lua 5.1.5  Copyright (C) 1994-2012 Lua.org, PUC-Rio
> t={}
> loadstring('t[1]=2')()
> print(t[1])
2
>

But when I execute exactly the same commands inside Minecraft, I get "string:1: index expected, got nil". Loadstring doesn't throw the error, but it's when the resulting function is called that the problem happens.

If instead of trying to stuff something into a table, I just do loadstring('foo=2')(), it works perfectly.

Is this a bug, or am I doing something wrong? Thanks!
theoriginalbit #2
Posted 11 January 2013 - 02:30 PM
I've never tried to modify tables with a loadstring… I'm assuming it should work though… maybe try this

loadstring("table.insert( t, 2 )")
Tinyboss #3
Posted 11 January 2013 - 03:12 PM
Okay, I've got a more accurate idea of what's going on here. It's not anything to do with tables, rather, it's that code run through loadstring can't see any variables unless they were declared through loadstring itself. But anything loadstring does declare will be visible to the top-level shell, and visible to subsequent calls to loadstring. E.g.:


EXAMPLE 1
lua> a=5
lua> loadstring("print(a)")()
-- nothing was printed because loadstring thinks a is nil.

EXAMPLE 2
lua> loadstring("b=5 print(b)")()
5
-- loadstring declared b, so it can see b.
lua> b
5
-- we can also see b from the top level.

EXAMPLE 3
lua> loadstring("c=3.14")()
lua> loadstring("print(2*c)")()
6.28
-- the declaration does not have to be in the same call.

EXAMPLE 4
lua> loadstring("t={}")()
lua> loadstring("t[1]=2")()
lua> t[1]
2
-- nothing to do with tables, after all.

EXAMPLE 5
lua> loadstring("d=1")()
lua> loadstring("print(d)")()
1
lua> d
1
lua> d=d+1
lua> d
2

lua> loadstring("print(d)")()
1
-- loadstring does not see the change we made to d from outside!
lua> loadstring("d=99")()
lua> loadstring("print(d)")()
99
lua> d
2

Example 5 shows that there are definitely two different copies of d, one accessed through the Lua shell and the other through loadstring. In fact, the only interaction the two scopes ever have, it seems, is that if a variable which is nil gets assigned a value through loadstring, then that value will also be assigned to it in the top (Lua shell) level. From then on, as far as I can tell, the variables are totally separate.

For the purpose I have in mind this might not actually be a problem, since just about everything is going to be done by loadstring. But it might not be intended behavior, and is definitely not the behavior of the Lua 5.1 installation on my PC (where code run through loadstring seems to share one and the same scope as code I type at the prompt).
Orwell #4
Posted 11 January 2013 - 03:17 PM
That's interesting. You could try this as a 'solution':


t = {}
local func = loadstring( 't[1] = 42' )
setfenv( func, getfenv() )
func()
print( t[1] )
Cloudy #5
Posted 11 January 2013 - 03:28 PM
Loadstring is generally a crappy way of doing something anyway.
Tinyboss #6
Posted 11 January 2013 - 03:37 PM
That's interesting. You could try this as a 'solution':


t = {}
local func = loadstring( 't[1] = 42' )
setfenv( func, getfenv() )
func()
print( t[1] )
That seems to work perfectly, thank you!


Loadstring is generally a crappy way of doing something anyway.
I am aware that the "eval" equivalent in any language is not needed for most tasks, and is especially prone to misuse by novices. But for my application, a read-evaluate-print loop, I think it's usually the right thing, isn't it? Is there a better way to accomplish what I'm trying?
ChunLing #7
Posted 12 January 2013 - 04:58 AM
Use pcall to pass arguments to the function created by loadstring. You should use pcall anywhere you use loadstring anyway, because the fact that you're using loadstring means that you haven't verified that it is good code yet.
Orwell #8
Posted 12 January 2013 - 06:11 AM
Use pcall to pass arguments to the function created by loadstring. You should use pcall anywhere you use loadstring anyway, because the fact that you're using loadstring means that you haven't verified that it is good code yet.
That wouldn't really fit the needs of a read-eval-print loop. You need support for arbitrary (user defined) variables. You could pass the environment into loadstring though. But then you could as well just set the environment using setfenv(). pcall still is a good idea though, but using setfenv() rather than passing arguments into the function.
ChunLing #9
Posted 12 January 2013 - 08:58 AM
True, and it's already working by using setfenv to pass the environment.