Jump to content




[Lua] Need to test for nil


  • You cannot reply to this topic
20 replies to this topic

#1 darkroom

  • Members
  • 70 posts

Posted 05 April 2013 - 08:54 AM

Ok so my error is on line 142 and is "attempt to index ? (a function value)" Basically I need to test if a function exists in a container or not but I don't know why it doesn't work.
Here is my code
termW, termH = term.getSize()
running = true
function newObject(type)
		local object = {type = type or "object"}
		function object:description()
				return "I am a "..object.type.." object."
		end
		return object
end
function newRect(x, y, w, h, backgroundColor, supertype)
		local rect = {
				x = x or 1,
				y = y or 1,
				w = w or termW,
				h = h or termH,
				backgroundColor = backgroundColor
		}
		super = newObject(supertype or "rect")
		setmetatable(rect,{__index = super})
		function rect:collidesWithPoint(x,y)
				return x >= self.x and x <= self.w and y >= self.y and y <= self.h
		end
		function rect:collidesWithRect(rect)
				local a = {
						left = self.x,
						right = self.x + self.w,
						top = self.y,
						bottom = self.y + self.h
				}
				local b = {
						left = rect.x,
						right = rect.x + rect.w,
						top = rect.y,
						bottom = rect.y + rect.h
				}
				local lMost, rMost, tMost, bMost
				if a.left < b.left then
						lMost = a
						rMost = b
				else
						lMost = b
						rMost = a
				end
				if a.top < b.top then
						tMost = a
						bMost = b
				else
						tMost = b
						bMost = a
				end
				local x = rMost.left - lMost.right
				local y = bMost.top - tMost.bottom
				return x <= 0 and y <= 0
		end
		function rect:draw()
				if self.backgroundColor then
						term.setBackgroundColor(self.backgroundColor)
						for x=self.x, self.w do
								for y=self.y, self.h do
										term.setCursorPos(x,y)
										term.write(" ")
								end
						end
				else
						error("This rect is non-drawable")
				end
		end
		return rect
end
function newText(x, y, string, textColor, supertype)
		local text = {
				x = x or 1,
				y = y or 1,
				string = string or "",
				textColor = textColor or colors.white
		}
		super = newObject(supertype or "text")
		setmetatable(text,{__index = super})
		function text:draw()
				term.setCursorPos(self.x,self.y)
				term.setTextColor(self.textColor)
				term.write(self.string)
		end
		return text
end
function newTextButton(x, y, text, backgroundColor, textColor, action, supertype)
		local textButton = {
				text = newText(x+1,y+1,text,textColor),
				rect = newRect(x,y,x+#text+2,y+2,backgroundColor),
				action = action or function() end
		}
		super = newObject(supertype or "textButton")
		setmetatable(textButton,{__index = super})
		function textButton:draw()
				self.rect:draw()
				self.text:draw()
		end
		function textButton:handleEvents(e)
				if e[1] == "mouse_click" then
						if self:collidesWithPoint(e[2],e[3],e[4]) then
								self:action()
						end
				end
		end
end
function newContainer()
		local container = { }
		super = newObject("container")
		setmetatable(container,{__index = super})
		function container:addObject(object)
				table.insert(self,object)
		end
		function container:removeObject(object)
				table.remove(self,object)
		end
		function container:drawObjects()
				for _,v in pairs(self) do
						if v.draw then
								v:draw()
						end
				end
		end
		function container:handleObjectEvents(e)
				for _,v in pairs(self) do
						if v.handleEvents then
								v:handleEvents(e)
						end
				end
		end
		return container
end
function toggleBackgroundColor(self)
		if self.backgroundColor == colors.red then
				self.backgroundColor = colors.lime
		else
				self.backgroundColor = colors.red
		end
end
function quit()
		running = false
end
term.clear()
term.setCursorPos(1,1)
local container = newContainer()
container:addObject(newTextButton(10,10,"Quit",colors.red,colors.white,quit))
container:addObject(newTextButton(3,3,"Toggle",colors.red,colors.white,toggleBackgroundColor))
while running do
		container:drawObjects()	  
		container:handleObjectEvents({os.pullEvent()})
end
term.setBackgroundColor(colors.black)
term.setTextColor(colors.white)
term.clear()
term.setCursorPos(1,1)
142 is the 3rd line of this function
function container:handleObjectEvents(e)
						    for _,v in pairs(self) do
										    if v.handleEvents then
														    v:handleEvents(e)
										    end
						    end
		    end


#2 LBPHacker

  • Members
  • 766 posts
  • LocationBudapest, Hungary

Posted 05 April 2013 - 08:59 AM

Gonna be ninja'd anyways...

You can check if a function is really a function using this:
if type(yourFunction) == "function" then

EDIT: Misunderstood, sorry...

#3 Telokis

  • Members
  • 88 posts
  • LocationToulouse, France

Posted 05 April 2013 - 09:00 AM

View PostLBPHacker, on 05 April 2013 - 08:59 AM, said:

Gonna be ninja'd anyways...

Not today... :s

#4 darkroom

  • Members
  • 70 posts

Posted 05 April 2013 - 09:01 AM

I need to test if the function exists :)

