#!Lua50.exe
-- But works better with Lua 5.1, which I used to generate the HTML file...
--[[
A non-exhaustive look at table and array capabilities of Lua.
Made for educational purpose, and to allow comparison of syntax/features
of JavaScript and PHP in the same domain.

by Philippe Lhoste <PhiLho(a)GMX.net> http://Phi.Lho.free.fr
File/Project history:
 1.00 -- 2007/04/17 (PL) -- Creation.
]]
--[[ Copyright notice: See the PhiLhoSoftLicence.txt file for details.
This file is distributed under the zlib/libpng license.
Copyright (c) 2007 Philippe Lhoste / PhiLhoSoft
]]

eol = '\n'

function DoTest()

function StupidFunction(p) return p end

testPart = 3

---- Arrays with numerical index
-- Indexes in Lua start at 1

-- Simple definition and putting values in it
local simpleArray = {}
simpleArray[1] = "GET"
simpleArray[2] = "POST"
simpleArray[3] = "PUT"
simpleArray[4] = "DELETE"

-- Pre-definition and initialization
local predefinedArray = { nil, nil, nil, nil } -- Clumsy way, no other...
table.insert(predefinedArray, "HTTP")
table.insert(predefinedArray, "FTP")
table.insert(predefinedArray, "NNTP")
table.insert(predefinedArray, "IRC")
-- We can skip entries. But some functions like table.sort doesn't like that...
-- And table.getn will count only up to the first empty entry
-- Slots 5 and 6 are empty
table.insert(predefinedArray, table.getn(predefinedArray) + 3, "FTPS")

-- Definition and initialization at once
local initializedArray =  { "html", "head", "body", }   -- Trailing comma is OK

-- Generating an array
local generatedArray1 = Split("a:b:i:p:u:random:garbage", ":", 5)
local generatedArray2 = Split("hr , br ;  p ,span, div", '%s*[;,]%s*')

-- Assigning an array
local gArray = Split("aa ! bb ! cc ! dd ! ee", " ! ")
local aArray = gArray  -- aArray gets a reference, the arrays are the same
gArray[4] = "gg"
aArray[5] = "zz"

-- Array as function parameter and return value
function ProcessArray(a)
    a[1] = "ff0"  -- Change original array
    local la = { "1", "2", "3", a } -- Get a reference
    a[2] = "ff1"  -- Change original array and la
    return la
end
local fcArray = { "F1", "F2", "F3" }
local frArray = ProcessArray(fcArray)

-- Putting arrays in an array, and other objects too
local subArray1 = { "com", "org", "net", "int" }
local subArray2 = { "edu", "mil", "gov" }
local multiDimensionalArray =
{
    22 / 7, -- You can compute values, of course
    "TLDs",
    subArray1,
    subArray2,
    StupidFunction,
    42,
}
local result = ""
result = result .. FormatArrayMessage("simple", simpleArray)
result = result .. FormatArrayMessage("predefined", predefinedArray)
result = result .. FormatArrayMessage("initialized", initializedArray)
result = result .. FormatArrayMessage("generated 1", generatedArray1)
result = result .. FormatArrayMessage("generated 2", generatedArray2)
result = result .. FormatArrayMessage("original", gArray)
result = result .. FormatArrayMessage("assigned", aArray)
result = result .. FormatArrayMessage("function call", fcArray)
result = result .. FormatArrayMessage("function return", frArray)
result = result .. FormatArrayMessage("multi-dimensional", multiDimensionalArray)

AddResult(result, "Array Init")


-- Getting values:
result = ""
result = result .. simpleArray[3] .. eol
result = result .. predefinedArray[3] .. eol
result = result .. initializedArray[1 + 2] .. eol
result = result .. multiDimensionalArray[3][2] .. eol

result = result .. "[2] is: " .. predefinedArray[2] .. eol
result = result .. "[100] is: " .. (predefinedArray[100] or 'nil') .. eol

AddResult(result, "Array Access")


-- Some array operations
local r
result = ""
--~ predefinedArray[8] = "POP3"
-- Using table.insert updates the table size despite the holes, what [] doesn't do
-- As such, table.sort successfully takes out the empty slots with below function
table.insert(predefinedArray, 8, "POP3")
table.insert(predefinedArray, "Z1")
table.insert(predefinedArray, "Z2")
result = result .. FormatArrayMessage("array", predefinedArray) ..
    -- getn is deprecated in Lua 5.1 but still works... Should use #predefinedArray in this version.
    FormatMessage("getn", table.getn(predefinedArray)) ..
    FormatMessage("type=='table'", tostring(type(predefinedArray) == 'table')) .. eol

