[Date Prev][Date Next][Thread Prev][Thread Next]
[Date Index]
[Thread Index]
- Subject: Re: autocompletion code
- From: RLake@...
- Date: Mon, 26 Apr 2004 16:15:47 -0400
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