Commits

alfonse  committed d862a85

The most basic of code-generation for suites complete.
No param substitution, or param generation.

  • Participants
  • Parent commits 3b79211

Comments (0)

Files changed (4)

File tests/codegen.lua

+
+local gen = require("generate")
+
+local codegen = {}
+codegen._suites = {}
+
+local function assert_suite(operation)
+	assert(codegen._curr, "Attempt to " .. operation .. " when a suite is not current");
+	assert(codegen._curr._type == "suite", "Attempt to " .. operation .. " when a test is current");
+end
+
+local function assert_test(operation)
+	assert(codegen._curr, "Attempt to " .. operation .. " when a test is not current");
+	assert(codegen._curr._type == "test", "Attempt to " .. operation .. " when a suite is current");
+end
+
+local function get_suite_or_assert(operation)
+	assert(codegen._curr, "Attempt to " .. operation .. " when a suite is not current");
+	if(codegen._curr._type == "test") then
+		return codegen._curr._suite
+	else
+		return codegen._curr
+	end
+end
+
+local function assert_enum(enumValues, testValue, operation)
+	assert(enumValues[testValue], testValue .. " is not a legal " .. operation .. " value")
+end
+
+--Used for setting enumerated flags into a list of flags.
+local function set_flags(flagDataList, flagEnum, newFlags, operation)
+	if(type(newFlags) == "string") then
+		print(newFlags)
+		assert_enum(flagEnum, newFlags, operation)
+		flagDataList[newFlags] = true
+	elseif(type(newFlags) == "table") then
+		for _,flag in ipairs(newFlags) do
+			assert_enum(flagEnum, flag, operation)
+			flagDataList[flag] = true
+		end
+	else
+		assert(nil, "You must provide a string or a list of fields when trying to set the " .. operation .. " flags")
+	end
+end
+
+--Use the current directory as the base directory for all file operations.
+--Suites must have a `_type` field equal to "suite".
+local function create_new_suite(name)
+	local suite = {}
+	suite._type = "suite"
+	suite._base_dir = os.getcwd()
+	suite._tests = {}
+	
+	suite.name = name
+	suite.desc = "No Description"
+	suite.params = {}
+	suite.timings = {}
+	suite.includes = {}
+	suite.variables = {}
+	suite.initialize = {}
+	return suite
+end
+
+--Use the current directory as the base directory for all file operations.
+--Tests must have a `_type` field equal to "test".
+--Test has its current suite as the `_suite` field.
+local function create_new_test(name)
+	local test = {}
+	test._type = "test"
+	test._suite = codegen._curr
+	test._base_dir = os.getcwd()
+	
+	test.name = name
+	test.desc = "No Description"
+	test.flags = {}
+	test.render_flags = {}
+	test.includes = {}
+	test.variables = {}
+	test.initialize = {}
+	return test
+end
+
+-------------------------------
+-- Suite interface functions.
+
+--Interface functions for doing codegen work.
+function codegen.suite(name)
+	name = tostring(name)
+	assert(name, "Name of a suite must be a string")
+	
+	if(not codegen._suites[name]) then
+		codegen._suites[name] = create_new_suite(name)
+	end
+	
+	codegen._curr = codegen._suites[name]
+	return codegen._curr
+end
+
+--Sets the discription for a suite or test.
+function codegen.description(desc)
+	assert(codegen._curr, "Attempt to set a description for a suite/test that doesn't exist.")
+	
+	codegen._curr.desc = tostring(desc)
+end
+
+codegen.desc = codegen.description
+
+function codegen.range(first, count)
+	assert(count > 0, "Ranges must have a size > 0.")
+	local rng = {}
+	rng.first = tonumber(first)
+	rng.count = tonumber(count)
+	rng._rng = "range"
+	return rng
+end
+
+function codegen.half_open_range(first, last)
+	assert(last > first, "Half-open ranges must have last be larger than first")
+	return codegen.range(first, last - first)
+end
+
+function codegen.closed_range(first, last)
+	assert(last >= first, "Closed ranges must have last be larger than first")
+	return codegen.range(first, last - first + 1)
+end
+
+function codegen.params(paramList)
+	assert_suite("set parameters")
+	assert(type(paramList) == "table", "Parameters are a table of named values")
+	
+	for k,v in pairs(paramList) do
+		assert(type(k) == "string", "Parameter names must be strings.")
+		assert(k:match("^[%a%_][%w%_]*$"), "Parameter names must be valid Lua and C++ identifiers.")
+		--TODO: verify that v is a valid number range or series of numbers.
+		codegen._curr.params[k] = v;
+	end
+end
+
+local legalTimings = {cpu_time = true, gpu_time = true, gpu_latency = true}
+
+function codegen.timings(timings)
+	assert_suite("set timings")
+	
+	set_flags(codegen._curr.timings, legalTimings, timings, "timings")
+end
+
+function codegen.executions(numExecutions)
+	assert_suite("set default execution count")
+	numExecutions = tonumber(numExecutions)
+	assert(type(numExecutions) == "number")
+	
+	codegen._curr.executions = math.floor(numExecutions)
+end
+
+local function InsertTableOfStrings(output, input, funcName)
+	if(type(input) == "string") then
+		output[#output + 1] = input
+	elseif(type(input) == "table") then
+		for _,v in ipairs(input) do
+			output[#output + 1] = v
+		end
+	else
+		assert(nil, funcName .. " takes a string or a table of strings.");
+	end
+
+end
+
+function codegen.includes(includes)
+	assert(codegen._curr, "Attempt to set includes for a suite/test that doesn't exist.")
+	
+	InsertTableOfStrings(codegen._curr.includes, includes, "includes")
+end
+
+function codegen.variables(vars)
+	assert(codegen._curr, "Attempt to set variables for a suite/test that doesn't exist.")
+
+	for varname, vartype in pairs(vars) do
+		codegen._curr.variables[varname] = vartype
+	end
+end
+
+codegen.vars = codegen.variables
+
+function codegen.initialize(init_cmds)
+	assert(codegen._curr, "Attempt to set initialization data for a suite/test that doesn't exist.")
+	
+	InsertTableOfStrings(codegen._curr.initialize, init_cmds, "initialize")
+end
+
+codegen.init = codegen.initialize
+
+
+--------------------------------
+-- Test specific functions.
+
+function codegen.test(name)
+	assert_suite("create a test")
+	
+	name = tostring(name)
+	assert(name, "Name of a test must be a string")
+	
+	if(not codegen._curr._tests[name]) then
+		codegen._curr._tests[name] = create_new_test(name)
+	end
+	
+	codegen._curr = codegen._curr._tests[name]
+	return codegen._curr
+end
+
+local legalFlags = {cache_flush = true, finish = true, swap = true}
+
+function codegen.flags(flags)
+	assert_test("set flags")
+	set_flags(codegen._curr.flags, legalFlags, flags, "flags")
+end
+
+local legalRenderFlags = {discard = true}
+
+function codegen.render_flags(render_flags)
+	assert_test("set render flags")
+	set_flags(codegen._curr.render_flags, legalRenderFlags, render_flags, "render_flags")
+end
+
+--Build the environment that the codegen-loaded scripts execute within.
+local copyFromGlobal =
+{
+	--Take these packages.
+	"debug",
+	"io",
+	"os",
+	"package",
+	"string",
+	"table",
+	"coroutine",
+	
+	--Functions
+	"assert",
+	"collectgarbage",
+	"error",
+	"getfenv",
+	"getmetatable",
+	"ipairs",
+	"load",
+	"loadstring",
+	"module",
+	"next",
+	"pairs",
+	"pcall",
+	"print",
+	"rawequal",
+	"rawget",
+	"rawset",
+	"require",
+	"select",
+	"setfenv",
+	"setmetatable",
+	"tonumber",
+	"tostring",
+	"type",
+	"unpack",
+	"xpcall",
+}
+
+--Copy functions/modules from the Lua envrionment.
+codegen._env = {}
+for _, copy in ipairs(copyFromGlobal) do
+	codegen._env[copy] = _G[copy]
+end
+
+--Add our functions from the codegen space to the environment.
+for name, copy in pairs(codegen) do
+	if(not name:match("^%_")) then
+		codegen._env[name] = copy
+	end
+end
+
+--Emulate lua dofile, but change the current directory.
+--Also, change the environment, so they can't see Premake4 (outside of Premake's
+--additions to global tables).
+function codegen.dofile(filename)
+	local oldcwd = os.getcwd()
+	local fullPathname = path.getabsolute(filename)
+	
+	local hFile = io.open(fullPathname, "rt")
+	local fileString = hFile:read("*a")
+	hFile:close()
+	
+	os.chdir(path.getdirectory(fullPathname))
+	local chunk = assert(loadstring(fileString, path.getname(filename)))
+	setfenv(chunk, codegen._env)
+	chunk()
+	os.chdir(oldcwd)
+end
+
+local function WriteInclude(hFile, hdrsWritten, include)
+	if(type(include) == "table") then
+		for _,file in ipairs(include) do
+			WriteInclude(hFile, hdrsWritten, file)
+		end
+	else
+		if(not hdrsWritten[include]) then
+			hFile:write("#include ", include, "\n")
+			hdrsWritten[include] = true
+		end
+	end
+end
+
+local function WriteIncludes(hFile, suite)
+	local hdrsWritten = {}
+	
+	--Write the standard headers.
+	WriteInclude(hFile, hdrsWritten, gen.GetStandardIncludes())
+	--TODO: Use OpenGL version/profile that was asked for.
+	WriteInclude(hFile, hdrsWritten, gen.GetOpenGLIncludes(3, 3))
+	--TODO: Use FreeGLUT only if asked.
+	WriteInclude(hFile, hdrsWritten, gen.GetFreeglutIncludes())
+
+	--Now include what the user wanted.
+	WriteInclude(hFile, hdrsWritten, suite.includes)
+	
+	for testname, test in pairs(suite._tests) do
+		WriteInclude(hFile, hdrsWritten, test.includes)
+	end
+end
+
+local function WriteAndFormatInitStmts(hFile, suite, stmt)
+	--TODO: Load a file if stmt is a file.
+	--TODO: do the formatting.
+	
+	hFile:write("\t\t", stmt:gsub("%\n", "\n\t\t"), "\n\n")
+end
+
+local specialVarTypes =
+{
+	["gl buffer object"] =
+		{type = "GLuint", destroy = "glDeleteBuffers(1, &%s);"},
+	["gl vao"] =
+		{type = "GLuint", destroy = "glDeleteVertexArrays(1, &%s);"},
+	["gl program"] =
+		{type = "GLuint", destroy = "glDeleteProgram(%s);"},
+	["gl list buffer object"] = 
+		{type = "std::vector<GLuint>", destroy = "util::DestroyBufferObjects(%s);"},
+	["gl list vao"] =
+		{type = "std::vector<GLuint>", destroy = "util::DestroyVertexArrays(%s);"},
+	["gl list program"] =
+		{type = "std::vector<GLuint>", destroy = "util::DestroyPrograms(%s);"},
+}
+
+local function WriteSuiteClass(hFile, suite)
+	hFile:write("struct ", gen.GetSuiteClassname(suite.name), "\n{\n")
+	
+	--Write variables first.
+	for varname, vartype in pairs(suite.variables) do
+		hFile:write("\t")
+		if(specialVarTypes[vartype]) then
+			hFile:write(specialVarTypes[vartype].type)
+		else
+			hFile:write(vartype)
+		end
+		
+		hFile:write(" ", varname, ";\n")
+	end
+	hFile:write("\n")
+	
+	--Write the constructor.
+	hFile:write("\t", gen.GetSuiteClassname(suite.name), "()\n\t{\n")
+	for _, stmt in ipairs(suite.initialize) do
+		WriteAndFormatInitStmts(hFile, suite, stmt)
+	end
+	hFile:write("\t}\n\n")
+	
+	--Write the destructor.
+	hFile:write("\t~", gen.GetSuiteClassname(suite.name), "()\n\t{\n")
+	for varname, vartype in pairs(suite.variables) do
+		if(specialVarTypes[vartype]) then
+			hFile:write("\t\t")
+			hFile:write(string.format(specialVarTypes[vartype].destroy, varname))
+			hFile:write("\n")
+		end
+	end
+	hFile:write("\t}\n")
+	
+	
+	hFile:write("};\n");
+end
+
+function codegen.gencode()
+	for name, suite in pairs(codegen._suites) do
+		local mainFilename = suite._base_dir .. "/" .. gen.GetMainFilename(name)
+
+		local hFile = io.open(mainFilename, "w+")
+		gen.WritePreamble(hFile, name)
+		
+		WriteIncludes(hFile, suite)
+		hFile:write("\n\n")
+		
+		--TODO: Write basic parameter data.
+		
+		WriteSuiteClass(hFile, suite)
+		hFile:write("\n\n")
+		
+		
+		hFile:close()
+
+		--[[
+		print("Suite:", gen.GetMainFilename(name), suite._base_dir)
+		for testname, test in pairs(suite._tests) do
+			print("\tTest name:", testname)
+		end
+		]]
+	end
+end
+
+return codegen

File tests/generate.lua

+
+local gen = {}
+
+function gen.GetMainFilename(suiteName)
+	return suiteName .. "_codegen.cpp"
+end
+
+function gen.GetSuiteClassname(suiteName)
+	return suiteName .. "_suite"
+end
+
+function gen.GetTestClassname(testName)
+	return testName .. "_test"
+end
+
+function gen.WritePreamble(hFile, suite_name)
+	hFile:write([=[
+	/* Here's some preamble text. */
+	/* Here's some preamble text. */
+	/* Here's some preamble text. */
+	/******************************/
+]=])
+end
+
+function gen.GetStandardIncludes()
+	return {
+		"<vector>",
+		"<iostream>",
+		"<fstream>",
+		"<boost/range/irange.hpp>",
+		"<boost/foreach.hpp>",
+	}
+end
+
+function gen.GetOpenGLIncludes(majorVersion, minorVersion, bCompatibility)
+	local mainGLFuncName =
+		string.format("<glload/gl_%i_%i%s.h>",
+			majorVersion,
+			minorVersion, 
+			iif(bCompatibility, "_comp", ""))
+
+	return {
+		mainGLFuncName,
+		"<glload/gll.hpp>",
+		"<glutil/Shader.h>",
+		[["../util/util.h"]],
+	}
+end
+
+function gen.GetFreeglutIncludes()
+	return {
+		"<GL/freeglut.h>",
+	}
+end
+
+return gen

File tests/interleave_arrays/suite.lua

+
+suite "interleave_arrays"
+	desc "Determine how much better interleaved arrays perform compared to non-interleaved and multiple-buffered arrays."
+	
+	includes "<boost/range.hpp>"
+	includes "<sstream>"
+	includes "<boost/range/irange.hpp>"
+	
+	params
+	{
+		attribute_count = closed_range(3, 8),
+		vertex_count = {25000, 50000, 100000},
+	}
+	
+	timings {"gpu_time", "gpu_latency",}
+	executions(3)
+	
+	variables {
+		test1 = "std::vector<int>",
+		a_buffer = "gl buffer object",
+		buffers = "gl list buffer object",
+	}
+	
+	initialize [[This is some code]]
+	initialize [[
+Code on multiple lines.
+This should be indented properly.
+
+Here's one empty line. This should remain.]]
+
+test "arrayIndivSep"
+	desc "Separate arrays, where each array is within its own buffer object."
+	
+	flags {"finish", "swap"}
+	render_flags {"discard"}
+

File tests/premake4.lua

 
 
 dofile "../glsdk/links.lua"
+local codegen = require "codegen"
 
 solution "perftests"
 	configurations { "Debug", "Release" }
 	configuration "linux"
 		links {"GL", "GLU"}
 
+local matches = os.matchfiles("**suite.lua")
+
+for _,v in ipairs(matches) do
+	codegen.dofile(v)
+end
+
+codegen.gencode()
+
+