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

Adding Functions To A Running Programs Environment

Started by Imred Gemu, 27 August 2013 - 05:00 PM
Imred Gemu #1
Posted 27 August 2013 - 07:00 PM
Sorry that the title doesn't explain this very well, I'll explain my issue here with more detail of course. So I am working on an api, and for simplicities sake I'll label the files now as A B C, A and C are both generated by whoever is using my api and B is my api so let's say I have this
A:

os.loadAPI("B")
B.somefunctionintmyapi()
B.someotherfunctionintmyapi()
B.hello()
B:

function somefunctionintmyapi()
--stuff
end
function someotherfunctionintmyapi()
--stuff
end
C:

function hello()
  print("Hello world")
end
Now in A it calls a function in C but does so from the metatable that represents B, what I wanna know is can I do something in B when it is first loaded that loads the functions in C and adds it to B's metatable so calling B.hello() from A will actually work. Also to consider since I plan to put this up for download, when the person using it downloads it they can name it whatever they want so the program won't know what it's metatable is called, it also won't know the name of the functions in C nor how many functions are in C since C is user generated.
Yevano #2
Posted 27 August 2013 - 07:53 PM
Since the API has its own function environment, I don't think you can get the environment of the code which loads it unless it passes its environment explicitly. It would be helpful to know why you want to do this so that maybe we can give alternative suggestions.
Imred Gemu #3
Posted 27 August 2013 - 08:14 PM
Since the API has its own function environment, I don't think you can get the environment of the code which loads it unless it passes its environment explicitly. It would be helpful to know why you want to do this so that maybe we can give alternative suggestions.
Please forgive me if I am misinterpreting what your saying. From what you said I understood "Since B has its own function environment, I don't think you can get the environment of A unless it passes its environment explicitly." I don't need B to access A's environment, I basically want to merge B and C's environment under B's name from within B so that A can reference functions within B and C under B's name without A having to do anything except load B.
Yevano #4
Posted 27 August 2013 - 08:35 PM
That's possible. Just call dofile from B on C. This will automatically load all the globals in C into B.


-- At the top of B
dofile("C")
H4X0RZ #5
Posted 27 August 2013 - 08:42 PM
To inject functions into an env of an api:

local function inject(api,func)
  if not _G[api] return "No API with this name" end
  if type(func) ~= "function" then return "not a function" end
  table.insert(_G[api], func)
end
Imred Gemu #6
Posted 27 August 2013 - 08:49 PM
That's possible. Just call dofile from B on C. This will automatically load all the globals in C into B.


-- At the top of B
dofile("C")
Alright to test what you suggested I did this:
test1:

os.loadAPI("compactor/test2")
test2.print2()
test2:

print(pcall(dofile, "compactor/test3"))
test3:

function print2()
  print("Hello world!")
end
This code outputs:
true
test1:2: attempt to call nil.
Do you know why?
Imred Gemu #7
Posted 27 August 2013 - 08:57 PM
To inject functions into an env of an api:

local function inject(api,func)
  if not _G[api] return "No API with this name" end
  if type(func) ~= "function" then return "not a function" end
  table.insert(_G[api], func)
end
Could you please explain how I would use that, and also will that work from withing my API or would it require the person using my API to have that code. If that latter is the case I don't want the person using my API to have to do anything special to use my api besides loading it and calling the methods that they need.
Lyqyd #8
Posted 27 August 2013 - 09:01 PM
The more important question here, is what are you actually trying to do? What's the full use case?
Imred Gemu #9
Posted 27 August 2013 - 09:38 PM
The more important question here, is what are you actually trying to do? What's the full use case?
Alright to clarify what this is being used for if anyone is able to provide a working answer, I'm building a program that would allow me to package a folder containing multiple code and resource files into a single executable file which may either be run like any other program or loaded as an API and have functions within itself called just like any other API would, the file is built on the concept that when a lua program is loaded in CC the entire program is loaded and then the file is not accessed again by the lua interpreter, on this principle I designed this program in two pieces, the main packaging program that packs all the contents of a folder into one and also replaces a few things, to be specific it replaces any reference to one of the files packaged in the folder with a call to a function that writes that folder down in a temp folder then returns the name of the files in the temp folder, then on the next line adds another call to a function to delete it again. In testing so far the only time this concept causes a failure is if the program that is packaged attempts to write to any of the files within itself so the packaging program simple stops running if it sees a call to fs.open or io.open in write mode and prints out an error message, of course this can be bypassed if the call for opening the file is referenced in another API outside of the package, for that there is nothing I can really do that I can see. The second part is what this post is about, the code that is installed in the package to save the files as they are called, since when loading this function as an API the users will be loading the package and therefore this code, the API they load will only contain the functions of this code, but not the code of the "main file" (which is specified while packaging) which would contain the API that the user would want to load and use. So with the information I hope to gain from this post I will set up the code so that it loads the functions of the "main file" and adds them to it's own environment, B would be the package run code, C is the code in the main file, and A is the code that is loading the package B to get to the code stored in C. Not only must the code in C be able to load the other files in the package, but it also must be able to read from them but not write to them.
Yevano #10
Posted 27 August 2013 - 10:13 PM
That's possible. Just call dofile from B on C. This will automatically load all the globals in C into B.