#5 LBPHacker

  • Members
  • 766 posts
  • LocationBudapest, Hungary

Posted 05 April 2013 - 09:03 AM

View Postdarkroom, on 05 April 2013 - 09:01 AM, said:

I need to test if the function exists :)

Heh... Then I was right :D (cuz if a functions doesn't exist - if it's nil, type(func) == "function" will evaluate false)

#6 darkroom

  • Members
  • 70 posts

Posted 05 April 2013 - 09:04 AM

I will try that but the error is that i am indexing nil. That is the problem

#7 MysticT

    Lua Wizard

  • Members
  • 1,597 posts

Posted 05 April 2013 - 09:05 AM

The problem is that the "container" table has the functions stored inside it. So, when you iterate the table with pairs, it gets those functions and then try to index them (in v.draw).
A solution would be to add the values to another table inside the "container" table, like this:
function newContainer()
  local container = {}
  super = newObject("container")
  setmetatable(container, { __index = super })
  container.objects = {}
  function container:addObject(object)
	table.insert(self.objects, object)
  end
  function container:removeObject(object)
	table.remove(self.object, object) -- NOTE: this won't look fot the object and remove it, you'll have to make a loop to iterate trhough the table and remove it yourself
  end
  function container:drawObjects()
	for _,v in pairs(self.objects) do
	  if v.draw then
		v.draw()
	  end
	end
  end
  function container:handleObjectEvents(e)
	for _,v in pairs(self.objects) do
	  if v.handleEvents then
		v:handleEvents(e)
	  end
	end
  end
  return container
end


#8 darkroom

  • Members
  • 70 posts

Posted 05 April 2013 - 09:07 AM

Thanks man first time working with containers in lua so its a process :P

#9 Kingdaro

    The Doctor

  • Members
  • 1,636 posts
  • Location'MURICA

Posted 05 April 2013 - 09:08 AM

Or just use a numeric loop.

  function container:drawObjects()
    for i=1, #self do
      if self[i].draw then
        self[i]:draw()
      end
    end
  end
  function container:handleObjectEvents(e)
    for i=1, #self do
      if self[i].handleEvents then
        self[i]:handleEvents(e)
      end
    end
  end

It's way faster than using pairs anyway. I also recommend setting functions like "draw" and "handleEvents" as default in the objects for your container, so you don't have to check if the methods exist before calling them.

#10 MysticT

    Lua Wizard

  • Members
  • 1,597 posts

Posted 05 April 2013 - 09:13 AM

No problem.
One more thing. You could make tables that contains the functions you want, and then use metatables to "add" them to your "objects", so you don't create new functions for every new object.
Example:
local myClass = {}

function myClass:doSomething(arg1, arg2)
  print("Hello world!")
  self.someVariable = arg1 + arg2
end

function newObject()
  local obj = {}
  obj.someVariable = 0
  setmetatable(obj, { __index = myClass })
  return obj
end

local testObject = newObject()
testObject:doSomething()

View PostKingdaro, on 05 April 2013 - 09:08 AM, said:

Or just use a numeric loop.

  function container:drawObjects()
	for i=1, #self do
	  if self[i].draw then
		self[i]:draw()
	  end
	end
  end
  function container:handleObjectEvents(e)
	for i=1, #self do
	  if self[i].handleEvents then
		self[i]:handleEvents(e)
	  end
	end
  end

