Extending Identifier Characters

lua-users home
wiki

After some discussion of how nice it might be if '?' and '!' were legitimate characters in Lua identifiers, I realized that it's possible to do simply by changing the locale, since Lua is locale-aware. It's only necessary to create a locale whose LC_CTYPE identifies '?' and '!' as alpha.

In general, there is a problem with distributing Lua programs which use a locale other than the C locale, because it makes the files locale-dependent. However, with a bit of hackery, it's possible to replace the various Lua functions which read files with functions which are locale-aware.

The following example only causes require to be locale aware, but that's the most difficult case; replace loadfile is quite a bit easier. It assumes that files start with a standard Unix "shebang" line which looks something like:

#!/bin/env LC_CTYPE=pt_BR lua

The loader defined below checks for anything that looks like a LC_CTYPE definition in the shebang line, and sets the locale accordingly while parsing the file. This has not been tested very much, but it seemed useful enough to record.

local pkg = require"package"
local function findfile(pname, pathtype)
  local path = pkg[pathtype]
  local fname = pname:gsub("%.", "/")
  local errs = {} 
  for seg in path:gmatch"[^;]+" do
    local fname = seg:gsub("%?", fname)
    local file, err = io.open(fname)
    if file then
      return file, fname
    else
      errs[#errs+1] = ("\n\tno file '%s'"):format(fname)
    end
  end
  return nil, table.concat(errs)
end
  
local yield = coroutine.yield
local function Reader(file)
  return coroutine.wrap(function()
    local data = file:read(1)
    if data ~= '#' then
      yield(data)
      yield(file:read"*a")
    else
      file:seek("set")
      local lines = file:lines() 
      local shebang = lines()
      local ctype = shebang:match"LC_CTYPE=(%S+)"
      if ctype then os.setlocale(ctype, "ctype") end
      yield"\n"
      for l in lines do yield(l.."\n") end
    end 
  end)
end
  
local function myloader(pname)
  local file, fname = findfile(pname, "path")
  if file then
    local locale = os.setlocale(nil, "ctype")
    local chunk, err = load(Reader(file), "@"..fname)
    os.setlocale(locale, "ctype")
    file:close()
    if chunk then
      return chunk
    else
      error(("error loading '%s' from '%s':\n\t%s"):format(pname, fname, err), 0)
    end
  else return fname end
end
    
pkg.loaders[2] = myloader

RecentChanges · preferences
edit · history
Last edited July 13, 2007 6:10 am GMT (diff)