-- At the top of B
dofile("C")
Alright to test what you suggested I did this:
test1:

os.loadAPI("compactor/test2")
test2.print2()
test2:

print(pcall(dofile, "compactor/test3"))
test3:

function print2()
  print("Hello world!")
end
This code outputs:
true
test1:2: attempt to call nil.
Do you know why?

I'm stumped. Can't think of why that didn't work. You could instead use loadfile, set the resulting function's environment to a new table, and copy that table's entries into _G after the loaded function is run. I would give an example but I'm on my phone right now.
immibis #11
Posted 28 August 2013 - 04:52 AM
Is it acceptable to put the code in the file that calls loadAPI?

os.loadAPI("A")
os.loadAPI("B")

for k,v in pairs(B)/> do
  A[k] = v
end
H4X0RZ #12
Posted 28 August 2013 - 08:27 AM
My Idea (moving one File into the second one):

--Enter this into your first API which should have the functions

local fileName = "someName" --The name of the API
local loadFileName = "someOtherName" --The Name of the file which contains the functions

local env = {} --the environment for the "someOtherName" file
local function load() doFile(loadFileName) end --The function which loads the file
setfenv(load, env) --setting the function environment
load() --loading all global vars/functions/etc. into the environment
for _,var in pairs(env) do --moving every entry of "someOtherName" into "someName"
  table.insert(_G[fileName], var)
end
LBPHacker #13
Posted 28 August 2013 - 12:00 PM
To inject functions into an env of an api:

local function inject(api,func)
  if not _G[api] return "No API with this name" end
  if type(func) ~= "function" then return "not a function" end
  table.insert(_G[api], func)
end
My Idea (moving one File into the second one):

--Enter this into your first API which should have the functions

local fileName = "someName" --The name of the API
local loadFileName = "someOtherName" --The Name of the file which contains the functions

local env = {} --the environment for the "someOtherName" file
local function load() doFile(loadFileName) end --The function which loads the file
setfenv(load, env) --setting the function environment
load() --loading all global vars/functions/etc. into the environment
for _,var in pairs(env) do --moving every entry of "someOtherName" into "someName"
  table.insert(_G[fileName], var)
end
Oh God please forget table.insert. It creates numeric indices. The first piece of code has no chance of working because of that, but the second could be fixed like this:
for key,var in pairs(env) do --moving every entry of "someOtherName" into "someName"
  _G[fileName][key] = var
end

Back on topic; dofile DOES work fine (yes, I tested it), you did something wrong. Actually, the pcalling messed it up, since dofile was executed inside the pcall function, not in B. Not pcalling it will fix the whole problem. +1ing Yevano, he solved your problem almost immediately.
Imred Gemu #14
Posted 28 August 2013 - 03:36 PM
To inject functions into an env of an api:

local function inject(api,func)
  if not _G[api] return "No API with this name" end
  if type(func) ~= "function" then return "not a function" end
  table.insert(_G[api], func)
end
My Idea (moving one File into the second one):

--Enter this into your first API which should have the functions

local fileName = "someName" --The name of the API
local loadFileName = "someOtherName" --The Name of the file which contains the functions

local env = {} --the environment for the "someOtherName" file
local function load() doFile(loadFileName) end --The function which loads the file
setfenv(load, env) --setting the function environment
load() --loading all global vars/functions/etc. into the environment
for _,var in pairs(env) do --moving every entry of "someOtherName" into "someName"
  table.insert(_G[fileName], var)
end
Oh God please forget table.insert. It creates numeric indices. The first piece of code has no chance of working because of that, but the second could be fixed like this:
for key,var in pairs(env) do --moving every entry of "someOtherName" into "someName"
  _G[fileName][key] = var
end

