mirror of
https://github.com/vcmi/vcmi.git
synced 2025-12-24 00:36:29 +02:00
Entities redesign and a few ERM features
* Made most Handlers derived from CHandlerBase and moved service API there. * Declared existing Entity APIs. * Added basic script context caching * Started Lua script module * Started Lua spell effect API * Started script state persistence * Started battle info callback binding * CommitPackage removed * Extracted spells::Caster to own header; Expanded Spell API. * implemented !!MC:S, !!FU:E, !!FU:P, !!MA, !!VR:H, !!VR:C * !!BU:C, !!BU:E, !!BU:G, !!BU:M implemented * Allow use of "MC:S@varName@" to declare normal variable (technically v-variable with string key) * Re-enabled VERM macros. * !?GM0 added * !?TM implemented * Added !!MF:N * Started !?OB, !!BM, !!HE, !!OW, !!UN * Added basic support of w-variables * Added support for ERM indirect variables * Made !?FU regular trigger * !!re (ERA loop receiver) implemented * Fixed ERM receivers with zero args.
This commit is contained in:
375
scripts/lib/verm.lua
Normal file
375
scripts/lib/verm.lua
Normal file
@@ -0,0 +1,375 @@
|
||||
|
||||
local _G=_G
|
||||
local ipairs=ipairs
|
||||
local select=select
|
||||
local pairs=pairs
|
||||
local type = type
|
||||
local unpack=unpack
|
||||
local logError = logError
|
||||
|
||||
local DATA = DATA
|
||||
|
||||
--/////////////////////////
|
||||
|
||||
local function table_print (tt, done)
|
||||
done = done or {}
|
||||
if type(tt) == "table" then
|
||||
local sb = {}
|
||||
table.insert(sb, "{");
|
||||
for key, value in pairs (tt) do
|
||||
if type (value) == "table" and not done [value] then
|
||||
done [value] = true
|
||||
table.insert(sb, key .. ":{");
|
||||
table.insert(sb, table_print (value, done))
|
||||
table.insert(sb, "}");
|
||||
else
|
||||
table.insert(sb, string.format(
|
||||
"%s:\"%s\"", tostring (key), tostring(value)))
|
||||
end
|
||||
table.insert(sb, ",");
|
||||
end
|
||||
table.insert(sb, "}");
|
||||
|
||||
m = getmetatable(tt);
|
||||
if m and m.__index then
|
||||
table.insert(sb, table_print (m.__index, done));
|
||||
end
|
||||
|
||||
return table.concat(sb)
|
||||
else
|
||||
return tt .. ""
|
||||
end
|
||||
end
|
||||
|
||||
local function to_string( tbl )
|
||||
if "nil" == type( tbl ) then
|
||||
return tostring(nil)
|
||||
elseif "table" == type( tbl ) then
|
||||
return table_print(tbl)
|
||||
elseif "string" == type( tbl ) then
|
||||
return tbl
|
||||
else
|
||||
return tostring(tbl)
|
||||
end
|
||||
end
|
||||
|
||||
--/////////////////////////
|
||||
|
||||
local VERM = {}
|
||||
|
||||
local function createEnv(parent, current)
|
||||
return setmetatable(
|
||||
current or {},
|
||||
{
|
||||
__parent = parent,
|
||||
__index = parent,
|
||||
__newindex = function(t, k ,v)
|
||||
if type(k) ~= "string" then
|
||||
error("String key for env. table required, but got:"..to_string(k))
|
||||
end
|
||||
|
||||
local function setOnFirstHit(t, k, v)
|
||||
local vv = rawget(t, k)
|
||||
if vv~= nil then rawset(t, k, v); return true end
|
||||
|
||||
local m = getmetatable(t)
|
||||
|
||||
if not m then return false end --assume top
|
||||
|
||||
local p = m.__parent
|
||||
|
||||
if not p then
|
||||
return false
|
||||
else
|
||||
return setOnFirstHit(p, k, v)
|
||||
end
|
||||
end
|
||||
|
||||
if not setOnFirstHit(t, k, v) then
|
||||
rawset(t, k, v)
|
||||
end
|
||||
end
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
local function isNIL(v)
|
||||
return (type(v) == "table") and (next(v) == nil)
|
||||
end
|
||||
|
||||
local function prognForm(e, ...)
|
||||
--eval each argument, return last result
|
||||
|
||||
local argc = select('#',...)
|
||||
|
||||
if argc == 0 then return {} end
|
||||
|
||||
for n = 1, argc - 1 do
|
||||
VERM:eval(e, (select(n,...)))
|
||||
end
|
||||
|
||||
return VERM:eval(e, (select(argc,...)))
|
||||
end
|
||||
|
||||
local function lambdaOrMacro(e, isMacro, args, ...)
|
||||
|
||||
--TODO: get rid of pack-unpack
|
||||
local body = {...}
|
||||
local oldEnv = e
|
||||
|
||||
local ret = function(e, ...)
|
||||
|
||||
-- we need a function that have parameters with names from `args` table
|
||||
-- pack parameters from '...' and bind to new environment
|
||||
|
||||
local newEnv = createEnv(oldEnv, {})
|
||||
|
||||
for i, v in ipairs(args) do
|
||||
local p = select(i,...)
|
||||
if isMacro then
|
||||
newEnv[v] = p
|
||||
else
|
||||
newEnv[v] = VERM:evalValue(e, p)
|
||||
end
|
||||
end
|
||||
if isMacro then
|
||||
local buffer = {}
|
||||
for _, v in ipairs(body) do
|
||||
table.insert(buffer, (VERM:eval(newEnv, v)))
|
||||
end
|
||||
return prognForm(newEnv, unpack(buffer))
|
||||
else
|
||||
return prognForm(newEnv, unpack(body))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return ret
|
||||
end
|
||||
|
||||
local function lambdaForm(e, args, ...)
|
||||
return lambdaOrMacro(e, false, args, ...)
|
||||
end
|
||||
|
||||
local function defunForm(e, name, args, ...)
|
||||
local ret = lambdaOrMacro(e, false, args, ...)
|
||||
e[name] = ret
|
||||
return ret
|
||||
end
|
||||
|
||||
local function defmacroForm(e, name, args, ...)
|
||||
local ret = lambdaOrMacro(e, true, args, ...)
|
||||
e[name] = ret
|
||||
return ret
|
||||
end
|
||||
|
||||
local function backquoteEval(e, v)
|
||||
if isNIL(v) then
|
||||
return v
|
||||
elseif type(v) == "table" then
|
||||
local car = v[1]
|
||||
|
||||
if car == "," then
|
||||
return VERM:evalValue(e, v[2])
|
||||
else
|
||||
local ret = {}
|
||||
|
||||
for _, v in ipairs(v) do
|
||||
table.insert(ret, (backquoteEval(e, v)))
|
||||
end
|
||||
return ret
|
||||
end
|
||||
else
|
||||
return v
|
||||
end
|
||||
end
|
||||
|
||||
local specialForms =
|
||||
{
|
||||
["<"] = function(e, lhs, rhs)
|
||||
lhs = VERM:evalValue(e, lhs)
|
||||
rhs = VERM:evalValue(e, rhs)
|
||||
if lhs < rhs then
|
||||
return lhs
|
||||
else
|
||||
return {}
|
||||
end
|
||||
end,
|
||||
["<="] = function(e, lhs, rhs)
|
||||
lhs = VERM:evalValue(e, lhs)
|
||||
rhs = VERM:evalValue(e, rhs)
|
||||
if lhs <= rhs then
|
||||
return lhs
|
||||
else
|
||||
return {}
|
||||
end
|
||||
end,
|
||||
[">"] = function(e, lhs, rhs)
|
||||
lhs = VERM:evalValue(e, lhs)
|
||||
rhs = VERM:evalValue(e, rhs)
|
||||
if lhs > rhs then
|
||||
return lhs
|
||||
else
|
||||
return {}
|
||||
end
|
||||
end,
|
||||
[">="] = function(e, lhs, rhs)
|
||||
lhs = VERM:evalValue(e, lhs)
|
||||
rhs = VERM:evalValue(e, rhs)
|
||||
if lhs >= rhs then
|
||||
return lhs
|
||||
else
|
||||
return {}
|
||||
end
|
||||
end,
|
||||
["="] = function(e, lhs, rhs)
|
||||
lhs = VERM:evalValue(e, lhs)
|
||||
rhs = VERM:evalValue(e, rhs)
|
||||
if lhs == rhs then
|
||||
return lhs
|
||||
else
|
||||
return {}
|
||||
end
|
||||
end,
|
||||
["+"] = function(e, ...)
|
||||
local ret = 0
|
||||
for n=1,select('#',...) do
|
||||
local v = VERM:evalValue(e, (select(n,...)))
|
||||
ret = ret + v
|
||||
end
|
||||
return ret
|
||||
end,
|
||||
["*"] = function(e, ...)
|
||||
local ret = 1
|
||||
for n=1,select('#',...) do
|
||||
local v = VERM:evalValue(e, (select(n,...)))
|
||||
ret = ret * v
|
||||
end
|
||||
return ret
|
||||
end,
|
||||
["-"] = function(e, lhs, rhs)
|
||||
lhs = VERM:evalValue(e, lhs)
|
||||
rhs = VERM:evalValue(e, rhs)
|
||||
return lhs - rhs
|
||||
end,
|
||||
["/"] = function(e, lhs, rhs)
|
||||
lhs = VERM:evalValue(e, lhs)
|
||||
rhs = VERM:evalValue(e, rhs)
|
||||
return lhs / rhs
|
||||
end,
|
||||
["%"] = function(e, lhs, rhs)
|
||||
lhs = VERM:evalValue(e, lhs)
|
||||
rhs = VERM:evalValue(e, rhs)
|
||||
return lhs % rhs
|
||||
end,
|
||||
-- ["comma-unlist"] = function(e, ...) end,
|
||||
["`"] = backquoteEval,
|
||||
-- ["get-func"] = function(e, ...) end,
|
||||
["'"] = function(e, v)
|
||||
return v
|
||||
end,
|
||||
["if"] = function(e, cond, v1, v2)
|
||||
cond = VERM:evalValue(e, cond)
|
||||
|
||||
if isNIL(cond) then
|
||||
return VERM:evalValue(e, v2)
|
||||
else
|
||||
return VERM:evalValue(e, v1)
|
||||
end
|
||||
end,
|
||||
-- ["set"] = function(e, ...) end,
|
||||
-- ["setf"] = function(e, ...) end,
|
||||
["setq"] = function(e, name, value)
|
||||
e[name] = VERM:evalValue(e, value)
|
||||
end,
|
||||
["lambda"] = lambdaForm,
|
||||
["defun"] = defunForm,
|
||||
["progn"] = prognForm,
|
||||
["defmacro"] = defmacroForm,
|
||||
["do"] = function(e, cond, body)
|
||||
local c = VERM:eval(e, cond)
|
||||
while not isNIL(c) do
|
||||
VERM:eval(e, body)
|
||||
c = VERM:eval(e, cond)
|
||||
end
|
||||
return {}
|
||||
end,
|
||||
["car"] = function(e, list)
|
||||
list = VERM:eval(e, list)
|
||||
return list[1] or {}
|
||||
end,
|
||||
["cdr"] = function(e, list)
|
||||
list = VERM:eval(e, list)
|
||||
local ret = {}
|
||||
for i, v in ipairs(list) do
|
||||
if i > 1 then
|
||||
table.insert(ret, v)
|
||||
end
|
||||
end
|
||||
|
||||
return ret
|
||||
end,
|
||||
["list"] = function(e, ...)
|
||||
local ret = {}
|
||||
for n=1,select('#',...) do
|
||||
local v = VERM:evalValue(e, (select(n,...)))
|
||||
table.insert(ret, v)
|
||||
end
|
||||
|
||||
return ret
|
||||
end,
|
||||
["setq-erm"] = function(e, var, varIndex, value)
|
||||
local v = VERM:evalValue(e, value)
|
||||
DATA.ERM[var][tostring(VERM:evalValue(e, varIndex))] = v
|
||||
return v
|
||||
end,
|
||||
}
|
||||
|
||||
function VERM:evalValue(e, v)
|
||||
if isNIL(v) then
|
||||
return v
|
||||
elseif type(v) == "table" then
|
||||
return self:eval(e, v)
|
||||
elseif type(v) == "string" then
|
||||
return e[v]
|
||||
elseif type(v) == "function" then
|
||||
error("evalValue do not accept functions")
|
||||
else
|
||||
return v
|
||||
end
|
||||
end
|
||||
|
||||
function VERM:eval(e, t)
|
||||
if type(t) ~= "table" then
|
||||
logError("Not valid form: ".. to_string(t))
|
||||
return {}
|
||||
end
|
||||
|
||||
local car = t[1]
|
||||
local origCar = car
|
||||
|
||||
if type(car) == "string" then
|
||||
car = e[car]
|
||||
end
|
||||
|
||||
if type(car) == "table" then
|
||||
car = self:eval(e, car)
|
||||
end
|
||||
|
||||
if type(car) == "function" then
|
||||
return car(e, unpack(t,2))
|
||||
else
|
||||
logError(to_string(t) .. " is not callable. Car()="..to_string(car))
|
||||
logError("Env:"..to_string(e))
|
||||
return {}
|
||||
end
|
||||
end
|
||||
|
||||
function VERM:E(line)
|
||||
self:eval(self.topEnv, line)
|
||||
end
|
||||
|
||||
VERM.topEnv = createEnv(specialForms)
|
||||
|
||||
return VERM
|
||||
|
||||
Reference in New Issue
Block a user