<commit_before>
-- precore

-- TODO: Substitution table access for solution and project

precore = {
	internal = {},
	configs = {},

	state = {
		initialized = false,
		env = {},
		enabled_opts = {},
		configs = {},
		solutions = {},
	},
}

local SubBlockFunctionKind = {
	init = 1,
	solution = 2,
	project = 3,
	non_global = 4,
}

local ConfigScopeKind = {
	global = 1,
	solution = 2,
	project = 3,
}

function precore.internal.init_guard()
	if not precore.state.initialized then
		error("precore must be initialized")
	end
end

function precore.internal.table_find_name(t, name)
	for _, v in pairs(t) do
		if v.name == name then
			return v
		end
	end
	return nil
end

function precore.internal.table_has(t, v)
	for _, tv in pairs(t) do
		if tv == v then
			return true
		end
	end
	return false
end

function precore.internal.table_flatten(t)
	for i, v in ipairs(t) do
		if type(v) == "table" then
			table.remove(t, i)
			for ii, vv in ipairs(v) do
				table.insert(t, i + ii - 1, vv)
			end
		end
	end
	return t
end

function precore.internal.do_subst(str, env)
	return string.gsub(str, "%${(%w+)}", env)
end

function precore.internal.execute_func_block(func, kind, scope_kind)
	assert(type(func) == "function")

	local execute = false
	local obj = nil

	if kind == SubBlockFunctionKind.init then
		execute = (scope_kind == ConfigScopeKind.global)
	elseif kind == SubBlockFunctionKind.solution then
		execute = (scope_kind == ConfigScopeKind.solution)
		obj = precore.active_solution()
	elseif kind == SubBlockFunctionKind.project then
		execute = (scope_kind == ConfigScopeKind.project)
		obj = precore.active_project()
	elseif kind == SubBlockFunctionKind.non_global then
		execute = (scope_kind ~= ConfigScopeKind.global)
	else
		error(string.format("unexpected function kind: '%s'", tostring(kind)))
	end

	if execute then
		func(obj)
	end
end

function precore.internal.execute_opt_block(opt_table, scope_kind)
	assert(
		type(opt_table.data) == "table" and
		(
			opt_table.init_handler == nil or
			type(opt_table.init_handler) == "function"
		)
	)

	if not precore.state.enabled_opts[opt_table] then
		precore.state.enabled_opts[opt_table] = true
		newoption(opt_table.data)
		if
			opt_table.init_handler ~= nil and
			scope_kind == ConfigScopeKind.global
		then
			opt_table.init_handler()
		end
	end
end

function precore.internal.execute_table_block(sub_block, scope_kind)
	for name, value in pairs(sub_block) do
		if name == "option" then
			if scope_kind == ConfigScopeKind.global then
				precore.internal.execute_opt_block(value, scope_kind)
			end
		elseif
			name == "init" or
			name == "solution" or
			name == "project"
		then
			precore.internal.execute_func_block(
				value,
				SubBlockFunctionKind[name],
				scope_kind
			)
		else
			error(string.format("unrecognized sub-block table key: '%s'", name))
		end
	end
end

function precore.internal.execute_block(scope, block, scope_kind)
	for _, sub_block in pairs(block) do
		if type(sub_block) == "string" then
			precore.internal.execute_block_by_name(
				scope,
				sub_block,
				scope_kind
			)
		elseif type(sub_block) == "function" then
			precore.internal.execute_func_block(
				sub_block,
				SubBlockFunctionKind.non_global,
				scope_kind
			)
		elseif type(sub_block) == "table" then
			precore.internal.execute_table_block(
				sub_block,
				scope_kind
			)
		else
			error(string.format(
				"sub-block of type '%s' not expected", type(sub_block)
			))
		end
	end
end

function precore.internal.execute_block_by_name(scope, name, scope_kind)
	local block = precore.configs[name]
	if not block then
		error(string.format("config '%s' does not exist", name))
	elseif not precore.internal.table_has(scope, name) then
		table.insert(scope, name)
		precore.internal.execute_block(scope, block, scope_kind)
	end
end

