Jump to content




[SOLVED] calling code from string


17 replies to this topic

#1 BrunoZockt

  • Members
  • 56 posts
  • LocationGermany

Posted 09 August 2018 - 07:58 PM

EDIT: Didn't want to open another thread because this is somewhat related, have a look at my current problem all the way down.

Hi Guys,

I need to run code from a string (e.g. "if true then print("bla") end"). loadstring() doesn't work for me since I need to call functions that are not global but only exist within my program. I found a script by Lupus590 which works perfectly, but somehow tables don't work.

This works
This prints "Hello World" like I want it to.

This doesn't work
This throws an
Unknown chunk:1: attempt to index ? (a nil value)
error

I know it's not good to use scripts that you don't understand, but I tried to and couldn't because the documentation of load() is so bad. Does anybody understand why this is happening and if there is a way to fix it?

Thanks in advance!
Bruno

Edited by BrunoZockt, 20 August 2018 - 07:14 PM.


#2 KingofGamesYami

  • Members
  • 3,002 posts
  • LocationUnited States of America

Posted 10 August 2018 - 12:23 AM

View PostBrunoZockt, on 09 August 2018 - 07:58 PM, said:

I need to run code from a string (e.g. "if true then print("bla") end"). loadstring() doesn't work for me since I need to call functions that are not global but only exist within my program.

First off, WHY? There is most likely a MUCH better solution to whatever problem you have than doing this (which is why the default behavior is to not allow it).

View PostBrunoZockt, on 09 August 2018 - 07:58 PM, said:

the documentation of load() is so bad

It's actually very good, IMO.

Finally, neither example #1 nor example #2 work for me. A minimal reproduction is easy:

local foo = "Hello World"
print( getfenv().foo ) --# nil

Unfortunately, it is completely impossible to get locally delcared variables in ComputerCraft. In regular Lua it is possible to do via the debug library, but this is disabled for security reasons.

The easiest workaround would be to not declare your stuff as local. But that is going to pollute the global env. and do various bad things. A better workaround is to pass all the required variables to the function when it is called. If this is impractical, you might want to consider a different approach to whatever problem you're attempting to solve.

#3 Bomb Bloke

    Hobbyist Coder

  • Moderators
  • 7,099 posts
  • LocationTasmania (AU)

Posted 10 August 2018 - 01:21 AM

View PostKingofGamesYami, on 10 August 2018 - 12:23 AM, said:

View PostBrunoZockt, on 09 August 2018 - 07:58 PM, said:

I need to run code from a string (e.g. "if true then print("bla") end").

First off, WHY? There is most likely a MUCH better solution to whatever problem you have than doing this (which is why the default behavior is to not allow it).

https://en.wikipedia...wiki/XY_problem

View PostBrunoZockt, on 09 August 2018 - 07:58 PM, said:

Does anybody understand why this is happening and if there is a way to fix it?

Lupus' snippet simply compiles your string to use the same environment table as the rest of your script does. The environment table holds global variables: but not local ones. As Yami points out, you're localising "foo", which is why neither of your examples work as written.

http://lua-users.org...i/ScopeTutorial

https://www.lua.org/pil/14.html

(Note that although _G is available to most all ComputerCraft scripts, they generally aren't using _G as their global environment table.)

#4 BrunoZockt

  • Members
  • 56 posts
  • LocationGermany

Posted 10 August 2018 - 12:32 PM

I'm very confused now, I tested this several times yesterday, but apparently you are right. I can't get the first example to work anymore. However that at least explains that the Problem lies somewhere else, like you both explained.
Thanks!

My reason to do all this is that I want to make my program to resume after a server restart. To achieve this (without GPS) I need a list of every code that needs to be called to save it externally. So I'm rewriting my whole program to instead of directly calling code, adding it to the list, which gets called by a central loop.

Edited by BrunoZockt, 24 June 2019 - 10:17 PM.


#5 Bomb Bloke

    Hobbyist Coder

  • Moderators
  • 7,099 posts
  • LocationTasmania (AU)

Posted 10 August 2018 - 01:34 PM

Are you familiar with how ComputerCraft computers & turtles rely upon events to get information from the world? For example, if a turtle attempts to move, your script will typically yield until it's resumed with event data indicating whether the attempt was successful or not (yes, even if you're not calling os.pullEvent() yourself, many other functions are doing it for you!). You'll need to record this event information if you want to "replay" a list of old executed commands, or you'll have no idea which of them worked and which of them didn't.

