Hi all,
So here's a little problem that some of you might enjoy playing with
in case you're bored.
Let's play "variable expansion". We want to convert "Hello, $planet!"
to "Hello, Earth!" given { planet = "Earth" }, but we also want it to
support "Hello, ${planet}!".
That is, we want to perform "variable expansion" in a string s, so
that given a table of "variables" we want to expand every $foo or
${foo} in s to the value of variables.foo.
HOWEVER, we don't want recursive expansion (ie, if the value of
variables.foo is "$bar", then we _don't_ want it expanded to the value
of variables.bar).
For simplicity, assume that variable names are in the form [A-Za-z0-9_]+
Here's a test script for you to play with:
-----------------------------------------
local variables = {
foo = "$bar",
foo_bar = "blabla",
wee = "${foo}",
bar = "foo",
}
local function expand_variables(str)
-- TODO
end
local tests = {
{ from = "hello $foo", to = "hello $bar" },
{ from = "hello ${foo}", to = "hello $bar" },
{ from = "hello $wee", to = "hello ${foo}" },
{ from = "hello$foo_bar $foo", to = "helloblabla $bar" },
{ from = "hel${bar}lo", to = "helfoolo" },
{ from = "hel${foo_bar}lo", to = "helblablalo" },
{ from = "${foo_bar}", to = "blabla" },
{ from = "end${bar}", to = "endfoo" },
{ from = "${bar}end", to = "fooend" },
{ from = "end$foo", to = "end$bar" },
-- At your discretion: how to deal with...
-- "$fooend"
-- "${}"
-- "${bo{bozo}zo}"
-- "${bo${bar}zo}"
}
for _, test in ipairs(tests) do
local exp = expand_variables(test.from)
if exp == test.to then
print("OK", test.from, exp)
else
print("ERROR", test.from, test.to, exp)
end
end
-----------------------------------------
Note that the naive approach of chaining two str:gsub() calls like
this won't work:
expand_variables = function(str)
return str:gsub("%$([A-Za-z0-9_]+)",
variables):gsub("%${([A-Za-z0-9_]+)}", variables)
end
...because this will make the second or third test case fail
(depending on which you expand first).
My first attempt at doing this was the following:
local function expand_variables(str)
return str:gsub("%$([A-Za-z0-9_{}]+)", function(cap)
local br, rest = cap:match("^{([^{}]+)}(.*)")
if br then return (variables[br] or "") .. rest end
local nobr, rest = cap:match("^([^{}]+)(.*)")
return (variables[nobr] or "") .. (rest or "")
end)
end
which I then slightly changed to the following because of the test
cases commented above:
local function expand_variables(str)
return str:gsub("%$([A-Za-z0-9_{}]+)", function(cap)
local br, rest = cap:match("^{([^{}]+)}(.*)")
if br then return (variables[br] or "") .. rest end
local nobr, rest = cap:match("^([^{}]+)(.*)")
if nobr then return (variables[nobr] or "") .. rest end
return "$"..cap
end)
end
Can you suggest any nicer and/or shorter solution?
Ah! NO LPEG ALLOWED! :)
Cheers,
-- Hisham