function precore.internal.configure(scope, names, scope_kind)
	for _, name in pairs(names) do
		precore.internal.execute_block_by_name(scope, name, scope_kind)
	end
end

--[[
	Get list of all solutions.
--]]
function precore.solutions()
	return precore.state.solutions
end

--[[
	Get the active precore solution for the active premake solution.

	Returns nil if either there is no active solution or the active
	solution was not created with make_solution().
--]]
function precore.active_solution()
	local sol = solution()
	if sol ~= nil then
		return precore.state.solutions[sol.name]
	end
	return nil
end

--[[
	Get the active precore project for the active premake project.

	Returns nil if there is no active solution or project, or if the
	active solution or project was not created with make_solution()
	or make_project().
--]]
function precore.active_project()
	local pc_sol = precore.active_solution()
	if pc_sol ~= nil then
		local proj = project()
		if proj ~= nil then
			return pc_sol.projects[proj.name]
		end
	end
	return nil
end

--[[
	Substitute substrings in the form "${NAME}" with their respective
	values from the given substitution table, the active project
	table, the active solution table, and the global table, in that
	order.

	If 'env' is nil, it is not used for substitution.
--]]
function precore.subst(str, env)
	if env ~= nil then
		str = precore.internal.do_subst(str, env)
	end
	local pc_proj = precore.active_project()
	if pc_proj ~= nil then
		str = precore.internal.do_subst(str, pc_proj.env)
	end
	local pc_sol = precore.active_solution()
	if pc_sol ~= nil then
		str = precore.internal.do_subst(str, pc_sol.env)
	end

	--[[
		NB: do_subst returns gsub values, which is (str, repcount).
		Because this will generally be passed straight to list-taking
		premake functions, we don't want to leak that number in the
		return.
	--]]
	str = precore.internal.do_subst(str, precore.state.env)
	return str
end

