lua-users home
lua-l archive

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



A couple more comments, following up on Philippe's:

I have the hunch that iterating backwards over the string with string.sub and string.find will turn out to be quite a bit slower than just searching forwards. (Shame that there is no rfind.) These are not likely to be long strings, anyway. You can guarantee that the match is at the end by using $.

So here is one option:

---
local pathStart = string.find(s, "[%w_.]*$") -- could be [%w_.:]
local prefixStart = string.find(s, "[%w_]*$", pathStart)
local tab
if pathStart == prefixStart then
  tab = _G
 else
  tab = assert(loadstring("return " .. string.sub(s, pathStart, prefixStart - 2)))() or {} -- but see note **
end
local prefix = string.sub(s, prefixStart)
---

The difference between the two patterns is that the first accepts dots and the second doesn't. If they turn out to return the same string, then it must be because there were no dots. Hence the if statement. Note that both search patterns are guaranteed to find something (the pattern matches the empty string at the end of the input); in this case, pathStart and prefixStart will both be string.len(s)+1, and so tab will be assigned to _G (I personally prefer getfenv() though) and prefix will be the empty string.

And here is another alternative, which doesn't rely on compiling and executing code, but may not be reliable either:

---
local _, _, path = string.find(s, "([%w_.]*)$")
if not string.find(path, "^[^.]") then return nil end
  -- fails if path is empty or starts with a .
local tab, prefixStart = getfenv()
for w, e in string.gfind(path, "([%w_]*)%.()") do
  local nextTab = tab[w]    -- see note **
  if type(nextTab) ~= "table" then return nil end
    -- intermediate table didn't exist or isn't a table
  tab, prefixStart = nextTab, e
end
-- prefixStart is just after the last . found
local prefix = string.sub(path, prefixStart)
---

There are a few problems, still. The obvious one is that this will only work with global variables; locals will not be autocompleted. (And use of locals really ought to be encouraged.) Beyond that, there is the problem that somewhere along the path there might be a table with an __index metamethod, hence arbitrary code might be run at the lines marked **. (In the second option, this could be avoided by replacing that line with "local nextTab = rawget(tab, w)" but avoiding metamethods might also produce misleading results.

I am assuming that this whole thing will run inside a protected call, but even so in the first version there is no guarantee that the object returned at the line marked "**" is actually a table, or even that running the code will not result in an error; if it turns out to be a userdata with a table interface, the attempt to iterate over its keys might fail in unpredictable ways (or do unpredictable things), and so on. In the second version, the explicit test will prevent autocomplete from working in the case of userdatas, but it quite possibly wouldn't work anyway.

---------------------

Also:

for k in pairs(table) do
--[[ Perhaps a bit faster, not going the regex route (and avoid two concats)...
 len = string.len(prefix) -- Before the loop
 if string.sub(k, 1, len) == prefix then
]]
 if string.find(k, "^" .. prefix .. ".+") then
   print(k)
 end
end


I don't find either of these alternatives particularly attractive.

local pat = "^" .. prefix .. "."   -- just do the concat once, and the . is redundant (but see below)
for k in pairs(tab) do
  if string.find(k, pat) then print(k) end
end


string.find will usually fail on the first character, without creating any new strings; this is likely to be faster than extracting a substring of every key (even with the length check). Precomputing the pattern is an obvious optimisation; leaving out the "+" saves scanning to the end of the key, since the only thing you care about is that the key not be the same as what is typed.

However, I think that is not correct: if the current string is a valid key, that should be included, with the addition of an appropriate character (space if it is a scalar, "(" if it is a function, "." if it is a table.) So that could be done, too:

-- table with a default value
local enders = setmetatable({["function"] = "(", table = "."}, {__index = function() return " " end})
-- this time we won't worry about the character because of the explicit test
local pat = "^" .. prefix
for k, v in pairs(tab) do
  if k == prefix then print(k .. enders[type(v)])
   elseif string.find(k, pat) then print(k)
  end
end

 

I usually find it nicer when comp