It's way faster than using pairs anyway. I also recommend setting functions like "draw" and "handleEvents" as default in the objects for your container, so you don't have to check if the methods exist before calling them.
Using another table would be better (imho), since you know that the "objects" you are looking for will be there and nothing else.
It's true that using a numerical iterator is faster than pairs, so it probably would be better to use it instead (but also using another table).
And it's always good to check for values before using them, it prevents weird unexpected errors :P

#11 darkroom

  • Members
  • 70 posts

Posted 05 April 2013 - 09:23 AM

It still isn't working for some reason I am not adding the objects the the container here is the new code:
termW, termH = term.getSize()
running = true
function newObject(type)
	    local object = {type = type or "object"}
	    function object:description()
			    return "I am a "..object.type.." object."
	    end
	    return object
end
function newRect(x, y, w, h, backgroundColor, supertype)
	    local rect = {
			    x = x or 1,
			    y = y or 1,
			    w = w or termW,
			    h = h or termH,
			    backgroundColor = backgroundColor
	    }
	    super = newObject(supertype or "rect")
	    setmetatable(rect,{__index = super})
	    function rect:collidesWithPoint(x,y)
			    return x >= self.x and x <= self.w and y >= self.y and y <= self.h
	    end
	    function rect:collidesWithRect(rect)
			    local a = {
					    left = self.x,
					    right = self.x + self.w,
					    top = self.y,
					    bottom = self.y + self.h
			    }
			    local b = {
					    left = rect.x,
					    right = rect.x + rect.w,
					    top = rect.y,
					    bottom = rect.y + rect.h
			    }
			    local lMost, rMost, tMost, bMost
			    if a.left < b.left then
					    lMost = a
					    rMost = b
			    else
					    lMost = b
					    rMost = a
			    end
			    if a.top < b.top then
					    tMost = a
					    bMost = b
			    else
					    tMost = b
					    bMost = a
			    end
			    local x = rMost.left - lMost.right
			    local y = bMost.top - tMost.bottom
			    return x <= 0 and y <= 0
	    end
	    function rect:draw()
			    if self.backgroundColor then
					    term.setBackgroundColor(self.backgroundColor)
					    for x=self.x, self.w do
							    for y=self.y, self.h do
									    term.setCursorPos(x,y)
									    term.write(" ")
							    end
					    end
			    else
					    error("This rect is non-drawable")
			    end
	    end
	    return rect
end
function newText(x, y, string, textColor, supertype)
	    local text = {
			    x = x or 1,
			    y = y or 1,
			    string = string or "",
			    textColor = textColor or colors.white
	    }
	    super = newObject(supertype or "text")
	    setmetatable(text,{__index = super})
	    function text:draw()
			    term.setCursorPos(self.x,self.y)
			    term.setTextColor(self.textColor)
			    term.write(self.string)
	    end
	    return text
