Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- --=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
- -- Driver Declarations
- --=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
- --[[
- Command Handler Tables
- --]]
- EX_CMD = {}
- PRX_CMD = {}
- NOTIFY = {}
- DEV_MSG = {}
- LUA_ACTION = {}
- --[[
- Tables of functions
- The following tables are function containers that are called within the following functions:
- OnDriverInit()
- - first calls all functions contained within ON_DRIVER_EARLY_INIT table
- - then calls all functions contained within ON_DRIVER_INIT table
- OnDriverLateInit()
- - calls all functions contained within ON_DRIVER_LATEINIT table
- OnDriverUpdate()
- - calls all functions contained within ON_DRIVER_UPDATE table
- OnDriverDestroyed()
- - calls all functions contained within ON_DRIVER_DESTROYED table
- OnPropertyChanged()
- - calls all functions contained within ON_PROPERTY_CHANGED table
- --]]
- ON_DRIVER_INIT = {}
- ON_DRIVER_EARLY_INIT = {}
- ON_DRIVER_LATEINIT = {}
- ON_DRIVER_UPDATE = {}
- ON_DRIVER_DESTROYED = {}
- ON_PROPERTY_CHANGED = {}
- -- Constants
- DEFAULT_PROXY_BINDINGID = 5001
- --=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
- -- Common Driver Code
- --=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
- --[[
- OnPropertyChanged
- Function called by Director when a property changes value.
- Parameters
- sProperty
- Name of property that has changed.
- Remarks
- The value of the property that has changed can be found with: Properties[sName]. Note
- that OnPropertyChanged is not called when the Property has been changed by the driver
- calling the UpdateProperty command, only when the Property is changed by the user from
- the Properties Page. This function is called by Director when a property changes value.
- --]]
- _G.id = ""
- local random = math.random
- local function uuid()
- local template ='xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'
- return string.gsub(template, '[xy]', function (c)
- local v = (c == 'x') and random(0, 0xf) or random(8, 0xb)
- return string.format('%x', v)
- end)
- end
- function OnPropertyChanged(sProperty)
- id = uuid()
- Dbg:Trace("OnPropertyChanged(" .. sProperty .. ") changed to: " .. Properties[sProperty])
- local propertyValue = Properties[sProperty]
- -- Remove any spaces (trim the property)
- local trimmedProperty = string.gsub(sProperty, " ", "")
- -- if function exists then execute (non-stripped)
- if (ON_PROPERTY_CHANGED[sProperty] ~= nil and type(ON_PROPERTY_CHANGED[sProperty]) == "function") then
- ON_PROPERTY_CHANGED[sProperty](propertyValue)
- return
- -- elseif trimmed function exists then execute
- elseif (ON_PROPERTY_CHANGED[trimmedProperty] ~= nil and type(ON_PROPERTY_CHANGED[trimmedProperty]) == "function") then
- ON_PROPERTY_CHANGED[trimmedProperty](propertyValue)
- return
- end
- end
- function ON_PROPERTY_CHANGED.DebugMode(propertyValue)
- gDebugTimer:KillTimer()
- Dbg:OutputPrint(propertyValue:find("Print") ~= nil)
- Dbg:OutputC4Log(propertyValue:find("Log") ~= nil)
- if (propertyValue == "Off") then return end
- gDebugTimer:StartTimer()
- end
- function ON_PROPERTY_CHANGED.DebugLevel(propertyValue)
- Dbg:SetLogLevel(tonumber(string.sub(propertyValue, 1, 1)))
- end
- ---------------------------------------------------------------------
- -- ExecuteCommand Code
- ---------------------------------------------------------------------
- --[[
- ExecuteCommand
- Function called by Director when a command is received for this DriverWorks driver.
- This includes commands created in Composer programming.
- Parameters
- sCommand
- Command to be sent
- tParams
- Lua table of parameters for the sent command
- --]]
- function ExecuteCommand(sCommand, tParams)
- Dbg:Trace("ExecuteCommand(" .. sCommand .. ")")
- Dbg:Info(tParams)
- -- Remove any spaces (trim the command)
- local trimmedCommand = string.gsub(sCommand, " ", "")
- -- if function exists then execute (non-stripped)
- if (EX_CMD[sCommand] ~= nil and type(EX_CMD[sCommand]) == "function") then
- EX_CMD[sCommand](tParams)
- -- elseif trimmed function exists then execute
- elseif (EX_CMD[trimmedCommand] ~= nil and type(EX_CMD[trimmedCommand]) == "function") then
- EX_CMD[trimmedCommand](tParams)
- -- handle the command
- elseif (EX_CMD[sCommand] ~= nil) then
- QueueCommand(EX_CMD[sCommand])
- else
- Dbg:Alert("ExecuteCommand: Unhandled command = " .. sCommand)
- end
- end
- --[[
- Define any functions of commands (EX_CMD.<command>) received from ExecuteCommand that need to be handled by the driver.
- --]]
- function EX_CMD.SendSMS()
- SendSMS()
- end
- function EX_CMD.SendCustomSMS(tParams)
- Dbg:Debug("SendCustomSMS Phone Number = " .. tParams["Phone Number"])
- Dbg:Debug("SendCustomSMS Message = " .. tParams["Message"])
- --[[ Send the SMS and pass the phone number and message ]]--
- SendSMS(tParams["Phone Number"], tParams["Message"])
- end
- --[[
- EX_CMD.LUA_ACTION
- Function called for any actions executed by the user from the Actions Tab in Composer.
- --]]
- function EX_CMD.LUA_ACTION(tParams)
- if tParams ~= nil then
- for cmd,cmdv in pairs(tParams) do
- if cmd == "ACTION" then
- if (LUA_ACTION[cmdv] ~= nil) then
- LUA_ACTION[cmdv]()
- else
- Dbg:Alert("Undefined Action")
- Dbg:Alert("Key: " .. cmd .. " Value: " .. cmdv)
- end
- else
- Dbg:Alert("Undefined Command")
- Dbg:Alert("Key: " .. cmd .. " Value: " .. cmdv)
- end
- end
- end
- end
- --[[
- LUA_ACTION.DisplayGlobals
- Implementation of Action "Display Globals". Executed when selecting the "Display Globals" action within Composer.
- Provided as an example for actions.
- --]]
- function LUA_ACTION.DisplayGlobals()
- print ("Global Variables")
- print ("----------------------------")
- for k,v in pairs(_G) do -- globals`
- if not (type(v) == "function") then
- --print(k .. ": " .. tostring(v))
- if (string.find(k, "^g%L") == 1) then
- print(k .. ": " .. tostring(v))
- if (type(v) == "table") then
- PrintTable(v, " ")
- end
- end
- end
- end
- print ("")
- end
- function PrintTable(tValue, sIndent)
- sIndent = sIndent or " "
- for k,v in pairs(tValue) do
- print(sIndent .. tostring(k) .. ": " .. tostring(v))
- if (type(v) == "table") then
- PrintTable(v, sIndent .. " ")
- end
- end
- end
- function LUA_ACTION.SendSMS()
- SendSMS()
- end
- function SendSMS(to, message)
- to = to or Properties["To"]
- message = message or Properties["Message"]
- local postdata = "To=" .. urlencode(to) .. "&From=" .. urlencode(Properties["From"]) .. "&Body=" .. urlencode(message)
- local username = Properties["Account SID"]
- local password = Properties["Auth Token"]
- local url = Properties["ServerIP"]
- local snapurl = Properties["CameraIp"]
- --[[ Create the basic authentication header ]]--
- local http_headers = {}
- --[[
- local http_auth = "Basic " .. C4:Base64Encode(username .. ":" .. password)
- http_headers["Authorization"] = http_auth
- --]]
- --[[ Set the correct content type ]]--
- http_headers["Content-Type"] = "application/x-www-form-urlencoded"
- --[[ Output the debug info ]]--
- Dbg:Debug("Twilio API Data")
- Dbg:Debug("---------------")
- Dbg:Debug("To = " .. Properties["To"])
- Dbg:Debug("From = " .. Properties["From"])
- Dbg:Debug("Body = " .. Properties["Message"])
- Dbg:Debug("Post Data = " .. postdata)
- Dbg:Debug("Account SID = " .. Properties["Account SID"])
- Dbg:Debug("Auth Token = " .. Properties["Auth Token"])
- Dbg:Debug("URL = " .. url)
- Dbg:Debug("URL snap = " .. snapurl)
- --Dbg:Debug("HTTP Authentication: " .. http_auth)
- --[[ Send request to twilio ]]--
- --C4:urlPost(url, postdata, http_headers)
- C4:urlGet(snapurl)
- end
- _G.strb64ff = ""
- function ReceivedAsync(idTicket, strData, errMsg, responseCode, tHeaders)
- Dbg:Debug("Data received from server : " .. enc(strData))
- Dbg:Debug("Server Return Code: " .. errMsg)
- Dbg:Debug("tHeaders: "..tostring(tHeaders))
- --Dbg:Debug("first frame: "..strb64ff)
- if tostring(errMsg) == "200" then
- local strb64 = enc(strData)
- local json_data = {}
- json_data["frame"] = strb64
- json_data["email"] = "uxmakefiresmoke@gmail.com"
- if strb64ff == "" then
- --json_data["first_frame"] = strb64
- else
- json_data["first_frame"] = strb64ff
- end
- --json_data["first_frame"] = strb64
- local result = json_encode(json_data)
- local headers = {}
- Dbg:Debug("result ="..result)
- headers["Content-Type"] = "application/json"
- headers["Content-Length"] = result:len()
- local url = Properties["ServerIP"]
- C4:urlPost(url, result, headers)
- strb64ff = strb64
- end
- end
- local b='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' -- You will need this for encoding/decoding
- -- encoding
- function enc(data)
- return ((data:gsub('.', function(x)
- local r,b='',x:byte()
- for i=8,1,-1 do r=r..(b%2^i-b%2^(i-1)>0 and '1' or '0') end
- return r;
- end)..'0000'):gsub('%d%d%d?%d?%d?%d?', function(x)
- if (#x < 6) then return '' end
- local c=0
- for i=1,6 do c=c+(x:sub(i,i)=='1' and 2^(6-i) or 0) end
- return b:sub(c+1,c+1)
- end)..({ '', '==', '=' })[#data%3+1])
- end
- -- decoding
- function dec(data)
- data = string.gsub(data, '[^'..b..'=]', '')
- return (data:gsub('.', function(x)
- if (x == '=') then return '' end
- local r,f='',(b:find(x)-1)
- for i=6,1,-1 do r=r..(f%2^i-f%2^(i-1)>0 and '1' or '0') end
- return r;
- end):gsub('%d%d%d?%d?%d?%d?%d?%d?', function(x)
- if (#x ~= 8) then return '' end
- local c=0
- for i=1,8 do c=c+(x:sub(i,i)=='1' and 2^(8-i) or 0) end
- return string.char(c)
- end))
- end
- function urlencode(str)
- if (str) then
- str = string.gsub (str, "\n", "\r\n")
- str = string.gsub (str, "([^%w ])",
- function (c) return string.format ("%%%02X", string.byte(c)) end)
- str = string.gsub (str, " ", "+")
- end
- return str
- end
- ---------------------------------------------------------------------
- -- ReceivedFromProxy Code
- ---------------------------------------------------------------------
- --[[
- ReceivedFromProxy(idBinding, sCommand, tParams)
- Function called by Director when a proxy bound to the specified binding sends a
- BindMessage to the DriverWorks driver.
- Parameters
- idBinding
- Binding ID of the proxy that sent a BindMessage to the DriverWorks driver.
- sCommand
- Command that was sent
- tParams
- Lua table of received command parameters
- --]]
- function ReceivedFromProxy(idBinding, sCommand, tParams)
- if (sCommand ~= nil) then
- if(tParams == nil) -- initial table variable if nil
- then tParams = {}
- end
- Dbg:Trace("ReceivedFromProxy(): " .. sCommand .. " on binding " .. idBinding .. "; Call Function " .. sCommand .. "()")
- Dbg:Info(tParams)
- if (PRX_CMD[sCommand]) ~= nil then
- PRX_CMD[sCommand](idBinding, tParams)
- else
- Dbg:Alert("ReceivedFromProxy: Unhandled command = " .. sCommand)
- end
- end
- end
- ---------------------------------------------------------------------
- -- Notification Code
- ---------------------------------------------------------------------
- -- notify with parameters
- function SendNotify(notifyText, Parms, bindingID)
- C4:SendToProxy(bindingID, notifyText, Parms, "NOTIFY")
- end
- -- A notify with no parameters
- function SendSimpleNotify(notifyText, ...)
- bindingID = select(1, ...) or DEFAULT_PROXY_BINDINGID
- C4:SendToProxy(bindingID, notifyText, {}, "NOTIFY")
- end
- ---------------------------------------------------------------------
- -- Initialization/Destructor Code
- ---------------------------------------------------------------------
- --[[
- OnDriverInit
- Invoked by director when a driver is loaded. This API is provided for the driver developer to contain all of the driver
- objects that will require initialization.
- --]]
- function OnDriverInit()
- C4:ErrorLog("INIT_CODE: OnDriverInit()")
- -- Call all ON_DRIVER_EARLY_INIT functions.
- for k,v in pairs(ON_DRIVER_EARLY_INIT) do
- if (ON_DRIVER_EARLY_INIT[k] ~= nil and type(ON_DRIVER_EARLY_INIT[k]) == "function") then
- C4:ErrorLog("INIT_CODE: ON_DRIVER_EARLY_INIT." .. k .. "()")
- ON_DRIVER_EARLY_INIT[k]()
- end
- end
- -- Call all ON_DRIVER_INIT functions
- for k,v in pairs(ON_DRIVER_INIT) do
- if (ON_DRIVER_INIT[k] ~= nil and type(ON_DRIVER_INIT[k]) == "function") then
- C4:ErrorLog("INIT_CODE: ON_DRIVER_INIT." .. k .. "()")
- ON_DRIVER_INIT[k]()
- end
- end
- -- Fire OnPropertyChanged to set the initial Headers and other Property global sets, they'll change if Property is changed.
- for k,v in pairs(Properties) do
- OnPropertyChanged(k)
- end
- end
- --[[
- OnDriverUpdate
- Invoked by director when an update to a driver is requested. This request can occur either by adding a new version of a driver
- through the driver search list or right clicking on the driver and selecting "Update Driver" from within ComposerPro.
- Its purpose is to initialize all components of the driver that are reset during a driver update.
- --]]
- function OnDriverUpdate()
- C4:ErrorLog("INIT_CODE: OnDriverUpdate()")
- -- Call all ON_DRIVER_UPDATE functions
- for k,v in pairs(ON_DRIVER_UPDATE) do
- if (ON_DRIVER_UPDATE[k] ~= nil and type(ON_DRIVER_UPDATE[k]) == "function") then
- C4:ErrorLog("INIT_CODE: ON_DRIVER_UPDATE." .. k .. "()")
- ON_DRIVER_UPDATE[k]()
- end
- end
- end
- --[[
- OnDriverLateInit
- Invoked by director after all drivers in the project have been loaded. This API is provided
- for the driver developer to contain all of the driver objects that will require initialization
- after all drivers in the project have been loaded.
- --]]
- function OnDriverLateInit()
- C4:ErrorLog("INIT_CODE: OnDriverLateInit()")
- -- Call all ON_DRIVER_LATEINIT functions
- for k,v in pairs(ON_DRIVER_LATEINIT) do
- if (ON_DRIVER_LATEINIT[k] ~= nil and type(ON_DRIVER_LATEINIT[k]) == "function") then
- C4:ErrorLog("INIT_CODE: ON_DRIVER_LATEINIT." .. k .. "()")
- ON_DRIVER_LATEINIT[k]()
- end
- end
- end
- --[[
- OnDriverDestroyed
- Function called by Director when a driver is removed. Release things this driver has allocated such as timers.
- --]]
- function OnDriverDestroyed()
- C4:ErrorLog("INIT_CODE: OnDriverDestroyed()")
- -- Call all ON_DRIVER_DESTROYED functions
- for k,v in pairs(ON_DRIVER_DESTROYED) do
- if (ON_DRIVER_DESTROYED[k] ~= nil and type(ON_DRIVER_DESTROYED[k]) == "function") then
- C4:ErrorLog("INIT_CODE: ON_DRIVER_DESTROYED." .. k .. "()")
- ON_DRIVER_DESTROYED[k]()
- end
- end
- end
- --=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
- -- Debug Logging Code
- --=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
- Log = {}
- -- Create a Table with Logging functions
- function Log:Create()
- -- table for logging functions
- local lt = {}
- lt._logLevel = 0
- lt._outputPrint = false
- lt._outputC4Log = false
- lt._logName = "Set Log Name to display"
- function lt:SetLogLevel(level)
- self._logLevel = level
- end
- function lt:OutputPrint(value)
- self._outputPrint = value
- end
- function lt:OutputC4Log(value)
- self._outputC4Log = value
- end
- function lt:SetLogName(name)
- self._logName = name
- end
- function lt:Enabled()
- return (self._outputPrint or self._outputC4Log)
- end
- function lt:PrintTable(tValue, sIndent)
- if (type(tValue) == "table") then
- if (self._outputPrint) then
- for k,v in pairs(tValue) do
- print(sIndent .. tostring(k) .. ": " .. tostring(v))
- if (type(v) == "table") then
- self:PrintTable(v, sIndent .. " ")
- end
- end
- end
- if (self._outputC4Log) then
- for k,v in pairs(tValue) do
- C4:ErrorLog(self._logName .. ": " .. sIndent .. tostring(k) .. ": " .. tostring(v))
- if (type(v) == "table") then
- self:PrintTable(v, sIndent .. " ")
- end
- end
- end
- else
- if (self._outputPrint) then
- print (sIndent .. tValue)
- end
- if (self._outputC4Log) then
- C4:ErrorLog(self._logName .. ": " .. sIndent .. tValue)
- end
- end
- end
- function lt:Print(logLevel, sLogText)
- if (self._logLevel >= logLevel) then
- if (type(sLogText) == "table") then
- self:PrintTable(sLogText, " ")
- return
- end
- if (self._outputPrint) then
- print (sLogText)
- end
- if (self._outputC4Log) then
- C4:ErrorLog(self._logName .. ": " .. sLogText)
- end
- end
- end
- function lt:Alert(strDebugText)
- self:Print(0, strDebugText)
- end
- function lt:Error(strDebugText)
- self:Print(1, strDebugText)
- end
- function lt:Warn(strDebugText)
- self:Print(2, strDebugText)
- end
- function lt:Info(strDebugText)
- self:Print(3, strDebugText)
- end
- function lt:Trace(strDebugText)
- self:Print(4, strDebugText)
- end
- function lt:Debug(strDebugText)
- self:Print(5, strDebugText)
- end
- return lt
- end
- function ON_DRIVER_EARLY_INIT.LogLib()
- -- Create and initialize debug logging
- Dbg = Log.Create()
- Dbg:SetLogName("base_template PLEASE CHANGE")
- end
- function ON_DRIVER_INIT.LogLib()
- -- Create Debug Timer
- gDebugTimer = Timer:Create("Debug", 45, "MINUTES", OnDebugTimerExpired)
- end
- --[[
- OnDebugTimerExpired
- Debug timer callback function
- --]]
- function OnDebugTimerExpired()
- Dbg:Warn("Turning Debug Mode Off (timer expired)")
- gDebugTimer:KillTimer()
- C4:UpdateProperty("Debug Mode", "Off")
- OnPropertyChanged("Debug Mode")
- end
- ---------------------------------------------------------------------
- -- Timer Code
- ---------------------------------------------------------------------
- Timer = {}
- -- Create a Table with Timer functions
- function Timer:Create(name, interval, units, Callback, repeating, Info)
- -- timers table
- local tt = {}
- tt._name = name
- tt._timerID = TimerLibGetNextTimerID()
- tt._interval = interval
- tt._units = units
- tt._repeating = repeating or false
- tt._Callback = Callback
- tt._info = Info or ""
- tt._id = 0
- function tt:StartTimer(...)
- self:KillTimer()
- -- optional parameters (interval, units, repeating)
- if ... then
- local interval = select(1, ...)
- local units = select(2, ...)
- local repeating = select(3, ...)
- self._interval = interval or self._interval
- self._units = units or self._units
- self._repeating = repeating or self._repeating
- end
- if (self._interval > 0) then
- Dbg:Trace("Starting Timer: " .. self._name)
- self._id = C4:AddTimer(self._interval, self._units, self._repeating)
- end
- end
- function tt:KillTimer()
- if (self._id) then
- self._id = C4:KillTimer(self._id)
- end
- end
- function tt:TimerStarted()
- return (self._id ~= 0)
- end
- function tt:TimerStopped()
- return not self:TimerStarted()
- end
- gTimerLibTimers[tt._timerID] = tt
- Dbg:Trace("Created timer " .. tt._name)
- return tt
- end
- function TimerLibGetNextTimerID()
- gTimerLibTimerCurID = gTimerLibTimerCurID + 1
- return gTimerLibTimerCurID
- end
- function ON_DRIVER_EARLY_INIT.TimerLib()
- gTimerLibTimers = {}
- gTimerLibTimerCurID = 0
- end
- function ON_DRIVER_DESTROYED.TimerLib()
- -- Kill open timers
- for k,v in pairs(gTimerLibTimers) do
- v:KillTimer()
- end
- end
- --[[
- OnTimerExpired
- Function called by Director when the specified Control4 timer expires.
- Parameters
- idTimer
- Timer ID of expired timer.
- --]]
- function OnTimerExpired(idTimer)
- for k,v in pairs(gTimerLibTimers) do
- if (idTimer == v._id) then
- if (v._Callback) then
- v._Callback(v._info)
- end
- end
- end
- end
- --kopirana biblioteka
- local encode
- local escape_char_map = {
- [ "\\" ] = "\\\\",
- [ "\"" ] = "\\\"",
- [ "\b" ] = "\\b",
- [ "\f" ] = "\\f",
- [ "\n" ] = "\\n",
- [ "\r" ] = "\\r",
- [ "\t" ] = "\\t",
- }
- local escape_char_map_inv = { [ "\\/" ] = "/" }
- for k, v in pairs(escape_char_map) do
- escape_char_map_inv[v] = k
- end
- local function escape_char(c)
- return escape_char_map[c] or string.format("\\u%04x", c:byte())
- end
- local function encode_nil(val)
- return "null"
- end
- local function encode_table(val, stack)
- local res = {}
- stack = stack or {}
- -- Circular reference?
- if stack[val] then error("circular reference") end
- stack[val] = true
- if val[1] ~= nil or next(val) == nil then
- -- Treat as array -- check keys are valid and it is not sparse
- local n = 0
- for k in pairs(val) do
- if type(k) ~= "number" then
- error("invalid table: mixed or invalid key types")
- end
- n = n + 1
- end
- if n ~= #val then
- error("invalid table: sparse array")
- end
- -- Encode
- for i, v in ipairs(val) do
- table.insert(res, encode(v, stack))
- end
- stack[val] = nil
- return "[" .. table.concat(res, ",") .. "]"
- else
- -- Treat as an object
- for k, v in pairs(val) do
- if type(k) ~= "string" then
- error("invalid table: mixed or invalid key types")
- end
- table.insert(res, encode(k, stack) .. ":" .. encode(v, stack))
- end
- stack[val] = nil
- return "{" .. table.concat(res, ",") .. "}"
- end
- end
- local function encode_string(val)
- return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
- end
- --function encode_string(val)
- -- return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
- --end
- local function encode_number(val)
- -- Check for NaN, -inf and inf
- if val ~= val or val <= -math.huge or val >= math.huge then
- error("unexpected number value '" .. tostring(val) .. "'")
- end
- return string.format("%.14g", val)
- end
- local type_func_map = {
- [ "nil" ] = encode_nil,
- [ "table" ] = encode_table,
- [ "string" ] = encode_string,
- [ "number" ] = encode_number,
- [ "boolean" ] = tostring,
- }
- encode = function(val, stack)
- local t = type(val)
- local f = type_func_map[t]
- if f then
- return f(val, stack)
- end
- error("unexpected type '" .. t .. "'")
- end
- function json_encode(val)
- return ( encode(val) )
- end
- -------------------------------------------------------------------------------
- -- Decode
- -------------------------------------------------------------------------------
- local parse
- local function create_set(...)
- local res = {}
- for i = 1, select("#", ...) do
- res[ select(i, ...) ] = true
- end
- return res
- end
- local space_chars = create_set(" ", "\t", "\r", "\n")
- local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",")
- local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u")
- local literals = create_set("true", "false", "null")
- local literal_map = {
- [ "true" ] = true,
- [ "false" ] = false,
- [ "null" ] = nil,
- }
- local function next_char(str, idx, set, negate)
- for i = idx, #str do
- if set[str:sub(i, i)] ~= negate then
- return i
- end
- end
- return #str + 1
- end
- local function decode_error(str, idx, msg)
- local line_count = 1
- local col_count = 1
- for i = 1, idx - 1 do
- col_count = col_count + 1
- if str:sub(i, i) == "\n" then
- line_count = line_count + 1
- col_count = 1
- end
- end
- error( string.format("%s at line %d col %d", msg, line_count, col_count) )
- end
- local function codepoint_to_utf8(n)
- -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa
- local f = math.floor
- if n <= 0x7f then
- return string.char(n)
- elseif n <= 0x7ff then
- return string.char(f(n / 64) + 192, n % 64 + 128)
- elseif n <= 0xffff then
- return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)
- elseif n <= 0x10ffff then
- return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,
- f(n % 4096 / 64) + 128, n % 64 + 128)
- end
- error( string.format("invalid unicode codepoint '%x'", n) )
- end
- local function parse_unicode_escape(s)
- local n1 = tonumber( s:sub(3, 6), 16 )
- local n2 = tonumber( s:sub(9, 12), 16 )
- -- Surrogate pair?
- if n2 then
- return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)
- else
- return codepoint_to_utf8(n1)
- end
- end
- local function parse_string(str, i)
- local has_unicode_escape = false
- local has_surrogate_escape = false
- local has_escape = false
- local last
- for j = i + 1, #str do
- local x = str:byte(j)
- if x < 32 then
- decode_error(str, j, "control character in string")
- end
- if last == 92 then -- "\\" (escape char)
- if x == 117 then -- "u" (unicode escape sequence)
- local hex = str:sub(j + 1, j + 5)
- if not hex:find("%x%x%x%x") then
- decode_error(str, j, "invalid unicode escape in string")
- end
- if hex:find("^[dD][89aAbB]") then
- has_surrogate_escape = true
- else
- has_unicode_escape = true
- end
- else
- local c = string.char(x)
- if not escape_chars[c] then
- decode_error(str, j, "invalid escape char '" .. c .. "' in string")
- end
- has_escape = true
- end
- last = nil
- elseif x == 34 then -- '"' (end of string)
- local s = str:sub(i + 1, j - 1)
- if has_surrogate_escape then
- s = s:gsub("\\u[dD][89aAbB]..\\u....", parse_unicode_escape)
- end
- if has_unicode_escape then
- s = s:gsub("\\u....", parse_unicode_escape)
- end
- if has_escape then
- s = s:gsub("\\.", escape_char_map_inv)
- end
- return s, j + 1
- else
- last = x
- end
- end
- decode_error(str, i, "expected closing quote for string")
- end
- local function parse_number(str, i)
- local x = next_char(str, i, delim_chars)
- local s = str:sub(i, x - 1)
- local n = tonumber(s)
- if not n then
- decode_error(str, i, "invalid number '" .. s .. "'")
- end
- return n, x
- end
- local function parse_literal(str, i)
- local x = next_char(str, i, delim_chars)
- local word = str:sub(i, x - 1)
- if not literals[word] then
- decode_error(str, i, "invalid literal '" .. word .. "'")
- end
- return literal_map[word], x
- end
- local function parse_array(str, i)
- local res = {}
- local n = 1
- i = i + 1
- while 1 do
- local x
- i = next_char(str, i, space_chars, true)
- -- Empty / end of array?
- if str:sub(i, i) == "]" then
- i = i + 1
- break
- end
- -- Read token
- x, i = parse(str, i)
- res[n] = x
- n = n + 1
- -- Next token
- i = next_char(str, i, space_chars, true)
- local chr = str:sub(i, i)
- i = i + 1
- if chr == "]" then break end
- if chr ~= "," then decode_error(str, i, "expected ']' or ','") end
- end
- return res, i
- end
- local function parse_object(str, i)
- local res = {}
- i = i + 1
- while 1 do
- local key, val
- i = next_char(str, i, space_chars, true)
- -- Empty / end of object?
- if str:sub(i, i) == "}" then
- i = i + 1
- break
- end
- -- Read key
- if str:sub(i, i) ~= '"' then
- decode_error(str, i, "expected string for key")
- end
- key, i = parse(str, i)
- -- Read ':' delimiter
- i = next_char(str, i, space_chars, true)
- if str:sub(i, i) ~= ":" then
- decode_error(str, i, "expected ':' after key")
- end
- i = next_char(str, i + 1, space_chars, true)
- -- Read value
- val, i = parse(str, i)
- -- Set
- res[key] = val
- -- Next token
- i = next_char(str, i, space_chars, true)
- local chr = str:sub(i, i)
- i = i + 1
- if chr == "}" then break end
- if chr ~= "," then decode_error(str, i, "expected '}' or ','") end
- end
- return res, i
- end
- local char_func_map = {
- [ '"' ] = parse_string,
- [ "0" ] = parse_number,
- [ "1" ] = parse_number,
- [ "2" ] = parse_number,
- [ "3" ] = parse_number,
- [ "4" ] = parse_number,
- [ "5" ] = parse_number,
- [ "6" ] = parse_number,
- [ "7" ] = parse_number,
- [ "8" ] = parse_number,
- [ "9" ] = parse_number,
- [ "-" ] = parse_number,
- [ "t" ] = parse_literal,
- [ "f" ] = parse_literal,
- [ "n" ] = parse_literal,
- [ "[" ] = parse_array,
- [ "{" ] = parse_object,
- }
- parse = function(str, idx)
- local chr = str:sub(idx, idx)
- local f = char_func_map[chr]
- if f then
- return f(str, idx)
- end
- decode_error(str, idx, "unexpected character '" .. chr .. "'")
- end
- function json_decode(str)
- if type(str) ~= "string" then
- error("expected argument of type string, got " .. type(str))
- end
- local res, idx = parse(str, next_char(str, 1, space_chars, true))
- idx = next_char(str, idx, space_chars, true)
- if idx <= #str then
- decode_error(str, idx, "trailing garbage")
- end
- return res
- end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement