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

recursive loop causing vm error

Started by Termanater13, 12 April 2014 - 09:45 PM
Termanater13 #1
Posted 12 April 2014 - 11:45 PM
im writing a a function similar to textuils.serialize that handels recursive entries. The issue I have is when the entry causes a loop in the table, my code does not find it to keep that from happening. without the loop at the end the code works as intended, but when a loop is introduced it breaks.

The full error code
test:26: vm error:
java.lang.ArrayIndexOutOFBoundsException: 256

the full code for test
Spoiler
function meta(func)
  local metaTable = {}
  local metaComm = {
	isIn = function (self, compare)
	  for k, v in pairs(self) do
		if v == comapre then
		  return true
		end
	  end
	  return false
	end,
  }
  local new = {}
  for k,v in pairs(func) do
	new[v] = metaComm[v]
  end
  setmetatable(metaTable, {__index = new})
  return metaTable
end

local function betser(t, tTracker, tabs)
  -- get type and store it
  iType = type(t)
  -- code to check if it is a recursion loop
  local iTracker = tTracker
  if iTracker:isIn(t) and iType == "table" then	 -- line 26
	error("Cannot handle Recrsive loop")
  elseif iType == "table" then
	iTracker[#iTracker+1] = t
  end
  -- Ready tabs
  local tab = ""
  local tabM1 = ""
  for i = 0, tabs, 1 do
	tab = tab .. "\t\t"
	if i ~= 0 then
	  tabM1 = tabM1 .. "\t\t"
	end
  end
  -- begin serialization
  if iType == "table" then
	result = "{\n"
	for k,v in pairs(t) do
	  result = result..tab.."[" .. betser(k, iTracker, (tabs+1)) .. "] = " .. betser(v, iTracker, (tabs+1)) .. ",\n"
	end
	result = result .. tabM1 .. "}"
	return result
  elseif iType == "string" then
	return string.format("%q", t)
  elseif iType == "number" or iType == "boolean" or iType == "nil" then
	return tostring(t)
  elseif iType == "function" then
	return "**function**"
  else
	error("cannot serialize type " .. iType)
  end
end

function serialize(t)
  local nTracker = meta({"isIn"})
  return betser(t, nTracker, 0)
end
-- test table for testing
test = {}
test.test = {}
test.test.beta = "test"
test.beta = "test"
test.aplha = test
--Print what is returned
print(serialize(test))
Bomb Bloke #2
Posted 13 April 2014 - 12:11 AM
You may want to define an extra table of your own. Whenever you detect a table inside the one you're serialising, shove it in that new table (if it's not already in there), then remove it when you've finished serialising that child entry.

If you encounter a given child table that already exists in your tracking table, then you'll know you've encountered an infinite loop in the serialised table structure and will need to throw up a custom error message stating that it's impossible to continue.
Termanater13 #3
Posted 13 April 2014 - 12:30 AM
You may want to define an extra table of your own. Whenever you detect a table inside the one you're serialising, shove it in that new table (if it's not already in there), then remove it when you've finished serialising that child entry.