--  Mutator methods

table.sort(predefinedArray,
    -- I provide a sort function because default doesn't like empty elements...
    function (a, b)
        if a == nil then return false
        elseif b == nil then return true
        else return a < b
        end
    end
)
result = result .. FormatArrayMessage("sort", predefinedArray)

table.remove(predefinedArray)  -- Remove last element
result = result .. FormatArrayMessage("remove (pop)", predefinedArray, "")

Reverse(predefinedArray)
result = result .. FormatArrayMessage("(reverse)", predefinedArray)

table.remove(predefinedArray, 1)  -- Remove first element
result = result .. FormatArrayMessage("remove 1 (shift)", predefinedArray, "")

predefinedArray[1] = "HTTPS"
result = result .. FormatArrayMessage("(replace) 1", predefinedArray)

r = Splice(predefinedArray, 2, 2, "messaging") -- Remove 2, insert 1 in place
result = result .. FormatArrayMessage("Splice 2 2 s", predefinedArray, " '" .. (DumpObject(r)) .. "' removed")

r = Splice(predefinedArray, 4, 1) -- Remove 1
result = result .. FormatArrayMessage("Splice 4 1", predefinedArray, " '" .. (DumpObject(r)) .. "' removed")

r = Splice(predefinedArray, 2, 0, "POP3", "IMAP4") -- Insert 2
result = result .. FormatArrayMessage("Splice 2 0 s s", predefinedArray, " '" .. (DumpObject(r)) .. "' removed")

-- Add at end, no simple way to do it one op
table.insert(predefinedArray, "Y")
table.insert(predefinedArray, "Z")
result = result .. FormatArrayMessage("insert Y Z (push)", predefinedArray)

-- Add at beginning, idem
table.insert(predefinedArray, 1, "B")
table.insert(predefinedArray, 1, "A")
result = result .. FormatArrayMessage("insert B A (unshift)", predefinedArray)

-- Accessor methods

r = Slice(predefinedArray, 3, 7)  -- Take a slice, from position 3 up to (not including) position 7
result = result .. FormatArrayMessage("slice 3 7", r)

r = Slice(predefinedArray, 7)  -- Take a slice, from position 7 up to end
result = result .. FormatArrayMessage("slice 7", r)

predefinedArray = Slice(predefinedArray, 3, -2)  -- Remove two elements at both ends
result = result .. FormatArrayMessage("slice 3 -2", predefinedArray)

predefinedArray = Concat(predefinedArray, simpleArray, initializedArray)
result = result .. FormatArrayMessage("(concat/merge) a1 a2 a3", predefinedArray)

result = result .. "No native search in values, just iterate on array to search" .. eol

result = result .. FormatMessage("concat ' | '", table.concat(predefinedArray, ' | '))

AddResult(result, "Array Operations")


function PrintElement(key, value)
    result = result .. "[" .. key .. "] " .. value .. ", "
end

result = PadMessage"for ipairs"
for i in ipairs(predefinedArray) do
  PrintElement(i, predefinedArray[i])
end
result = result .. eol

result = result .. PadMessage"for pairs"
for i in pairs(predefinedArray) do
  PrintElement(i, predefinedArray[i])
end
result = result .. eol

AddResult(result, "Array Iteration")


---- Associative arrays
testPart = testPart + 2

-- Manual creation
local tableSimple = {}
tableSimple["one"] = "1"
tableSimple["two"] = "2_2"
tableSimple["three"] = "3_3_3"
tableSimple["#@!\\%"] = "garbage"

-- Table literal
local tableLiteral =
{
    ["one"] = "un_uno_ichi",
    ["two"] = "deux_dos_ni",
    ["three"] = "trois_tres_san",
    ["and... more"] = "...",
}

-- If the key name is a valid identifier, the quotes can be omitted
local tableId =
{
    one = "1", un = 1, uno = true, ichi = ""
}

-- The values can be any object
local tableObj =
{
    ["1, 3.14"] = "1" .. StupidFunction(", " .. 22 / 7),
    _ = tableId,
    [0] = StupidFunction,
    __ = { 4, 2 },
    [3.14159] = aArray
}
local tableNested =
{
    deep =
    {
        into =
        {
            table = '!'
        }
    }
}

local od = ""
result = ""
result = result .. FormatArrayMessage("simple", tableSimple)
result = result .. FormatArrayMessage("literal", tableLiteral)
result = result .. FormatArrayMessage("id", tableId)
result = result .. FormatArrayMessage("obj", tableObj)
result = result .. FormatArrayMessage("nested", tableNested)