If you only intend to store commands which haven't been executed, then I suppose my question would be: if you have time to record them, then why not just execute them?

You might consider taking a look at Lion4ever's old State Restore script. It's been a while since I last tested it, but it wouldn't surprise me if it'll straight-up do what you want. In any case, the basic principles behind it are probably the best for resuming arbitrary code - pay attention to how it works with events. It doesn't even need to record the code that's later replayed!

Any particular reason to avoid using a GPS, though? If you're on CC1.76 or later, you can even set one up using Ender Modems, thus providing coverage across an entire dimension.

#6 BrunoZockt

  • Members
  • 56 posts
  • LocationGermany

Posted 11 August 2018 - 12:42 PM

View PostBomb Bloke, on 10 August 2018 - 01:34 PM, said:

Are you familiar with how ComputerCraft computers & turtles rely upon events to get information from the world? For example, if a turtle attempts to move, your script will typically yield until it's resumed with event data indicating whether the attempt was successful or not (yes, even if you're not calling os.pullEvent() yourself, many other functions are doing it for you!).
I was indeed familiar with this.

Quote

You'll need to record this event information if you want to "replay" a list of old executed commands, or you'll have no idea which of them worked and which of them didn't.
I agree, but I'm storing commands which haven't yet been executed.

Quote

If you only intend to store commands which haven't been executed, then I suppose my question would be: if you have time to record them, then why not just execute them?
Because if the program crashes and gets restarted I need to know which commands haven't yet been executed to then execute them.

Quote

You might consider taking a look at Lion4ever's old State Restore script. It's been a while since I last tested it, but it wouldn't surprise me if it'll straight-up do what you want. In any case, the basic principles behind it are probably the best for resuming arbitrary code - pay attention to how it works with events. It doesn't even need to record the code that's later replayed!
This is indeed very interesting, however I thought about doing something like this myself, and already have done it with some functions (e.g. instead of calling turtle.up(), I call a custom function which edits a variable to save that the level above bedrock did increase), but find it too complicated to extract the information of what still needs to be done out of the information what has already been done. If I'm not mistaken I'd need a complete list of every command that needs to be done, in which case I could also just use that list like I intended to do.

Quote

Any particular reason to avoid using a GPS, though? If you're on CC1.76 or later, you can even set one up using Ender Modems, thus providing coverage across an entire dimension.
Well, I haven't really worked with GPS yet, because I don't like the idea of needing to preinstall "satellites". If every Computercraft world had GPS "preinstalled" I would definitly use it, but I don't like the idea of depending on a system that probably isn't availabe. And as long as I haven't been proving wrong that it is possible to resume without GPS, I will try to achieve this for the sake of perfectionism.

However I really don't like my approach because it makes my code a total mess and seems very -don't know how to say it- unclean?
So I will definitly try out different approaches to see which satisfies me most.

Thanks for your extensive feedback!

Cheers

#7 Lupus590

  • Members
  • 2,029 posts
  • LocationUK

Posted 11 August 2018 - 01:42 PM

You may also be interested in Checkpoint and LAMA.

#8 Bomb Bloke

    Hobbyist Coder

  • Moderators
  • 7,099 posts
  • LocationTasmania (AU)

Posted 11 August 2018 - 02:47 PM

View PostBrunoZockt, on 11 August 2018 - 12:42 PM, said:

View PostBomb Bloke, on 10 August 2018 - 01:34 PM, said:

If you only intend to store commands which haven't been executed, then I suppose my question would be: if you have time to record them, then why not just execute them?

Because if the program crashes and gets restarted I need to know which commands haven't yet been executed to then execute them.

But how will you know that? That is to say, when resuming, what assurance will you have that the command at the top of the list hasn't been executed yet?

There's no way to write out your list to disk at the exact same time as a given task is performed, after all. You either have to write before or after. In the former case, you could be shut down before the task is performed, meaning it's struck off your list too early. In the latter case, you could be shut down after the task is performed, but before the list is updated: meaning you'll perform it a second time after the reboot.

