Created
May 31, 2011 12:18
-
-
Save jsimmons/1000412 to your computer and use it in GitHub Desktop.
Hacky GL header generator in Lua. Goal: Create a LuaJIT FFI interface for OpenGL
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| local parser = require 'parser' | |
| -- | |
| -- Generation options. | |
| -- | |
| local SPEC_PATH = './specs/' | |
| local TM_PATH = SPEC_PATH .. 'gl.tm' | |
| local ENUM_PATH = 'enum.spec' | |
| local ENUMEXT_PATH = SPEC_PATH .. 'enumext.spec' | |
| local GL_PATH = SPEC_PATH .. 'gl.spec' | |
| -- local MAX_GL_VERSION = 44 | |
| local INCLUDE_DEPRECATED_SYMBOLS = false | |
| local BLACKLIST = {} | |
| -- | |
| -- Utilities | |
| -- | |
| local function read_file(path) | |
| local file = io.open(path) | |
| return file:read('*a') | |
| end | |
| local output = {} | |
| local n = 1 | |
| local function insert(data) | |
| output[n] = data | |
| n = n + 1 | |
| end | |
| local function insert_f(format, ...) | |
| output[n] = format:format(...) | |
| n = n + 1 | |
| end | |
| -- | |
| -- The coordination | |
| -- | |
| -- Annoyingly, the easiest way is to put this here since enum generation needs the deprecated info. | |
| local functions, versions, deprecated = parse.gl_spec(GL_PATH) | |
| if not INCLUDE_DEPRECATED_SYMBOLS then | |
| for k, v in pairs(deprecated) do | |
| BLACKLIST[k] = true | |
| end | |
| end | |
| insert(read_file('gen/header.lua')) | |
| -- Begin ffi C section. | |
| insert('ffi.cdef [[') | |
| insert(read_file('gen/typedefs.h')) | |
| insert(generate_enums()) | |
| insert(generate_funcs()) | |
| insert(']]') | |
| -- End ffi C section | |
| insert(read_file('gen/glmodule.lua')) | |
| -- | |
| -- The meat, if a bit grizzly. | |
| -- | |
| local hit = {} | |
| function generate_enums() | |
| local enums = parse.enum_spec(ENUM_PATH, ENUMEXT_PATH) | |
| for name, enum in pairs(enums) do | |
| if not BLACKLIST[name] then | |
| -- Output the definition no matter what, we always want to know if an extension is supported. | |
| insert_f('static const int GL_%s = 1;', name) | |
| -- But only spit out the enum declaration if there are entries to put in there. | |
| if next(enum) then | |
| insert_f('enum %s {', name) | |
| for ident, value in pairs(enum) do | |
| -- Avoid re-declarations (luajit doesn't like these) | |
| if not hit[ident] then | |
| hit[ident] = true | |
| -- Work around hash tables being un-ordered breaking internal symbol dependencies. | |
| local intern_ident = value:gsub('GL_', '') | |
| insert_f('\tGL_%s = %s;', ident, enum[intern_ident] or intern_ident) | |
| end | |
| end | |
| insert('}') | |
| end | |
| end | |
| end | |
| end | |
| function generate_funcs() | |
| local tm = parse.typemap(TM_PATH) | |
| local function translate_param(def) | |
| -- Check if the argument is a pointer/array | |
| if def.extra ~= 'value' then | |
| return tm[def.name] .. '*' | |
| else | |
| return tm[def.name] | |
| end | |
| end | |
| for name, func in pairs(functions) do | |
| -- TODO: Make use of the deprecated version information. | |
| if INCLUDE_DEPRECATED_SYMBOLS or func.deprecated == nil then | |
| if not BLACKLIST[func.category] then | |
| local args = {} | |
| for _, param in ipairs(func.params) do | |
| if param ~= '' then | |
| table.insert(args, translate_param(func.param_details[param])) | |
| end | |
| end | |
| insert_f('%s gl%s(%s);', tm[func['return'] ], name, table.concat(args, ', ')) | |
| end | |
| end | |
| end | |
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| local io, error, pairs, unpack, print, type = io, error, pairs, unpack, print, type | |
| local util = require 'util' | |
| module 'parser' | |
| -- Simple function to run a set of patterns / callbacks over a file. | |
| local function generic_parser(path, patterns) | |
| local i = 0 | |
| for line in io.lines(path) do | |
| i = i + 1 | |
| line = line:gsub('#.*', '') | |
| -- If nothing other than comments, or just an empty line, no point continuing. | |
| if line:find('%S') then | |
| for pattern, callback in pairs(patterns) do | |
| local matches = {line:match(pattern)} | |
| -- Check if there were matches before hitting up the callback. | |
| if matches[1] ~= nil and callback(unpack(matches)) then | |
| hit = true | |
| break | |
| end | |
| end | |
| end | |
| end | |
| end | |
| --[[ | |
| EXAMPLE FILE CONTENTS | |
| VertexPointerType,*,*, GLenum,*,* | |
| VertexWeightPointerTypeEXT,*,*, GLenum,*,* | |
| Void,*,*, GLvoid,*,* | |
| VoidPointer,*,*, GLvoid*,*,* | |
| ConstVoidPointer,*,*, GLvoid* const,*,* | |
| ]] | |
| function typemap(path) | |
| local map = {} | |
| local filters = { | |
| -- Best way looks to be separating by colon and whitespace. | |
| ['([^,]+),%W+([^,]+),'] = function(a, b) | |
| if a and b then | |
| -- Corner case for void and maybe others? | |
| if b == '*' then | |
| map[a] = a | |
| else | |
| map[a] = b | |
| end | |
| return true | |
| else | |
| return false | |
| end | |
| end; | |
| } | |
| generic_parser(path, filters) | |
| return map | |
| end | |
| --[[ | |
| EXAMPLE FILE CONTENTS | |
| ShadingModel enum: | |
| FLAT = 0x1D00 | |
| SMOOTH = 0x1D01 | |
| StencilFunction enum: | |
| use AlphaFunction NEVER | |
| passthru: /* Random Shit */ | |
| use AlphaFunction LESS | |
| ]] | |
| function enum_spec(path, extpath) | |
| local enums = {} | |
| local current_enum = nil | |
| local filters = { | |
| -- If we find a passthrough command, just drop it on the floor. | |
| ['^passthru:'] = function() | |
| return true | |
| end; | |
| -- Sets the name for the following set of definitions. | |
| ['(%S+)%s+enum:'] = function(name) | |
| current_enum = {} | |
| enums[name] = current_enum | |
| return true | |
| end; | |
| -- Definition. | |
| ['(%S+)%s*=%s*(%S+)'] = function(symbol, value) | |
| if not current_enum then return false end | |
| current_enum[symbol] = value | |
| return true | |
| end; | |
| -- Lookup definition from other enumeration. | |
| ['^%s+use%s+(%S+)%s+(%S+)'] = function(from, name) | |
| if not current_enum then return false end | |
| current_enum[name] = {from} | |
| return true | |
| end; | |
| } | |
| -- We need to load definitions from the enum.spec before the enumext.spec | |
| generic_parser(path, filters) | |
| generic_parser(extpath, filters) | |
| -- Resolve use directives. | |
| for _, list in pairs(enums) do | |
| for k, v in pairs(list) do | |
| if type(v) == 'table' then | |
| local enum = enums[v[1]] | |
| if enum then | |
| list[k] = enum[k] | |
| else | |
| print(('#### WARNING cannot find definition for %s in %s'):format(k, v[1])) | |
| end | |
| end | |
| end | |
| end | |
| return enums | |
| end | |
| function gl_spec(path) | |
| local definitions = {} | |
| local versions, deprecated = {}, {} | |
| local current_def = nil | |
| local filters = { | |
| -- If we find a passthrough command, just drop it on the floor. | |
| ['^passthru:'] = function() | |
| return true | |
| end; | |
| -- Categories, we handle the version categories specially. | |
| ['^category:%s+(.+)'] = function(name_list) | |
| for i, cat in pairs(util.split(name_list, '%s+')) do | |
| local major, minor = cat:match('^VERSION_(%d)_(%d)$') | |
| if major then | |
| versions[cat] = major * 10 + minor | |
| else | |
| major, minor = cat:match('^VERSION_(%d)_(%d)_DEPRECATED$') | |
| if major then | |
| deprecated[cat] = major * 10 + minor | |
| end | |
| end | |
| end | |
| return true | |
| end; | |
| -- Name and arguments. | |
| ['^(%S+)%(([^%)]*)%)'] = function(name, args) | |
| current_def = {} | |
| definitions[name] = current_def | |
| current_def.params = util.split(args, ', ', 0, true) | |
| current_def.param_details = {} | |
| return true | |
| end; | |
| -- Fine details. | |
| ['(%S+)%s*(%S*)%s*(%S?.*)'] = function(name, arg1, arg2) | |
| if not current_def then return false end | |
| if name == 'param' then | |
| local name, middle, extra = arg2:match('(%S+)%s*(%S+)%s*(%S+)') | |
| current_def.param_details[arg1] = {name=name, middle=middle, extra=extra} | |
| else | |
| current_def[name] = arg1 | |
| end | |
| return true | |
| end; | |
| } | |
| generic_parser(path, filters) | |
| return definitions, versions, deprecated | |
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| local error = error | |
| module 'util' | |
| local function split(str, delim, count, no_patterns, kill_if_no_match) | |
| if delim == '' then error('invalid delimiter', 2) end | |
| count = count or 0 | |
| if kill_if_no_match and not str:find(delim, 1, no_patterns) then | |
| return {} | |
| end | |
| local next_delim = 1 | |
| local i = 1 | |
| local results = {} | |
| repeat | |
| local start, finish = str:find(delim, next_delim, no_patterns) | |
| if start and finish then | |
| results[i] = str:sub(next_delim, start - 1) | |
| next_delim = finish + 1 | |
| else | |
| break | |
| end | |
| i = i + 1 | |
| until i == count | |
| results[i] = str:sub(next_delim) | |
| return results | |
| end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment