diff --git a/README.md b/README.md index 537f896..6405e53 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,100 @@ ToDo ToDo +## Tools + +### glkernel-cli + +The glkernel-cli tool provides a convenient way to generate and manipulate kernels using the command line. +The usage is as follows: + +``` +glkernel-cli [--force] [--beautify] [--output ] [--format ] + +Options: + -b, --beautify Beautify the output (only applies to json output format) + --force Override the output file, if it exists + -f, --format File format for the generated / converted kernel (e.g., json, png, h) + -o, --output File that the generated / converted kernel will be written to (defaults: .json for generation, .png for conversion) +``` + +- The default output format for kernel generation is JSON +- The default output format for kernel conversion is PNG +- If not output file name is given, the output file name will be deduced from the input file name (here, it would be `kernel.json`) +- If no output format is given, the output format will be deduced from the output file name (explicitly given or deduced) + +#### Generating a kernel using JavaScript + +Kernels can be generated from JavaScript by simply passing a `.js` file as input to the command line tool. +Examples: + +``` +$ glkernel-cli kernel.js + +$ glkernel-cli kernel.js -o random_noise_kernel.json --force --beautify + +$ glkernel-cli kernel.js -o sorted_kernel -f png +``` + +A JavaScript interface (`JSInterface.h`, `JSInterface.cpp`, `glkernel.js`) allows calling glkernel functionality from user scripts. +It is included in the tool's sources (C++ files), and in the `data/` directory (JavaScript file). +The interface is automatically generated from the existing glkernel API. + +__If you extend or change the glkernel API, please [re-generate the JavaScript interface](#generating-the-javascript-interface)!__ + +##### Writing kernel generation instructions in JavaScript + +While the glkernel library uses free functions, the JavaScript API provides manipulation of kernels via object methods on kernel objects. +The API method names are taken from the library. + +The following script shows the usage of the JavaScript API by example (it can be found in `scripts/kernel.js`): + +```javascript +// create new Kernel2 object (i.e., a 3D kernel of dimensions 10x5x2, holding vec2's as values +var kernel = new Kernel2(10, 5, 2); + +// translates to glkernel::sequence::uniform(kernel, 0.0, 1.0) +kernel.sequence.uniform(0.0, 1.0); + +// examples for other kernel methods +kernel.shuffle.random(); +kernel.scale.range(-1.0, 1.0); + +// glkernel::sort::distance for a Kernel2 requires a vec2 as parameter +// vec parameters can be passed as JavaScript arrays +kernel.sort.distance([0.0, 0.0]); +``` + +#### Converting an existing kernel + +After generating a kernel in JSON format, that kernel can be read by the tool to be converted into another representation (e.g., PNG). +This is achieved by simply passing a `.json` file as input to the command line tool. +Examples: + +``` +$ glkernel-cli kernel.json + +$ glkernel-cli kernel.json -o sorted_kernel -f png --force +``` + +If no output file or format is given, the default output format is PNG. + +#### Generating the JavaScript interface + +The JavaScript interface files can simply be re-generated using the CMake `generate` target, either from your IDE, or from the project's root folder using the following CMake command: + +``` +$ cmake --build ./build --target generate +``` + +This requires Python 2 or 3 to be installed on the system. +Alternatively, the Python script can be executed manually (also from the root folder): + +__TODO__: update this if the destination of JS and C++ files are different +``` +$ python scripts/generate.py -t scripts/templates -d source/tools/glkernel-cli +``` + ##### glkernel-cmd Additionally to using glkernel as a library, there is a standalone command line tool to generate kernels from JSON descriptions. diff --git a/data/glkernel.js b/data/glkernel.js new file mode 100644 index 0000000..d3e2a86 --- /dev/null +++ b/data/glkernel.js @@ -0,0 +1,192 @@ + +// THIS IS A GENERATED FILE! +// DO NOT EDIT + +GradientNoiseType = { + Perlin: 0, + Simplex: 1 +}; + +HemisphereMapping = { + Uniform: 0, + Cosine: 1 +}; + +OctaveType = { + Standard: 0, + Cloud: 1, + CloudAbs: 2, + Wood: 3, + Paper: 4 +}; + +var _Kernel = function(x,y,z) { + this._initialize = function(x,y,z) { + var that = this; + + this.kernel = this.generateKernel(x,y,z); + + this.noise = { + uniform: function(range_min, range_max) { + _glkernel.noise_uniform(that.kernel, range_min, range_max); + return that; + }, + normal: function(mean, stddev) { + _glkernel.noise_normal(that.kernel, mean, stddev); + return that; + }, + gradient: function(noise_type, octave_type, startFrequency, octaves) { + // Defaults + noise_type = (typeof noise_type !== 'undefined') ? noise_type : GradientNoiseType.Perlin; + octave_type = (typeof octave_type !== 'undefined') ? octave_type : OctaveType.Standard; + startFrequency = (typeof startFrequency !== 'undefined') ? startFrequency : 3; + octaves = (typeof octaves !== 'undefined') ? octaves : 5; + + _glkernel.noise_gradient(that.kernel, noise_type, octave_type, startFrequency, octaves); + return that; + } + }; + this.sample = { + poisson_square: function(num_probes) { + // Defaults + num_probes = (typeof num_probes !== 'undefined') ? num_probes : 32; + + _glkernel.sample_poisson_square(that.kernel, num_probes); + return that; + }, + poisson_square: function(min_dist, num_probes) { + // Defaults + num_probes = (typeof num_probes !== 'undefined') ? num_probes : 32; + + _glkernel.sample_poisson_square1(that.kernel, min_dist, num_probes); + return that; + }, + stratified: function() { + _glkernel.sample_stratified(that.kernel); + return that; + }, + hammersley: function() { + _glkernel.sample_hammersley(that.kernel); + return that; + }, + halton: function(base1, base2) { + _glkernel.sample_halton(that.kernel, base1, base2); + return that; + }, + hammersley_sphere: function(type) { + // Defaults + type = (typeof type !== 'undefined') ? type : HemisphereMapping.Uniform; + + _glkernel.sample_hammersley_sphere(that.kernel, type); + return that; + }, + halton_sphere: function(base1, base2, type) { + // Defaults + type = (typeof type !== 'undefined') ? type : HemisphereMapping.Uniform; + + _glkernel.sample_halton_sphere(that.kernel, base1, base2, type); + return that; + }, + best_candidate: function(num_candidates) { + // Defaults + num_candidates = (typeof num_candidates !== 'undefined') ? num_candidates : 32; + + _glkernel.sample_best_candidate(that.kernel, num_candidates); + return that; + }, + n_rooks: function() { + _glkernel.sample_n_rooks(that.kernel); + return that; + }, + multi_jittered: function(correlated) { + // Defaults + correlated = (typeof correlated !== 'undefined') ? correlated : false; + + _glkernel.sample_multi_jittered(that.kernel, correlated); + return that; + }, + golden_point_set: function() { + _glkernel.sample_golden_point_set(that.kernel); + return that; + } + }; + this.scale = { + range: function(rangeToLower, rangeToUpper, rangeFromLower, rangeFromUpper) { + // Defaults + rangeFromLower = (typeof rangeFromLower !== 'undefined') ? rangeFromLower : 0; + rangeFromUpper = (typeof rangeFromUpper !== 'undefined') ? rangeFromUpper : 1; + + _glkernel.scale_range(that.kernel, rangeToLower, rangeToUpper, rangeFromLower, rangeFromUpper); + return that; + } + }; + this.sequence = { + uniform: function(range_min, range_max) { + _glkernel.sequence_uniform(that.kernel, range_min, range_max); + return that; + } + }; + this.shuffle = { + bucket_permutate: function(subkernel_width, subkernel_height, subkernel_depth, permutate_per_bucket) { + // Defaults + subkernel_width = (typeof subkernel_width !== 'undefined') ? subkernel_width : 1; + subkernel_height = (typeof subkernel_height !== 'undefined') ? subkernel_height : 1; + subkernel_depth = (typeof subkernel_depth !== 'undefined') ? subkernel_depth : 1; + permutate_per_bucket = (typeof permutate_per_bucket !== 'undefined') ? permutate_per_bucket : false; + + _glkernel.shuffle_bucket_permutate(that.kernel, subkernel_width, subkernel_height, subkernel_depth, permutate_per_bucket); + return that; + }, + bayer: function() { + _glkernel.shuffle_bayer(that.kernel); + return that; + }, + random: function(start) { + // Defaults + start = (typeof start !== 'undefined') ? start : 1; + + _glkernel.shuffle_random(that.kernel, start); + return that; + } + }; + this.sort = { + distance: function(origin) { + _glkernel.sort_distance(that.kernel, origin); + return that; + } + }; + }; +}; + +var Kernel1 = function(x,y,z) { + this.generateKernel = function(x,y,z) { + return _glkernel.createKernel1(x,y,z); + } + this._initialize(x,y,z); +}; + +var Kernel2 = function(x,y,z) { + this.generateKernel = function(x,y,z) { + return _glkernel.createKernel2(x,y,z); + } + this._initialize(x,y,z); +}; + +var Kernel3 = function(x,y,z) { + this.generateKernel = function(x,y,z) { + return _glkernel.createKernel3(x,y,z); + } + this._initialize(x,y,z); +}; + +var Kernel4 = function(x,y,z) { + this.generateKernel = function(x,y,z) { + return _glkernel.createKernel4(x,y,z); + } + this._initialize(x,y,z); +}; + +Kernel1.prototype = new _Kernel; +Kernel2.prototype = new _Kernel; +Kernel3.prototype = new _Kernel; +Kernel4.prototype = new _Kernel; diff --git a/scripts/generate.py b/scripts/generate.py new file mode 100644 index 0000000..bfed048 --- /dev/null +++ b/scripts/generate.py @@ -0,0 +1,515 @@ + +import posixpath # instead of os.path, to always use forward slashes +import os +import re + +# TODOs: +# (more TODOs in code) + +standardTypes = { + "bool", + "char", + "short", + "int", + "long", + "long long", + "unsigned char", + "unsigned short", + "unsigned int", + "unsigned long", + "unsigned long long", + "float", + "double", + "long double", + + "size_t", + "glm::uint16" +} + +# ------------ +# large-scale parsing + +def findPairedBrace(code): + nl = 1 + + for i,c in enumerate(code): + if c == '}': nl -= 1 + if c == '{': nl += 1 + + if nl == 0: + return i + +def getNamespaces(code): + namespaces = dict() + global namespaceBeginPattern + namespaceBeginPattern = re.compile(r"^namespace(?:\s+(?P\w+))?\s*\{", re.M | re.S) + + lastEnd = 0 + for match in namespaceBeginPattern.finditer(code): + # skip inner namespaces + if match.start() < lastEnd: + continue + + nsStart = match.end() # behind opening brace + nsEnd = findPairedBrace(code[nsStart:]) + nsStart # index of closing brace + + subNamespaces = getNamespaces(code[nsStart:nsEnd]) + namespaces[(nsStart,nsEnd)] = (match.group("name") or "", subNamespaces) + + # remember end for skipping inner namespaces + lastEnd = nsEnd + + return namespaces + +def namespaceAtPosition(namespaces, pos): + for span in namespaces: + if pos in range(*span): + innerNS = namespaceAtPosition(namespaces[span][1], pos - span[0]) + + return namespaces[span][0] + ("::" + innerNS if innerNS else "") + + return "" + +# ------------ +# small-scale parsing + +def removeCVRef(typeString): + return re.sub(r'^(?:const |volatile )*(.*?)(?:\s*&)?$', r'\1', typeString) + +def splitParams(paramString): + splitParams = [p.strip() for p in paramString.split(',') if p.strip()] + + i = 0 + while i < len(splitParams)-1: + if splitParams[i].count('<') != splitParams[i].count('>'): + splitParams[i:i+2] = [splitParams[i] + ", " + splitParams[i+1]] + else: + i += 1 + + paramDefaults = [(split[0].strip(), split[1].strip() if len(split) > 1 else '') for split in [p.rsplit('=', 1) for p in splitParams]] + paramsSplit = [(l.strip(), r.strip(), d) for l,r,d in [p.rsplit(' ', 1) + [d] for p,d in paramDefaults]] + + return paramsSplit + +def removeParamDefaults(params): + return [(p[0], p[1]) for p in params] + +def getParamNames(params): + return [p[1] for p in params] + +def getParamTypes(params): + return [p[0] for p in params] + +def getParamDefaults(params): + return [(p[1], p[2]) for p in params if p[2]] + +def possibleTypes(argType, templateList): + if re.match("^\w+$", argType): # argType is just single word, e.g. 'T' + if "std::enable_if::value>::type" in templateList: + return {"float"} + else: + return {"float", "vec2", "vec3", "vec4"} + + genVecMatch = re.match("(\w+)\s*<\s*\w+\s*,\s*\w+\s*>", argType) # general glm vector, e.g. 'V' + if genVecMatch: + if re.search("template\s*<\s*(?:typename|class)\s*,\s*glm::precision\s*>\s*(?:typename|class)\s*" + genVecMatch.group(1), templateList): + return {"vec2", "vec3", "vec4"} + + specVecMatch = re.match("glm::tvec(\d)<.*?>", argType) # specific glm vector, e.g. 'glm::tcev4' + if specVecMatch: + return {"vec"+specVecMatch.group(1)} + + return {argType} + +def paramTypeFromKernelTypes(kernelTypeString, paramTypeString, templateList, enums): + if possibleTypes(paramTypeString, templateList) == {'float'}: + return "float" + + strippedTypeString = removeCVRef(paramTypeString) + + if kernelTypeString == strippedTypeString: # e.g. 'V' and 'const V&' + return "same" + + if strippedTypeString in kernelTypeString: # e.g. 'const T&' and 'V' + return "float" + + if strippedTypeString in [e["name"] for e in enums]: + return strippedTypeString + + if strippedTypeString in standardTypes: + return strippedTypeString + + print("Unknown Type encountered: " + paramTypeString) + +def getEnumValues(valueDefString): + definitions = [d.strip() for d in valueDefString.split(',')] + values = [] + + i = 0 + for d in definitions: + if '=' in d: + _, _, expr = d.partition('=') + i = eval(expr, dict(values)) + + values.append((d,i)) + i += 1 + + return values + +# ------------ +# generation + +def enumForJS(value, enums): + if "::" not in value: + return value + + enumDict = {enum["name"]: {valueName:value for valueName, value in enum["values"]} for enum in enums} + + enumName, _, valueName = value.partition("::") + + if enumName not in enumDict: + # TODO: Warning? + return value + + if valueName not in enumDict[enumName]: + # TODO: Warning? + return value + + return enumName + "." + valueName + +def jsFuncName(func): + name = func["name"] + if "alternativeNumber" in func: + name += str(func["alternativeNumber"]) + + return "_".join(func["namespace"].split('::')[1:] + [name]) + +def jsFunction(func, enums): + assert func["namespace"].startswith("glkernel::"), "function \""+func["name"]+"\" from outside glkernel namespace: " + func["namespace"] + + namespaceStack = func["namespace"].split("::") + namespaceStack.pop(0) # ignore outmost namespace glkernel + + defaultChecks = '\n'.join([" {name} = (typeof {name} !== 'undefined') ? {name} : {default};".format(name=name, default=enumForJS(default, enums)) for name, default in getParamDefaults(func["params"])]) + if defaultChecks: + defaultChecks = "\n // Defaults\n" + defaultChecks + "\n" + + paramString = ', '.join(getParamNames(func["params"])) + paramStringKomma = "" if not paramString else ', ' + paramString + + firstLine = " {name}: function({params}) {{".format(name = func["name"], params = paramString) + finalCall = " _glkernel.{generatedName}(that.kernel{paramsWithKomma});".format(generatedName = jsFuncName(func), paramsWithKomma = paramStringKomma) + + jsCode = """{firstLine}{defaultChecks} +{finalCall} + return that; + }}""".format(firstLine = firstLine, defaultChecks = defaultChecks, finalCall = finalCall) + + return jsCode + +def buildJSNamespaces(funcs, enums): + namespaces = dict() + + for func in funcs: + if func["namespace"] not in namespaces: + namespaces[func["namespace"]] = [] + + namespaces[func["namespace"]].append(jsFunction(func, enums)) + + nsCodes = [] + + for ns, codes in sorted(namespaces.items()): + name = ns[len("glkernel::"):] + + functionsCode = ",\n".join(codes) + nsCode = " this.{name} = {{\n{funcCodes}\n }};".format(name = name, funcCodes = functionsCode) + + nsCodes.append(nsCode) + + return "\n".join(nsCodes) + +def buildJSEnums(enums): + enumCodes = [] + for enum in sorted(enums, key=lambda e: e["name"]): + valueLines = [] + for name, value in enum["values"]: + valueLines.append(" " + name + ": " + str(value)) + + valuesCode = ',\n'.join(valueLines) + enumCode = "{name} = {{\n{members}\n}};".format(name = enum["name"], members = valuesCode) + enumCodes.append(enumCode) + + return "\n\n".join(enumCodes) + +def buildCPPFunctionAdds(funcs): + return '\n'.join([' addFunction("{name}", this, &JSInterface::{name});'.format(name = jsFuncName(func)) for func in funcs]) + +def buildCPPFunctionForwardDecl(func, enums): + enumNames = [enum["name"] for enum in enums] + + funcName = jsFuncName(func) + + # Deduce parameter types + kernelTypes = possibleTypes(func["kernelType"], func["template"]) + paramTypes = [paramTypeFromKernelTypes(func["kernelType"], param[0], func["template"], enums) for param in func["params"]] + cases = [(kernelType, [kernelType if param == "same" else param for param in paramTypes]) for kernelType in kernelTypes] + + if "alternatives" in func: + for alt in func["alternatives"]: + altKernelTypes = possibleTypes(alt["kernelType"], alt["template"]) + altParamTypes = [paramTypeFromKernelTypes(alt["kernelType"], param[0], alt["template"], enums) for param in alt["params"]] + cases += [(kernelType, [kernelType if param == "same" else param for param in altParamTypes]) for kernelType in altKernelTypes] + + cases.sort() + + typesPerParam = [{case[1][i] for case in cases} for i in range(len(cases[0][1]))] + variantNeeded = [len(types) > 1 for types in typesPerParam] + enumParam = [list(types)[0] in enumNames for types in typesPerParam] + + paramTypes = ["cppexpose::Object*"] + ["const cppexpose::Variant&" if needVariant else "int" if isEnum else list(types)[0] for types, needVariant, isEnum in zip(typesPerParam, variantNeeded, enumParam)] + paramNames = ["obj"] + [param[1] for param in func["params"]] + paramList = ", ".join(type + " " + name for type,name in zip(paramTypes, paramNames)) + + return " void " + funcName + "(" + paramList + ");" + +def buildCPPFunctionForwardDecls(funcs, enums): + return '\n'.join([buildCPPFunctionForwardDecl(func, enums) for func in funcs]) + +def buildCPPIncludes(fileNames): + includeFiles = [] + + for f in fileNames: + if not "include/" in f: + print("Error: " + f + " is outside include directory!") + continue + + while not f.startswith("include/"): + f = f[1:] + + f = f[len("include/"):] + includeFiles.append(f) + + return '\n'.join(['#include <' + name + '>' for name in includeFiles]) + +def buildCPPImplementation(func, enums): + enumNames = [enum["name"] for enum in enums] + + funcName = jsFuncName(func) + + # Deduce parameter types + kernelTypes = possibleTypes(func["kernelType"], func["template"]) + paramTypes = [paramTypeFromKernelTypes(func["kernelType"], param[0], func["template"], enums) for param in func["params"]] + cases = [(kernelType, [kernelType if param == "same" else param for param in paramTypes]) for kernelType in kernelTypes] + + if "alternatives" in func: + for alt in func["alternatives"]: + altKernelTypes = possibleTypes(alt["kernelType"], alt["template"]) + altParamTypes = [paramTypeFromKernelTypes(alt["kernelType"], param[0], alt["template"], enums) for param in alt["params"]] + cases += [(kernelType, [kernelType if param == "same" else param for param in altParamTypes]) for kernelType in altKernelTypes] + + cases.sort() + + typesPerParam = [{case[1][i] for case in cases} for i in range(len(cases[0][1]))] + variantNeeded = [len(types) > 1 for types in typesPerParam] + enumParam = [list(types)[0] in enumNames for types in typesPerParam] + + paramTypes = ["cppexpose::Object*"] + ["const cppexpose::Variant&" if needVariant else "int" if isEnum else list(types)[0] for types, needVariant, isEnum in zip(typesPerParam, variantNeeded, enumParam)] + paramNames = ["obj"] + [param[1] for param in func["params"]] + paramList = ", ".join(type + " " + name for type,name in zip(paramTypes, paramNames)) + + # Parameters with only one possible type may be handled before branching into kernel types + earlyConv = [] + for param, enumType in [(name, list(types)[0]) for name, types, isEnum in zip(paramNames[1:], typesPerParam, enumParam) if isEnum]: + enum = [e for e in enums if e["name"] == enumType][0] + earlyConv.append(" const auto {name}_enum = static_cast<{namespace}::{type}>({name});".format(name=param, type=enum["name"], namespace = enum["namespace"])) + + earlyConversions = '\n'.join(earlyConv) + if earlyConversions: + earlyConversions += '\n\n' + + # Split cases by kernel type + casesByKernelType = dict() + for kernel, params in cases: + if kernel not in casesByKernelType: + casesByKernelType[kernel] = [] + casesByKernelType[kernel].append(params) + + # Build code for different kernel types + kernelCases = [] + for kernelType, cases in sorted(casesByKernelType.items()): + kernelDim = 1 if kernelType == "float" else int(kernelType[-1]) + firstLine = " if (auto kernelObj = dynamic_cast(obj))" + neededVariantChecks = False + + # Build code for specific parameter type constellations + paramCases = [] + for case in cases: + # Check if variants contain acceptable values + variantChecks = [] + for name, type, needsVariant in zip(paramNames[1:], case, variantNeeded): + if not needsVariant: + continue + checkFunction = "canBe" + type[0].upper() + type[1:] + variantChecks.append(checkFunction + "(" + name + ")") + + neededVariantChecks = True + + # Unpack variants to usable values + variantUnpackers = [] + for name, type, needsVariant in zip(paramNames[1:], case, variantNeeded): + if not needsVariant: + continue + convFunction = "variantTo" + type[0].upper() + type[1:] + variantUnpackers.append(" const auto {name}_conv = {func}({name});".format(name = name, func = convFunction)) + + variantUnpackingCode = '\n'.join(variantUnpackers) + if variantUnpackingCode: + variantUnpackingCode += '\n\n' + + finalCallParams = ["kernelObj->kernel()"] + [name + ("_enum" if isEnum else "_conv" if needsVariant else "") for name, isEnum, needsVariant in zip(paramNames[1:], enumParam, variantNeeded)] + finalCallParamString = ', '.join(finalCallParams) + finalCallString = " {namespace}::{name}({params});".format(namespace = func["namespace"], name = func["name"], params = finalCallParamString) + + innerCode = "{variants}{finalCall}\n return;".format(variants = variantUnpackingCode, finalCall = finalCallString) + + caseCode = innerCode + if variantChecks: + variantCheckCode = ' && '.join(variantChecks) + indentedInnerCode = '\n'.join([(" " + line).rstrip() for line in innerCode.split('\n')]) + caseCode = " if ({varChecks})\n {{\n{innerCode}\n }}".format(varChecks = variantCheckCode, innerCode = indentedInnerCode) + + paramCases.append(caseCode) + + if neededVariantChecks: + paramCases.append(" cppassist::error(\"glkernel-JSInterface\") << \"Invalid parameters for " + funcName + "\";\n return;") + + paramCasesCode = '\n\n'.join(paramCases) + + kernelCaseCode = "{firstLine}\n {{\n{cases}\n }}".format(firstLine = firstLine, cases = paramCasesCode) + kernelCases.append(kernelCaseCode) + + kernelCasesCode = '\n\n'.join(kernelCases) + + fullCode = """void JSInterface::{funcName}({paramList}) +{{ +{earlyConv}{cases} + + cppassist::error("glkernel-JSInterface") << "Invalid kernel object for {funcName}"; +}}""".format(funcName = funcName, paramList = paramList, earlyConv = earlyConversions, cases = kernelCasesCode) + + return fullCode + +def buildCPPImplementations(funcs, enums): + return '\n\n\n'.join([buildCPPImplementation(func, enums) for func in funcs]) + +# ------------ +# misc + +def dedupeFuncs(funcs): + i = 1 + while i < len(funcs): + currentFunc = funcs[i] + for otherFunc in funcs[:i]: + if otherFunc["namespace"] != currentFunc["namespace"]: + continue + if otherFunc["name"] != currentFunc["name"]: + continue + + if getParamNames(otherFunc["params"]) == getParamNames(currentFunc["params"]): + # identical in JS -> can be safely removed + funcs.remove(currentFunc) + i -= 1 + + if "alternatives" not in otherFunc: + otherFunc["alternatives"] = [] + otherFunc["alternatives"].append(currentFunc) + + break + + if "renamedAlternatives" not in otherFunc: + otherFunc["renamedAlternatives"] = 0 + + otherFunc["renamedAlternatives"] += 1 + currentFunc["alternativeNumber"] = otherFunc["renamedAlternatives"] + + break + + i += 1 + +# ------------ +# main + +def main(args): + glkernelIncludeDir = "../source/glkernel/include/glkernel" + sourceFiles = [posixpath.join(glkernelIncludeDir, p) for p in os.listdir(glkernelIncludeDir) if p not in ["Kernel.h", "glm_compatability.h"] and p.endswith(".h")] + + funcPattern = re.compile(r"^template\s*<(?P