lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


On 04/16/2014 08:39 PM, Eduardo Ochs wrote:
On Wed, Apr 16, 2014 at 3:19 AM, steve donovan
<steve.j.donovan@gmail.com>  wrote:
On Tue, Apr 15, 2014 at 10:14 PM, Leo Razoumov<slonik.az@gmail.com>  wrote:
very interesting, indeed!
'ctags' supports emacs via '-e' switch. Does 'ltags' support emacs as well?
I'm afraid not.  I looked at the file format and it seemed a relic of
some dinosaur age....

But I will overcome my prejudices if someone thinks it is important.
I would _really_ like to have a better tags support for Lua in Emacs,
and I would put it to use immediately to index tons of things - my
scripts, plus many rocks...

There are at least a handful of other Emacs users here on the list -
the lua-mode for Emacs is quite good, and at several points of the
past people have posted (prototype-like) hacks to Emacs's "Grand
Universal Debugger" to make it able to debug Lua too... and maybe one
of the reasons why we don't use the mailing list more to share our
stuff & findings is exactly because we don't have very good tools to
navigate other people's code - so we take more time to feel that we've
done our homework.

Please do add support for the etags file format to ltags, it is going
to be very useful!

   Cheers,
     Eduardo Ochs
     http://angg.twu.net/


I'm trying GNU GLOBAL recently. I'm mostly happy with it by now. the biggest
advantage over cscope (which I previously used) is, IMO, the ability to
incrementally update the tags database, _very fast_ even for really large codebases.
I can even hook my editor so that everytime I save my file, the tags database
is automatically updated. I have never managed to use cscope this way.

Although GLOBAL has builtin language supports only for c, c++, java, asm, and php,
it has a plugin mechanism to support other languages. it has a ctags plugin by
default, and this is how I use GLOBAL with my Lua files.

Combined with Steve's ltags, I wrote a simple plugin for gtags (part of GLOBAL),
the plugin actually acts as a binding/wrapper of gtags plugin interface for Lua.
so we can now write gtags plugin in Lua, to build tags database for _any_ file!

usage: build the c file into a shared library, and modify your gtags.conf accordingly.
for example, my gtags.conf (this file is in the termcap syntax) now contains:
--------------8x-----------------------------------
default:\
    :tc=builtin:tc=gtagslua:

gtagslua:\
    :langmap=Lua\:.lua:\
    :gtags-parser=Lua\:/home/pzc/.local/lib/gtags/gtagslua.la
--------------8x-----------------------------------

although gtags asks you to build the plugin with libtool, you don't need to really do so.
just build a shared library as usual, and drop a .la file containing the following line:

  dlname='gtagslua.so'

and it will work nicely (Linux).


some more introduction: the plugin interface contains one callback function. gtags will
call the plugin for each file of the configured file type. the plugin function receives
a param struct containing the file path and some callables to report information to gtags.

GLOBAL uses BerkeleyDB to store the tags information instead of plain text file.
the db record format is quite simple, though.

P.S. since GLOBAL is GPL, could someone please tell me is it OK to give a non-GPL plugin?

/**
 * Copyright (c) 2014 Peng Zhicheng <pengzhicheng1986@gmail.com>
 *
 * This is a simple plugin for `gtags'.
 * This file is NOT part of GNU GLOBAL.
 *
 * 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.
 * 
 * [ MIT license: http://opensource.org/licenses/mit-license.html ]
 */

#include <stdlib.h> /* for getenv() */

#include "parser.h"

#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"


static lua_State *L = NULL;

static void initialize (const struct parser_param *param);
static void wrapparam (lua_State *L, const struct parser_param *param);

/**
 * this is the gtags plugin call entry function.
 *
 * gtags calls this function on a per file basis. when first called, it
 * calls the initialize function, which would load a Lua script. the script
 * is expected to returns a parser function which takes a param object as
 * its only argument. the `initialize' function then stores the parser
 * function in the registry. the Lua script can be specified by setting
 * a environment variable GTAGALUA_PARSERSCRIPT. the default script path
 * is set by the compile time constant DEFAULT_PARSERSCRIPT.
 *
 * everytime this function is called, it retrieves the parser function
 * from the registry, then wraps the param struct into a Lua table,
 * then calls the parser function to do the parsing.
 */
#define GTAGSLUA_KEY_PARSER "gtagslua:key:parser"

void parser(const struct parser_param *param) {
   if (L == NULL) {
      initialize(param);
   }
   lua_getfield(L, LUA_REGISTRYINDEX, GTAGSLUA_KEY_PARSER);
   wrapparam(L, param);
   if(lua_pcall(L, 1, 0, 0) != LUA_OK) {
      param->die("error running parser script: %s.",
                  luaL_tolstring(L, -1, NULL));
   };
}


/* create Lua state, and load the parser script, and store it in the registry */

#define DEFAULT_PARSERSCRIPT "/usr/share/gtags/lua/parser.lua"