If you encounter a given child table that already exists in your tracking table, then you'll know you've encountered an infinite loop in the serialised table structure and will need to throw up a custom error message stating that it's impossible to continue.
I thought I already implemented that with this.

  -- code to check if it is a recursion loop
  local iTracker = tTracker
  if iTracker:isIn(t) and iType == "table" then  -- line 26
	    error("Cannot handle Recrsive loop")
  elseif iType == "table" then
	    iTracker[#iTracker+1] = t
  end
Bomb Bloke #4
Posted 13 April 2014 - 12:52 AM
That's what I get for skimming - I make a fool out of myself. Apologies.

Anyway:

        isIn = function (self, compare)
          for k, v in pairs(self) do
                if v == comapre then

I won't pretend I have a full understanding of how meta tables should be set up, but I can tell you that function will never find a match.

I'm also not sure why you define an "iTracker" as a copy of "tTracker". Bear in mind that doing so doesn't clone the table itself, but rather gives you a pointer leading to the one copy of the table in memory. Each instance of the function that's running is already handed such a pointer and shouldn't need another…?

You don't seem to have a system for removing child tables from your tracker when they've been serialised. This may lead to false positives. For eg:

local table1 = {1,2}
local table2 = {table1, table1}

Your current system will declare this recursive.

Edit: And that's what I get for skimming my own thought process. It strikes me that you were trying to use the table-copying thing to deal with the false-positive thing, but unfortunately, that's not going to work as-is. Unless there's a meta-table quirk I'm unaware of…
Edited on 12 April 2014 - 10:59 PM
Termanater13 #5
Posted 13 April 2014 - 05:26 AM
		isIn = function (self, compare)
		  for k, v in pairs(self) do
				if v == comapre then
this function allows the table to see if a value is stored in it if it is it returns true, if not returns false. using setmetatable for it really is not needed, but its easier for me to look at the code this way.
I'm also not sure why you define an "iTracker" as a copy of "tTracker". Bear in mind that doing so doesn't clone the table itself, but rather gives you a pointer leading to the one copy of the table in memory. Each instance of the function that's running is already handed such a pointer and shouldn't need another…?
this is my mine problem if I can get it to not point to the table, but copy it. it will fix alot of things. I was trying to use local variables to remove the need to add code to remove no longer needed entrys. the trackers just held information to see if the table already existed in the chain
local table1 = {1,2}
local table2 = {table1, table1}
Your current system will declare this recursive.
not it would not, it would display it no problem. When a table shows up twice, it has no problem, unless this causes a loop.

test = {}
test.test = {}
test.test.beta = "test"
test.beta = "test"
test.delta = test.test
test.alpha = test
test.delta in this example is fine. test.alpha calls the table test.alpha is stored and a loop is crated, this should not happen because the code earlier should stop it. this has every instance of alpha in this table will store the whole table again, which has alpha in the value which again stores the whole table.
Bomb Bloke #6
Posted 13 April 2014 - 05:37 AM
this function allows the table to see if a value is stored in it if it is it returns true, if not returns false. using setmetatable for it really is not needed, but its easier for me to look at the code this way.

That's what you intend it to do. Let's just say that there's a reason I quoted those lines in particular, and that you should read them again. ;)/>

not it would not, it would display it no problem. When a table shows up twice, it has no problem, unless this causes a loop.

Best I can make out, the table pointer issue would need to be resolved for that to be the case.
Termanater13 #7
Posted 13 April 2014 - 08:14 AM
That's what you intend it to do. Let's just say that there's a reason I quoted those lines in particular, and that you should read them again. ;)/>
I call that function on line 26 as iTracker:isIn(t), and is equal to iTracker.isIn(iTracker, t). the second one I think is the function call you are thinking of.

Best I can make out, the table pointer issue would need to be resolved for that to be the case.
it works, just not when a loop is created, I tested it in another lua interpreter(SigmaScript for android) since it has nothing involving computercraft in it. The script will not stop if a loop is given. basically if I give it a loop free table it works as intended, but I need to figure out why my preventive measures against loops is not working.
Bomb Bloke #8
Posted 13 April 2014 - 08:20 AM
I call that function on line 26 as iTracker:isIn(t), and is equal to iTracker.isIn(iTracker, t). the second one I think is the function call you are thinking of.

You misspelled "compare" as "comapre". As a result, the function will always return false.

Sorry not to state it simply sooner. I sometimes like to think I have a sense of humor.

it works, just not when a loop is created, I tested it in another lua interpreter(SigmaScript for android) since it has nothing involving computercraft in it. The script will not stop if a loop is given. basically if I give it a loop free table it works as intended, but I need to figure out why my preventive measures against loops is not working.

The bit that stops the script is busted due to the above typo. You may need to test this again once you've fixed that.
Termanater13 #9
Posted 14 April 2014 - 05:35 AM
I call that function on line 26 as iTracker:isIn(t), and is equal to iTracker.isIn(iTracker, t). the second one I think is the function call you are thinking of.

You misspelled "compare" as "comapre". As a result, the function will always return false.

Sorry not to state it simply sooner. I sometimes like to think I have a sense of humor.

it works, just not when a loop is created, I tested it in another lua interpreter(SigmaScript for android) since it has nothing involving computercraft in it. The script will not stop if a loop is given. basically if I give it a loop free table it works as intended, but I need to figure out why my preventive measures against loops is not working.

The bit that stops the script is busted due to the above typo. You may need to test this again once you've fixed that.
OK, that fixed the error from happening, those typos always get me. I still need to figure out the coping of the variable contents and not the address and that will fix that, this way I can isolate different branches of a table. I think the following code should work.

local function varCopy(var)
  local newVar = {}
  for k,v in pairs(var) do
	newVar[k] = v
  end
  return newVar
end
Edited on 14 April 2014 - 03:35 AM
Bomb Bloke #10
Posted 14 April 2014 - 09:12 AM
Normally, yeah, but I'm honestly not sure how well that'll handle your metatable. It might miss its function. I dunno how that works.

But personally, I wouldn't bother trying to make another copy of the table - there's really no need. Maybe just do something like this:

  -- begin serialization
  if iType == "table" then
        -- These two lines from higher up can be dragged down into this "if" block:
        if tTracker:isIn(t) then error("Cannot handle Recrsive loop") end
        tTracker[#tTracker+1] = t

        -- Serialise:
        result = "{\n"
        for k,v in pairs(t) do
          result = result..tab.."[" .. betser(k, tTracker, (tabs+1)) .. "] = " .. betser(v, tTracker, (tabs+1)) .. ",\n"
        end

        -- Now we've serialised the sub-table, remove it from tTracker and return:
        tTracker[#tTracker] = nil
        result = result .. tabM1 .. "}"
        return result
Termanater13 #11
Posted 14 April 2014 - 04:23 PM
Normally, yeah, but I'm honestly not sure how well that'll handle your metatable. It might miss its function. I dunno how that works.

But personally, I wouldn't bother trying to make another copy of the table - there's really no need. Maybe just do something like this:

I'm copying the table into a new local table so when it comes out of the deeper tables it automatically removes unwanted information. In the example you just gave it will error if there is a recursive table, because you do not remove data no longer needed, My way makes local variables automatically remove the unwanted information. I was able to fix that easily with out help.

function varCopy(var)
  local newVar = meta({"isIn"})
  for k,v in pairs(var) do
    newVar[k] = v
  end
  return newVar
end

let's say we have test.table1.a.b and test.table2.a.b the tracer variable that is there would be different going down both paths, if test.table2.a was the same table as test.table1.a the textuils.serialize will error, and as long as test.table1.a.b doe sot loop back to a previous table to make a loop I want it written out.

the table copying function I made earlier did need some tweaking to get it to work right. you can save a function to a value of a table and it would of worked fine, but if you set it with setmetatable the for loop I guess does not see it since as it is above it did not copy it.

To be honest this all started with openperipherals getAdvancedMethodsData function where he uses a recursive table to display Identical information. I had a different thread here on figuring out what was wrong there when trying to serialize the table it returned. not happy with the functions out there that did the "same" as what the one I made does I made this one and had no Idea what was wrong. You did point out another issue I did not see that would of basically done the same thing you just suggested. so in the long run I can thank Mikeemoo for putting me in a position to learn about the problems with recursive entries in tables can cause.

I am also calling this solve because the functions work as intended. I just need to edit them before I release them so it will work properly as an API. I could have it overwrite texuils.serialize so it would improve compatibility, but I know there will be more issues if I do not handle that right.
CometWolf #12
Posted 14 April 2014 - 05:46 PM
the table copying function I made earlier did need some tweaking to get it to work right. you can save a function to a value of a table and it would of worked fine, but if you set it with setmetatable the for loop I guess does not see it since as it is above it did not copy it.
This is because the metatable is a different table altogether, it is not stored as a key in the actual table. Use getmetatable(table) if you want it's memory adress.
Bomb Bloke #13
Posted 15 April 2014 - 03:51 AM
In the example you just gave it will error if there is a recursive table, because you do not remove data no longer needed,

You know, I could've sworn I even marked the relevant line with a comment, but… well, never mind, so long as you're happy. ;)/>

Here's another conundrum for you, which you may or may not be interested in resolving. Say you build the table structure I listed out earlier:

local table1 = {1,2}
local table2 = {table1, table1}

Let's say you serialise table2. How would you go about rigging things so that it can be unserialised back into the same structure - that is to say, a table containing two identical pointers to another single table?