--[[
	Add a precore configuration block.

	'block' is a table of sub-blocks -- references, functions, and
	tables -- to apply.<commit_msg>precore: parse any substitution not containing whitespace; tidy.<commit_after>
-- precore

-- TODO: Substitution table access for solution and project

precore = {
	internal = {},
	configs = {},

	state = {
		initialized = false,
		env = {},
		enabled_opts = {},
		configs = {},
		solutions = {},
	},
}

local SubBlockFunctionKind = {
	init = 1,
	solution = 2,
	project = 3,
	non_global = 4,
}

local ConfigScopeKind = {
	global = 1,
	solution = 2,
	project = 3,
}

function precore.internal.init_guard()
	if not precore.state.initialized then
		error("precore must be initialized")
	end
end

function precore.internal.table_find_name(t, name)
	for _, v in pairs(t) do
		if v.name == name then
			return v
		end
	end
	return nil
end

function precore.internal.table_has(t, v)
	for _, tv in pairs(t) do
		if tv == v then
			return true
		end
	end
	return false
end

function precore.internal.table_flatten(t)
	for i, v in ipairs(t) do
		if type(v) == "table" then
			table.remove(t, i)
			for ii, vv in ipairs(v) do
				table.insert(t, i + ii - 1, vv)
			end
		end
	end
	return t
end

function precore.internal.do_subst(str, env)
	local str = string.gsub(str, "%${([^%s]+)}", env)
	return str
end

function precore.internal.execute_func_block(func, kind, scope_kind)
	assert(type(func) == "function")

	local execute = false
	local obj = nil

	if kind == SubBlockFunctionKind.init then
		execute = (scope_kind == ConfigScopeKind.global)
	elseif kind == SubBlockFunctionKind.solution then
		execute = (scope_kind == ConfigScopeKind.solution)
		obj = precore.active_solution()
	elseif kind == SubBlockFunctionKind.project then
		execute = (scope_kind == ConfigScopeKind.project)
		obj = precore.active_project()
	elseif kind == SubBlockFunctionKind.non_global then
		execute = (scope_kind ~= ConfigScopeKind.global)
	else
		error(string.format("unexpected function kind: '%s'", tostring(kind)))
	end

	if execute then
		func(obj)
	end
end

function precore.internal.execute_opt_block(opt_table, scope_kind)
	assert(
		type(opt_table.data) == "table" and
		(
			opt_table.init_handler == nil or
			type(opt_table.init_handler) == "function"
		)
	)

	if not precore.state.enabled_opts[opt_table] then
		precore.state.enabled_opts[opt_table] = true
		newoption(opt_table.data)
		if
			opt_table.init_handler ~= nil and
			scope_kind == ConfigScopeKind.global
		then
			opt_table.init_handler()
		end
	end
end

function precore.internal.execute_table_block(sub_block, scope_kind)
	for name, value in pairs(sub_block) do
		if name == "option" then
			if scope_kind == ConfigScopeKind.global then
				precore.internal.execute_opt_block(value, scope_kind)
			end
		elseif
			name == "init" or
			name == "solution" or
			name == "project"
		then
			precore.internal.execute_func_block(
				value,
				SubBlockFunctionKind[name],
				scope_kind
			)
		else
			error(string.format("unrecognized sub-block table key: '%s'", name))
		end
	end
end

function precore.internal.execute_block(scope, block, scope_kind)
	for _, sub_block in pairs(block) do
		if type(sub_block) == "string" then
			precore.internal.execute_block_by_name(
				scope,
				sub_block,
				scope_kind
			)
		elseif type(sub_block) == "function" then
			precore.internal.execute_func_block(
				sub_block,
				SubBlockFunctionKind.non_global,
				scope_kind
			)
		elseif type(sub_block) == "table" then
			precore.internal.execute_table_block(
				sub_block,
				scope_kind
			)
		else
			error(string.format(
				"sub-block of type '%s' not expected", type(sub_block)
			))
		end
	end
end

function precore.internal.execute_block_by_name(scope, name, scope_kind)
	local block = precore.configs[name]
	if not block then
		error(string.format("config '%s' does not exist", name))
	elseif not precore.internal.table_has(scope, name) then
		table.insert(scope, name)
		precore.internal.execute_block(scope, block, scope_kind)
	end
end

function precore.internal.configure(scope, names, scope_kind)
	for _, name in pairs(names) do
		precore.internal.execute_block_by_name(scope, name, scope_kind)
	end
end

--[[
	Get list of all solutions.
--]]
function precore.solutions()
	return precore.state.solutions
end

--[[
	Get the active precore solution for the active premake solution.

	Returns nil if either there is no active solution or the active
	solution was not created with make_solution().
--]]
function precore.active_solution()
	local sol = solution()
	if sol ~= nil then
		return precore.state.solutions[sol.name]
	end
	return nil
end

--[[
	Get the active precore project for the active premake project.

	Returns nil if there is no active solution or project, or if the
	active solution or project was not created with make_solution()
	or make_project().
--]]
function precore.active_project()
	local pc_sol = precore.active_solution()
	if pc_sol ~= nil then
		local proj = project()
		if proj ~= nil then
			return pc_sol.projects[proj.name]
		end
	end
	return nil
end

--[[
	Substitute substrings in the form "${NAME}" with their respective
	values from the given substitution table, the active project
	table, the active solution table, and the global table, in that
	order.

	If 'env' is nil, it is not used for substitution.
--]]
function precore.subst(str, env)
	if env ~= nil then
		str = precore.internal.do_subst(str, env)
	end
	local pc_proj = precore.active_project()
	if pc_proj ~= nil then
		str = precore.internal.do_subst(str, pc_proj.env)
	end
	local pc_sol = precore.active_solution()
	if pc_sol ~= nil then
		str = precore.internal.do_subst(str, pc_sol.env)
	end
	str = precore.internal.do_subst(str, precore.state.env)
	return str
end

--[[
	Add a precore configuration block.

	'block' is a table of sub-blocks -- references, functions, and
	tables -- to apply.
{'fraction_non_alphanumeric': 0.10187414030261348, 'fraction_numerical': 0.001375515818431912, 'mean_word_length': 3.946003401360544, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4755120', 'n_tokens_mistral': 4276, 'n_tokens_neox': 3936, 'n_words': 1141}