source: trunk/lua/util.lua @ 135

Revision 135, 6.6 KB checked in by reyalp, 16 months ago (diff)

use xpcall to trap errors in cli commands, show stack trace by default
add simple table average function table_amean to util

  • Property svn:eol-style set to native
Line 
1--[[
2 Copyright (C) 2010-2011 <reyalp (at) gmail dot com>
3
4  This program is free software; you can redistribute it and/or modify
5  it under the terms of the GNU General Public License version 2 as
6  published by the Free Software Foundation.
7
8  This program is distributed in the hope that it will be useful,
9  but WITHOUT ANY WARRANTY; without even the implied warranty of
10  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  GNU General Public License for more details.
12
13  You should have received a copy of the GNU General Public License
14  along with this program; if not, write to the Free Software
15  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16--]]
17--[[
18common generic lua utilities
19utilities that depend on the chdkptp api go in chdku
20]]
21local util={}
22
23--[[
24to allow overriding, e.g. for gui
25--]]
26util.util_stderr = io.stderr
27util.util_stdout = io.stdout
28
29function util.fprintf(f,...)
30        local args={...}
31        if #args == 0 or type(args[1]) ~= 'string' then
32                args[1]=''
33        end
34        f:write(string.format(unpack(args)))
35end
36
37function util.printf(...)
38        fprintf(util.util_stdout,...)
39end
40
41function util.warnf(format,...)
42        fprintf(util.util_stderr,"WARNING: " .. format,...)
43end
44
45function util.errf(format,...)
46        fprintf(util.util_stderr,"ERROR: " .. format,...)
47end
48
49function util.err_traceback(err)
50        return debug.traceback(err,2)
51end
52
53util.extend_table_max_depth = 10
54local extend_table_r
55extend_table_r = function(target,source,seen,depth) 
56        if not seen then
57                seen = {}
58        end
59        if not depth then
60                depth = 1
61        end
62        if depth > util.extend_table_max_depth then
63                error('extend_table: max depth');
64        end
65        -- a source could have references to the target, don't want to do that
66        seen[target]=true
67        if seen[source] then
68                error('extend_table: cycles');
69        end
70        seen[source]=true
71        for k,v in pairs(source) do
72                if type(v) == 'table' then
73                        if type(target[k]) ~= 'table' then
74                                target[k] = {}
75                        end
76                        extend_table_r(target[k],v,seen,depth+1)
77                else
78                        target[k]=v
79                end
80        end
81        return target
82end
83
84--[[
85copy members of source into target
86by default, not deep so any tables will be copied as references
87returns target so you can do x=extend_table({},...)
88if deep, cycles result in an error
89deep does not copy keys which are themselves tables (the key will be a reference to the original key table)
90]]
91function util.extend_table(target,source,deep)
92        if type(target) ~= 'table' then
93                error('extend_table: target not table')
94        end
95        if source == nil then -- easier handling of default options
96                return target
97        end
98        if type(source) ~= 'table' then 
99                error('extend_table: source not table')
100        end
101        if source == target then
102                error('extend_table: source == target')
103        end
104        if deep then
105                return extend_table_r(target, source)
106        else 
107                for k,v in pairs(source) do
108                        target[k]=v
109                end
110                return target
111        end
112end
113
114--[[
115does table have value in it ?
116]]
117function util.in_table(table,value)
118        if table == nil then
119                return false
120        end
121        for k,v in pairs(table) do
122                if v == value then
123                        return true
124                end
125        end
126end
127
128function util.table_amean(table)
129        if #table == 0 then
130                return nil
131        end
132        local sum = 0
133        for i=1,#table do
134                sum = sum + table[i]
135        end
136        return sum/#table
137end
138
139--[[
140very simple meta-table inheritance
141]]
142function util.mt_inherit(t,base)
143        local mt={
144                __index=function(table, key)
145                        return base[key]
146                end
147        }
148        setmetatable(t,mt)
149        return t
150end
151
152function util.hexdump(str,offset)
153        local c, result, byte
154        if not offset then
155                offset = 0
156        end
157        c = 0
158        result = ''
159        for i=1,#str do
160                if c == 0 then
161                        result = result .. string.format("%8x: ",offset)
162                end
163                result = result .. string.format("%02x ",string.byte(str,i))
164                c = c + 1
165                if c == 16 then
166                        c = 0
167                        offset = offset + 16
168                        result = result .. "| " .. string.gsub(string.sub(str,i-15,i),'[%c%z%s\128-\255]','.') .. '\n'
169                end
170        end
171        if c ~= 0 then
172                for i=1,16-c do
173                        result = result .. "-- "
174                end
175                result = result .. "| " .. string.gsub(string.sub(str,-c),'[%c%z%s\128-\255]','.')
176        end
177        return result
178end
179
180local serialize_r
181serialize_r = function(v,opts,seen,depth)
182        local vt = type(v)
183        if vt == 'nil' or  vt == 'boolean' then
184                return tostring(v)
185        elseif vt == 'number' then
186                -- camera has problems with decimal constants that would be negative
187                if opts.fix_bignum and v > 0x7FFFFFFF then
188                        return string.format("0x%x",v)
189                end
190                return tostring(v)
191        elseif vt == 'string' then
192                return string.format('%q',v)
193        elseif vt == 'table' then
194                if not depth then
195                        depth = 1
196                end
197                if depth >= opts.maxdepth then
198                        error('serialize: max depth')
199                end
200                if not seen then
201                        seen={}
202                elseif seen[v] then 
203                        if opts.err_cycle then
204                                error('serialize: cycle')
205                        else
206                                return '"cycle:'..tostring(v)..'"'
207                        end
208                end
209                seen[v] = true;
210                local r='{'
211                for k,v1 in pairs(v) do
212                        if opts.pretty then
213                                r = r .. '\n' ..  string.rep(' ',depth)
214                        end
215                        -- more compact/friendly format simple string keys
216                        -- TODO we could make integers more compact by doing array part first
217                        if type(k) == 'string' and string.match(k,'^[_%a][%a%d_]*$') then
218                                r = r .. tostring(k)
219                        else
220                                r = r .. '[' .. serialize_r(k,opts,seen,depth+1) .. ']'
221                        end
222                        r = r .. '=' .. serialize_r(v1,opts,seen,depth+1) .. ','
223                end
224                if opts.pretty then
225                        r = r .. '\n' .. string.rep(' ',depth-1)
226                end
227                r = r .. '}'
228                return r
229        elseif opts.err_type then
230                error('serialize: unsupported type ' .. vt, 2)
231        else
232                return '"'..tostring(v)..'"'
233        end
234end
235
236util.serialize_defaults = {
237        -- maximum nested depth
238        maxdepth=10,
239        -- ignore or error on various conditions
240        err_type=true, -- bad type, e.g. function, userdata
241        err_cycle=true, -- cyclic references
242        pretty=true, -- indents and newlines
243        fix_bignum=true, -- send values > 2^31 as hex, to avoid problems with camera conversion from decimal
244--      forceint=false, -- TODO convert numbers to integer
245}
246
247--[[
248serialize lua values
249options as documented above
250]]
251function util.serialize(v,opts)
252        return serialize_r(v,util.extend_table(util.extend_table({},util.serialize_defaults),opts))
253end
254
255--[[
256turn string back into lua data by executing it and returning the value
257the value is sandboxed in an empty function environment
258returns the resulting value, or false + an error message on failure
259check the message, since the serialized value might be false or nil!
260]]
261function util.unserialize(s)
262        local f,err=loadstring('return ' .. s)
263        if not f then
264                return false, err
265        end
266        setfenv(f,{}) -- empty fenv
267        local status,r=pcall(f)
268        if status then
269                return r
270        end
271        return false,r
272end
273
274--[[
275hacky hacky
276"import" values from a table into globals
277]]
278function util.import(t,names)
279        if names == nil then
280                for name,val in pairs(t) do
281                        _G[name] = val
282                end
283                return
284        end
285        for i,name in ipairs(names) do
286                if t[name] ~= nil then
287                        _G[name] = t[name]
288                end
289        end
290end
291return util
Note: See TracBrowser for help on using the repository browser.