Back on topic; dofile DOES work fine (yes, I tested it), you did something wrong. Actually, the pcalling messed it up, since dofile was executed inside the pcall function, not in B. Not pcalling it will fix the whole problem. +1ing Yevano, he solved your problem almost immediately.
Thanks so much, dofile works without the pcall, I feel kinda stupid for not considering that the function that is supposed to handle errors was actually causing the error XD, also thank you Yevano for coming up with a solution to my issue so fast, if only I hadn't loused it up lol. But anyway I have one more question, the reason I called dofile from within pcall is because dofile doesn't handle errors in the code that it's loading like how loadfile and loadstring do, since the file being loaded is user generated, I can't be sure that it will run properly when dofile is called, I can test if it will compile in the packaging program but if it errors at runtime then the whole program will fail, the problem with this is if the program crashes during execution like this the temp folder will be left over for the user to have to clean up, to me that seems a bit sloppy that I would leave that possibility in the program so is there any way I can avoid this and still have dofile run properly?
Lyqyd #15
Posted 28 August 2013 - 03:42 PM
Create your own dofile that uses loadstring, then assigns the created function the API's environment table, then executes it in a pcall.
Imred Gemu #16
Posted 29 August 2013 - 07:49 PM
Alright I've mostly got the program working, thanks to everyone for all the help, still more work to do on it and I have another question to ask for this project but it's not related to this one so I'm going to start it in another post. Your suggestion worked beautifully Lyqyd, thanks! And again thank you everyone for all the help.
Imred Gemu #17
Posted 29 August 2013 - 08:08 PM
So I'm writing a program that allows users to package lots of files into one single file and then load or read them at runtime. It works as both an executable and a loadable api, I'm almost done with the program but I have one small issue left holding it back. This issue is not a serious problem, the issue would only come up if the person using my code didn't know what they are doing. Let's say that file A is the compacted file containing the other files and containing the code that extracts them into the temp folder to be loaded/run and B and C are two file packed into A. B is the first file that A is supposed to run so it puts B in temp and loads it, B opens C through fs or io and reads from/ writes to it (writing to it doesn't do much though since the file is deleted shortly after), however B never closes the file stream created by fs/io so when A goes to clean it up it errors out since a file in the temp directory is still open. I don't really like this since it leaves the file there for the user to have to clean up and just feels sloppy to me on my part. What I want to know is how can I close all the streams open in a program without rebooting the computer?
Lyqyd #18
Posted 29 August 2013 - 08:28 PM
Threads merged. Stick to one topic for all questions about a piece of software.
KaoS #19
Posted 30 August 2013 - 12:30 AM
The only way I can see is modifying the fs and/or io APIs

local tOpenFiles={}
local open=fs.open
fs.open=function(sDir,...)
  local result=open(sDir,...)
  if result then
	tOpenFiles[shell.resolve(sDir)]=result
	local close=result.close
	result.close=function(...)
	  tOpenFiles[shell.resolve(sDir)]=nil
	  return close(...)
	end
  end
  return result
end

Then the tOpenFiles table will be a list of all open files with their file handles so you can close them there. You can even make fs.open return the existing file handle if it is denied access or fs.delete auto-close it


local delete=fs.delete
fs.delete=function(sDir,...)
  if tOpenFiles[shell.resolve(sDir)] then
	tOpenFiles[shell.resolve(sDir)].close()
  end
  return delete(sDir,...)
end

but beware of using a pairs loop to remove entries as you can cause errors that way (the pairs loop requires the last entry given to still be defined in order to find the next entry)
Imred Gemu #20
Posted 30 August 2013 - 05:29 PM
The only way I can see is modifying the fs and/or io APIs

local tOpenFiles={}
local open=fs.open
fs.open=function(sDir,...)
  local result=open(sDir,...)
  if result then
	tOpenFiles[shell.resolve(sDir)]=result
	local close=result.close
	result.close=function(...)
	  tOpenFiles[shell.resolve(sDir)]=nil
	  return close(...)
	end
  end
  return result
end

Then the tOpenFiles table will be a list of all open files with their file handles so you can close them there. You can even make fs.open return the existing file handle if it is denied access or fs.delete auto-close it


local delete=fs.delete
fs.delete=function(sDir,...)
  if tOpenFiles[shell.resolve(sDir)] then
	tOpenFiles[shell.resolve(sDir)].close()
  end
  return delete(sDir,...)
end

but beware of using a pairs loop to remove entries as you can cause errors that way (the pairs loop requires the last entry given to still be defined in order to find the next entry)
Before I was trying to avoid overwriting globals but now that I think about it overwriting this wouldn't make much of a difference to other programs since it would still work the same to programs who didn't know it had been changed. I'll give it a try, and one last thing, ipairs avoids that issue doesn't it?
GopherAtl #21
Posted 30 August 2013 - 06:05 PM
not really. ipairs (or just a "for i=1,#list do" loop, which is the same thing with less overhead) will skip the next item if you removed the current one, ex, remove 1, everything is shifted down, so what was index 2 is now 1. Next pass through the loop will be i==2, so the original #2, now #1, will be skipped unless you handle this specifically.
KaoS #22
Posted 31 August 2013 - 12:18 AM
in addition to what Gopher said the system that I showed you is indexed by the file path… ipairs only works for numbers but I used strings
Edited on 31 August 2013 - 12:32 AM
Imred Gemu #23
Posted 31 August 2013 - 01:39 AM
in addition to what Gother said the system that I showed you is indexed by the file path… ipairs only works for numbers but I used strings
Oh sorry, I didn't know that, I've got your suggestion implemented and it works great, thanks for the help.
KaoS #24
Posted 31 August 2013 - 02:32 AM
No problem :)/> I just realized that I spelled Gopher wrong, my apologies there