mirror of
https://github.com/grumpycoders/pcsx-redux.git
synced 2025-04-02 10:41:54 -04:00
297 lines
6.6 KiB
Lua
297 lines
6.6 KiB
Lua
--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"
|