Skip to content

Instantly share code, notes, and snippets.

@jsimmons
Created May 31, 2011 12:18
Show Gist options
  • Select an option

  • Save jsimmons/1000412 to your computer and use it in GitHub Desktop.

Select an option

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
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
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
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