View PostBrunoZockt, on 11 August 2018 - 12:42 PM, said:

However I really don't like my approach because it makes my code a total mess and seems very -don't know how to say it- unclean?

FWIW, bear in mind that you can stick function pointers into tables, and then index into those tables using strings. That's generally how a Lua library / API is already set up, conveniently. For example:

local funcToCall = "up"

turtle[funcToCall]()

You can also find all the main API tables inside of _G:

local funcToCall = {"turtle", "up"}

local func = _G

for i = 1, #funcToCall do
  func = func[funcToCall[i]]
end

func()


#9 BrunoZockt

  • Members
  • 56 posts
  • LocationGermany

Posted 11 August 2018 - 05:21 PM

View PostLupus590, on 11 August 2018 - 01:42 PM, said:

You may also be interested in Checkpoint and LAMA.
Thanks, LAMA was actually my first approach to orientate during ore excavation. Will check out Checkpoint!

#10 BrunoZockt

  • Members
  • 56 posts
  • LocationGermany

Posted 11 August 2018 - 05:46 PM

Quote

There's no way to write out your list to disk at the exact same time as a given task is performed, after all. You either have to write before or after. In the former case, you could be shut down before the task is performed, meaning it's struck off your list too early. In the latter case, you could be shut down after the task is performed, but before the list is updated: meaning you'll perform it a second time after the reboot.
I am aware of that. However I'm willing to test this out and see how reliably it works. I decided to remove commands from the list after they've been executed, with the risk of it being executed twice rather than not at all. I do however believe that the probabilities are in my favor since the movement of the turtle takes ~0.4 seconds (just an educated guess, I don't know the exact time) and the editing of the file only takes a few milliseconds. Also I once tried to measure the time that it takes for a turtle to move with a small script, and it showed 1 tick meaning the line to get the time got executed instantly after the call of turtle.forward() and not after finishing of the movement. With that being said, I believe there is a high probability that the program terminates during such a movement (after the list update), which would be perfect. So I'm gonna try this out. Just to be save: Without GPS there is no way to guarantee that it works, right? Or is it possible to run the command and the documentation in parrallel?

Quote

FWIW, bear in mind that you can stick function pointers into tables, and then index into those tables using strings. That's generally how a Lua library / API is already set up, conveniently. For example:

local funcToCall = "up"

turtle[funcToCall]()

You can also find all the main API tables inside of _G:

local funcToCall = {"turtle", "up"}

local func = _G

for i = 1, #funcToCall do
  func = func[funcToCall[i]]
end

func()
I did know this was possible but didn't really think about this. This is indeed very helpful, thank you!

Edited by BrunoZockt, 11 August 2018 - 05:48 PM.


#11 Bomb Bloke

    Hobbyist Coder

  • Moderators
  • 7,099 posts
  • LocationTasmania (AU)

Posted 12 August 2018 - 04:53 AM

View PostBrunoZockt, on 11 August 2018 - 05:46 PM, said:

Or is it possible to run the command and the documentation in parrallel?

View PostBomb Bloke, on 11 August 2018 - 02:47 PM, said:

There's no way to write out your list to disk at the exact same time as a given task is performed, after all. You either have to write before or after.

You can sync things pretty closely, but there's no way to get a write done at the moment you're getting confirmation an action is complete. You're either doing one or the other.

#12 Lyqyd

    Lua Liquidator

  • Moderators
  • 8,465 posts

Posted 12 August 2018 - 05:14 AM

You'll really want to look at LAMA (linked earlier in the thread) to see how they do their persistent-across-reload location tracking. The fundamentals will apply directly to the problem you are working on.

Hint: Fuel levels make everything except turns much easier to track.

#13 BrunoZockt

  • Members
  • 56 posts
  • LocationGermany

Posted 12 August 2018 - 03:36 PM

View PostBomb Bloke, on 12 August 2018 - 04:53 AM, said:

You can sync things pretty closely, but there's no way to get a write done at the moment you're getting confirmation an action is complete. You're either doing one or the other.
Thanks for the confirmation

View PostLyqyd, on 12 August 2018 - 05:14 AM, said:

You'll really want to look at LAMA (linked earlier in the thread) to see how they do their persistent-across-reload location tracking. The fundamentals will apply directly to the problem you are working on.

Hint: Fuel levels make everything except turns much easier to track.

Thank you for the reminder, I had almost forgotten about that solution!

#14 BrunoZockt

  • Members
  • 56 posts
  • LocationGermany

Posted 19 August 2018 - 07:33 PM

Hi, I feel very stupid now because I still couldn't figure this out.

I'm still trying to write code to a table and then call every item on the table one by one. With the changed environment it should be able to call all the functions, but somehow I get this Error:
"Unknown chunk:3: attempt to call nil"

still Lupus script
The loop that is running
And this is part of eventList.1
The called function. Notice that 'test' is never printed. It is located below the environment defining stuff by Lupus so that can't be the problem.

What strikes me is that this is the only function that throws this error. I can't tell what is different about this one compared to the other functions I call.

What seems to me like the same, with the difference that it works:
Another chunk of EventList that makes no problems
the called function

And just for your information, function insert() and function dig() are right next to each other in my code, nothing between them.

Maybe I've overlooked something but I don't know what else to do.

Edited by BrunoZockt, 19 August 2018 - 07:36 PM.


#15 Bomb Bloke

    Hobbyist Coder

  • Moderators
  • 7,099 posts
  • LocationTasmania (AU)

Posted 20 August 2018 - 04:56 AM

I'm scratching my head over how you're not getting an unfinished string error - your use of linebreaks should, well, break them. And what's with the backslashes? What are you intending to escape, exactly?

The way I'd typically define a multi-line string is:

{
  [[while not turtle.forward() do --notice that I put a block in front of the turtle so that dig() is called
        if turtle.getFuelLevel == 0 then
          Fuel()
        elseif dig() == false then --dig() is called
          turtle.attack()
        end
  end]]

Note that this still won't call dig() if the turtle is out of fuel, unless Fuel() successfully rectifies that for later iterations!

Why is your loop setting index to 2? Should it not be lowering its old value by one instead?

It'd be helpful to provide the full list of code, as I'm not sure you've provided enough information here to determine the cause of your reported error.

#16 BrunoZockt

  • Members
  • 56 posts
  • LocationGermany

Posted 20 August 2018 - 05:25 AM

EDIT: Ok, now again. This time I will provide all the information so that there is no confusion.

Just repeating my last post

View PostBomb Bloke, on 20 August 2018 - 04:56 AM, said:

I'm scratching my head over how you're not getting an unfinished string error - your use of linebreaks should, well, break them. And what's with the backslashes? What are you intending to escape, exactly?

The way I'd typically define a multi-line string is:

{
  [[while not turtle.forward() do --notice that I put a block in front of the turtle so that dig() is called
		if turtle.getFuelLevel == 0 then
		  Fuel()
		elseif dig() == false then --dig() is called
		  turtle.attack()
		end
  end]]

I didn't provide the full string to simplify it, turns out that was a bad idea. The full string (referred to as "Unknown chunk" in the error message):
Spoiler

Again, function insert():
function insert(code)
  table.insert(eventList, index, code)
  index = index + 1
  save("database/OCM/resume/eventList", eventList)
end

save-function, if interested (irrelevant)

View PostBomb Bloke, on 20 August 2018 - 04:56 AM, said:

Note that this still won't call dig() if the turtle is out of fuel, unless Fuel() successfully rectifies that for later iterations!
I don't really understand what you mean, this code works exactly like it's intended to.
[[while not turtle.forward() do
    if turtle.getFuelLevel == 0 then
	  Fuel()
    elseif dig() == false then
	  turtle.attack()
    end
  end]]
When called the turtle tries to move forward. If it has no fuel left, Fuel() is called and it tries again. If there is a block in the way it destroys it and tries again. If there is a mob it attacks it and tries again.
The point of this snippet only was to show that the calling of other functions like Fuel(), dig(), turtle.forward() and turtle.attack() works. Only insert() is making problems.

View PostBomb Bloke, on 20 August 2018 - 04:56 AM, said:

Why is your loop setting index to 2? Should it not be lowering its old value by one instead?
This is a little bit complicated and not relevant but let me try to explain:
Spoiler

View PostBomb Bloke, on 20 August 2018 - 04:56 AM, said:

It'd be helpful to provide the full list of code, as I'm not sure you've provided enough information here to determine the cause of your reported error.
I really don't recommend looking into it, it's horribly messy and I've provided everything that's related but I guess I can't stop you:
https://pastebin.com/fptxKN9n

I know this is a lot to read through and very difficult to wrap your head around but I really need the help :(

Edited by BrunoZockt, 20 August 2018 - 12:38 PM.


#17 Bomb Bloke

    Hobbyist Coder

  • Moderators
  • 7,099 posts
  • LocationTasmania (AU)

Posted 20 August 2018 - 02:14 PM

View PostBrunoZockt, on 20 August 2018 - 05:25 AM, said:

I don't really understand what you mean, this code works exactly like it's intended to.

As an example, this code won't error out:

if true then
  print("hello")
elseif error("goodbye") then
  print("argh")
end

... and this snippet won't attempt to evaluate tbl.key if tbl isn't a table (hence avoiding an "attempt to index into something that isn't a table" error):

if type(tbl) == "table" and tbl.key == "pie" then

Evaluation stops as soon as the final result is known - the Lua VM doesn't bother to parse the rest of your conditional statement if it doesn't need to. I mentioned it because without access to your full source, I had no way to know whether Fuel() could return prior to refuelling the turtle. If it could, then that could in turn prevent dig() from ever being called, as it's contingent on a certain "elseif" being reached within your code.

View PostBrunoZockt, on 20 August 2018 - 05:25 AM, said:

Insert() inserts a chunk of code into the toDoList. It uses index 2 and increasing because the inserted code needs to be called next. Index 1 however is the chunk of code that called insert() in the first place and needs to be removed after successfull execution. The index is set back to 2 now, so that insert() -if called- doesn't push away the chunk at index 1 which again needs to be removed.

Say you're calling chunk 1 from slot 1. It inserts chunk 2 into slot 2, and then chunk 3 into slot 3. "index" has been increased to 4 by this point, by the insert() function.

Chunk 1 returns and is removed from the list, chunk 2 goes into slot 1, chunk 3 goes into slot 2, index gets "set to 2" (as opposed to "reduced by 1").

Chunk 2 starts execution and inserts chunk 4 into slot 2, bumping chunk 3 back into slot 3 again...

The result isn't the first-in-first-out system I would expect.

View PostBrunoZockt, on 20 August 2018 - 05:25 AM, said:

I really don't recommend looking into it, it's horribly messy and I've provided everything that's related but I guess I can't stop you:

You're localising "insert" on line 99. If you assigned a function pointer to it in the global scope before doing that you'd technically have two different variables, but since you're doing it after, your function declaration is just overwriting the table pointer stored within the local variable.

Best to use separate names there.

Edited by Bomb Bloke, 20 August 2018 - 02:30 PM.


#18 BrunoZockt

  • Members
  • 56 posts
  • LocationGermany

Posted 20 August 2018 - 04:06 PM

View PostBomb Bloke, on 20 August 2018 - 02:14 PM, said:

Say you're calling chunk 1 from slot 1. It inserts chunk 2 into slot 2, and then chunk 3 into slot 3. "index" has been increased to 4 by this point, by the insert() function.

Chunk 1 returns and is removed from the list, chunk 2 goes into slot 1, chunk 3 goes into slot 2, index gets "set to 2" (as opposed to "reduced by 1").

Chunk 2 starts execution and inserts chunk 4 into slot 2, bumping chunk 3 back into slot 3 again...

The result isn't the first-in-first-out system I would expect.
I know that it's a little bit confusing, but you understood it right, the newly added chunks are supposed to push all other entries (except the 1st since it is already running) "further down" because the most recent is always the next that needs to be called.

View PostBomb Bloke, on 20 August 2018 - 02:14 PM, said:

You're localising "insert" on line 99. If you assigned a function pointer to it in the global scope before doing that you'd technically have two different variables, but since you're doing it after, your function declaration is just overwriting the table pointer stored within the local variable.

Best to use separate names there.
Thank you soooo much, I wouldn't have found that in a thousend years, I was so focused on the "relevant" snippets... However I have no idea where that table declaration even comes from, I never used a table called "insert" :blink:





1 user(s) are reading this topic

0 members, 1 guests, 0 anonymous users