--lualoader, R"EOF(-- local string = string local tonumber = tonumber local setmetatable = setmetatable local error = error local ipairs = ipairs local io = io local table = table local math = math local assert = assert local tostring = tostring local type = type local insert_tab = table.insert local function meta(name, t) t = t or {} t.__name = name t.__index = t return t end local function default(t, k, def) local v = t[k] if not v then v = def or {} t[k] = v end return v end local Lexer = meta "Lexer" do local escape = { a = "\a", b = "\b", f = "\f", n = "\n", r = "\r", t = "\t", v = "\v" } local function tohex(x) return string.byte(tonumber(x, 16)) end local function todec(x) return string.byte(tonumber(x, 10)) end local function toesc(x) return escape[x] or x end function Lexer.new(name, src) local self = { name = name, src = src, pos = 1 } return setmetatable(self, Lexer) end function Lexer:__call(patt, pos) return self.src:match(patt, pos or self.pos) end function Lexer:test(patt) self:whitespace() local pos = self('^'..patt..'%s*()') if not pos then return false end self.pos = pos return true end function Lexer:expected(patt, name) if not self:test(patt) then return self:error((name or ("'"..patt.."'")).." expected") end return self end function Lexer:pos2loc(pos) local linenr = 1 pos = pos or self.pos for start, stop in self.src:gmatch "()[^\n]*()\n?" do if start <= pos and pos <= stop then return linenr, pos - start + 1 end linenr = linenr + 1 end end function Lexer:error(fmt, ...) local ln, co = self:pos2loc() return error(("%s:%d:%d: "..fmt):format(self.name, ln, co, ...)) end function Lexer:opterror(opt, msg) if not opt then return self:error(msg) end return nil end function Lexer:whitespace() local pos, c = self "^%s*()(%/?)" self.pos = pos if c == '' then return self end return self:comment() end function Lexer:comment() local pos = self "^%/%/[^\n]*\n?()" if not pos then if self "^%/%*" then pos = self "^%/%*.-%*%/()" if not pos then self:error "unfinished comment" end end end if not pos then return self end self.pos = pos return self:whitespace() end function Lexer:line_end(opt) self:whitespace() local pos = self '^[%s;]*%s*()' if not pos then return self:opterror(opt, "';' expected") end self.pos = pos return pos end function Lexer:eof() self:whitespace() return self.pos > #self.src end function Lexer:keyword(kw, opt) self:whitespace() local ident, pos = self "^([%a_][%w_]*)%s*()" if not ident or ident ~= kw then return self:opterror(opt, "''"..kw..'" expected') end self.pos = pos return kw end function Lexer:ident(name, opt) self:whitespace() local b, ident, pos = self "^()([%a_][%w_]*)%s*()" if not ident then return self:opterror(opt, (name or 'name')..' expected') end self.pos = pos return ident, b end function Lexer:full_ident(name, opt) self:whitespace() local b, ident, pos = self "^()([%a_][%w_.]*)%s*()" if not ident or ident:match "%.%.+" then return self:opterror(opt, (name or 'name')..' expected') end self.pos = pos return ident, b end function Lexer:integer(opt) self:whitespace() local ns, oct, hex, s, pos = self "^([+-]?)(0?)([xX]?)([0-9a-fA-F]+)%s*()" local n if oct == '0' and hex == '' then n = tonumber(s, 8) elseif oct == '' and hex == '' then n = tonumber(s, 10) elseif oct == '0' and hex ~= '' then n = tonumber(s, 16) end if not n then return self:opterror(opt, 'integer expected') end self.pos = pos return ns == '-' and -n or n end function Lexer:number(opt) self:whitespace() if self:test "nan%f[%A]" then return 0.0/0.0 elseif self:test "inf%f[%A]" then return 1.0/0.0 end local ns, d1, s, d2, s2, pos = self "^([+-]?)(%.?)([0-9]+)(%.?)([0-9]*)()" if not ns then return self:opterror(opt, 'floating-point number expected') end local es, pos2 = self("(^[eE][+-]?[0-9]+)%s*()", pos) if d1 == "." and d2 == "." then return self:error "malformed floating-point number" end self.pos = pos2 or pos local n = tonumber(d1..s..d2..s2..(es or "")) return ns == '-' and -n or n end function Lexer:quote(opt) self:whitespace() local q, start = self '^(["\'])()' if not start then return self:opterror(opt, 'string expected') end self.pos = start local patt = '()(\\?'..q..')%s*()' while true do local stop, s, pos = self(patt) if not stop then self.pos = start-1 return self:error "unfinished string" end self.pos = pos if s == q then return self.src:sub(start, stop-1) :gsub("\\x(%x+)", tohex) :gsub("\\(%d+)", todec) :gsub("\\(.)", toesc) end end end function Lexer:structure(opt) self:whitespace() if not self:test "{" then return self:opterror(opt, 'opening curly brace expected') end local t = {} while not self:test "}" do local pos, name, npos = self "^%s*()(%b[])()" if not pos then name = self:full_ident "field name" else self.pos = npos end self:test ":" local value = self:constant() self:test "," self:line_end "opt" t[name] = value end return t end function Lexer:array(opt) self:whitespace() if not self:test "%[" then return self:opterror(opt, 'opening square bracket expected') end local t = {} while not self:test "]" do local value = self:constant() self:test "," t[#t + 1] = value end return t end function Lexer:constant(opt) local c = self:full_ident('constant', 'opt') or self:number('opt') or self:quote('opt') or self:structure('opt') or self:array('opt') if not c and not opt then return self:error "constant expected" end return c end function Lexer:option_name() local ident if self:test "%(" then ident = self:full_ident "option name" self:expected "%)" else ident = self:ident "option name" end while self:test "%." do ident = ident .. "." .. self:ident() end return ident end function Lexer:type_name() if self:test "%." then local id, pos = self:full_ident "type name" return "."..id, pos else return self:full_ident "type name" end end end return Lexer -- )EOF"