Ftf libs

From GiderosMobile
Revision as of 04:05, 22 August 2026 by MoKaLux (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Here are some longer code (classes) from the Gideros forum and some other places :-)

All samples are ready to use. Enjoy!

AUDIO

Producing Sound Without Resources @PaulH

Creates files on the fly using the .wav format for playback

-- https://forum.gideros.rocks/discussion/comment/68095/#Comment_68095
function make_wave(file_name, length, wave, freq, vol)
	-- We'll need to write some 32 bit values in binary. Helper function for that:
	local function to4Bytes(n) return n & 0xff, (n>>8) & 0xff, (n>>16) & 0xff, (n>>24) & 0xff end

	-- For a given point (sample number) in a wav file, return the 16 bit value for the specified sound wave and frequency
	local function sound_wave(wave,p,freq,vol)
		local sample_rate = 6000
		local n = 0 -- Default to silence
		if wave == "noise" then n = math.random(-255*128*vol, 255*128*vol)
		elseif wave == "sine" then n = math.floor(math.sin(p/sample_rate*freq*math.rad(180))*256*vol*127)
		elseif wave == "sawtooth" then n = math.floor((freq*p/sample_rate)*256*vol*256)
		end
		local c2, c1 = (n>>8) & 0xff, n & 0xff
		return string.char(c1, c2)
	end

	-- We're just dealing with a fixed format wav file:
	local sample_rate = 6000
	local samples = length*sample_rate
	local data_size = samples*2
	local total_file_length = 44+data_size
	local fl1, fl2, fl3, fl4 = to4Bytes(total_file_length)
	local ds1, ds2, ds3, ds4 = to4Bytes(data_size)
	local f = io.open(file_name, "wb")
	-- Write the WAV file header:
	f:write(
		string.char(
			82,73,70,70,--RIFF
			fl1, fl2, fl3, fl4,-- File size overall
			87,65,86,69,-- WAVE header
			102,109,116,32,--fmt chunk marker w/ trailing null
			16,0,0,0, -- Length of format data (16 bytes before this)
			1,0, -- Wave type. 1 == PCM
			1,0, -- Channels. 1 == mono
			112,23,0,0,-- Sample rate (Hertz)
			224,46,0,0, -- Bits per sample * channels * sample rate / 8
			2,0,
			16,0, -- Bytes per sample - this is 16 
			100,97,116,97, -- "data" - chunk header
			ds1,ds2,ds3,ds4-- size of data section
		)
	)
	-- Write the data:
	for l = 1, samples do
		f:write(sound_wave(wave,l,freq, vol or 1))
	end
	f:close()
end

-- Demo some white noise:
--make_wave("|D|noise.wav", 1, "noise") -- 0.1 second sine wave at 256 Herz (middle C) at full volume
--s=Sound.new("|D|noise.wav")
--s:play()

-- one note with a sawtooth wave:
--make_wave("|D|note.wav", 2, "sawtooth", 262, 1) -- 0.1 second sine wave at 256 Herz (middle C) at full volume
--s=Sound.new("|D|note.wav")
--s:play()

-- Make wave files for the chromatic scale.
-- The frequencies in Hertz of the notes in octave zero:
notes = { 16.35, 17.32, 18.35, 19.45, 20.60, 21.83, 23.12, 24.5, 25.96, 27.50, 29.14, 30.87 }
-- 3 letter names for those notes:
names = { "C0n", "C0s", "D0n", "D0s", "E0n", "F0n", "F0s", "G0n", "G0s", "A0n", "A0s", "B0n" }
-- Compute the ratio of each note to the C:
local note_multipliers = {}
for i = 1, #notes do
	note_multipliers[i] = notes[i]/notes[1]
end

-- Make a sound for each note in several octaves:
local sounds = {}
for octave = 3, 7 do
	for i = 1, #names do
		local original_note = names[i]
		local octave_note = string.gsub(original_note, "0", octave)
		local file_name = "|D|note_" .. octave_note .. ".wav"
		-- Compute the frequency for the note by applying the ratio of that note over C
		-- and then apply the octave multiplier. Each octave is double the frequency
		-- of the one below it:
		local fr = math.floor(notes[1] * note_multipliers[i] * math.pow(2, octave))
		-- We'll make each note 0.2 seconds, using a sine wave
		--function make_wave(file_name, length, wave, freq, vol)
		make_wave(file_name, 0.2, "sine", fr, 1)
		sounds[octave_note] = Sound.new(file_name)
	end
end

-- Given a string of 3 letter note names, play those notes in order:
function play_sequence(seq)
	for i = 1, string.len(seq), 3 do
		local note = string.sub(seq, i, i+2)
		if sounds[note] then
			Timer.delayedCall(60 * (i-1), function()
				sounds[note]:play()
			end)
		end
	end
end

