mirror of
https://github.com/appgurueu/modlib.git
synced 2024-11-25 16:53:46 +01:00
Refactor texmod:read
to pass reader around
This commit is contained in:
parent
fe7fb6aeec
commit
95159c48e8
@ -12,316 +12,319 @@ local transforms = {
|
|||||||
{flip_axis = "y", rotation_deg = 90},
|
{flip_axis = "y", rotation_deg = 90},
|
||||||
}
|
}
|
||||||
|
|
||||||
return function(read_char)
|
-- Generator readers
|
||||||
-- TODO this currently uses closures rather than passing around a "reader" object,
|
|
||||||
-- which is inconsistent with the writer and harder to port to more static languages
|
|
||||||
local level = 0
|
|
||||||
local invcube = false
|
|
||||||
local eof = false
|
|
||||||
|
|
||||||
local escapes, character
|
local gr = {}
|
||||||
|
|
||||||
local function peek()
|
function gr.png(r)
|
||||||
if eof then return end
|
r:expect":"
|
||||||
local expected_escapes = 0
|
local base64 = r:match_str"[a-zA-Z0-9+/=]"
|
||||||
if level > 0 then
|
return assert(minetest.decode_base64(base64), "invalid base64")
|
||||||
-- Premature optimization my beloved (this is `2^(level-1)`)
|
|
||||||
expected_escapes = math.ldexp(0.5, level)
|
|
||||||
end
|
end
|
||||||
if character:match"[&^:]" then
|
|
||||||
if escapes == expected_escapes then return character end
|
function gr.inventorycube(r)
|
||||||
elseif escapes <= expected_escapes then
|
local top = r:invcubeside()
|
||||||
return character
|
local left = r:invcubeside()
|
||||||
elseif escapes >= 2*expected_escapes then
|
local right = r:invcubeside()
|
||||||
|
return top, left, right
|
||||||
|
end
|
||||||
|
|
||||||
|
function gr.combine(r)
|
||||||
|
r:expect":"
|
||||||
|
local w = r:int()
|
||||||
|
r:expect"x"
|
||||||
|
local h = r:int()
|
||||||
|
local blits = {}
|
||||||
|
while r:match":" do
|
||||||
|
if r.eof then break end -- we can just end with `:`, right?
|
||||||
|
local x = r:int()
|
||||||
|
r:expect","
|
||||||
|
local y = r:int()
|
||||||
|
r:expect"="
|
||||||
|
table.insert(blits, {x = x, y = y, texture = r:subtexp()})
|
||||||
|
end
|
||||||
|
return w, h, blits
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Parameter readers
|
||||||
|
|
||||||
|
local pr = {}
|
||||||
|
|
||||||
|
function pr.brighten() end
|
||||||
|
|
||||||
|
function pr.noalpha() end
|
||||||
|
|
||||||
|
function pr.resize(r)
|
||||||
|
r:expect":"
|
||||||
|
local w = r:int()
|
||||||
|
r:expect"x"
|
||||||
|
local h = r:int()
|
||||||
|
return w, h
|
||||||
|
end
|
||||||
|
|
||||||
|
function pr.makealpha(r)
|
||||||
|
r:expect":"
|
||||||
|
local red = r:int()
|
||||||
|
r:expect","
|
||||||
|
local green = r:int()
|
||||||
|
r:expect","
|
||||||
|
local blue = r:int()
|
||||||
|
return red, green, blue
|
||||||
|
end
|
||||||
|
|
||||||
|
function pr.opacity(r)
|
||||||
|
r:expect":"
|
||||||
|
local ratio = r:int()
|
||||||
|
return ratio
|
||||||
|
end
|
||||||
|
|
||||||
|
function pr.invert(r)
|
||||||
|
r:expect":"
|
||||||
|
local channels = {}
|
||||||
|
while true do
|
||||||
|
local c = r:match_charset"[rgba]"
|
||||||
|
if not c then break end
|
||||||
|
channels[c] = true
|
||||||
|
end
|
||||||
|
return channels
|
||||||
|
end
|
||||||
|
|
||||||
|
function pr.transform(r)
|
||||||
|
if r:match"I" then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local flip_axis
|
||||||
|
if r:match"F" then
|
||||||
|
flip_axis = assert(r:match_charset"[XY]", "axis expected"):lower()
|
||||||
|
end
|
||||||
|
local rot_deg
|
||||||
|
if r:match"R" then
|
||||||
|
rot_deg = r:int()
|
||||||
|
end
|
||||||
|
if flip_axis or rot_deg then
|
||||||
|
return flip_axis, rot_deg
|
||||||
|
end
|
||||||
|
local transform = assert(transforms[r:int()], "out of range")
|
||||||
|
return transform.flip_axis, transform.rotation_deg
|
||||||
|
end
|
||||||
|
|
||||||
|
function pr.verticalframe(r)
|
||||||
|
r:expect":"
|
||||||
|
local framecount = r:int()
|
||||||
|
r:expect":"
|
||||||
|
local frame = r:int()
|
||||||
|
return framecount, frame
|
||||||
|
end
|
||||||
|
|
||||||
|
function pr.crack(r)
|
||||||
|
r:expect":"
|
||||||
|
local framecount = r:int()
|
||||||
|
r:expect":"
|
||||||
|
local frame = r:int()
|
||||||
|
if r:match":" then
|
||||||
|
return framecount, frame, r:int()
|
||||||
|
end
|
||||||
|
return framecount, frame
|
||||||
|
end
|
||||||
|
pr.cracko = pr.crack
|
||||||
|
|
||||||
|
function pr.sheet(r)
|
||||||
|
r:expect":"
|
||||||
|
local w = r:int()
|
||||||
|
r:expect"x"
|
||||||
|
local h = r:int()
|
||||||
|
r:expect":"
|
||||||
|
local x = r:int()
|
||||||
|
r:expect","
|
||||||
|
local y = r:int()
|
||||||
|
return w, h, x, y
|
||||||
|
end
|
||||||
|
|
||||||
|
function pr.multiply(r)
|
||||||
|
r:expect":"
|
||||||
|
return r:colorspec()
|
||||||
|
end
|
||||||
|
|
||||||
|
function pr.colorize(r)
|
||||||
|
r:expect":"
|
||||||
|
local color = r:colorspec()
|
||||||
|
if not r:match":" then
|
||||||
|
return color
|
||||||
|
end
|
||||||
|
if not r:match"a" then
|
||||||
|
return color, r:int()
|
||||||
|
end
|
||||||
|
for c in ("lpha"):gmatch"." do
|
||||||
|
r:expect(c)
|
||||||
|
end
|
||||||
|
return color, "alpha"
|
||||||
|
end
|
||||||
|
|
||||||
|
function pr.mask(r)
|
||||||
|
r:expect":"
|
||||||
|
return r:subtexp(r)
|
||||||
|
end
|
||||||
|
|
||||||
|
function pr.lowpart(r)
|
||||||
|
r:expect":"
|
||||||
|
local percent = r:int()
|
||||||
|
assert(percent)
|
||||||
|
r:expect":"
|
||||||
|
return percent, r:subtexp()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Reader methods. We use `r` instead of the `self` "sugar" for consistency (and to save us some typing).
|
||||||
|
local rm = {}
|
||||||
|
|
||||||
|
function rm.peek(r)
|
||||||
|
if r.eof then return end
|
||||||
|
local expected_escapes = 0
|
||||||
|
if r.level > 0 then
|
||||||
|
-- Premature optimization my beloved (this is `2^(level-1)`)
|
||||||
|
expected_escapes = math.ldexp(0.5, r.level)
|
||||||
|
end
|
||||||
|
if r.character:match"[&^:]" then
|
||||||
|
if r.escapes == expected_escapes then return r.character end
|
||||||
|
elseif r.escapes <= expected_escapes then
|
||||||
|
return r.character
|
||||||
|
elseif r.escapes >= 2*expected_escapes then
|
||||||
return "\\"
|
return "\\"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
function rm.popchar(r)
|
||||||
local function popchar()
|
r.escapes = 0
|
||||||
escapes = 0
|
|
||||||
while true do
|
while true do
|
||||||
character = read_char()
|
r.character = r:read_char()
|
||||||
if character ~= "\\" then break end
|
if r.character ~= "\\" then break end
|
||||||
escapes = escapes + 1
|
r.escapes = r.escapes + 1
|
||||||
end
|
end
|
||||||
if character == nil then
|
if r.character == nil then
|
||||||
assert(escapes == 0, "end of texmod expected")
|
assert(r.escapes == 0, "end of texmod expected")
|
||||||
eof = true
|
r.eof = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
function rm.pop(r)
|
||||||
popchar()
|
|
||||||
|
|
||||||
local function pop()
|
|
||||||
local expected_escapes = 0
|
local expected_escapes = 0
|
||||||
if level > 0 then
|
if r.level > 0 then
|
||||||
-- Premature optimization my beloved (this is `2^(level-1)`)
|
-- Premature optimization my beloved (this is `2^(level-1)`)
|
||||||
expected_escapes = math.ldexp(0.5, level)
|
expected_escapes = math.ldexp(0.5, r.level)
|
||||||
end
|
end
|
||||||
if escapes > 0 and escapes >= 2*expected_escapes then
|
if r.escapes > 0 and r.escapes >= 2*expected_escapes then
|
||||||
escapes = escapes - 2*expected_escapes
|
r.escapes = r.escapes - 2*expected_escapes
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
return popchar()
|
return r:popchar()
|
||||||
end
|
end
|
||||||
|
function rm.match(r, char)
|
||||||
local function match(char)
|
if r:peek() == char then
|
||||||
if peek() == char then
|
r:pop()
|
||||||
pop()
|
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
function rm.expect(r, char)
|
||||||
local function expect(char)
|
if not r:match(char) then
|
||||||
if not match(char) then
|
|
||||||
error(("%q expected"):format(char))
|
error(("%q expected"):format(char))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
function rm.hat(r)
|
||||||
local function hat()
|
return r:match(r.invcube and "&" or "^")
|
||||||
return match(invcube and "&" or "^")
|
|
||||||
end
|
end
|
||||||
|
function rm.match_charset(r, set)
|
||||||
local function match_charset(set)
|
local char = r:peek()
|
||||||
local char = peek()
|
|
||||||
if char and char:match(set) then
|
if char and char:match(set) then
|
||||||
pop()
|
r:pop()
|
||||||
return char
|
return char
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
function rm.match_str(r, set)
|
||||||
local function match_str(set)
|
local c = r:match_charset(set)
|
||||||
local c = match_charset(set)
|
if not c then
|
||||||
if not c then error ("character in " .. set .. " expected") end
|
error(("character in %s expected"):format(set))
|
||||||
|
end
|
||||||
local t = {c}
|
local t = {c}
|
||||||
while true do
|
while true do
|
||||||
c = match_charset(set)
|
c = r:match_charset(set)
|
||||||
if not c then break end
|
if not c then break end
|
||||||
table.insert(t, c)
|
table.insert(t, c)
|
||||||
end
|
end
|
||||||
return table.concat(t)
|
return table.concat(t)
|
||||||
end
|
end
|
||||||
|
function rm.int(r)
|
||||||
local function int()
|
|
||||||
local sign = 1
|
local sign = 1
|
||||||
if match"-" then sign = -1 end
|
if r:match"-" then sign = -1 end
|
||||||
return sign * tonumber(match_str"%d")
|
return sign * tonumber(r:match_str"%d")
|
||||||
end
|
end
|
||||||
|
function rm.fname(r)
|
||||||
local texp
|
|
||||||
local function subtexp()
|
|
||||||
level = level + 1
|
|
||||||
local res = texp()
|
|
||||||
level = level - 1
|
|
||||||
return res
|
|
||||||
end
|
|
||||||
|
|
||||||
local read_base = {
|
|
||||||
png = function()
|
|
||||||
expect":"
|
|
||||||
local base64 = match_str"[a-zA-Z0-9+/=]"
|
|
||||||
return assert(minetest.decode_base64(base64), "invalid base64")
|
|
||||||
end,
|
|
||||||
inventorycube = function()
|
|
||||||
local function read_side()
|
|
||||||
assert(not invcube, "can't nest inventorycube")
|
|
||||||
invcube = true
|
|
||||||
assert(match"{", "'{' expected")
|
|
||||||
local res = texp()
|
|
||||||
invcube = false
|
|
||||||
return res
|
|
||||||
end
|
|
||||||
local top = read_side()
|
|
||||||
local left = read_side()
|
|
||||||
local right = read_side()
|
|
||||||
return top, left, right
|
|
||||||
end,
|
|
||||||
combine = function()
|
|
||||||
expect":"
|
|
||||||
local w = int()
|
|
||||||
expect"x"
|
|
||||||
local h = int()
|
|
||||||
local blits = {}
|
|
||||||
while match":" do
|
|
||||||
if eof then break end -- we can just end with `:`, right?
|
|
||||||
local x = int()
|
|
||||||
expect","
|
|
||||||
local y = int()
|
|
||||||
expect"="
|
|
||||||
level = level + 1
|
|
||||||
local t = texp()
|
|
||||||
level = level - 1
|
|
||||||
table.insert(blits, {x = x, y = y, texture = t})
|
|
||||||
end
|
|
||||||
return w, h, blits
|
|
||||||
end,
|
|
||||||
}
|
|
||||||
|
|
||||||
local function fname()
|
|
||||||
-- This is overly permissive, as is Minetest;
|
-- This is overly permissive, as is Minetest;
|
||||||
-- we just allow arbitrary characters up until a character which may terminate the name.
|
-- we just allow arbitrary characters up until a character which may terminate the name.
|
||||||
-- Inside an inventorycube, `&` also terminates names.
|
-- Inside an inventorycube, `&` also terminates names.
|
||||||
return match_str(invcube and "[^:^&){]" or "[^:^){]")
|
return r:match_str(r.invcube and "[^:^&){]" or "[^:^){]")
|
||||||
end
|
end
|
||||||
|
function rm.subtexp(r)
|
||||||
local function basexp()
|
r.level = r.level + 1
|
||||||
if match"(" then
|
local res = r:texp()
|
||||||
local res = texp()
|
r.level = r.level - 1
|
||||||
expect")"
|
|
||||||
return res
|
return res
|
||||||
end
|
end
|
||||||
if match"[" then
|
function rm.invcubeside(r)
|
||||||
local name = match_str"[a-z]"
|
assert(not r.invcube, "can't nest inventorycube")
|
||||||
local reader = read_base[name]
|
r.invcube = true
|
||||||
if not reader then
|
assert(r:match"{", "'{' expected")
|
||||||
error("invalid texture modifier: " .. name)
|
local res = r:texp()
|
||||||
|
r.invcube = false
|
||||||
|
return res
|
||||||
end
|
end
|
||||||
return texmod[name](reader())
|
function rm.basexp(r)
|
||||||
|
if r:match"(" then
|
||||||
|
local res = r:texp()
|
||||||
|
r:expect")"
|
||||||
|
return res
|
||||||
end
|
end
|
||||||
return texmod.file(fname())
|
if r:match"[" then
|
||||||
|
local type = r:match_str"[a-z]"
|
||||||
|
local gen_reader = gr[type]
|
||||||
|
if not gen_reader then
|
||||||
|
error("invalid texture modifier: " .. type)
|
||||||
end
|
end
|
||||||
|
return texmod[type](gen_reader(r))
|
||||||
local function pcolorspec()
|
end
|
||||||
|
return texmod.file(r:fname())
|
||||||
|
end
|
||||||
|
function rm.colorspec(r)
|
||||||
-- Leave exact validation up to colorspec, only do a rough greedy charset matching
|
-- Leave exact validation up to colorspec, only do a rough greedy charset matching
|
||||||
return assert(colorspec.from_string(match_str"[#%xa-z]"))
|
return assert(colorspec.from_string(r:match_str"[#%xa-z]"))
|
||||||
end
|
end
|
||||||
|
function rm.texp(r)
|
||||||
local function crack()
|
local base = r:basexp()
|
||||||
expect":"
|
while r:hat() do
|
||||||
local framecount = int()
|
if r:match"[" then
|
||||||
expect":"
|
local type = r:match_str"[a-z]"
|
||||||
local frame = int()
|
local param_reader, gen_reader = pr[type], gr[type]
|
||||||
if match":" then
|
|
||||||
return framecount, frame, int()
|
|
||||||
end
|
|
||||||
return framecount, frame
|
|
||||||
end
|
|
||||||
|
|
||||||
local param_readers = {
|
|
||||||
brighten = function()end,
|
|
||||||
noalpha = function()end,
|
|
||||||
resize = function()
|
|
||||||
expect":"
|
|
||||||
local w = int()
|
|
||||||
expect"x"
|
|
||||||
local h = int()
|
|
||||||
return w, h
|
|
||||||
end,
|
|
||||||
makealpha = function()
|
|
||||||
expect":"
|
|
||||||
local r = int()
|
|
||||||
expect","
|
|
||||||
local g = int()
|
|
||||||
expect","
|
|
||||||
local b = int()
|
|
||||||
return r, g, b
|
|
||||||
end,
|
|
||||||
opacity = function()
|
|
||||||
expect":"
|
|
||||||
local ratio = int()
|
|
||||||
return ratio
|
|
||||||
end,
|
|
||||||
invert = function()
|
|
||||||
expect":"
|
|
||||||
local channels = {}
|
|
||||||
while true do
|
|
||||||
local c = match_charset"[rgba]"
|
|
||||||
if not c then break end
|
|
||||||
channels[c] = true
|
|
||||||
end
|
|
||||||
return channels
|
|
||||||
end,
|
|
||||||
transform = function()
|
|
||||||
if match"I" then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
local flip_axis
|
|
||||||
if match"F" then
|
|
||||||
flip_axis = assert(match_charset"[XY]", "axis expected"):lower()
|
|
||||||
end
|
|
||||||
local rot_deg
|
|
||||||
if match"R" then
|
|
||||||
rot_deg = int()
|
|
||||||
end
|
|
||||||
if flip_axis or rot_deg then
|
|
||||||
return flip_axis, rot_deg
|
|
||||||
end
|
|
||||||
local transform = assert(transforms[int()], "out of range")
|
|
||||||
return transform.flip_axis, transform.rotation_deg
|
|
||||||
end,
|
|
||||||
verticalframe = function()
|
|
||||||
expect":"
|
|
||||||
local framecount = int()
|
|
||||||
expect":"
|
|
||||||
local frame = int()
|
|
||||||
return framecount, frame
|
|
||||||
end,
|
|
||||||
crack = crack,
|
|
||||||
cracko = crack,
|
|
||||||
sheet = function()
|
|
||||||
expect":"
|
|
||||||
local w = int()
|
|
||||||
expect"x"
|
|
||||||
local h = int()
|
|
||||||
expect":"
|
|
||||||
local x = int()
|
|
||||||
expect","
|
|
||||||
local y = int()
|
|
||||||
return w, h, x, y
|
|
||||||
end,
|
|
||||||
multiply = function()
|
|
||||||
expect":"
|
|
||||||
return pcolorspec()
|
|
||||||
end,
|
|
||||||
colorize = function()
|
|
||||||
expect":"
|
|
||||||
local color = pcolorspec()
|
|
||||||
if not match":" then
|
|
||||||
return color
|
|
||||||
end
|
|
||||||
if not match"a" then
|
|
||||||
return color, int()
|
|
||||||
end
|
|
||||||
for c in ("lpha"):gmatch"." do
|
|
||||||
expect(c)
|
|
||||||
end
|
|
||||||
return color, "alpha"
|
|
||||||
end,
|
|
||||||
mask = function()
|
|
||||||
expect":"
|
|
||||||
return subtexp()
|
|
||||||
end,
|
|
||||||
lowpart = function()
|
|
||||||
expect":"
|
|
||||||
local percent = int()
|
|
||||||
assert(percent)
|
|
||||||
expect":"
|
|
||||||
return percent, subtexp()
|
|
||||||
end,
|
|
||||||
}
|
|
||||||
|
|
||||||
function texp()
|
|
||||||
local base = basexp()
|
|
||||||
while hat() do
|
|
||||||
if match"[" then
|
|
||||||
local name = match_str"[a-z]"
|
|
||||||
local param_reader = param_readers[name]
|
|
||||||
local gen_reader = read_base[name]
|
|
||||||
if not (param_reader or gen_reader) then
|
if not (param_reader or gen_reader) then
|
||||||
error("invalid texture modifier: " .. name)
|
error("invalid texture modifier: " .. type)
|
||||||
end
|
end
|
||||||
if param_reader then
|
if param_reader then
|
||||||
base = base[name](base, param_reader())
|
base = base[type](base, param_reader(r))
|
||||||
elseif gen_reader then
|
elseif gen_reader then
|
||||||
base = base:overlay(texmod[name](gen_reader()))
|
base = base:overlay(texmod[type](gen_reader(r)))
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
base = base:overlay(basexp())
|
base = base:overlay(r:basexp())
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return base
|
return base
|
||||||
end
|
end
|
||||||
local res = texp()
|
|
||||||
assert(eof, "eof expected")
|
local mt = {__index = rm}
|
||||||
|
return function(read_char)
|
||||||
|
local r = setmetatable({
|
||||||
|
level = 0,
|
||||||
|
invcube = false,
|
||||||
|
eof = false,
|
||||||
|
read_char = read_char,
|
||||||
|
}, mt)
|
||||||
|
r:popchar()
|
||||||
|
local res = r:texp()
|
||||||
|
assert(r.eof, "eof expected")
|
||||||
return res
|
return res
|
||||||
end
|
end
|
Loading…
Reference in New Issue
Block a user