AddResult(result, "Associative Array Init")


-- Getting values:
result = ""
result = result .. tableSimple["two"] .. eol
result = result .. tableSimple.three .. eol
result = result .. tableSimple["#@!\\%"] .. eol -- No shortcut here
result = result .. (tableSimple[1] or 'nil') .. eol  -- Doesn't exist
result = result .. (tableSimple.unknown or 'nil') .. eol  -- Doesn't exist
result = result .. tostring(tableObj._.uno) .. eol
result = result .. tableNested.deep.into.table .. eol

AddResult(result, "Associative Array Access")


-- Some array operations
result = ""
tableLiteral.newOne = "New entry"
tableLiteral[1] = 1
tableLiteral["+-/*"] = "ops"
result = result .. FormatArrayMessage("(add)", tableLiteral)

tableLiteral.two = nil
tableLiteral[1] = nil
tableLiteral["and... more"] = nil
result = result .. FormatArrayMessage("(delete)", tableLiteral)

AddResult(result, "Associative Array Operations")

-- Iteration is shown in the DumpObject function...

end -- function DoTest()

-- Emulation of functions found in other languages

-- Split the array str along the pattern delim, up to maxNb (default as no limits) items.
-- delim is part of a regular expression, you might need to escape some chars.
function Split(str, delim, maxNb)
    -- Eliminate bad cases...
    if string.find(str, delim) == nil then
        return { str }
    end
    if maxNb == nil or maxNb < 1 then
        maxNb = 0   -- No limit
    end
    local result = {}
    local pat = "(.-)" .. delim .. "()"
    local nb = 0
    local lastPos
    for part, pos in string.gfind(str, pat) do
        nb = nb + 1
        result[nb] = part
        lastPos = pos
        if nb == maxNb then break end
    end
    -- Handle the last field
    if nb ~= maxNb then
        result[nb + 1] = string.sub(str, lastPos)
    end
    return result
end

function Reverse(t)
    local l = table.getn(t) -- table length
    local j = l
    for i = 1, l / 2 do
        t[i], t[j] = t[j], t[i]
        j = j - 1
    end
end

-- Emulate the splice function of JS (or array_splice of PHP)
-- I keep the imperfect parameter names from the Mozilla doc.
-- http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Array:splice
-- I use 1-based indices, of course.
function Splice(t, index, howMany, ...)
    local removed = {}
    local tableSize = table.getn(t) -- Table size
    -- Lua 5.0 handling of vararg...
    local argNb = table.getn(arg) -- Number of elements to insert
    -- Check parameter validity
    if index < 1 then index = 1 end
    if howMany < 0 then howMany = 0 end
    if index > tableSize then
        index = tableSize + 1 -- At end
        howMany = 0 -- Nothing to delete
    end
    if index + howMany - 1 > tableSize then
        howMany = tableSize - index + 1 -- Adjust to number of elements at index
    end

    local argIdx = 1 -- Index in arg
    -- Replace min(howMany, argNb) entries
    for pos = index, index + math.min(howMany, argNb) - 1 do
        -- Copy removed entry
        table.insert(removed, t[pos])
        -- Overwrite entry
        t[pos] = arg[argIdx]
        argIdx = argIdx + 1
    end
    argIdx = argIdx - 1
    -- If howMany > argNb, remove extra entries
    for i = 1, howMany - argNb do
        table.insert(removed, table.remove(t, index + argIdx))
    end
    -- If howMany < argNb, insert remaining new entries
    for i = argNb - howMany, 1, -1 do
        table.insert(t, index + howMany, arg[argIdx + i])
    end
    return removed
end

-- Emulate the slice function of JS (or array_slice of PHP)
-- http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Array:slice
-- I use 1-based indices, of course.
function Slice(t, startPos, endPos)
    local tableSize = table.getn(t) -- Table size
    if endPos == nil then
        -- Only one parameter: extract to end of table
        endPos = tableSize + 1
    end
    if startPos < 0 then
        startPos = tableSize + 1 + startPos
        -- -1 -> last element
        -- -2 -> last two elements, etc.
    end
    if endPos < 0 then
        endPos = tableSize + 1 + endPos
    end
    local result = {}
    for i = startPos, endPos - 1 do
        result[i - startPos + 1] = t[i]
    end
    return result
end