local fur_elise =
	"E4nD4sE4nD4sE4nB3nD4nC4nA3n      "..
	"C3nE3nA3nB3n      "..
	"E3nG3sB3nC4n      "..
	"E3nE4nD4sE4nD4sE4nB3nD4nC4nA3n      "..
	"C3nE3nA3nB3n      "..
	"E3nC4nB3nA3n"
play_sequence(fur_elise)
-- To play an octave higher:
Timer.delayedCall(9000, function()
	play_sequence(string.gsub(string.gsub(fur_elise,"4","5"),"3","4"))
end)

STRING

Score Counting TextField @errorpi

--[[
	Score Counting TextField
	by: @errorpi
]]

Score = Core.class(Sprite)
 
function Score:init(xscore, xincrease)
	self.score = xscore or 0
	self.countTime = (xincrease or 5) * 100
	-- a textfield
	self.scoreText = TextField.new(nil, tostring(self.score))
	self.scoreText:setLayout({wid = 80, hei = 40, flags=FontBase.TLF_CENTER})
	self.scoreText:setTextColor(math.random(0xffffff))
	self.scoreText:setScale(5)
	self.scoreText:setPosition(64, 64)
	self:addChild(self.scoreText)
	-- a timer
	self.t = Timer.new(1) -- timer init
	self.t:addEventListener(Event.TIMER, self.updateText, self)
end
 
function Score:updateText()
	local displayScore = tonumber(self.scoreText:getText())
	if displayScore < self.score then
		self.scoreText:setText(tostring( displayScore + 1) )
	end
end

function Score:addScore(add)
	self.score += add
	local displayScore = tonumber(self.scoreText:getText())
	-- set timer properties (bigger add score means faster steps)
	local tRepeat = self.score - displayScore
	local tDelay =  self.countTime / tRepeat
	self.t:stop()
	self.t:reset()
	self.t:setDelay(tDelay)
	self.t:setRepeatCount(tRepeat)
	self.t:start()

	return self.score
end

--------------------
application:setBackgroundColor(0x4d4d4d)
-- demo
local initialscore = 500
local score = Score.new(initialscore, 6) -- initial score value, increase speed
stage:addChild(score)
local earnedpoints = 300

Timer.delayedCall(3000, function()
	score:addScore(earnedpoints) -- target score value
end)

GFX

Hex to Number / Number to Hex @hanzhao

