Lua 5.1.4: pretty.lua


L0001    -- pretty.lua
L0002    -- Formating C code using tags from exuberant ctags.
L0003    -- You will first need to generate a tags file with line number info, e.g
L0004    -- $ ctags -n *.c *.h
L0005    -- Requires Penlight.
L0006    --
L0007    -- Steve Donovan, 2010, X11/MIT
L0008    
L0009    require 'pl'
L0010    
L0011    local url_shortcuts = {
L0012        manl = 'doc/manual.html#pdf-',
L0013        manc = 'doc/manual.html#',
L0014        wiki = 'http://lua-users.org/wiki/'
L0015    }
L0016    
L0017    function read_annotations(file)
L0018        file = file or 'annotations.txt'
L0019        local anot = {}
L0020        local f = io.open(file)
L0021        local line = f:read();
L0022        while line do
L0023            local file,sym = line:match('#%s+([^:]+):(.*)')
L0024            if not anot[file] then anot[file] = {} end
L0025            line = f:read()
L0026            local body = List()
L0027            while line and not line:match '^#' do
L0028                body:append(line)
L0029                line = f:read()
L0030            end
L0031            body:remove(#body)
L0032            body = body:join '\n'
L0033            if #sym == 0 then
L0034                anot[file]["#file"] = body
L0035            else
L0036                anot[file][sym] = body
L0037            end
L0038        end
L0039        f:close()
L0040        return anot
L0041    end
L0042    
L0043    local append = table.insert
L0044    
L0045    function readtags(file)
L0046        file = file or 'tags'
L0047        local f = io.open(file)
L0048        local symbols = {}    
L0049    
L0050        -- skip the comment header
L0051        local line = f:read()
L0052        while line:find '^!' do
L0053            line = f:read()
L0054        end
L0055    
L0056        local k = 1
L0057        for line in f:lines() do
L0058            local sym,file,lno,tp,rest = line:match('([%w_]+)\t(%S+)\t(%S+)\t(%a)(.*)')
L0059            lno = lno:match '(%d+);"'
L0060            if lno then lno = tonumber(lno) end
L0061            -- generally more than one entry per symbol
L0062            if not symbols[sym] then symbols[sym] = {} end
L0063            append(symbols[sym],{name=sym,file=file,lno=lno,tp=tp, ts=k })
L0064            k = k + 1
L0065        end
L0066    
L0067        f:close()
L0068    
L0069        return symbols
L0070    end
L0071    
L0072    function get_file_refs(syms,file)
L0073        -- need all symbols contained within this file
L0074        local file_refs = {}
L0075        for name,entries in pairs(syms) do
L0076            for _,entry in ipairs(entries) do
L0077                if entry.file == file then
L0078                    file_refs[name] = entry
L0079                end
L0080            end
L0081        end
L0082        return file_refs
L0083    end
L0084    
L0085    function find_refs(syms,fname)
L0086        local res = List()
L0087        for name,entries in pairs(syms) do for _,entry in ipairs(entries) do
L0088            if entry.refs then
L0089                for rname,e in pairs(entry.refs) do
L0090                    if rname == fname then res:append(entry) end
L0091                end                
L0092            end
L0093        end end
L0094        return res
L0095    end
L0096    
L0097    
L0098    local header = [[
L0099    <html>
L0100    <head>
L0101    <link rel='stylesheet' type='text/css' href='style.css'></link>
L0102    <body>
L0103    ]]
L0104    
L0105    local footer = [[
L0106    <hr/>
L0107    Generated by <a href="pretty.lua.html">pretty.lua</html>
L0108    </body></html>
L0109    ]]
L0110    
L0111    local syms = readtags()
L0112    local anot = read_annotations()
L0113    
L0114    function do_refs(file)
L0115        local file_refs = get_file_refs(syms,file)
L0116        print('refs',file)
L0117        local res = List()
L0118        res:append(header)
L0119        res:append (('<h1>Lua 5.1.4: %s references</h1>\n<hr/>\n'):format(file))    
L0120        -- for all the symbols referenced in this file.....
L0121        for name,entry in pairs(file_refs) do
L0122            local refs = find_refs(syms,name)
L0123            if refs and #refs > 0 then
L0124                res:append(('<a name="%s"/><h3>%s</h3>\n<ul>'):format(name,name))
L0125                for _,ref in ipairs(refs) do
L0126                    res:append(('<li><a href="%s.html#%s">%s</a> in %s</li>\n'):format(
L0127                                            ref.file,ref.name,ref.name,ref.file) )
L0128                end
L0129                res:append '</ul>\n'
L0130            end
L0131        end
L0132        res:append(footer)
L0133        utils.writefile(file..'.ref.html',res:join '')
L0134    end
L0135    
L0136    local lbrack,rbrack = '\001','\002'
L0137    local escaped_chars = {
L0138        ['&'] = '&amp;',
L0139        ['<'] = '&lt;',
L0140        ['>'] = '&gt;',
L0141        [lbrack] = '<',
L0142        [rbrack] = '>',
L0143    }
L0144    local escape_pat = '[&<>'..lbrack..rbrack..']'
L0145    
L0146    local function escape(str)
L0147        str = str:gsub('<%a+:[^>]->',function(ref)
L0148            local url
L0149            ref = ref:sub(2,-2)
L0150            local proto,rest = ref:match('^(%a+):(.+)')
L0151            if proto ~= 'http' then
L0152                local base_url = url_shortcuts[proto]
L0153                assert(base_url,"unknown url shortcut "..proto)
L0154                url = base_url .. rest
L0155            else
L0156                url = ref
L0157            end
L0158            return ('%sa href="%s"%s%s%s/a%s'):format(lbrack,url,rbrack,ref,lbrack,rbrack)    
L0159        end)
L0160        return (str:gsub(escape_pat,escaped_chars))
L0161    end
L0162    
L0163    local function span(t,val)
L0164        return ('<span class="%s">%s</span>'):format(t,val)
L0165    end
L0166    
L0167    local function link(file,ref,text)
L0168        text = text or ref
L0169        return ('<a class="L" href="%s.html#%s">%s</a>'):format(file,ref,text)           
L0170    end
L0171    
L0172    local function anchor(name)
L0173        return ('<a name="%s"/a>'):format(name)
L0174    end
L0175    
L0176    local function block(text)
L0177        return ('<div class="block">%s\n</div>'):format(escape(text))
L0178    end
L0179    
L0180    local function add_ref(fun,sym)
L0181        if not fun then return end
L0182        if not fun.refs then fun.refs = {} end    
L0183        fun.refs[sym.name] = sym
L0184    end
L0185    
L0186    function prettify_c (file)
L0187        local code = List()
L0188        local f,err = io.open(file)
L0189        if not f then return nil,err end
L0190        local aa = anot[file]
L0191    
L0192        print('reading',file, aa and 'annotated' or '')
L0193        
L0194        -- serious hack; add line numbers to the orginal source
L0195        -- (must do this because this lexical scanner is not line-driven.)
L0196        local lno = 1
L0197        for line in f:lines() do
L0198            code:append(('L%04d    %s\n'):format(lno,line))
L0199            lno = lno + 1
L0200        end
L0201        code = code:join ''
L0202    
L0203        local res = List()
L0204        res:append(header)
L0205        res:append (('<h1>Lua 5.1.4: %s</h1>\n<hr/>\n'):format(file))    
L0206        res:append '<pre>\n'
L0207    
L0208        if aa and aa['#file'] then
L0209            res:append(block(aa['#file']))
L0210        end
L0211    
L0212        local no_refs = path.extension(file) == '.lua'
L0213        local scanner = lexer.cpp
L0214        if no_refs then
L0215            scanner = lexer.lua
L0216            syms = {}
L0217        end
L0218        
L0219        local spans = {keyword=true,number=true,string=true,comment=true,prepro=true}
L0220        local curr_fun
L0221        local dcl
L0222        
L0223        for t,val in scanner(code,{},{}) do
L0224            val = escape(val)
L0225            if t == 'iden' then
L0226                -- a bit of a hack
L0227                local ll = val:match('^L(%d%d%d%d)')
L0228                if ll then
L0229                    lno = tonumber(ll)
L0230                    --val = span('lno',ll)
L0231                    if dcl then
L0232                        if aa and aa[dcl] then
L0233                            res:append(block(aa[dcl]))
L0234                        end                
L0235                        dcl = nil
L0236                    end
L0237                else
L0238                    local sym = syms[val]
L0239                    if sym then
L0240                        local e = sym[1]
L0241                        if e.file == file and e.lno == lno then
L0242                            -- hit a symbol definition
L0243                            curr_fun = e
L0244                            res:append (anchor(val))
L0245                            val = link(file..'.ref',val)
L0246                            dcl = e.name
L0247                        elseif e.tp ~= 'm' then -- ignore struct field refs
L0248                            val =  link(e.file,val)
L0249                            add_ref(curr_fun,e)
L0250                        end
L0251                    end
L0252                end
L0253                res:append(val)
L0254            elseif spans[t] then
L0255                if t == 'prepro' then
L0256                    local mname = val:match('#%s*define%s+([%w_]+)')
L0257                    if mname then
L0258                        res:append(anchor(mname))
L0259                        dcl = mname
L0260                    end
L0261                    local file = val:match('#%s*include "([^"]+)"')
L0262                    if file then
L0263                        val = link(file,'',val)
L0264                    end
L0265                end
L0266                res:append(span(t,val))
L0267            else
L0268                res:append(val)
L0269            end
L0270        end
L0271    
L0272        res:append '</pre>\n'
L0273        res:append(footer)    
L0274        utils.writefile(file..'.html',res:join '')
L0275    end
L0276    
L0277    if arg[1] then
L0278        prettify_c(arg[1])
L0279        if path.extension(arg[1]) == '.c' then
L0280            do_refs(arg[1])
L0281        end
L0282    else
L0283        local function getfiles(mask)
L0284            return dir.getfiles('.',mask):map(path.basename)        
L0285        end
L0286        local c_files = getfiles '*.c'
L0287        local h_files = getfiles '*.h'
L0288        c_files:foreach(prettify_c)
L0289        h_files:foreach(prettify_c)
L0290        -- once the main .html files are generated, can make .ref.html files
L0291        c_files:foreach(do_refs)
L0292        h_files:foreach(do_refs)
L0293    end

Generated by pretty.lua