-- Emulate the concat function of JS (or (more or less) array_merge of PHP)
-- http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Array:concat
-- Different of Lua's table.concat!
function Concat(...)
    local argNb = table.getn(arg) -- Number of elements
    local result = {}
    for i = 1, argNb do
        local a = arg[i]
        if type(a) == 'table' then
            for it = 1, table.getn(a) do
                table.insert(result, a[it])
            end
        else
            table.insert(result, arg[i])
        end
    end
    return result
end

-- Helper functions

function DumpObject(obj)
    local result = ""
    local len = 0

    if type(obj) ~= 'table' then
        return tostring(obj), 0
    end

    -- First, walk the "array" part, with successive numerical indexes
    -- As with the PHP and JS scripts, just put the values, indexes are implicit
    for idx, value in ipairs(obj) do
        if type(value) == 'table' then break end    -- Stop there
        result = result .. tostring(value) .. ","
        len = len + 1
    end
    local arraySize = len

    -- Walk the whole table
    for key, value in pairs(obj) do
        if type(key) == 'number' and key == math.floor(key) and key <= arraySize then
            -- Pure integer in ipairs range, already processed
            -- A continue keyword would be nice here...
        else
            -- Find elegant (legal) key notation
            if type(key) == 'number' then
                result = result .. "[" .. key .. "]"
            elseif string.find(key, "^[%w_]+$") ~= nil then
                -- Pure word, should exclude keywords...
                result = result .. key
            else
                -- Simplified, I don't use too exotic keys here...
                result = result .. "['" .. tostring(key) .. "']"
            end
            if type(value) == 'string' then
                value = "'" .. value .. "'"
                trail = ' '
            elseif type(value) == 'table' then
                value = "\n{ " .. (DumpObject(value)) .. " }"
                trail = '\n'
            else
                value = tostring(value)
                trail = ' '
            end
            result = result .. " = " .. value .. "," .. trail
            len = len + 1
        end
    end
    -- Remove trailing separator
    return string.gsub(result, ",%s*$", ""), len
end

function PadMessage(message)
    return message .. ':' .. string.rep(' ', 25 - string.len(message))
end

-- Data formating with message
function FormatMessage(message, data, after)
    return PadMessage(message) .. data .. (after or '') .. eol
end

-- Array formating with message
function FormatArrayMessage(message, array, after)
    local od, len = DumpObject(array)
    return PadMessage(message) .. od .. " (" .. len .. ")" .. (after or '') .. eol
end

function AddResult(result, message)
    table.insert(htmlPage, testPart, "<h3>" .. message .. "</h3>\n<pre>" .. result .. "</pre>")
    testPart = testPart + 1
end

----- The script itself...

-- Template of HTML page
htmlPage =
{
    -- 1
    [[
<!DOCTYPE html PUBLIC
      "-//W3C//DTD XHTML 1.0 Transitional//EN"
      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
  <title>Testing Lua array (table) operations</title>

  <!-- For dynamically generated pages -->
  <meta http-equiv="pragma" content="no-cache" />
  <meta http-equiv="cache-control" content="no-cache" />
  <meta http-equiv="expires" content="0" />

  <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
  <meta http-equiv="Content-Language" content="en-EN" />

  <meta name="Author" content="Philippe Lhoste" />
  <meta name="Copyright" content="Copyright (c) 2006 Philippe Lhoste" />
  <meta name="Generator" content="My typing hands with SciTE" />
  <meta http-equiv="Keywords" content="test,Lua,language" />
  <meta http-equiv="Description" content="A test page for Lua experiments" />

<style type="text/css">
body { background-color: #F0F8F0; }
h1
{
  background-color: #BFC;
  color: #008;
  text-align: center;
  padding: 20px;
}
h2
{
  background-color: #CFD;
  color: #048;
  padding: 10px;
}
h3
{
  background-color: #DFE;
  color: #088;
  padding: 5px;
}
pre
{
  background-color: #FAFAFF;
  color: #228;
}
</style>
</head>
    ]],
    -- 2
    [[
<body>

<h1>Testing Lua array/table operations</h1>

<h2>Array (table with numerical index)</h2>
    ]],
    -- 3
    [[ ]],
    -- 4
    [[
<h2>Associative array</h2>
    ]],
    -- 5
    [[ ]],
    -- 6
    [[
<hr />
<a href="TestLuaArray.lua.html">View source of script generating this page</a> /
<a href="TestLuaArray.lua">Get source of script</a>
</body>
</html>
    ]]
}

DoTest()
outputFileName = "TestLuaArray.html"
fh = io.open(outputFileName, "w")
fh:write(table.concat(htmlPage))
fh:close()