end
function newTextButton(x, y, text, backgroundColor, textColor, action, supertype)
	    local textButton = {
			    text = newText(x+1,y+1,text,textColor),
			    rect = newRect(x,y,x+#text+2,y+2,backgroundColor),
			    action = action or function() end
	    }
	    super = newObject(supertype or "textButton")
	    setmetatable(textButton,{__index = super})
	    function textButton:draw()
			    self.rect:draw()
			    self.text:draw()
	    end
	    function textButton:handleEvents(e)
			    if e[1] == "mouse_click" then
					    if self:collidesWithPoint(e[2],e[3],e[4]) then
							    self:action()
					    end
			    end
	    end
end
function newContainer()
	    local container = {objects = { }}
	    super = newObject("container")
	    setmetatable(container,{__index = super})
	    function container:addObject(object)
			    table.insert(self.objects,object)
	    end
	    function container:removeObject(object)
			    for k,v in pairs(self.objects) do
					    if v == object then
							    table.remove(self.objects,k)
					    end
			    end
	    end
	    function container:drawObjects()
			    print(#self.objects)
			    for _,v in pairs(self.objects) do
					    if v.draw then
							    v:draw()
					    end
			    end
	    end
	    function container:handleObjectEvents(e)
			    for _,v in pairs(self.objects) do
					    if v.handleEvents then
							    v:handleEvents(e)
					    end
			    end
	    end
	    return container
end
function toggleBackgroundColor(self)
	    if self.backgroundColor == colors.red then
			    self.backgroundColor = colors.lime
	    else
			    self.backgroundColor = colors.red
	    end
end
function quit()
	    running = false
end
term.clear()
term.setCursorPos(1,1)
local container = newContainer()
container:addObject(newTextButton(10,10,"Quit",colors.red,colors.white,quit))
container:addObject(newTextButton(3,3,"Toggle",colors.red,colors.white,toggleBackgroundColor))

while running do
	    container:drawObjects()
	    container:handleObjectEvents({os.pullEvent()})
end
term.setBackgroundColor(colors.black)
term.setTextColor(colors.white)
term.clear()
term.setCursorPos(1,1)



#12 MysticT

    Lua Wizard

  • Members
  • 1,597 posts

Posted 05 April 2013 - 09:32 AM

I don't see anything wrong. Are you getting an error or it's just not working as expected?

Btw, I have a similar gui api in my os. The file's on github if you are interested.

#13 Kingdaro

    The Doctor

  • Members
  • 1,636 posts
  • Location'MURICA

Posted 05 April 2013 - 09:38 AM

View PostMysticT, on 05 April 2013 - 09:13 AM, said:

Using another table would be better (imho), since you know that the "objects" you are looking for will be there and nothing else.
It's true that using a numerical iterator is faster than pairs, so it probably would be better to use it instead (but also using another table).
And it's always good to check for values before using them, it prevents weird unexpected errors :P/>/>
Using another table is safer, yes, but if you know what you're doing, combining both numeric indexes and string indexes is very efficient and way more convenient than a separate table.

I like to think of it as a bank, where the numbered indices are the money, and the methods are the people (how you use/get the money.) If you use pairs, you're essentially going to eventually try handing a person to a customer as a deposit. :lol:/> But the people and the money can live together in harmony as long as you search for just the money, using a numeric loop.

On the subject of checking values, it's not really needed when the values are there and empty/callable by default. I always just use a table full of empty methods for a base.

local myObject = {
  doSomething = function() end;
  doSomethingElse = function() end;
  draw = function() end;
  etc = function() end;
}

But yeah, safer coding practices are safer. I just like playing with fire. :P/>

#14 darkroom

  • Members
  • 70 posts

Posted 05 April 2013 - 09:42 AM

If you test the code you will see that for some reason the objects don't add to the container thats why I have the print(#self.objects) in the draw function to see if they are in the container. It always comes out to 0. Also I like your API thing is I am going for OOP like interhance so I can build of classes and make more and more complex things

#15 remiX

  • Members
  • 2,076 posts
  • LocationSouth Africa

Posted 05 April 2013 - 11:53 PM

Using a pairs loop is the problem. Just tested it now and it's giving me the same error with another program.

I made a re-write of your program with better function use and functionality
pastebin
I commented most of it, ask if you still need help

#16 darkroom

  • Members
  • 70 posts

Posted 06 April 2013 - 08:05 AM

The code looks good but it gets rid of the higherarchy and the way I would like to code can you please find out why my code isn't working.

#17 remiX

  • Members
  • 2,076 posts
  • LocationSouth Africa

Posted 06 April 2013 - 08:23 AM

Okay, what is the [b]current[b] code you have now?

Could you please pastebin it and indent it properly :P

#18 Kingdaro

    The Doctor

  • Members
  • 1,636 posts
  • Location'MURICA

Posted 06 April 2013 - 08:38 AM

Also holy god what is up with the super hardcore tabbing!?

I meant to ask about it when making my first post and I now just remembered to do so.

#19 darkroom

  • Members
  • 70 posts

Posted 06 April 2013 - 08:51 AM

hummmm sorry guys here is the pastebin: http://pastebin.com/VjfZXuSD

#20 Smiley43210

  • Members
  • 204 posts

Posted 06 April 2013 - 03:37 PM

After some scrounging around, I found something. Look really, really, closely at your newTextButton function.

Found anything? Rather, didn't find something?

Solution, tested, drawing works:
Spoiler

Edit: TextButtons don't handle events properly.
Edit 2: Found the problem dealing with event handling.

Solution, tested, works:
Spoiler






1 user(s) are reading this topic

0 members, 1 guests, 0 anonymous users