forked from enlightenment/efl
elua: remove obsolete xgettext wrapper
This commit is contained in:
parent
f4f808f25d
commit
87a88b5685
|
@ -57,8 +57,7 @@ include Makefile_Elua_Helper.am
|
||||||
eluaappsdir = $(datadir)/elua/apps
|
eluaappsdir = $(datadir)/elua/apps
|
||||||
|
|
||||||
eluaapps_DATA = \
|
eluaapps_DATA = \
|
||||||
scripts/elua/apps/lualian.lua \
|
scripts/elua/apps/lualian.lua
|
||||||
scripts/elua/apps/xgettext.lua
|
|
||||||
|
|
||||||
EXTRA_DIST += $(eluaapps_DATA)
|
EXTRA_DIST += $(eluaapps_DATA)
|
||||||
|
|
||||||
|
@ -73,15 +72,6 @@ eluamodules_DATA = \
|
||||||
|
|
||||||
EXTRA_DIST += $(eluamodules_DATA)
|
EXTRA_DIST += $(eluamodules_DATA)
|
||||||
|
|
||||||
eluaxgettextdir = $(eluamodulesdir)/xgettext
|
|
||||||
|
|
||||||
eluaxgettext_DATA = \
|
|
||||||
scripts/elua/modules/xgettext/lexer.lua \
|
|
||||||
scripts/elua/modules/xgettext/parser.lua \
|
|
||||||
scripts/elua/modules/xgettext/generator.lua
|
|
||||||
|
|
||||||
EXTRA_DIST += $(eluaxgettext_DATA)
|
|
||||||
|
|
||||||
eluaeinadir = $(eluamodulesdir)/eina
|
eluaeinadir = $(eluamodulesdir)/eina
|
||||||
|
|
||||||
eluaeina_DATA = \
|
eluaeina_DATA = \
|
||||||
|
|
|
@ -1,428 +0,0 @@
|
||||||
-- Xgettext application
|
|
||||||
-- provides a drop-in replacement of xgettext that supports Lua (but not any
|
|
||||||
-- other language)
|
|
||||||
|
|
||||||
local util = require("util")
|
|
||||||
local cutil = require("cutil")
|
|
||||||
local getopt = require("getopt")
|
|
||||||
|
|
||||||
local generator = require("xgettext.generator")
|
|
||||||
|
|
||||||
local VERSION = "1.0.0"
|
|
||||||
|
|
||||||
local input_sources = {}
|
|
||||||
local search_dirs = {}
|
|
||||||
local excluded_files = {}
|
|
||||||
local keywords = {}
|
|
||||||
local flags = { valid = {} }
|
|
||||||
|
|
||||||
local opts_final = {}
|
|
||||||
local opts_nolua = {}
|
|
||||||
|
|
||||||
local add_loc = true
|
|
||||||
|
|
||||||
local opts, args = getopt.parse {
|
|
||||||
usage = "Usage: %prog [OPTION] [INPUTFILE]...",
|
|
||||||
args = arg,
|
|
||||||
descs = {
|
|
||||||
{ category = "Input file location" },
|
|
||||||
|
|
||||||
{ metavar = "INPUTFILE ...", help = "input files" },
|
|
||||||
{ "f", "files-from", true, metavar = "FILE",
|
|
||||||
help = "get list of input files from FILE", list = input_sources
|
|
||||||
},
|
|
||||||
{ "D", "directory", true, help = "add DIRECTORY to list for input "
|
|
||||||
.. "files search\nIf input file is -, standard input is read.",
|
|
||||||
list = search_dirs, opts = opts_nolua
|
|
||||||
},
|
|
||||||
|
|
||||||
{ category = "Output file location" },
|
|
||||||
|
|
||||||
{ "d", "default-domain", true, metavar = "NAME",
|
|
||||||
help = "use NAME.po for output (instead of messages.po)",
|
|
||||||
opts = opts_final
|
|
||||||
},
|
|
||||||
{ "o", "output", true, metavar = "FILE",
|
|
||||||
help = "write output to specified file", opts = opts_final
|
|
||||||
},
|
|
||||||
{ "p", "output-dir", true, metavar = "DIR", help = "output files "
|
|
||||||
.. "will be placed in directory DIR\nIf output file is -, "
|
|
||||||
.. "output is written to standard output.",
|
|
||||||
opts = opts_final
|
|
||||||
},
|
|
||||||
|
|
||||||
{ category = "Input file interpretation" },
|
|
||||||
|
|
||||||
{ "L", "language", true, help = false },
|
|
||||||
{ "C", "c++", false, help = false,
|
|
||||||
callback = function(desc, parser, val, opts) opts["L"] = "C++" end
|
|
||||||
},
|
|
||||||
{ nil, "from-code", true, metavar = "NAME", help = "encoding of "
|
|
||||||
.. "input files\nOnly relevant for non-Lua inputs "
|
|
||||||
.. "(Lua is always assumed to be UTF-8).", opts = opts_nolua
|
|
||||||
},
|
|
||||||
|
|
||||||
{ category = "Operation mode" },
|
|
||||||
|
|
||||||
{ "j", "join-existing", false,
|
|
||||||
help = "join messages with existing file", opts = opts_final
|
|
||||||
},
|
|
||||||
{ "x", "exclude-file", true, metavar = "FILE.po",
|
|
||||||
help = "entries from FILE.po are not extracted",
|
|
||||||
list = excluded_files
|
|
||||||
},
|
|
||||||
{ "c", "add-comments", nil, metavar = "TAG", help = "place comment "
|
|
||||||
.. "blocks (optionally starting with TAG) and preceding "
|
|
||||||
.. "keyword lines in output file", opts = opts_nolua
|
|
||||||
},
|
|
||||||
|
|
||||||
{ category = "Language specific options" },
|
|
||||||
|
|
||||||
{ "a", "extract-all", false, help = "extract all strings",
|
|
||||||
opts = opts_nolua
|
|
||||||
},
|
|
||||||
{ "k", "keyword", nil, metavar = "WORD", help = "look for WORD as an "
|
|
||||||
.. "additional keyword or if not given, do no use default keywords",
|
|
||||||
opts = opts_nolua, list = keywords
|
|
||||||
},
|
|
||||||
{ nil, "flag", true, metavar = "WORD:ARG:FLAG", help = "additional "
|
|
||||||
.. "flag for strings inside the argument number ARG of keyword WORD",
|
|
||||||
opts = opts_nolua, list = flags
|
|
||||||
},
|
|
||||||
{ "T", "trigraphs", false, help = false, opts = opts_nolua },
|
|
||||||
{ nil, "qt", false, help = false, opts = opts_nolua },
|
|
||||||
{ nil, "kde", false, help = false, opts = opts_nolua },
|
|
||||||
{ nil, "boost", false, help = false, opts = opts_nolua },
|
|
||||||
{ nil, "debug", false, help = "more detailed formatstring "
|
|
||||||
.. "recognition results", opts = opts_nolua
|
|
||||||
},
|
|
||||||
|
|
||||||
{ category = "Output details" },
|
|
||||||
|
|
||||||
{ nil, "color", nil, metavar = "WHEN", help = "use colors and other "
|
|
||||||
.. "text attributes always or if WHEN. WHEN may be 'always', "
|
|
||||||
.. "'never', 'auto', or 'html'", opts = opts_final
|
|
||||||
},
|
|
||||||
{ nil, "style", true, metavar = "STYLEFILE", help = "specify CSS "
|
|
||||||
.. "style rule file for --color", opts = opts_final
|
|
||||||
},
|
|
||||||
{ nil, "force-po", false, help = "write PO file even if empty",
|
|
||||||
opts = opts_final
|
|
||||||
},
|
|
||||||
{ "i", "indent", false, help = "write the .po file using indented "
|
|
||||||
.. "style", opts = opts_final
|
|
||||||
},
|
|
||||||
{ nil, "no-location", false, help = "do not write '#: filename:line' "
|
|
||||||
.. "lines", opts = opts_nolua,
|
|
||||||
callback = function() add_loc = false end
|
|
||||||
},
|
|
||||||
{ "n", "add-location", false, help = "generate '#: filename:line' "
|
|
||||||
.. "lines (default)", opts = opts_nolua,
|
|
||||||
callback = function() add_loc = true end
|
|
||||||
},
|
|
||||||
{ nil, "strict", false, help = "write out strict Uniforum "
|
|
||||||
.. "conforming .po file", opts = opts_final
|
|
||||||
},
|
|
||||||
{ nil, "properties-output", false, help = "write out a Java "
|
|
||||||
.. ".properties file", opts = opts_final
|
|
||||||
},
|
|
||||||
{ nil, "stringtable-output", false, help = "write out a NeXTstep/"
|
|
||||||
.. "GNUstep .strings file", opts = opts_final
|
|
||||||
},
|
|
||||||
{ "w", "width", true, metavar = "NUMBER", help = "set output page "
|
|
||||||
.. "width", opts = opts_final
|
|
||||||
},
|
|
||||||
{ nil, "no-wrap", false, "do not break long message lines, longer "
|
|
||||||
.. "than the output page width, into several lines",
|
|
||||||
opts = opts_final
|
|
||||||
},
|
|
||||||
{ "s", "sort-output", false, help = "generate sorted output",
|
|
||||||
opts = opts_final
|
|
||||||
},
|
|
||||||
{ "F", "sort-by-file", false, help = "sort output by file location",
|
|
||||||
opts = opts_final
|
|
||||||
},
|
|
||||||
{ nil, "omit-header", false, help = "don't write header with "
|
|
||||||
.. "'msgid \"\"' entry", opts = opts_final
|
|
||||||
},
|
|
||||||
{ nil, "copyright-holder", true, metavar = "STRING", help = "set "
|
|
||||||
.. "copyright holder in output", opts = opts_final
|
|
||||||
},
|
|
||||||
{ nil, "foreign-user", false, help = "omit copyright in output "
|
|
||||||
.. "for foreign user", opts = opts_final
|
|
||||||
},
|
|
||||||
{ nil, "package-name", true, metavar = "PACKAGE", help = "set package "
|
|
||||||
.. "name in output", opts = opts_final
|
|
||||||
},
|
|
||||||
{ nil, "package-version", true, metavar = "VERSION", help = "set "
|
|
||||||
.. "package version in output", opts = opts_final
|
|
||||||
},
|
|
||||||
{ nil, "msgid-bugs-address", true, metavar = "EMAIL@ADDRESS", help =
|
|
||||||
"set report address for msgid bugs", opts = opts_final
|
|
||||||
},
|
|
||||||
{ "m", "msgstr-prefix", true, metavar = "STRING", help = "use STRING "
|
|
||||||
.. "or \"\" as prefix for msgstr values", opts = opts_final
|
|
||||||
},
|
|
||||||
{ "M", "msgstr-suffix", true, metavar = "STRING", help = "use STRING "
|
|
||||||
.. "or \"\" as suffix for msgstr values", opts = opts_final
|
|
||||||
},
|
|
||||||
|
|
||||||
{ category = "Binaries" },
|
|
||||||
|
|
||||||
{ "X", "xgettext", true, metavar = "PATH", help = "path to xgettext." },
|
|
||||||
|
|
||||||
{ category = "Informative output" },
|
|
||||||
|
|
||||||
{ "h", "help", nil, help = "display this help and exit",
|
|
||||||
callback = getopt.help_cb(io.stdout)
|
|
||||||
},
|
|
||||||
{ "v", "version", false, help = "output version information and exit",
|
|
||||||
callback = function(p, d)
|
|
||||||
print("elua-xgettext (EFL) " .. VERSION)
|
|
||||||
end
|
|
||||||
},
|
|
||||||
error_cb = function(parser, msg)
|
|
||||||
io.stderr:write(msg, "\n")
|
|
||||||
getopt.help(parser, io.stderr)
|
|
||||||
end,
|
|
||||||
done_cb = function(parser, opts, args)
|
|
||||||
end
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if not opts or opts["h"] or opts["v"] then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
-- default keywords
|
|
||||||
if #keywords == 0 then
|
|
||||||
keywords = { "_", "gettext.gettext" , "gettext.dgettext:2",
|
|
||||||
"gettext.dcgettext:2" , "gettext.ngettext:1,2",
|
|
||||||
"gettext.dngettext:2,3", "gettext.dcngettext:2,3" }
|
|
||||||
end
|
|
||||||
|
|
||||||
-- transform some lists into mappings
|
|
||||||
for i = 1, #excluded_files do
|
|
||||||
excluded_files[excluded_files[i]] = true
|
|
||||||
excluded_files[i] = nil
|
|
||||||
end
|
|
||||||
for i = 1, #keywords do
|
|
||||||
local kw = keywords[i]
|
|
||||||
local kwb, specs = kw:match("^(.+):(.+)$")
|
|
||||||
local n1, n1c, n2, n2c, n3, n3c, xcmt, argnum
|
|
||||||
if not kwb then
|
|
||||||
kwb = kw
|
|
||||||
else
|
|
||||||
n1, n1c, n2, n2c, n3, n3c, xcmt
|
|
||||||
= specs:match("^(%d+)(c?),(%d+)(c?),(%d+)(c?)(.*)$")
|
|
||||||
if not n1 then
|
|
||||||
n1, n1c, n2, n2c, xcmt = specs:match("^(%d+)(c?),(%d+)(c?)(.*)$")
|
|
||||||
if not n1 then
|
|
||||||
n1, n1c, xcmt = specs:match("^(%d+)(c?)(.*)$")
|
|
||||||
if not n1 then error("invalid keyword specifier") end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
-- all matched, sanitize now
|
|
||||||
if n1c == "" then n1c = nil end
|
|
||||||
if n2c == "" then n2c = nil end
|
|
||||||
if n3c == "" then n3c = nil end
|
|
||||||
if xcmt == "" then xcmt = nil end
|
|
||||||
-- sanitize/retrieve comment and potential total number of args
|
|
||||||
if n3 and xcmt == "t" then
|
|
||||||
if n3c then error("invalid keyword specifier") end
|
|
||||||
argnum = n3
|
|
||||||
n3 = nil
|
|
||||||
elseif n2 and xcmt == "t" then
|
|
||||||
if n2c then error("invalid keyword specifier") end
|
|
||||||
argnum = n2
|
|
||||||
n2 = nil
|
|
||||||
elseif n1 and xcmt == "t" then
|
|
||||||
if n1c then error("invalid keyword specifier") end
|
|
||||||
argnum = n1
|
|
||||||
n1 = nil
|
|
||||||
elseif xcmt then
|
|
||||||
local xcmtm, rest = xcmt:match('^,"(.+)"(.*)$')
|
|
||||||
if not xcmtm then
|
|
||||||
xcmtm, rest = xcmt:match("^,'(.+)'(.*)$")
|
|
||||||
end
|
|
||||||
if xcmtm then
|
|
||||||
xcmt = xcmtm
|
|
||||||
else
|
|
||||||
rest = xcmt
|
|
||||||
xcmt = nil
|
|
||||||
end
|
|
||||||
argnum = rest:match("^,(%d+)t$")
|
|
||||||
-- the rest doesn't match either comment nor argnum nor both
|
|
||||||
if not xcmt and not argnum then
|
|
||||||
error("invalid keyword specifier")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
-- allow only one context arg
|
|
||||||
if (n1c and n2c) or (n2c and n3c) or (n1c and n3c) then
|
|
||||||
error("invalid keyword specifier")
|
|
||||||
end
|
|
||||||
-- retrieve context
|
|
||||||
local context
|
|
||||||
if n1c then
|
|
||||||
context = tonumber(n1)
|
|
||||||
n1 = n2
|
|
||||||
n2 = n3
|
|
||||||
n3 = nil
|
|
||||||
elseif n2c then
|
|
||||||
context = tonumber(n2)
|
|
||||||
n2 = n3
|
|
||||||
n3 = nil
|
|
||||||
elseif n3c then
|
|
||||||
context = tonumber(n3)
|
|
||||||
elseif n1 and n2 and n3 then -- 3 regular args, forbidden
|
|
||||||
error("invalid keyword specifier")
|
|
||||||
end
|
|
||||||
|
|
||||||
if keywords[kwb] then
|
|
||||||
error("cannot specify the same keyword more than twice")
|
|
||||||
end
|
|
||||||
|
|
||||||
-- all sanitized, store :)
|
|
||||||
keywords[kwb] = { context = context, argnum = tonumber(argnum),
|
|
||||||
xcomment = xcmt, tonumber(n1) or 1, tonumber(n2) }
|
|
||||||
keywords[i] = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
-- at least one default path
|
|
||||||
if #search_dirs == 0 then
|
|
||||||
search_dirs[1] = "."
|
|
||||||
end
|
|
||||||
|
|
||||||
local build_opt = function(opt)
|
|
||||||
local buf = {}
|
|
||||||
if opt.short then
|
|
||||||
buf[1] = "-"
|
|
||||||
buf[2] = opt.short
|
|
||||||
if opt.val then
|
|
||||||
buf[3] = opt.val
|
|
||||||
end
|
|
||||||
else
|
|
||||||
buf[1] = "--"
|
|
||||||
buf[2] = opt.long
|
|
||||||
if opt.val then
|
|
||||||
buf[3] = "="
|
|
||||||
buf[4] = opt.val
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return table.concat(buf)
|
|
||||||
end
|
|
||||||
|
|
||||||
local onlylua = opts["L"] and opts["L"]:lower() == "lua"
|
|
||||||
local neverlua = opts["L"] and opts["L"]:lower() ~= "lua"
|
|
||||||
|
|
||||||
local hasxgettext = opts["X"]
|
|
||||||
if not hasxgettext then
|
|
||||||
hasxgettext = os.getenv("XGETTEXT")
|
|
||||||
end
|
|
||||||
|
|
||||||
if not hasxgettext then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
local f = io.open(hasxgettext)
|
|
||||||
if not f then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
f:close()
|
|
||||||
|
|
||||||
local input_files = {}
|
|
||||||
for i, v in ipairs(input_sources) do
|
|
||||||
local f = io.open(v)
|
|
||||||
if f then
|
|
||||||
for line in f:lines() do
|
|
||||||
input_files[#input_files + 1] = line
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
for i, v in ipairs(args) do
|
|
||||||
input_files[#input_files + 1] = v
|
|
||||||
end
|
|
||||||
|
|
||||||
local args_nolua = {}
|
|
||||||
for i, opt in ipairs(opts_nolua) do
|
|
||||||
args_nolua[#args_nolua + 1] = build_opt(opt)
|
|
||||||
end
|
|
||||||
args_nolua[#args_nolua + 1] = "--omit-header"
|
|
||||||
args_nolua[#args_nolua + 1] = "--output=-"
|
|
||||||
args_nolua[#args_nolua + 1] = false
|
|
||||||
|
|
||||||
local found_files = {}
|
|
||||||
|
|
||||||
-- make sure all files exist first
|
|
||||||
for i, fname in ipairs(input_files) do
|
|
||||||
if fname ~= "-" and not excluded_files[fname] then
|
|
||||||
local ff = util.find_file(fname, search_dirs)
|
|
||||||
if not ff then
|
|
||||||
error(fname .. ": no such file or directory")
|
|
||||||
end
|
|
||||||
found_files[fname] = ff
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- mapping to real flags
|
|
||||||
local allowed_lua_flags = {
|
|
||||||
["lua-format" ] = true,
|
|
||||||
["pass-lua-format"] = true,
|
|
||||||
["no-lua-format" ] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
local parsed_files = {}
|
|
||||||
for i, fname in ipairs(input_files) do
|
|
||||||
if not excluded_files[fname] then
|
|
||||||
if onlylua or (not neverlua and fname:lower():match("^.+%.lua$")) then
|
|
||||||
-- parse lua files here
|
|
||||||
local fcontents, fpath
|
|
||||||
-- handle stdin if needed
|
|
||||||
if fname == "-" then
|
|
||||||
fpath, fcontents = "=stdin", io.stdin:read("*all")
|
|
||||||
else
|
|
||||||
fpath = found_files[fname]
|
|
||||||
local f = io.open(fpath, "r")
|
|
||||||
fcontents = f:read("*all")
|
|
||||||
f:close()
|
|
||||||
fpath = "@" .. fpath
|
|
||||||
end
|
|
||||||
local actual_flags = { valid = flags.valid }
|
|
||||||
for i, v in ipairs(flags) do
|
|
||||||
local word, argn, flag = v:match("([^:]+):(%d+):([%a-]+)")
|
|
||||||
if word and allowed_lua_flags[flag] then
|
|
||||||
actual_flags[#actual_flags + 1] = { word,
|
|
||||||
tonumber(argn), flag }
|
|
||||||
local ft = actual_flags[word]
|
|
||||||
if not ft then
|
|
||||||
ft = {}
|
|
||||||
actual_flags[word] = ft
|
|
||||||
end
|
|
||||||
ft[#ft + 1] = { tonumber(argn), flag }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
parsed_files[#parsed_files + 1] = generator.init(fpath, fcontents,
|
|
||||||
keywords, actual_flags, add_loc, opts)
|
|
||||||
else
|
|
||||||
args_nolua[#args_nolua] = fname
|
|
||||||
local f = assert(cutil.popenv(hasxgettext, "r",
|
|
||||||
unpack(args_nolua)))
|
|
||||||
local s = f:read("*all")
|
|
||||||
parsed_files[#parsed_files + 1] = s
|
|
||||||
f:close()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local args_final = {}
|
|
||||||
for i, opt in ipairs(opts_final) do
|
|
||||||
args_final[#args_final + 1] = build_opt(opt)
|
|
||||||
end
|
|
||||||
args_final[#args_final + 1] = "--language=PO"
|
|
||||||
args_final[#args_final + 1] = "-"
|
|
||||||
local f = assert(cutil.popenv(hasxgettext, "w", unpack(args_final)))
|
|
||||||
f:write(table.concat(parsed_files, "\n\n"))
|
|
||||||
f:close()
|
|
||||||
|
|
||||||
return true
|
|
|
@ -1,128 +0,0 @@
|
||||||
-- Elua xgettext: generator
|
|
||||||
|
|
||||||
local lexer = require("xgettext.lexer")
|
|
||||||
local parser = require("xgettext.parser")
|
|
||||||
|
|
||||||
local tconc = table.concat
|
|
||||||
|
|
||||||
local gen_comment = function(cmt)
|
|
||||||
local cmtret = {}
|
|
||||||
for line in cmt:gmatch("([^\n]+)") do
|
|
||||||
cmtret[#cmtret + 1] = "#. " .. line:match("^%s*(.+)$")
|
|
||||||
end
|
|
||||||
return tconc(cmtret, "\n")
|
|
||||||
end
|
|
||||||
|
|
||||||
local gen_message = function(str)
|
|
||||||
local mline = not not str:find("\n")
|
|
||||||
if not mline then
|
|
||||||
return '"' .. str .. '"'
|
|
||||||
end
|
|
||||||
local ret = { '""' }
|
|
||||||
for line in cmt:gmatch("([^\n]+)") do
|
|
||||||
ret[#ret + 1] = '"' .. line .. '\\n"'
|
|
||||||
end
|
|
||||||
return tconc(ret, "\n")
|
|
||||||
end
|
|
||||||
|
|
||||||
local gen_msgstr = function(str, prefix, suffix)
|
|
||||||
if not prefix and not suffix then
|
|
||||||
return '""'
|
|
||||||
end
|
|
||||||
return gen_message(tconc(prefix
|
|
||||||
and { prefix, str, suffix } or { str, suffix }))
|
|
||||||
end
|
|
||||||
|
|
||||||
local cmp_msgs = function(msg1, msg2)
|
|
||||||
return msg1[1] == msg2[1] and msg1.context == msg2.context
|
|
||||||
end
|
|
||||||
|
|
||||||
local new_msg = function(msg)
|
|
||||||
local copyflags = {}
|
|
||||||
for i, v in ipairs(msg.flags) do copyflags[#copyflags + 1] = v end
|
|
||||||
return {
|
|
||||||
msg[1], msg[2], msg.context, comments = { msg.comment },
|
|
||||||
xcomment = msg.xcomment, lines = { msg.line }, flags = copyflags
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
local gen_grouped_messages = function(ps)
|
|
||||||
local msg = ps()
|
|
||||||
local ret = { new_msg(msg) }
|
|
||||||
msg = ps()
|
|
||||||
while msg do
|
|
||||||
local found = false
|
|
||||||
for i, amsg in ipairs(ret) do
|
|
||||||
if cmp_msgs(msg, amsg) then
|
|
||||||
if not amsg[2] then
|
|
||||||
amsg[2] = msg[2]
|
|
||||||
end
|
|
||||||
amsg.lines [#amsg.lines + 1] = msg.line
|
|
||||||
amsg.comments[#amsg.comments + 1] = msg.comment
|
|
||||||
for i, v in ipairs(msg.flags) do
|
|
||||||
amsg.flags[#amsg.flags + 1] = v
|
|
||||||
end
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if not found then
|
|
||||||
ret[#ret + 1] = new_msg(msg)
|
|
||||||
end
|
|
||||||
msg = ps()
|
|
||||||
end
|
|
||||||
for i, msg in ipairs(ret) do
|
|
||||||
msg.flags = table.uniq(msg.flags)
|
|
||||||
end
|
|
||||||
return ret
|
|
||||||
end
|
|
||||||
|
|
||||||
local gen_line_info = function(chunkname, lines)
|
|
||||||
local cname = lexer.source_to_msg(chunkname)
|
|
||||||
local linestrs = {}
|
|
||||||
local linestr = "#:"
|
|
||||||
local i = 1
|
|
||||||
while i <= #lines do
|
|
||||||
local tmps = linestr .. tconc { " ", cname, ":", lines[i] }
|
|
||||||
if #tmps > 80 then
|
|
||||||
linestrs[#linestrs + 1] = linestr
|
|
||||||
linestr = "#:"
|
|
||||||
else
|
|
||||||
linestr = tmps
|
|
||||||
i = i + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
linestrs[#linestrs + 1] = linestr
|
|
||||||
return tconc(linestrs, "\n")
|
|
||||||
end
|
|
||||||
|
|
||||||
return { init = function(chunkname, input, keywords, flags, add_loc, opts)
|
|
||||||
local rets = {}
|
|
||||||
for i, msg in ipairs(gen_grouped_messages(parser.init(chunkname,
|
|
||||||
input, keywords, flags, opts))) do
|
|
||||||
local ret = {}
|
|
||||||
if msg.xcomment then
|
|
||||||
ret[#ret + 1] = gen_comment(msg.xcomment)
|
|
||||||
end
|
|
||||||
for i, cmt in ipairs(msg.comments) do
|
|
||||||
ret[#ret + 1] = gen_comment(cmt)
|
|
||||||
end
|
|
||||||
if msg.context then
|
|
||||||
ret[#ret + 1] = "msgctxt " .. gen_message(msg.context)
|
|
||||||
end
|
|
||||||
if add_loc then
|
|
||||||
ret[#ret + 1] = gen_line_info(chunkname, msg.lines)
|
|
||||||
end
|
|
||||||
ret[#ret + 1] = "msgid " .. gen_message(msg[1])
|
|
||||||
local spf, ssf = opts["m"], opts["M"]
|
|
||||||
if msg[2] then
|
|
||||||
ret[#ret + 1] = "msgid_plural " .. gen_message(msg[2])
|
|
||||||
ret[#ret + 1] = "msgstr[0] " .. gen_msgstr(msg[1], spf, ssf)
|
|
||||||
ret[#ret + 1] = "msgstr[1] " .. gen_msgstr(msg[2], spf, ssf)
|
|
||||||
else
|
|
||||||
ret[#ret + 1] = "msgstr " .. gen_msgstr(msg[1], spf, ssf)
|
|
||||||
end
|
|
||||||
rets[#rets + 1] = tconc(ret, "\n")
|
|
||||||
end
|
|
||||||
return tconc(rets, "\n\n")
|
|
||||||
end }
|
|
|
@ -1,353 +0,0 @@
|
||||||
-- Elua xgettext: lexer
|
|
||||||
|
|
||||||
local yield = coroutine.yield
|
|
||||||
local tconc = table.concat
|
|
||||||
|
|
||||||
local keywords = {
|
|
||||||
["and" ] = true, ["break" ] = true, ["do" ] = true, ["else"] = true,
|
|
||||||
["elseif" ] = true, ["end" ] = true, ["false"] = true, ["for" ] = true,
|
|
||||||
["function"] = true, ["goto" ] = true, ["if" ] = true, ["in" ] = true,
|
|
||||||
["local" ] = true, ["nil" ] = true, ["not" ] = true, ["or" ] = true,
|
|
||||||
["repeat" ] = true, ["return"] = true, ["then" ] = true, ["true"] = true,
|
|
||||||
["until" ] = true, ["while" ] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
local tokens = {
|
|
||||||
"..", "...", "==", ">=", "<=", "~=", "::",
|
|
||||||
"<name>", "<string>", "<number>", "<eof>"
|
|
||||||
}
|
|
||||||
|
|
||||||
local max_custom_len = 79
|
|
||||||
local max_fname_len = 72
|
|
||||||
local max_str_len = 63
|
|
||||||
|
|
||||||
local source_to_msg = function(source)
|
|
||||||
local c = source:sub(1, 1)
|
|
||||||
local srclen = #source
|
|
||||||
if c == "@" then
|
|
||||||
if srclen <= (max_fname_len + 1) then
|
|
||||||
return source:sub(2)
|
|
||||||
else
|
|
||||||
return "..." .. source:sub(srclen - max_fname_len + 1)
|
|
||||||
end
|
|
||||||
elseif c == "=" then
|
|
||||||
return source:sub(2, max_custom_len + 1)
|
|
||||||
else
|
|
||||||
return '[string "' .. source:sub(1, max_str_len)
|
|
||||||
.. ((srclen > max_str_len) and '..."]' or '"]')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local lex_error = function(ls, msg, tok)
|
|
||||||
msg = ("%s:%d: %s"):format(source_to_msg(ls.source), ls.line_number, msg)
|
|
||||||
if tok then
|
|
||||||
msg = msg .. " near '" .. tok .. "'"
|
|
||||||
end
|
|
||||||
error(msg, 0)
|
|
||||||
end
|
|
||||||
|
|
||||||
local syntax_error = function(ls, msg)
|
|
||||||
lex_error(ls, msg, ls.token.value or ls.token.name)
|
|
||||||
end
|
|
||||||
|
|
||||||
local next_char = function(ls)
|
|
||||||
local c = ls.reader()
|
|
||||||
ls.current = c
|
|
||||||
return c
|
|
||||||
end
|
|
||||||
|
|
||||||
local next_line = function(ls, cs)
|
|
||||||
local old = ls.current
|
|
||||||
assert(old == "\n" or old == "\r")
|
|
||||||
local c = next_char(ls)
|
|
||||||
if (c == "\n" or c == "\r") and c ~= old then
|
|
||||||
c = next_char(ls)
|
|
||||||
end
|
|
||||||
ls.line_number = ls.line_number + 1
|
|
||||||
return c
|
|
||||||
end
|
|
||||||
|
|
||||||
local read_number = function(ls, beg)
|
|
||||||
local buf = {}
|
|
||||||
if beg then buf[1] = beg end
|
|
||||||
local c = ls.current
|
|
||||||
while c == "." or c:match("%d") do
|
|
||||||
buf[#buf + 1] = c
|
|
||||||
c = next_char(ls)
|
|
||||||
end
|
|
||||||
if c == "e" or c == "E" then
|
|
||||||
buf[#buf + 1] = c
|
|
||||||
c = next_char(ls)
|
|
||||||
if c == "+" or c == "-" then
|
|
||||||
buf[#buf + 1] = c
|
|
||||||
c = next_char(ls)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
while c:match("%w") do
|
|
||||||
buf[#buf + 1] = c
|
|
||||||
c = next_char(ls)
|
|
||||||
end
|
|
||||||
local str = tconc(buf)
|
|
||||||
if not tonumber(str) then
|
|
||||||
lex_error(ls, "malformed number", str)
|
|
||||||
end
|
|
||||||
return str
|
|
||||||
end
|
|
||||||
|
|
||||||
local skip_sep = function(ls, buf)
|
|
||||||
local cnt = 0
|
|
||||||
local s = ls.current
|
|
||||||
assert(s == "[" or s == "]")
|
|
||||||
buf[#buf + 1] = s
|
|
||||||
local c = next_char(ls)
|
|
||||||
while c == "=" do
|
|
||||||
buf[#buf + 1] = c
|
|
||||||
c = next_char(ls)
|
|
||||||
cnt = cnt + 1
|
|
||||||
end
|
|
||||||
return c == s and cnt or ((-cnt) - 1)
|
|
||||||
end
|
|
||||||
|
|
||||||
local read_long_string = function(ls, sep, cmt)
|
|
||||||
local buf = {}
|
|
||||||
local c = next_char(ls)
|
|
||||||
if c == "\n" or c == "\r" then c = next_line(ls) end
|
|
||||||
while true do
|
|
||||||
if not c then
|
|
||||||
lex_error(ls, tok and cmt and "unfinished long comment"
|
|
||||||
or "unfinished long string", "<eof>")
|
|
||||||
elseif c == "]" then
|
|
||||||
local tbuf = {}
|
|
||||||
if skip_sep(ls, tbuf) == sep then
|
|
||||||
c = next_char(ls)
|
|
||||||
break
|
|
||||||
else
|
|
||||||
buf[#buf + 1] = tconc(tbuf)
|
|
||||||
end
|
|
||||||
c = ls.current
|
|
||||||
else
|
|
||||||
buf[#buf + 1] = c
|
|
||||||
c = next_char(ls)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return tconc(buf)
|
|
||||||
end
|
|
||||||
|
|
||||||
local read_string = function(ls)
|
|
||||||
local delim = ls.current
|
|
||||||
local buf = {}
|
|
||||||
local c = next_char(ls)
|
|
||||||
while c ~= delim do
|
|
||||||
if not c then lex_error(ls, "unfinished string", "<eof>")
|
|
||||||
elseif c == "\n" or c == "\r" then
|
|
||||||
lex_error(ls, "unfinished string", tconc(buf))
|
|
||||||
elseif c == "\\" then
|
|
||||||
c = next_char(ls)
|
|
||||||
if c == "n" then
|
|
||||||
buf[#buf + 1] = "\n"
|
|
||||||
else
|
|
||||||
buf[#buf + 1] = "\\" .. c
|
|
||||||
end
|
|
||||||
c = next_char(ls)
|
|
||||||
else
|
|
||||||
buf[#buf + 1] = c
|
|
||||||
c = next_char(ls)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
next_char(ls)
|
|
||||||
return tconc(buf)
|
|
||||||
end
|
|
||||||
|
|
||||||
local last_comment = false
|
|
||||||
|
|
||||||
local match_comment = function(ls, cmt)
|
|
||||||
cmt = cmt:match("^%s*(.+)%s*$")
|
|
||||||
if ls.flags[cmt] then
|
|
||||||
return "<flagcomment>", cmt
|
|
||||||
end
|
|
||||||
local lcmt = ls.lex_cmt
|
|
||||||
if not lcmt then return nil end
|
|
||||||
if type(lcmt) ~= "string" then
|
|
||||||
return "<comment>", cmt
|
|
||||||
end
|
|
||||||
lcmt = lcmt:match("^%s*(.+)%s*$")
|
|
||||||
if last_comment or cmt:sub(1, #lcmt) == lcmt then
|
|
||||||
return "<comment>", cmt
|
|
||||||
end
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
local lex_tbl = {
|
|
||||||
["\n"] = function(ls) next_line(ls) end,
|
|
||||||
[" " ] = function(ls) next_char(ls) end,
|
|
||||||
["-" ] = function(ls)
|
|
||||||
local c = next_char(ls)
|
|
||||||
if c ~= "-" then
|
|
||||||
return "-"
|
|
||||||
end
|
|
||||||
c = next_char(ls)
|
|
||||||
if c == "[" then
|
|
||||||
local sep = skip_sep(ls, {})
|
|
||||||
if sep >= 0 then
|
|
||||||
return match_comment(ls, read_long_string(ls, sep, true))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
local buf = {}
|
|
||||||
while ls.current and ls.current ~= "\n" and ls.current ~= "\r" do
|
|
||||||
buf[#buf + 1] = ls.current
|
|
||||||
next_char(ls)
|
|
||||||
end
|
|
||||||
return match_comment(ls, tconc(buf))
|
|
||||||
end,
|
|
||||||
["[" ] = function(ls)
|
|
||||||
local buf = {}
|
|
||||||
local sep = skip_sep(ls, {})
|
|
||||||
if sep >= 0 then
|
|
||||||
return "<string>", read_long_string(ls, sep)
|
|
||||||
elseif sep == -1 then
|
|
||||||
return "["
|
|
||||||
else
|
|
||||||
lex_error(ls, "invalid long string delimiter", tconc(buf))
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
["="] = function(ls)
|
|
||||||
local oc = ls.current
|
|
||||||
local c = next_char(ls)
|
|
||||||
if c ~= "=" then return c
|
|
||||||
else next_char(ls); return c .. "=" end
|
|
||||||
end,
|
|
||||||
['"' ] = function(ls)
|
|
||||||
return "<string>", read_string(ls)
|
|
||||||
end,
|
|
||||||
["." ] = function(ls)
|
|
||||||
local c = next_char(ls)
|
|
||||||
if c == "." then
|
|
||||||
c = next_char(ls)
|
|
||||||
if c == "." then
|
|
||||||
next_char(ls)
|
|
||||||
return "..."
|
|
||||||
else
|
|
||||||
return ".."
|
|
||||||
end
|
|
||||||
elseif c:match("%d") then
|
|
||||||
return "<number>", read_number(ls, ".")
|
|
||||||
else
|
|
||||||
return "."
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
["0" ] = function(ls)
|
|
||||||
return "<number>", read_number(ls)
|
|
||||||
end
|
|
||||||
}
|
|
||||||
lex_tbl["\r"] = lex_tbl["\n"]
|
|
||||||
lex_tbl["\f"] = lex_tbl[" " ]
|
|
||||||
lex_tbl["\t"] = lex_tbl[" " ]
|
|
||||||
lex_tbl["\v"] = lex_tbl[" " ]
|
|
||||||
lex_tbl["<" ] = lex_tbl["=" ]
|
|
||||||
lex_tbl[">" ] = lex_tbl["=" ]
|
|
||||||
lex_tbl["~" ] = lex_tbl["=" ]
|
|
||||||
lex_tbl["'" ] = lex_tbl['"' ]
|
|
||||||
lex_tbl["1" ] = lex_tbl["0" ]
|
|
||||||
lex_tbl["2" ] = lex_tbl["0" ]
|
|
||||||
lex_tbl["3" ] = lex_tbl["0" ]
|
|
||||||
lex_tbl["4" ] = lex_tbl["0" ]
|
|
||||||
lex_tbl["5" ] = lex_tbl["0" ]
|
|
||||||
lex_tbl["6" ] = lex_tbl["0" ]
|
|
||||||
lex_tbl["7" ] = lex_tbl["0" ]
|
|
||||||
lex_tbl["8" ] = lex_tbl["0" ]
|
|
||||||
lex_tbl["9" ] = lex_tbl["0" ]
|
|
||||||
|
|
||||||
local lex_default = function(ls)
|
|
||||||
local c = ls.current
|
|
||||||
if c == "_" or c:match("%a") then
|
|
||||||
local buf = {}
|
|
||||||
repeat
|
|
||||||
buf[#buf + 1] = c
|
|
||||||
c = next_char(ls)
|
|
||||||
if not c then break end
|
|
||||||
until not (c == "_" or c:match("%w"))
|
|
||||||
local str = tconc(buf)
|
|
||||||
if keywords[str] then
|
|
||||||
return str
|
|
||||||
end
|
|
||||||
return "<name>", str
|
|
||||||
else
|
|
||||||
next_char(ls)
|
|
||||||
return c
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local lex_main = function(ls)
|
|
||||||
yield()
|
|
||||||
while true do
|
|
||||||
local c = ls.current
|
|
||||||
if c == nil then
|
|
||||||
return "<eof>"
|
|
||||||
end
|
|
||||||
local opt = lex_tbl[c]
|
|
||||||
if opt then
|
|
||||||
local t, v = opt(ls)
|
|
||||||
if t then
|
|
||||||
last_comment = t == "<comment>"
|
|
||||||
yield(t, v)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
last_comment = false
|
|
||||||
yield(lex_default(ls))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local strstream = function(str)
|
|
||||||
return str:gmatch(".")
|
|
||||||
end
|
|
||||||
|
|
||||||
local skip_bom = function(rdr)
|
|
||||||
local c = rdr()
|
|
||||||
if c ~= 0xEF then return c end
|
|
||||||
c = rdr()
|
|
||||||
if c ~= 0xBB then return c end
|
|
||||||
c = rdr()
|
|
||||||
if c ~= 0xBF then return c end
|
|
||||||
return rdr()
|
|
||||||
end
|
|
||||||
|
|
||||||
local skip_shebang = function(rdr)
|
|
||||||
local c = skip_bom(rdr)
|
|
||||||
if c == 35 then -- #
|
|
||||||
repeat
|
|
||||||
c = rdr()
|
|
||||||
until not c or is_newline(c)
|
|
||||||
local e = c
|
|
||||||
c = rdr()
|
|
||||||
if (e == 10 and c == 13) or (e == 13 and c == 10) then -- LF, CR
|
|
||||||
c = rdr()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return c
|
|
||||||
end
|
|
||||||
|
|
||||||
local ls_get = function(self)
|
|
||||||
local tok = self.token
|
|
||||||
tok.name, tok.value = self.coro()
|
|
||||||
return tok
|
|
||||||
end
|
|
||||||
|
|
||||||
return { init = function(chunkname, input, flags, opts)
|
|
||||||
local reader = type(input) == "string" and strstream(input) or input
|
|
||||||
local current = skip_shebang(reader)
|
|
||||||
local ls = {
|
|
||||||
reader = reader,
|
|
||||||
token = {},
|
|
||||||
source = chunkname,
|
|
||||||
current = current,
|
|
||||||
line_number = 1,
|
|
||||||
get = ls_get,
|
|
||||||
lex_cmt = opts["c"],
|
|
||||||
flags = flags.valid
|
|
||||||
}
|
|
||||||
local coro = coroutine.wrap(lex_main, ls)
|
|
||||||
ls.coro = coro
|
|
||||||
coro(ls)
|
|
||||||
return ls
|
|
||||||
end, syntax_error = syntax_error, source_to_msg = source_to_msg }
|
|
|
@ -1,267 +0,0 @@
|
||||||
-- Elua xgettext: parser
|
|
||||||
|
|
||||||
local util = require("util")
|
|
||||||
local lexer = require("xgettext.lexer")
|
|
||||||
|
|
||||||
local syntax_error = lexer.syntax_error
|
|
||||||
|
|
||||||
local yield = coroutine.yield
|
|
||||||
local tconc = table.concat
|
|
||||||
|
|
||||||
local String = util.Object:clone {
|
|
||||||
__ctor = function(self, sing, plu, ctx, cmt, xcmt, flags, line)
|
|
||||||
self.singular = sing
|
|
||||||
self.plural = plu
|
|
||||||
self.context = ctx
|
|
||||||
self.comment = cmt
|
|
||||||
self.xcomment = xcmt
|
|
||||||
self.flags = flags
|
|
||||||
self.line = line
|
|
||||||
end,
|
|
||||||
|
|
||||||
guess_flags = function(self, flags)
|
|
||||||
end,
|
|
||||||
|
|
||||||
gen_flags = function(self)
|
|
||||||
local flags = {}
|
|
||||||
for i, v in ipairs(self.flags) do flags[i] = v end
|
|
||||||
if self.parent then
|
|
||||||
self.parent:add_flags(self, flags)
|
|
||||||
end
|
|
||||||
self:guess_flags(flags)
|
|
||||||
return flags
|
|
||||||
end,
|
|
||||||
|
|
||||||
generate = function(self)
|
|
||||||
yield {
|
|
||||||
self.singular, self.plural, context = self.context,
|
|
||||||
comment = self.xcomment, comment = self.comment, line = self.line,
|
|
||||||
flags = self:gen_flags()
|
|
||||||
}
|
|
||||||
end
|
|
||||||
}
|
|
||||||
|
|
||||||
local Call = util.Object:clone {
|
|
||||||
__ctor = function(self, flags, args)
|
|
||||||
self.flags = flags
|
|
||||||
self.args = args
|
|
||||||
for i, v in ipairs(args) do
|
|
||||||
v.parent = self
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
|
|
||||||
add_flags = function(self, argo, flags, flagstr)
|
|
||||||
local argn
|
|
||||||
for i, a in ipairs(self.args) do
|
|
||||||
if a == argo then
|
|
||||||
argn = i
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
for i, flag in ipairs(self.flags) do
|
|
||||||
if flag[1] == argn then
|
|
||||||
local pass = flag[2]:match("^pass%-(.+)$")
|
|
||||||
if not flagstr or flagstr == pass or flagstr == flag[2] then
|
|
||||||
if pass then
|
|
||||||
self.parent:add_flags(self, flags, pass)
|
|
||||||
else
|
|
||||||
flags[#flags + 1] = flag[2]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
|
|
||||||
generate = function(self)
|
|
||||||
for i, v in ipairs(self.args) do
|
|
||||||
v:generate()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
}
|
|
||||||
|
|
||||||
local saved_flagcomments = {}
|
|
||||||
local saved_comments = {}
|
|
||||||
|
|
||||||
local check_match = function(ls, a, b, line)
|
|
||||||
if ls.token.name ~= a then
|
|
||||||
if line == ls.line_number then
|
|
||||||
syntax_error(ls, "'" .. a .. "' expected")
|
|
||||||
else
|
|
||||||
syntax_error(ls, "'" .. a .. "' expected (to close '" .. b
|
|
||||||
.. "' at line " .. line .. ")")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local parse_simple_expr = function(ls)
|
|
||||||
local tok = ls.token
|
|
||||||
local tn = tok.name
|
|
||||||
if tn == "(" then
|
|
||||||
local line = ls.line_number
|
|
||||||
ls:get()
|
|
||||||
local v, tn = parse_expr(ls)
|
|
||||||
check_match(ls, ")", "(", line)
|
|
||||||
ls:get()
|
|
||||||
return v, tn
|
|
||||||
elseif tn == "<string>" or tn == "<number>" or tn == "<name>" then
|
|
||||||
local v = tok.value
|
|
||||||
ls:get()
|
|
||||||
return v, tn
|
|
||||||
else
|
|
||||||
syntax_error(ls, "unexpected symbol")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local parse_expr
|
|
||||||
parse_expr = function(ls)
|
|
||||||
local tok = ls.token
|
|
||||||
local line = ls.line_number
|
|
||||||
local lhs, tn = parse_simple_expr(ls)
|
|
||||||
while true do
|
|
||||||
if tok.name ~= ".." then break end
|
|
||||||
if tn ~= "<string>" and tn ~= "<number>" then
|
|
||||||
syntax_error(ls, "invalid lhs for concat")
|
|
||||||
end
|
|
||||||
tn = "<string>"
|
|
||||||
ls:get()
|
|
||||||
local rhs, rtn = parse_expr(ls)
|
|
||||||
if rtn ~= "<string>" and rtn ~= "<number>" then
|
|
||||||
syntax_error(ls, "invalid rhs for concat")
|
|
||||||
end
|
|
||||||
lhs = lhs .. rhs
|
|
||||||
end
|
|
||||||
return lhs, tn
|
|
||||||
end
|
|
||||||
|
|
||||||
local parse_arglist = function(ls)
|
|
||||||
local tok = ls.token
|
|
||||||
local rets = {}
|
|
||||||
while true do
|
|
||||||
rets[#rets + 1] = { parse_expr(ls) }
|
|
||||||
if tok.name == "," then
|
|
||||||
ls:get()
|
|
||||||
else
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return rets
|
|
||||||
end
|
|
||||||
|
|
||||||
local parse_kwcall = function(ls)
|
|
||||||
local tok = ls.token
|
|
||||||
if tok.name == "(" then
|
|
||||||
local line = ls.line_number
|
|
||||||
ls:get()
|
|
||||||
if tok.name == ")" then
|
|
||||||
ls:get()
|
|
||||||
return {}
|
|
||||||
end
|
|
||||||
local al = parse_arglist(ls)
|
|
||||||
check_match(ls, ")", "(", line)
|
|
||||||
ls:get()
|
|
||||||
return al
|
|
||||||
elseif tok.name == "<string>" then
|
|
||||||
local v = tok.value
|
|
||||||
ls:get()
|
|
||||||
return { { v, "<string>" } }
|
|
||||||
else
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local parse_kw = function(ls, keywords)
|
|
||||||
local tok = ls.token
|
|
||||||
local line = ls.line_number
|
|
||||||
local kw = keywords[tok.value]
|
|
||||||
ls:get()
|
|
||||||
local args = parse_kwcall(ls)
|
|
||||||
local n1, n2, cx, an = kw[1], kw[2], kw.context, kw.argnum
|
|
||||||
local n1arg, n2arg, cxarg = args[n1], args[n2], args[cx]
|
|
||||||
local n1argt, n2argt, cxargt = n1arg and (n1arg[2] ~= "<name>"),
|
|
||||||
n2arg and (n2arg[2] ~= "<name>"),
|
|
||||||
cxarg and (cxarg[2] ~= "<name>")
|
|
||||||
if not args then return false end
|
|
||||||
if an and #args ~= an then return false end
|
|
||||||
if #args < n1 then return false end
|
|
||||||
if n2 and #args < n2 then return false end
|
|
||||||
if cx and #args < cx then return false end
|
|
||||||
if not n1argt then return false end
|
|
||||||
if n2 and not n2argt then return false end
|
|
||||||
if cx and not cxargt then return false end
|
|
||||||
local sc = saved_comments
|
|
||||||
saved_comments = {}
|
|
||||||
sc = tconc(sc, "\n")
|
|
||||||
local fsc = saved_flagcomments
|
|
||||||
saved_flagcomments = {}
|
|
||||||
return String(n1arg[1], n2 and n2arg[1] or nil, cx and cxarg[1] or nil,
|
|
||||||
sc, kw.xcomment, fsc, line)
|
|
||||||
end
|
|
||||||
|
|
||||||
local parse_fg = function(ls, flags, keywords)
|
|
||||||
error("NYI")
|
|
||||||
end
|
|
||||||
|
|
||||||
local parse = function(ls, keywords, flags)
|
|
||||||
yield()
|
|
||||||
local tok = ls.token
|
|
||||||
while tok.name ~= "<eof>" do
|
|
||||||
if tok.name == "<comment>" then
|
|
||||||
saved_comments[#saved_comments + 1] = tok.value
|
|
||||||
ls:get()
|
|
||||||
elseif tok.name == "<flagcomment>" then
|
|
||||||
saved_flagcomments[#saved_flagcomments + 1] = tok.value
|
|
||||||
ls:get()
|
|
||||||
elseif tok.name == "<name>" then
|
|
||||||
if keywords[tok.value] then
|
|
||||||
local status, str = pcall(parse_kw, keywords)
|
|
||||||
if status and str then
|
|
||||||
str:generate()
|
|
||||||
end
|
|
||||||
elseif flags[tok.value] then
|
|
||||||
local status, call = pcall(parse_fg, flags, keywords)
|
|
||||||
if status then
|
|
||||||
call:generate()
|
|
||||||
end
|
|
||||||
else
|
|
||||||
ls:get()
|
|
||||||
end
|
|
||||||
else
|
|
||||||
ls:get()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local parse_all = function(ls)
|
|
||||||
yield()
|
|
||||||
local tok = ls.token
|
|
||||||
while tok.name ~= "<eof>" do
|
|
||||||
if tok.name == "<comment>" then
|
|
||||||
saved_comments[#saved_comments + 1] = tok.value
|
|
||||||
ls:get()
|
|
||||||
elseif tok.name == "<flagcomment>" then
|
|
||||||
saved_flagcomments[#saved_flagcomments + 1] = tok.value
|
|
||||||
ls:get()
|
|
||||||
elseif tok.name == "<string>" then
|
|
||||||
local line = ls.line_number
|
|
||||||
local val = tok.value
|
|
||||||
local sc = saved_comments
|
|
||||||
saved_comments = {}
|
|
||||||
sc = tconc(sc, "\n")
|
|
||||||
local fsc = saved_flagcomments
|
|
||||||
saved_flagcomments = {}
|
|
||||||
ls:get()
|
|
||||||
String(val, nil, nil, sc, nil, fsc, line):generate()
|
|
||||||
else
|
|
||||||
ls:get()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return { init = function (chunkname, input, keywords, flags, opts)
|
|
||||||
local ls = lexer.init(chunkname, input, flags, opts)
|
|
||||||
ls:get()
|
|
||||||
local coro = coroutine.wrap(opts["a"] and parse_all or parse, ls,
|
|
||||||
keywords, flags)
|
|
||||||
coro(ls, keywords)
|
|
||||||
return coro
|
|
||||||
end }
|
|
Loading…
Reference in New Issue