758 lines
19 KiB
Lua

local util = require "util"
local TabbedFile = require "TabbedFile"
local actionTypes = {}
local conditionals = {}
-------------------------------
-- Action base-class
local action = {}
function action:Process(context)
--Allow start-of-iteration only data to parse.
if(self.first) then
--Note that it's *specifically* equal to false. Being 'nil' isn't enough.
if(context._first == false) then
return
end
end
--Allow end-if-iteration only data to parse.
if(self.last) then
--Note that it's *specifically* equal to false. Being 'nil' isn't enough.
if(context._last == false) then
return
end
end
--Conditional
if(self._cond) then
if(not conditionals[self._cond](context)) then
return
end
end
--NO MORE RETURNS FROM THIS POINT FORWARD!
if(self.newStyle) then
context:PushStyle(self.newStyle)
end
local noChildren = nil
if(self.PreProcess) then
noChildren = self:PreProcess(context)
end
if(not noChildren) then
self:ProcessChildren(context)
end
if(self.PostProcess) then
self:PostProcess(context)
end
if(self.newStyle) then
context:PopStyle()
end
end
function action:ProcessChildren(context)
for _, action in ipairs(self) do
--Preserve the first value.
local oldFirst = context._first
local oldLast = context._last
action:Process(context)
context._first = oldFirst
context._last = oldLast
end
end
local valueResolvers =
{
enum = function(enum) return enum.name end,
func = function(func) return func.name end,
spec = function(spec) return spec.FuncNamePrefix() end,
}
local function ResolveValue(context, value)
--Find every occurrance of %chars, and try to turn that into a context variable.
local possibleVars = {}
for var in value:gmatch("%%([_%a][_%w]*)") do
possibleVars[var] = true
end
for var, _ in pairs(possibleVars) do
if(not context[var]) then
return nil, "The variable " .. var .. " from the value string was not found.\n" .. value
end
local replace = context[var]
if(type(replace) ~= "string") then
local str = tostring(replace)
if(str) then
replace = str
elseif(valueResolvers[var]) then
replace = valueResolvers[var](replace)
elseif(type(replace) == "table" and replace._ValueResolve) then
replace = replace:_ValueResolve()
end
end
if(type(replace) ~= "string") then
return nil, "Could not convert the variable " .. var .. " into a string."
end
value = value:gsub("%%" .. var, replace)
end
return value
end
function action:CallFunction(context, name)
name = name or self.name
self:Assert(name, "Unknown function name.")
local style = context:FindStyleForFunc(name)
if(not style) then
if(self.optional) then
return
else
self:Assert(nil, "The style does not have a function " .. name)
end
end
if(self.value) then
context.value = self:Assert(ResolveValue(context, self.value))
end
local paramList = {}
for _, param in ipairs(self.params) do
assert(context[param], "The function " .. name ..
" need a parameter " .. param .. " which doesn't exist at this point")
paramList[#paramList + 1] = context[param]
end
local rets = { style[name](unpack(paramList)) }
if(self.value) then
context.value = nil
end
return unpack(rets)
end
function action:Assert(...)
local test, text = ...
if(not test) then
local msg = ": " .. text
if(self.name) then
msg = self._actionType .. "." .. self.name .. msg
else
msg = self._actionType .. msg
end
assert(test, msg)
end
return ...
end
--Iterates over the list, setting the second element returned from the iterator
--as the given context table key.
function action:IterateChildren(context, list, key, PostProc)
PostProc = PostProc or function() end
local oldVal = context[key]
for _, val in ipairs(list) do
context[key] = val
context._first = (_ == 1)
context._last = (_ == #list)
self:ProcessChildren(context)
PostProc(context, val)
end
context[key] = oldVal
end
local function CreateAction(data, actionType)
local act = {}
util.DeepCopyTable(action, act)
assert(actionType, "No name given for action type")
--Create custom param list.
if(data.name) then
local name, params = data.name:match("([_%w]+)%s*%((.*)%)")
if(name) then
local paramList = {}
for param in params:gmatch("([_%a][_%w]*)") do
paramList[#paramList + 1] = param
end
params = paramList
else
name = data.name
end
act.name = name
act.params = params
end
if(data.cond) then
assert(conditionals[data.cond], "Unknown conditional " .. data.cond)
act._cond = data.cond
end
act.newStyle = data.style
act.optional = data.optional
act.value = data.value
--Make child actions recursively.
for _, child in ipairs(data) do
assert(actionTypes[child.type], "Unknown command type " .. child.type)
act[#act + 1] = actionTypes[child.type](child)
end
if(data.first) then
act.first = true
end
if(data.last) then
act.last = true
end
act._actionType = actionType
return act
end
local function MakeActionType(typeName, typeTable, PostInitFunc)
actionTypes[typeName] = function(data)
local act = CreateAction(data, typeName)
util.DeepCopyTable(typeTable, act)
PostInitFunc(act, data)
return act
end
end
-------------------------------------
-- Group Action
local groupAction = {}
MakeActionType("group", groupAction, function(self, data)
end)
-------------------------------------
-- Call Action
local callAction = {}
function callAction:PreProcess(context)
self:CallFunction(context, self.name)
end
MakeActionType("call", callAction, function(self, data)
self.params = self.params or {}
end)
-------------------------------------
-- Context Action
local contextAction = {}
function contextAction:PreProcess(context)
self:Assert(context[self.key] == nil,
"Attempt to nest the context variable " .. self.key)
if(self.data) then
context[self.key] = self.data
else
context[self.key] = self:CallFunction(context, self.name)
end
end
function contextAction:PostProcess(context)
if(self.dispose) then
local style = context:FindStyleForFunc(self.dispose)
self:Assert(style,
string.format("Could not find the disposal function %s for %s.",
self.dispose, self.key))
style[self.dispose](context[self.key])
end
context[self.key] = nil
end
MakeActionType("context", contextAction, function(self, data)
assert(data.key, "Context actions must have a `key`.")
assert(data.key:match("%_$"), "Context action keys must end in `_`.")
self.key = data.key
self.data = data.data
if(self.name) then
self.name = "State" .. self.name
end
self.dispose = data.dispose
if(self.dispose) then
self.dispose = "Dispose" .. self.dispose
end
assert(self.data or self.name, "Context actions must have either `data` or `name`.")
self.params = self.params or {}
end)
-------------------------------------------
-- Filter Action
local filterAction = {}
function filterAction:PreProcess(context)
local shouldFilter = self:CallFunction(context, self.name)
if(self.neg) then
shouldFilter = not shouldFilter
end
return not shouldFilter
end
MakeActionType("filter", filterAction, function(self, data)
assert(data.name, "Filter actions must have a `name`")
self.name = "Filter" .. self.name
self.neg = data.neg
self.params = self.params or {}
end)
----------------------------
-- File Action
local fileAction = {}
function fileAction:PreProcess(context)
self:Assert(context.hFile == nil, "You cannot nest `file` blocks.")
local filename = self:CallFunction(context)
context.hFile = util.CreateFile(filename, context.options.indent)
end
function fileAction:PostProcess(context)
context.hFile:close()
context.hFile = nil
end
MakeActionType("file", fileAction, function(self, data)
assert(data.style, "File actions must have a `style`")
assert(data.name, "File actions need a name to call.")
self.params = self.params or {"basename", "options"}
end)
-------------------------------------
-- Block Action
local blockAction = {}
function blockAction:PreProcess(context)
assert(context.hFile, "Cannot write a block outside of a file. " .. self.name)
self:CallFunction(context, "WriteBlockBegin" .. self.name)
end
function blockAction:PostProcess(context)
self:CallFunction(context, "WriteBlockEnd" .. self.name)
end
MakeActionType("block", blockAction, function(self, data)
assert(data.name, "Block actions must have a `name`")
self.params = self.params or {"hFile", "spec", "options"}
end)
------------------------------------------
-- Write Action
local writeAction = {}
function writeAction:PreProcess(context)
assert(context.hFile, "Cannot write data outside of a file.")
self:CallFunction(context)
end
MakeActionType("write", writeAction, function(self, data)
assert(data.name, "Write actions must have a `name`")
self.name = "Write" .. self.name
self.params = self.params or {"hFile", "specData", "spec", "options"}
end)
------------------------------------------
-- Blank Action
local blankAction = {}
function blankAction:PreProcess(context)
self:Assert(context.hFile, "Blanks must be in files.")
context.hFile:write("\n")
end
MakeActionType("blank", blankAction, function(self, data)
end)
---------------------------------------------
-- Extension Iterator Action
local extIterAction = {}
function extIterAction:PreProcess(context)
self:Assert(context.extName == nil, "Cannot nest ext-iter actions.")
self:IterateChildren(context, context.options.extensions, "extName")
return true --Stops regular child processing.
end
MakeActionType("ext-iter", extIterAction, function(self, data)
end)
conditionals["ext-iter"] = function(context)
return #context.options.extensions ~= 0
end
-----------------------------------------------
-- Version Iterator
local versionIterAction = {}
function versionIterAction:PreProcess(context)
self:Assert(context.version == nil, "Cannot nest version-iter actions.")
local rawVersionList = context.specData.versions or {}
local versionList = {}
for _, version in ipairs(rawVersionList) do
if(tonumber(version) <= tonumber(context.options.version)) then
versionList[#versionList + 1] = version
end
end
self:IterateChildren(context, versionList, "version")
return true --Stops regular child processing.
end
MakeActionType("version-iter", versionIterAction, function(self, data)
end)
conditionals["version-iter"] = function(context)
return context.specData.versions ~= nil
end
-----------------------------------------------
-- Sub-Version Iterator
local subVersionIterAction = {}
function subVersionIterAction:PreProcess(context)
self:Assert(context.sub_version == nil, "Cannot nest sub-version-iter actions.")
self:Assert(context.version, "Must put sub-version-iter inside versions.")
local rawVersionList = context.specData.versions or {}
local versionList = {}
for _, version in ipairs(rawVersionList) do
if(tonumber(version) <= tonumber(context.version)) then
versionList[#versionList + 1] = version
end
end
self:IterateChildren(context, versionList, "sub_version")
return true --Stops regular child processing.
end
MakeActionType("sub-version-iter", subVersionIterAction, function(self, data)
end)
---------------------------------------------
-- Core Extension Iterator Action
local coreExtIterAction = {}
function coreExtIterAction:PreProcess(context)
self:Assert(context.version, "Must put this in a version iterator")
self:Assert(context.extName == nil, "Cannot nest core-ext-iter actions.")
local coreExts = context._coreExts
if(coreExts[context.version]) then
self:IterateChildren(context, coreExts[context.version], "extName")
end
return true --Stops regular child processing.
end
MakeActionType("core-ext-iter", coreExtIterAction, function(self, data)
end)
conditionals["core-ext-iter"] = function(context)
assert(context.version, "Cannot have a core-ext-iter conditional outside of a version.")
return context._coreExts[context.version] ~= nil
end
--[==[
---------------------------------------------
-- Core Extension Iterator Action, culled against the requested extensions.
local coreExtCullIterAction = {}
local function BuildCulledExtList(context)
local coreExts = context._coreExts
if(coreExts[context.version]) then
local extList = {}
for _, ext in ipairs(coreExts[context.version]) do
if(not context._extTbl[ext]) then
extList[#extList + 1] = ext
end
end
return extList
else
return {}
end
end
function coreExtCullIterAction:PreProcess(context)
self:Assert(context.version, "Must put core-ext-cull-iters in a version")
self:Assert(context.extName == nil, "Cannot nest core-ext-cull-iter actions.")
local extList = BuildCulledExtList(context)
if(#extList > 0) then
self:IterateChildren(context, extList, "extName")
end
return true --Stops regular child processing.
end
MakeActionType("core-ext-cull-iter", coreExtCullIterAction, function(self, data)
end)
conditionals["core-ext-cull-iter"] = function(context)
assert(context.version, "Cannot have a core-ext-cull-iter conditional outside of a version.")
return #BuildCulledExtList(context) > 0
end
]==]
----------------------------------------------
-- Enum Seen Action
local enumSeenAction = {}
function enumSeenAction:PreProcess(context)
self:Assert(context.enumSeen == nil, "Cannot nest enum-seen actions.")
context.enumSeen = {}
end
function enumSeenAction:PostProcess(context)
context.enumSeen = nil
end
MakeActionType("enum-seen", enumSeenAction, function(self, data)
end)
-----------------------------------------------
-- Enumerator Iterator
local enumIterAction = {}
local function GetEnumList(context)
if(context.extName) then
--Get enum list for the extension.
return context.specData.extdefs[context.extName].enums, context.extName
else
--Get enum list from core version.
if(context.options.profile ~= "core") then
return context.specData.coredefs[context.version].enums, context.version
end
local defList = {}
local targetVersion = tonumber(context.options.version)
for _, def in ipairs(context.specData.coredefs[context.version].enums) do
for ix = #def.core, 1, -1 do
if(tonumber(def.core[ix][1]) <= targetVersion) then
if(def.core[ix][2] == "core") then
table.insert(defList, def)
end
break;
end
end
end
return defList, context.version
end
end
function enumIterAction:PreProcess(context)
self:Assert(context.version or context.extName, "Enumeration iterators must go within a version or extension iterator.")
local enumList, source = GetEnumList(context)
if(not source) then
print(context.version, context.extName)
end
context.enumTable = context.specData.enumtable
self:IterateChildren(context, enumList, "enum",
function(context, enum)
if(context.enumSeen) then
context.enumSeen[enum.name] = source
end
end)
context.enumTable = nil
return true --Stops regular child processing.
end
MakeActionType("enum-iter", enumIterAction, function(self, data)
end)
conditionals["enum-iter"] = function(context)
assert(context.version or context.extName, "Cannot have an enum-iter conditional outside of a version or extension iterator.")
return #GetEnumList(context) > 0
end
----------------------------------------------
-- Func Seen Action
local funcSeenAction = {}
function funcSeenAction:PreProcess(context)
self:Assert(context.funcSeen == nil, "Cannot nest func-seen actions.")
context.funcSeen = {}
end
function funcSeenAction:PostProcess(context)
context.funcSeen = nil
end
MakeActionType("func-seen", funcSeenAction, function(self, data)
end)
-----------------------------------------------
-- Function Iterator
local funcIterAction = {}
local function GetFuncList(context)
if(context.extName) then
--Get function list for the extension.
return context.specData.extdefs[context.extName].funcs, context.extName
else
--Get function list from core version.
if(context.options.profile ~= "core") then
return context.specData.coredefs[context.version].funcs, context.version
end
local defList = {}
local targetVersion = tonumber(context.options.version)
for _, def in ipairs(context.specData.coredefs[context.version].funcs) do
for ix = #def.core, 1, -1 do
if(tonumber(def.core[ix][1]) <= targetVersion) then
if(def.core[ix][2] == "core") then
table.insert(defList, def)
end
break;
end
end
end
return defList, context.version
end
end
function funcIterAction:PreProcess(context)
self:Assert(context.version or context.extName, "Function iterators must go within a version or extension iterator.")
local funcList, source = GetFuncList(context)
self:IterateChildren(context, funcList, "func",
function(context, func)
if(context.funcSeen) then
context.funcSeen[func.name] = source
end
end)
return true --Stops regular child processing.
end
MakeActionType("func-iter", funcIterAction, function(self, data)
end)
conditionals["func-iter"] = function(context)
assert(context.version or context.extName, "Cannot have a func-iter conditional outside of a version or extension iterator.")
return #GetFuncList(context) > 0
end
conditionals["core-funcs"] = function(context)
return context.options.spec == "gl"
end
local struct = {}
function struct.BuildStructure(structure)
local actions = {}
for _, data in ipairs(structure) do
assert(actionTypes[data.type], "Unknown command type " .. data.type)
actions[#actions + 1] = actionTypes[data.type](data)
end
actions.Proc = function(basename, style, specData, spec, options)
local context = {}
context.basename = basename
context.style = style
context.specData = specData
context.spec = spec
context.options = options
context._coreExts = spec.GetCoreExts()
context._extTbl = util.InvertTable(options.extensions)
context._styles = { style }
function context:GetStyle()
return context._styles[#context._styles]
end
function context:FindStyleForFunc(funcName)
for i = #context._styles, 1, -1 do
if(context._styles[i][funcName]) then
return context._styles[i]
end
end
return nil
end
function context:PushStyle(newStyleName)
--Find the style in the stack, from top to bottom.
local ix = nil
for styleIx = #context._styles, 1, -1 do
if(context._styles[styleIx][newStyleName]) then
ix = styleIx
break;
end
end
assert(ix, "Could not find a style named " .. newStyleName)
table.insert(context._styles, context._styles[ix][newStyleName])
context.style = context._styles[#context._styles]
if(context.style._init) then
context.style._init()
end
end
function context:PopStyle()
local ret = context._styles[#context._styles]
context._styles[#context._styles] = nil
context.style = context._styles[#context._styles]
if(ret._exit) then
ret._exit()
end
return ret
end
for _, action in ipairs(actions) do
action:Process(context)
end
end
return actions
end
return struct