--[[
Lua Hex v0.4
-------------------
Hex conversion lib for Lua.

How to use:
 lhex.to_hex(n) -- convert a number to a hex string
 lhex.to_dec(hex) -- convert a hex "string" (prefix with "0x..." or "0X...") to number

Part of LuaBit(http://luaforge.net/projects/bit/).

Under the MIT license.

copyright(c) 2006~2007 hanzhao (abrash_han@hotmail.com)
--]]

local function to_bits(n)
	-- checking not float
	if(n - (n//1) > 0) then error("trying to apply bitwise operation on non-integer!") end
	if(n < 0) then return to_bits(~(n//1) + 1) end -- negative
	-- to bits table
	local tbl = {}
	local cnt = 1
	while (n > 0) do
		local last = n%2
		if(last == 1) then tbl[cnt] = 1
		else tbl[cnt] = 0
		end
		n = (n-last)/2
		cnt += 1
	end
	return tbl
end

local function tbl_to_number(tbl)
	local n = #tbl
	local rslt = 0
	local power = 1
	for i = 1, n do
		rslt += tbl[i]*power
		power *= 2
	end
	return rslt
end

local function to_hex(n)
	if(type(n) ~= "number") then error("non-number type passed in.") end
	-- checking not float
	if(n - (n//1) > 0) then error("trying to apply bitwise operation on non-integer!") end
	if(n < 0) then -- negative
		n = to_bits(~(-n<>n) + 1)
		n = tbl_to_number(n)
	end
	local hex_tbl = { "A", "B", "C", "D", "E", "F" }
	local hex_str = ""
	while(n ~= 0) do
		local last = n%16
		if(last < 10) then hex_str = tostring(last) .. hex_str
		else hex_str = hex_tbl[last-10+1] .. hex_str
		end
		n = (n/16)//1 -- floor
	end
	if(hex_str == "") then hex_str = "0" end
	return "0x"..hex_str
end

local function to_dec(hexstring)
	if(type(hexstring) ~= "string") then error("non-string type passed in.") end
	local head = string.sub(hexstring, 1, 2)
	if(head ~= "0x" and head ~= "0X") then error("wrong hex format, should lead by 0x or 0X.") end
	return tonumber(hexstring:sub(3), 16) -- base 16
end

--------------------
-- lua hex lib interface
lhex = {
	to_hex=to_hex,
	to_dec=to_dec,
}

--------------------
-- examples
local dec = 4341688
local hex = lhex.to_hex(dec) -- number to hex
print(hex) -- 0x423FB8
local revdec = lhex.to_dec(hex) -- hex string (prefix with "0x" or "0X") to number
print(revdec) -- 4341688

print(lhex.to_dec("0x0")) -- 0, hex string (prefix with "0x" or "0X") to number
print(lhex.to_dec("0x00ff00")) -- 65280, hex string (prefix with "0x" or "0X") to number
print(lhex.to_hex(16777215)) -- 0xFFFFFF, number to hex

local pix = Pixel.new(lhex.to_hex(56816), 1, 32, 32)
stage:addChild(pix)

print("hex is", hex)

GTween Demo @rrraptor

require "easing"

application:setBackgroundColor(0x4c4c4c)

function animate(values) -- create a new GTween or use existing one
	local function NULL() end
	values.target.gtween = values.target.gtween or nil -- GTween
	values.duration = values.duration or 0
	values.values = values.values or {}
	values.props = values.props or { delay=0, ease=easing.linear, repeatCount=1, dispatchEvents=false, }
	values.target.callback = values.target.callback or NULL

	if values.target.gtween then -- use existing gtween
		print("using existing gtween", math.random(100))
		values.target.gtween:setValues(values.values)
	else -- create new gtween
		print("creating new gtween", math.random(100))
		--function GTween:init(target, duration, values, props)
		values.target.gtween = GTween.new(values.target, values.duration, values.values, values.props)
		values.target.gtween:addEventListener("complete", values.target.callback, values.target.gtween)
--		values.target.gtween:addEventListener("change", function() print("tween changing") end)
	end
	values.target.gtween:setPosition(0)
	values.target.gtween:setPaused(false)
end

local function followTween(data, e)
	if data.source == nil then
		print("you need to provide a source for the gtween!")
		return
	end

	data.source:setColor(math.random(0xffffff))

	local duration = 1
	local values = {} -- target values
	values.x = e.touch.x
	values.y = e.touch.y
	values.alpha = 1.2
	values.scaleX = data.source.sx * 1
	values.scaleY = data.source.sy * 1.5
	local properties = {}
	properties.delay = 64
	properties.ease = easing.outBack
	properties.repeatCount = 1
	properties.dispatchEvents = true

	animate( --function GTween:init(target, duration, values, props)
		{
			target = data.source, -- target = source + source.gtween + source.callback
			duration = duration,
			values = values,
			props = properties,
		}
	)
end

-- gtween playground
local pix = Pixel.new(math.random(0xffffff), 1, 64, 64)
pix:setAnchorPoint(0.5, 0)
pix:setPosition(application:getContentWidth()/2, application:getContentHeight()/2)

pix.gtween = nil
pix.sx = pix:getScaleX()
pix.sy = pix:getScaleY()
pix.callback = function()
	print("pix tween ended", math.random(100))
	pix.gtween.nextTween = GTween.new(
		pix, 0.3,
		{ -- target values
			alpha = 1,
			scaleX = pix.sx * 1.5,
			scaleY = pix.sy * 0.5,
		},
		{
			ease = easing.outBounce,
			repeatCount = 1,
		}
	)
end

stage:addChild(pix)
stage:addEventListener(Event.TOUCHES_END, followTween, { source=pix, })

2D Blue Noise points @ai

There are two main ways to generate "Blue Noise" in Lua, depending on what you need it for:

  • Poisson Disk Sampling (Bridson's Algorithm): Best for scattering objects (trees, stars, particles) evenly but randomly. This generates a list of coordinates.
  • Void and Cluster (Texture Generation): Best for creating a grid of pixel values (like a dithering texture). This is significantly more complex to implement from scratch.

Below is a robust, standard implementation of Poisson Disk Sampling in pure Lua. This is the most common approach for game development and procedural generation.

-- 2D Blue Noise points ai generated (https://chat.z.ai/c/efa8ba11-749f-4460-9342-b0928a65f40f)

local function generateBlueNoise(width, height, minDistance, k)
    -- Seed the random number generator
    math.randomseed(os.time())
    
    -- 1. Calculate cell size based on minimum distance
    -- The grid cell size ensures that we only check a small number of neighbors
    -- r / sqrt(2) is the maximum distance a point can be from the center of a cell
    local cellSize = minDistance / math.sqrt(2)
--    local gridWidth = math.ceil(width / cellSize)
--    local gridHeight = math.ceil(height / cellSize)

    -- 2. Initialize the Grid and Active List
    -- The grid acts as a spatial hash to speed up distance checks
    local grid = {} 
    local activeList = {}
    local points = {}

    -- Helper function to convert (x, y) to grid coordinates
    local function toGridCoords(x, y)
        return math.floor(x / cellSize) + 1, math.floor(y / cellSize) + 1
    end

    -- Helper to safely get a point from the grid
    local function getGridPoint(gx, gy)
        if grid[gx] then return grid[gx][gy] end
        return nil
    end

    -- Helper to put a point in the grid
    local function setGridPoint(gx, gy, point)
        if not grid[gx] then grid[gx] = {} end
        grid[gx][gy] = point
    end

    -- 3. Add the first random point
    local firstX = math.random() * width
    local firstY = math.random() * height
    local firstPoint = { x = firstX, y = firstY }
    
    table.insert(points, firstPoint)
    
    local gx, gy = toGridCoords(firstX, firstY)
    setGridPoint(gx, gy, firstPoint)
    table.insert(activeList, firstPoint)

    -- 4. Main Loop
    while #activeList > 0 do
        -- Pick a random index from the active list
        local randomIndex = math.random(1, #activeList)
        local parentPoint = activeList[randomIndex]
        local found = false

        -- Try to find a new point 'k' times (usually 30 is sufficient)
        for i = 1, k do
            -- Generate a random point in an annulus (donut) around the parent
            local angle = math.random() * 2 * math.pi
            -- Radius is between minDistance and 2 * minDistance
            local radius = math.random(minDistance, 2 * minDistance)
            
            local newX = parentPoint.x + math.cos(angle) * radius
            local newY = parentPoint.y + math.sin(angle) * radius

            -- Check if the point is within bounds
            if newX >= 0 and newX <= width and newY >= 0 and newY <= height then
                local ngx, ngy = toGridCoords(newX, newY)
                local isValid = true

                -- Check neighbors in the surrounding 3x3 grid cells
                for ix = -1, 1 do
                    for iy = -1, 1 do
                        local neighbor = getGridPoint(ngx + ix, ngy + iy)
                        if neighbor then
                            local dx = neighbor.x - newX
                            local dy = neighbor.y - newY
                            -- If distance is less than minDistance, it's too close
                            if math.sqrt(dx*dx + dy*dy) < minDistance then
                                isValid = false
                                break
                            end
                        end
                    end
                    if not isValid then break end
                end

                -- If valid, add to lists
                if isValid then
                    local newPoint = { x = newX, y = newY }
                    table.insert(points, newPoint)
                    setGridPoint(ngx, ngy, newPoint)
                    table.insert(activeList, newPoint)
                    found = true
                    break -- Found a valid point for this parent, stop trying
                end
            end
        end

        -- If we couldn't find a point after k tries, remove the parent from active list
        if not found then
            table.remove(activeList, randomIndex)
        end
    end

    return points
end

-- --- Usage Example ---
local w, h = 256, 256 -- 500, 500
local minDist = 16 -- 20
local noisePoints = generateBlueNoise(w, h, minDist, 30)

print("Generated " .. #noisePoints .. " blue noise points.")
for _, v in ipairs(noisePoints) do
--	print(v.x, v.y)
	local pixel = Pixel.new(math.random(0xffffff), 1, 4, 4)
	pixel:setPosition(v.x, v.y)
	stage:addChild(pixel)
end

3D Blue Noise points @ai

Generating 3D Blue Noise usually involves creating a "Poisson Disk Sampling" of points. This ensures that points are randomly distributed but evenly spaced (no two points are too close), which prevents clumping and creates that desirable "blue" frequency spectrum.

Here is a clean, efficient implementation in Lua using the Best Candidate Algorithm with a Spatial Hash Grid for optimization.

-- 3D Blue Noise points ai generated (https://chat.z.ai/c/efa8ba11-749f-4460-9342-b0928a65f40f)

local math = math
local random = math.random

--- Generate 3D Blue Noise points
--- @param count number: The number of points to generate
--- @param minDist number: The minimum distance between points (radius)
--- @param width number: Width of the volume
--- @param height number: Height of the volume
--- @param depth number: Depth of the volume
--- @return table: Array of points {x, y, z}
local function generate3DBlueNoise(count, minDist, width, height, depth)
    local points = {}
    local grid = {}
    
    -- Optimization: Cell size determines the resolution of our spatial grid.
    -- If points are at least minDist apart, we only need to check adjacent cells.
    local cellSize = minDist / 1.414 -- Slightly smaller to be safe
    local cellSizeInv = 1 / cellSize

    -- Helper: Get a unique key for a grid cell
    local function getGridKey(x, y, z)
        local gx = math.floor(x * cellSizeInv)
        local gy = math.floor(y * cellSizeInv)
        local gz = math.floor(z * cellSizeInv)
        return string.format("%d|%d|%d", gx, gy, gz)
    end

    -- Helper: Check if a point is valid (not too close to existing points)
    local function isValid(p)
        local gx = math.floor(p.x * cellSizeInv)
        local gy = math.floor(p.y * cellSizeInv)
        local gz = math.floor(p.z * cellSizeInv)
        
        -- Check the 3x3x3 neighborhood around the candidate
        for ix = -1, 1 do
            for iy = -1, 1 do
                for iz = -1, 1 do
                    local key = string.format("%d|%d|%d", gx + ix, gy + iy, gz + iz)
                    local existing = grid[key]
                    
                    if existing then
                        local dx = p.x - existing.x
                        local dy = p.y - existing.y
                        local dz = p.z - existing.z
                        
                        -- Optimization: compare squared distance to avoid square root
                        if (dx*dx + dy*dy + dz*dz) < (minDist * minDist) then
                            return false 
                        end
                    end
                end
            end
        end
        return true
    end

    -- Helper: Find approximate distance to nearest existing point
    -- (Used to pick the "best" candidate among the random tries)
    local function getNearestDist(p)
        local closestSq = minDist * minDist -- Initialize to max allowed
        
        local gx = math.floor(p.x * cellSizeInv)
        local gy = math.floor(p.y * cellSizeInv)
        local gz = math.floor(p.z * cellSizeInv)

        for ix = -1, 1 do
            for iy = -1, 1 do
                for iz = -1, 1 do
                    local key = string.format("%d|%d|%d", gx + ix, gy + iy, gz + iz)
                    local existing = grid[key]
                    if existing then
                        local dx = p.x - existing.x
                        local dy = p.y - existing.y
                        local dz = p.z - existing.z
                        local dSq = dx*dx + dy*dy + dz*dz
                        if dSq < closestSq then
                            closestSq = dSq
                        end
                    end
                end
            end
        end
        return closestSq
    end

    -- 1. Add the first point completely randomly
    local firstPoint = {
        x = random() * width,
        y = random() * height,
        z = random() * depth
    }
    table.insert(points, firstPoint)
    grid[getGridKey(firstPoint.x, firstPoint.y, firstPoint.z)] = firstPoint

    -- 2. Generate subsequent points
    local k = 30 -- The number of candidates to try per point (Standard is 30)
    
    for i = 2, count do
        local bestCandidate = nil
        local maxDistToNeighbor = -1

        -- Try 'k' times to find a valid point
        for attempt = 1, k do
            local candidate = {
                x = random() * width,
                y = random() * height,
                z = random() * depth
            }

            if isValid(candidate) then
                -- Among valid candidates, pick the one furthest from others
                local d = getNearestDist(candidate)
                if d > maxDistToNeighbor then
                    maxDistToNeighbor = d
                    bestCandidate = candidate
                end
            end
        end

        if bestCandidate then
            table.insert(points, bestCandidate)
            grid[getGridKey(bestCandidate.x, bestCandidate.y, bestCandidate.z)] = bestCandidate
        else
            -- If we fail to find a point after 'k' tries, the volume might be full.
            -- We can stop early or just print a warning.
            print("Warning: Could not place point " .. i .. ". Density limit reached?")
            break
        end
    end

    return points
end

-- ==========================================
-- EXAMPLE USAGE
-- ==========================================

math.randomseed(os.time()) -- Don't forget to seed!

-- Generate 500 points in a 100x100x100 cube
-- Points must be at least 5 units apart
local noisePoints = generate3DBlueNoise(500, 5.0, 100, 100, 100)

print("Generated " .. #noisePoints .. " points.")

-- Print first 5 points to console
for i = 1, 5 do
    local p = noisePoints[i]
    print(string.format("Point %d: x=%.2f, y=%.2f, z=%.2f", i, p.x, p.y, p.z))
end

FILES

Get Files in Folder @xxx

note: requires the Lfs plugin

Gets all the files in a folder matching a file extension and store them in a list.

local myos = application:getDeviceInfo()
local iswin32 = if myos == "win32" then true else false
--print(iswin32)

function getFilesInFolder(xpath, xlist)
	local lfs = require "lfs"
	for file in lfs.dir(xpath) do
		if file ~= "." and file ~= ".." then
			if file:match(".png$") then -- choose your file extension here
				if iswin32 then
					local f = xpath.."\\"..file
--					print(f)
					xlist[#xlist + 1] = f:gsub("/", "\\")
				else
					local f = xpath.."/"..file
--					print(f)
					xlist[#xlist + 1] = f:gsub("\\", "/")
				end
			end
		end
	end
	lfs = nil
end

Demo code using ButtonMonster

local myos = application:getDeviceInfo()
local iswin32 = if myos == "win32" then true else false
--print(iswin32)

local folderimages = {} -- list all images in user selected folder
local images = {} -- list all images with valid extension
local btnimagefolder = ButtonMonster.new({
	pixelcolorup=0xffaa00, text="import images", textcolorup=0xaa0000, textscalex=3,
	isautoscale=true,
})
btnimagefolder:setPosition(16, 16)
stage:addChild(btnimagefolder)
btnimagefolder:addEventListener("clicked", function()
	local path
	if iswin32 then path = application:get("openDirectoryDialog", "Select Folder", "C:\\tmp\\")
	else path = application:get("openDirectoryDialog", "Select Folder", "C:/tmp/")
	end -- print(path)
	if path then getFilesInFolder(path, folderimages) end
	if folderimages and #folderimages > 0 then
		for i = 1, #folderimages do
			images[i] = Bitmap.new(Texture.new(folderimages[i]), true)
			images[i]:setAnchorPoint(0.5, 1)
			images[i]:setPosition(8, 8)
			-- ...
		end
	end
end)

Xml Parser @Alexander Makeev

-----------------------------------------------------------------------------------------
-- LUA only XmlParser from Alexander Makeev
-----------------------------------------------------------------------------------------
XmlParser = {}

function XmlParser:ToXmlString(value)
	value = string.gsub(value, "&", "&amp;")		-- '&' -> "&amp;"
	value = string.gsub(value, "<", "&lt;")		-- '<' -> "&lt;"
	value = string.gsub(value, ">", "&gt;")		-- '>' -> "&gt;"
	--value = string.gsub(value, "'", "&apos;")	-- '\'' -> "&apos;"
	value = string.gsub(value, "\"", "&quot;")	-- '"' -> "&quot;"
	-- replace non printable char -> "&#xD;"
	value = string.gsub(value, "([^%w%&%;%p%\t% ])", function(c)
		return string.format("&#x%X;", string.byte(c))
		--return string.format("&#x%02X;", string.byte(c))
		--return string.format("&#%02d;", string.byte(c))
	end)
	return value
end

function XmlParser:FromXmlString(value)
	value = string.gsub(value, "&#x([%x]+)%;", function(h)
		return string.char(tonumber(h,16))
	end)
	value = string.gsub(value, "&#([0-9]+)%;", function(h)
		return string.char(tonumber(h,10))
	end)
	value = string.gsub(value, "&quot;", "\"")
	value = string.gsub(value, "&apos;", "'")
	value = string.gsub(value, "&gt;", ">")
	value = string.gsub(value, "&lt;", "<")
	value = string.gsub(value, "&amp;", "&")
	return value
end
   
function XmlParser:ParseArgs(s)
	local arg = {}
	string.gsub(s, "(%w+)=([\"'])(.-)%2", function(w, _, a)
		arg[w] = self:FromXmlString(a)
	end)
	return arg
end

function XmlParser:ParseXmlText(xmlText)
	local stack = {}
	local top = { Name=nil, Value=nil, Attributes={}, ChildNodes={} }
	table.insert(stack, top)
	local ni, c, label, xarg, empty
	local i, j = 1, 1
	while true do
		ni, j, c, label, xarg, empty = string.find(xmlText, "<(%/?)([%w:]+)(.-)(%/?)>", i)
		if not ni then break end
		local text = string.sub(xmlText, i, ni-1)
		if not string.find(text, "^%s*$") then
			top.Value=(top.Value or "")..self:FromXmlString(text)
		end
		if empty == "/" then -- empty element tag
			table.insert(top.ChildNodes,
				{
					Name=label, Value=nil, Attributes=self:ParseArgs(xarg), ChildNodes={}
				}
			)
		elseif c == "" then -- start tag
			top = { Name=label, Value=nil, Attributes=self:ParseArgs(xarg), ChildNodes={} }
			table.insert(stack, top) -- new level
			--log("openTag ="..top.Name)
		else -- end tag
			local toclose = table.remove(stack) -- remove top
			--log("closeTag="..toclose.Name)
			top = stack[#stack]
			if #stack < 1 then
				error("XmlParser: nothing to close with "..label)
			end
			if toclose.Name ~= label then
				error("XmlParser: trying to close "..toclose.Name.." with "..label)
			end
			table.insert(top.ChildNodes, toclose)
		end
		i = j + 1
	end
	local text = string.sub(xmlText, i)
	if not string.find(text, "^%s*$") then
		stack[#stack].Value=(stack[#stack].Value or "")..self:FromXmlString(text)
	end
	if #stack > 1 then
		error("XmlParser: unclosed "..stack[stack.n].Name)
	end
	return stack[1].ChildNodes[1]
end

function XmlParser:ParseXmlFile(xmlFileName)
	local hFile, err = io.open(xmlFileName,"r")
	if (not err) then
		local xmlText=hFile:read("*a") -- read file content
		io.close(hFile)
		return self:ParseXmlText(xmlText),nil
	else
		return nil, err
	end
end

--[[
-- Usage:
-- parse xml
local xml = XmlParser:ParseXmlText(myxmlfile)
]]

TABLE

inspect.lua @kikito

-- usage
inspect = require "inspect"

local t = {"a", "b", "x", 1, 8, 20, }
print("table inspection:", inspect.inspect(t))
--!NOEXEC

local inspect ={
  _VERSION = 'inspect.lua 3.0.0',
  _URL     = 'http://github.com/kikito/inspect.lua',
  _DESCRIPTION = 'human-readable representations of tables',
  _LICENSE = [[
    MIT LICENSE

    Copyright (c) 2013 Enrique García Cota

    Permission is hereby granted, free of charge, to any person obtaining a
    copy of this software and associated documentation files (the
    "Software"), to deal in the Software without restriction, including
    without limitation the rights to use, copy, modify, merge, publish,
    distribute, sublicense, and/or sell copies of the Software, and to
    permit persons to whom the Software is furnished to do so, subject to
    the following conditions:

    The above copyright notice and this permission notice shall be included
    in all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
    OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
    TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  ]]
}

inspect.KEY       = setmetatable({}, {__tostring = function() return 'inspect.KEY' end})
inspect.METATABLE = setmetatable({}, {__tostring = function() return 'inspect.METATABLE' end})

-- Apostrophizes the string if it has quotes, but not aphostrophes
-- Otherwise, it returns a regular quoted string
local function smartQuote(str)
  if str:match('"') and not str:match("'") then
    return "'" .. str .. "'"
  end
  return '"' .. str:gsub('"', '\\"') .. '"'
end

local controlCharsTranslation = {
  ["\a"] = "\\a",  ["\b"] = "\\b", ["\f"] = "\\f",  ["\n"] = "\\n",
  ["\r"] = "\\r",  ["\t"] = "\\t", ["\v"] = "\\v"
}

local function escape(str)
  local result = str:gsub("\\", "\\\\"):gsub("(%c)", controlCharsTranslation)
  return result
end

local function isIdentifier(str)
  return type(str) == 'string' and str:match( "^[_%a][_%a%d]*$" )
end

local function isSequenceKey(k, length)
  return type(k) == 'number'
     and 1 <= k
     and k <= length
     and math.floor(k) == k
end

local defaultTypeOrders = {
  ['number']   = 1, ['boolean']  = 2, ['string'] = 3, ['table'] = 4,
  ['function'] = 5, ['userdata'] = 6, ['thread'] = 7
}

local function sortKeys(a, b)
  local ta, tb = type(a), type(b)

  -- strings and numbers are sorted numerically/alphabetically
  if ta == tb and (ta == 'string' or ta == 'number') then return a < b end

  local dta, dtb = defaultTypeOrders[ta], defaultTypeOrders[tb]
  -- Two default types are compared according to the defaultTypeOrders table
  if dta and dtb then return defaultTypeOrders[ta] < defaultTypeOrders[tb]
  elseif dta     then return true  -- default types before custom ones
  elseif dtb     then return false -- custom types after default ones
  end

  -- custom types are sorted out alphabetically
  return ta < tb
end

local function getNonSequentialKeys(t)
  local keys, length = {}, #t
  for k,_ in pairs(t) do
    if not isSequenceKey(k, length) then table.insert(keys, k) end
  end
  table.sort(keys, sortKeys)
  return keys
end

local function getToStringResultSafely(t, mt)
  local __tostring = type(mt) == 'table' and rawget(mt, '__tostring')
  local str, ok
  if type(__tostring) == 'function' then
    ok, str = pcall(__tostring, t)
    str = ok and str or 'error: ' .. tostring(str)
  end
  if type(str) == 'string' and #str > 0 then return str end
end

local maxIdsMetaTable = {
  __index = function(self, typeName)
    rawset(self, typeName, 0)
    return 0
  end
}

local idsMetaTable = {
  __index = function (self, typeName)
    local col = setmetatable({}, {__mode = "kv"})
    rawset(self, typeName, col)
    return col
  end
}

local function countTableAppearances(t, tableAppearances)
  tableAppearances = tableAppearances or setmetatable({}, {__mode = "k"})

  if type(t) == 'table' then
    if not tableAppearances[t] then
      tableAppearances[t] = 1
      for k,v in pairs(t) do
        countTableAppearances(k, tableAppearances)
        countTableAppearances(v, tableAppearances)
      end
      countTableAppearances(getmetatable(t), tableAppearances)
    else
      tableAppearances[t] = tableAppearances[t] + 1
    end
  end

  return tableAppearances
end

local copySequence = function(s)
  local copy, len = {}, #s
  for i=1, len do copy[i] = s[i] end
  return copy, len
end

local function makePath(path, ...)
  local keys = {...}
  local newPath, len = copySequence(path)
  for i=1, #keys do
    newPath[len + i] = keys[i]
  end
  return newPath
end

local function processRecursive(process, item, path)
  if item == nil then return nil end

  local processed = process(item, path)
  if type(processed) == 'table' then
    local processedCopy = {}
    local processedKey

    for k,v in pairs(processed) do
      processedKey = processRecursive(process, k, makePath(path, k, inspect.KEY))
      if processedKey ~= nil then
        processedCopy[processedKey] = processRecursive(process, v, makePath(path, processedKey))
      end
    end

    local mt  = processRecursive(process, getmetatable(processed), makePath(path, inspect.METATABLE))
    setmetatable(processedCopy, mt)
    processed = processedCopy
  end
  return processed
end

-------------------------------------------------------------------

local Inspector = {}
local Inspector_mt = {__index = Inspector}

function Inspector:puts(...)
  local args   = {...}
  local buffer = self.buffer
  local len    = #buffer
  for i=1, #args do
    len = len + 1
    buffer[len] = tostring(args[i])
  end
end

function Inspector:down(f)
  self.level = self.level + 1
  f()
  self.level = self.level - 1
end

function Inspector:tabify()
  self:puts(self.newline, string.rep(self.indent, self.level))
end

function Inspector:alreadyVisited(v)
  return self.ids[type(v)][v] ~= nil
end

function Inspector:getId(v)
  local tv = type(v)
  local id = self.ids[tv][v]
  if not id then
    id              = self.maxIds[tv] + 1
    self.maxIds[tv] = id
    self.ids[tv][v] = id
  end
  return id
end

function Inspector:putKey(k)
  if isIdentifier(k) then return self:puts(k) end
  self:puts("[")
  self:putValue(k)
  self:puts("]")
end

function Inspector:putTable(t)
  if t == inspect.KEY or t == inspect.METATABLE then
    self:puts(tostring(t))
  elseif self:alreadyVisited(t) then
    self:puts('<table ', self:getId(t), '>')
  elseif self.level >= self.depth then
    self:puts('{...}')
  else
    if self.tableAppearances[t] > 1 then self:puts('<', self:getId(t), '>') end

    local nonSequentialKeys = getNonSequentialKeys(t)
    local length            = #t
    local mt                = getmetatable(t)
    local toStringResult    = getToStringResultSafely(t, mt)

    self:puts('{')
    self:down(function()
      if toStringResult then
        self:puts(' -- ', escape(toStringResult))
        if length >= 1 then self:tabify() end
      end

      local count = 0
      for i=1, length do
        if count > 0 then self:puts(',') end
        self:puts(' ')
        self:putValue(t[i])
        count = count + 1
      end

      for _,k in ipairs(nonSequentialKeys) do
        if count > 0 then self:puts(',') end
        self:tabify()
        self:putKey(k)
        self:puts(' = ')
        self:putValue(t[k])
        count = count + 1
      end

      if mt then
        if count > 0 then self:puts(',') end
        self:tabify()
        self:puts('<metatable> = ')
        self:putValue(mt)
      end
    end)

    if #nonSequentialKeys > 0 or mt then -- result is multi-lined. Justify closing }
      self:tabify()
    elseif length > 0 then -- array tables have one extra space before closing }
      self:puts(' ')
    end

    self:puts('}')
  end
end

function Inspector:putValue(v)
  local tv = type(v)

  if tv == 'string' then
    self:puts(smartQuote(escape(v)))
  elseif tv == 'number' or tv == 'boolean' or tv == 'nil' then
    self:puts(tostring(v))
  elseif tv == 'table' then
    self:putTable(v)
  else
    self:puts('<',tv,' ',self:getId(v),'>')
  end
end

-------------------------------------------------------------------

function inspect.inspect(root, options)
  options       = options or {}

  local depth   = options.depth   or math.huge
  local newline = options.newline or '\n'
  local indent  = options.indent  or '  '
  local process = options.process

  if process then
    root = processRecursive(process, root, {})
  end

  local inspector = setmetatable({
    depth            = depth,
    buffer           = {},
    level            = 0,
    ids              = setmetatable({}, idsMetaTable),
    maxIds           = setmetatable({}, maxIdsMetaTable),
    newline          = newline,
    indent           = indent,
    tableAppearances = countTableAppearances(root)
  }, Inspector_mt)

  inspector:putValue(root)

  return table.concat(inspector.buffer)
end

setmetatable(inspect, { __call = function(_, ...) return inspect.inspect(...) end })

return inspect

Var Dump @xxx

function vardump(value, depth, key)
	local linePrefix = ""
	local spaces = ""

	if key ~= nil then
		linePrefix = "["..key.."] = "
	end

	if depth == nil then
		depth = 0
	else
		depth = depth + 1
		for i = 1, depth do
			spaces = spaces .. "  "
		end
	end

	if type(value) == 'table' then
		local mTable = getmetatable(value)
		if mTable == nil then
			print(spaces ..linePrefix.."(table) ")
		else
			print(spaces .."(metatable) ")
			value = mTable
		end		
		for tableKey, tableValue in pairs(value) do
			vardump(tableValue, depth, tableKey)
		end
	elseif type(value) == 'function' or
		type(value)	== 'thread' or
		type(value)	== 'userdata' or
		value == nil
		then
		print(spaces..tostring(value))
	else
		print(spaces..linePrefix.."("..type(value)..") "..tostring(value))
	end
end

--[[
Usage:
foo="Hello world"
vardump(foo)
vardump( { print, 12, nil, io.stdin, math } )

foo = {
	"zero",1,2,3,{1,{1,2,3,4,{1,2,{1,"cool",2},4},6},3,vardump,5,6},          5,{Mary=10, Paul="10"},"last value"
}
vardump(foo)
--]]




More to come God's willing...