static void initialize (const struct parser_param *param) {
   char const *parserscript = NULL;
   L = luaL_newstate();
   if (L == NULL) {
      param->die("can't initialize Lua engine.");
   }
   luaL_openlibs(L);
   parserscript = getenv("GTAGSLUA_PARSERSCRIPT");
   if (parserscript == NULL) {
      parserscript = DEFAULT_PARSERSCRIPT;
   }
   if (luaL_dofile(L, parserscript) == LUA_OK) {
      lua_setfield(L, LUA_REGISTRYINDEX, GTAGSLUA_KEY_PARSER);
   }
   else {
      param->die("error loading parser script: %s.", parserscript);
   }
}

/**
 * these are wrapper/binding functions of the param->xxx function
 * the `param' struct is associated with these functions as an upvalue
 */

static int param_put (lua_State *L) {
   const struct parser_param *param = lua_touserdata(L, lua_upvalueindex(1));
   /* PARSER_DEF and PARSER_REF_SYM is defined as 1 and 2, respectively */
   static const char * const typeoptions[] = { "define", "reference", NULL };
   int type = 1 + luaL_checkoption(L, 1, NULL, typeoptions);
   const char *tag = luaL_checkstring(L, 2);
   int line = luaL_checkint(L, 3);
   const char *image = luaL_checkstring(L, 4);
   param->put(type, tag, line, param->file, image, param->arg);
   return 0;
}

/* helper function, call the standard library function to format messages */
static const char *formatmessage (lua_State *L) {
   lua_getglobal(L, "string");
   lua_getfield(L, -1, "format");
   lua_insert(L, 1);
   /* minus 2: the global `string' at top and the function `format' at bottom */
   lua_call(L, lua_gettop(L) - 2, 1);
   return lua_tostring(L, 1);
}

static int param_die (lua_State *L) {
   const struct parser_param *param = lua_touserdata(L, lua_upvalueindex(1));
   param->die("%s", formatmessage(L));
   return 0;
}

static int param_warning (lua_State *L) {
   const struct parser_param *param = lua_touserdata(L, lua_upvalueindex(1));
   param->warning("%s", formatmessage(L));
   return 0;
}

static int param_message (lua_State *L) {
   const struct parser_param *param = lua_touserdata(L, lua_upvalueindex(1));
   param->message("%s", formatmessage(L));
   return 0;
}

static int param_isnotfunction (lua_State *L) {
   const struct parser_param *param = lua_touserdata(L, lua_upvalueindex(1));
   const char *symbol = luaL_checkstring(L, 1);
   lua_pushboolean(L, param->isnotfunction(symbol));
   return 1;
}

/* this function leaves the wrapped object(table) on the stack */
static void wrapparam (lua_State *L, const struct parser_param *param) {
   lua_newtable(L);
   lua_pushstring(L, param->file);
   lua_setfield(L, -2, "file");
#define setwrapperfunction(fn) \
   lua_pushlightuserdata(L, (void*)param); \
   lua_pushcclosure(L, param_ ## fn, 1); \
   lua_setfield(L, -2, #fn)
   setwrapperfunction(put);
   setwrapperfunction(die);
   setwrapperfunction(warning);
   setwrapperfunction(message);
   setwrapperfunction(isnotfunction);
}

--[[
This script is to be used with the `gtagslua' plugin for gtags.
The contents here are almost verbatim copied from Steve Donovan's ltags.

ltags: [https://github.com/stevedonovan/ltags]
]]

return function (param)
    local file = param.file
    local tag_vars = true
    local skip, end_block, mod_name
    local linenumber = 0
    for line in io.lines(file) do
        linenumber = linenumber + 1
        -- skipping commentary --
        local comment = line:match '^%s*%-%-(.+)'
        skip = comment ~= nil
        if skip then
            if end_block then -- inside block comment
                if line:match (end_block) then
                    skip = false
                    end_block = nil
                end                    
            else
                local block = comment:match '%[(=*)%['
                if block then -- e.g. [=[, so hunt for ]=]
                    end_block = block:gsub('%[',']')
                else -- plain line comment
                    skip = false
                end
            end
        else
            local local_var, is_var
            local maybe_local,fname,arg = line:match '^%s*(%a*)%s*function%s+(%S+)%s*%(%s*(.+)'
            if not fname then 
                local mmod = line:match '^%s*module%s*(.+)'
                if mmod then
                    mod_name = mmod:match '^%(*[\'"](.-)[\'"]'
                    if not mod_name then
                        mod_name = file:match '([^%.\\/]+)%.lua$'
                    end
                end
                -- look for file-scope locals (we assume that everyone uses indentation)
                local_var = line:match '^local%s+(.+)'
                if local_var  and tag_vars then
                    -- not interested in actual values (for now)
                    local_var = local_var:gsub('%s*=.+','')
                    for w in local_var:gmatch('[%w_]+') do
                        param.put("define", w, linenumber, line)
                    end
                end
            else -- Houston, we have a Function
                local e = {file = file, line = line}
                e.file_scope = maybe_local == 'local'
                local tbl, sep, name = fname:match '([^.:]+)([.:])(.+)'
                if not tbl then
                    name = fname
                    e.tbl = mod_name
                else
                    e.klass = sep == ':' or arg:match 'self%s*[,%)]'
                    e.tbl = tbl
                end
                e.name = name
                param.put("define", e.name, linenumber, line)
            end
        end
    end
end