lua-users home
lua-l archive

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


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