import sys
import re
import optparse
from ctypes import *
"""
This script will use the prototypes from "checkdocs.py -s" to concoct
a 1:1 Python wrapper for Allegro.
"""
class _AL_UTF8String:
pass
class Allegro:
def __init__(self):
self.types = {}
self.functions = {}
self.constants = {}
def add_struct(self, name):
x = type(name, (Structure, ), {})
self.types[name] = x
def add_union(self, name):
x = type(name, (Union, ), {})
self.types[name] = x
def get_type(self, ptype):
conversion = {
"bool": c_bool,
"_Bool": c_bool,
"char": c_byte,
"unsignedchar": c_ubyte,
"int": c_int,
"unsigned": c_uint,
"unsignedint": c_uint,
"int16_t": c_int16,
"uint16_t": c_uint16,
"int32_t": c_int32,
"uint32_t": c_uint32,
"int64_t": c_int64,
"uint64_t": c_uint64,
"uintptr_t": c_void_p,
"intptr_t": c_void_p,
"GLuint": c_uint,
"unsignedlong": c_ulong,
"long": c_long,
"size_t": c_size_t,
"off_t": c_int64,
"time_t": c_int64,
"va_list": c_void_p,
"float": c_float,
"double": c_double,
"al_fixed": c_int,
"HWND": c_void_p,
"char*": _AL_UTF8String,
"postprocess_callback_t": c_void_p,
}
ptype = re.sub(r"\b(struct|union)\b", "", ptype)
ptype = re.sub(r"\bconst\b", "", ptype)
ptype = re.sub(r"\bextern\b", "", ptype)
ptype = re.sub(r"\b__inline__\b", "", ptype)
ptype = re.sub(r"\s+", "", ptype)
if ptype.endswith("*"):
if ptype in conversion:
return conversion[ptype]
t = ptype[:-1]
if t in self.types:
return POINTER(self.types[t])
return c_void_p
elif ptype in self.types:
return self.types[ptype]
else:
try:
return conversion[ptype]
except KeyError:
print("Type Error:" + str(ptype))
return None
def parse_funcs(self, funcs):
"""
Go through all documented functions and add their prototypes
as Python functions.
The file should have been generated by Allegro's documentation
generation scripts.
"""
for func in funcs:
name, proto = func.split(":", 1)
if not name.startswith("al_"):
continue
proto = proto.strip()
name = name[:-2]
if proto.startswith("enum"):
continue
if proto.startswith("typedef"):
continue
if "=" in proto:
continue
if proto.startswith("#"):
continue
funcstart = proto.find(name)
funcend = funcstart + len(name)
ret = proto[:funcstart].rstrip()
params = proto[funcend:].strip(" ;")
if params[0] != "(" or params[-1] != ")":
print("Error:")
print(params)
continue
params2 = params[1:-1]
balance = 0
params = ""
for c in params2:
if c == ")":
balance -= 1
if balance == 0:
params += c
if c == "(":
balance += 1
params = params.split(",")
plist = []
for param in params:
param = re.sub(r"\bconst\b", "", param)
param = param.strip()
if param == "void":
continue
if param == "":
continue
if param == "...":
continue
if param.endswith("]") or param.endswith("*"):
plist.append(c_void_p)
continue
if param.endswith(")"):
plist.append(c_void_p)
continue
mob = re.match("^.*?(\w+)$", param)
if mob:
pnamepos = mob.start(1)
if pnamepos == 0:
pnamepos = len(param)
else:
print(params)
print(proto)
print("")
continue
ptype = param[:pnamepos]
ptype = self.get_type(ptype)
plist.append(ptype)
f = type("", (object, ), {"restype": c_int})
if not ret.endswith("void"):
f.restype = self.get_type(ret)
try:
f.argtypes = plist
except TypeError as e:
print(e)
print(name)
print(plist)
self.functions[name] = f
def parse_protos(self, filename):
protos = []
unions = []
funcs = []
for line in open(filename):
name, proto = line.split(":", 1)
proto = proto.lstrip()
if name.endswith("()"):
funcs.append(line)
continue
if name and not name.startswith("ALLEGRO_"):
continue
if name == "ALLEGRO_OGL_EXT_API":
continue
if proto.startswith("union") or\
proto.startswith("typedef union"):
self.add_union(name)
unions.append((name, proto))
elif proto.startswith("struct") or\
proto.startswith("typedef struct"):
self.add_struct(name)
protos.append((name, proto))
elif proto.startswith("enum") or\
proto.startswith("typedef enum"):
if name:
self.types[name] = c_int
protos.append(("", proto))
elif proto.startswith("#define"):
if not name.startswith("_") and not name.startswith("GL_"):
i = eval(proto.split(None, 2)[2])
self.constants[name] = i
else:
mob = re.match("typedef (.*) " + name, proto)
if mob:
t = mob.group(1)
self.types[name] = self.get_type(t.strip())
else:
self.types[name] = c_void_p
protos += unions
for name, proto in protos:
bo = proto.find("{")
if bo == -1:
continue
bc = proto.rfind("}")
braces = proto[bo + 1:bc]
if proto.startswith("enum") or \
proto.startswith("typedef enum"):
fields = braces.split(",")
i = 0
for field in fields:
if "=" in field:
fname, val = field.split("=", 1)
fname = fname.strip()
val = re.sub("('.')", "ord(\\1)", val)
try:
i = int(eval(val, globals(), self.constants))
except NameError:
i = val
except Exception:
raise ValueError(
"Exception while parsing '{}'".format(
val))
else:
fname = field.strip()
if not fname:
continue
self.constants[fname] = i
try:
i += 1
except TypeError:
pass
continue
balance = 0
fields = [""]
for c in braces:
if c == "{":
balance += 1
if c == "}":
balance -= 1
if c == ";" and balance == 0:
fields.append("")
else:
fields[-1] += c
flist = []
for field in fields:
if not field:
continue
mob = re.match(".*?\(\*(\w+)\)", field)
if mob:
flist.append((mob.group(1), "c_void_p"))
continue
mob = re.match(".*?\*(\w+)$", field)
if mob:
flist.append((mob.group(1), "c_void_p"))
continue
mob = re.match("(.*)\s+(\w+)\[(.*?)\]$", field)
if mob:
n = 0
ftype = mob.group(1)
if ftype.startswith("struct"):
if ftype == "struct {float axis[3];}":
t = "c_float * 3"
else:
print("Error: Can't parse " + ftype + " yet.")
t = None
else:
n = mob.group(3)
if "][" in n:
n = n.replace("][", " * ")
if "/" in n:
n = "(" + n.replace("/", "//") + ")"
t = self.get_type(ftype).__name__ + " * " + n
fname = mob.group(2)
flist.append((fname, t))
continue
vars = field.split(",")
mob = re.match("\s*(.*?)\s+(\w+)\s*$", vars[0])
t = self.get_type(mob.group(1))
vname = mob.group(2)
if t is not None and vname is not None:
flist.append((vname, t.__name__))
for v in vars[1:]:
flist.append((v.strip(), t.__name__))
else:
print("Error: " + str(vars))
try:
self.types[name].my_fields = flist
except AttributeError:
print(name, flist)
self.parse_funcs(funcs)
def main():
p = optparse.OptionParser()
p.add_option("-o", "--output", help="location of generated file")
p.add_option("-p", "--protos", help="A file with all " +
"prototypes to generate Python wrappers for, one per line. "
"Generate it with docs/scripts/checkdocs.py -p")
p.add_option("-t", "--type", help="the library type to " +
"use, e.g. debug")
p.add_option("-v", "--version", help="the library version to " +
"use, e.g. 5.1")
options, args = p.parse_args()
if not options.protos:
p.print_help()
return
al = Allegro()
al.parse_protos(options.protos)
f = open(options.output, "w") if options.output else sys.stdout
release = options.type
version = options.version
f.write(r"""# Generated by generate_python_ctypes.py.
import os, platform, sys
from ctypes import *
from ctypes.util import *
# You must adjust this function to point ctypes to the A5 DLLs you are
# distributing.
_dlls = []
def _add_dll(name):
release = "%(release)s"
if os.name == "nt":
release = "%(release)s-%(version)s"
# Under Windows, DLLs are found in the current directory, so this
# would be an easy way to keep all your DLLs in a sub-folder.
# os.chdir("dlls")
path = find_library(name + release)
if not path:
if os.name == "mac":
path = name + release + ".dylib"
elif os.name == "nt":
path = name + release + ".dll"
elif os.name == "posix":
if platform.mac_ver()[0]:
path = name + release + ".dylib"
else:
path = "lib" + name + release + ".so"
else:
sys.stderr.write("Cannot find library " + name + "\n")
# In most cases, you actually don't want the above and instead
# use the exact filename within your game distribution, possibly
# even within a .zip file.
# if not os.path.exists(path):
# path = "dlls/" + path
try:
# RTLD_GLOBAL is required under OSX for some reason (?)
_dlls.append(CDLL(path, RTLD_GLOBAL))
except OSError:
# No need to fail here, might just be one of the addons.
pass
# os.chdir("..")
_add_dll("allegro")
_add_dll("allegro_acodec")
_add_dll("allegro_audio")
_add_dll("allegro_primitives")
_add_dll("allegro_color")
_add_dll("allegro_font")
_add_dll("allegro_ttf")
_add_dll("allegro_image")
_add_dll("allegro_dialog")
_add_dll("allegro_memfile")
_add_dll("allegro_physfs")
_add_dll("allegro_shader")
_add_dll("allegro_main")
_add_dll("allegro_video")
_add_dll("allegro_monolith")
# We don't have information ready which A5 function is in which DLL,
# so we just try them all.
def _dll(func, ret, params):
for dll in _dlls:
try:
f = dll[func]
f.restype = ret
f.argtypes = params
if ret is _AL_UTF8String:
# ctypes cannot do parameter conversion of the return type for us
f.restype = c_char_p
if sys.version_info[0] > 2: return lambda *x: f(*x).decode("utf8")
return f
except AttributeError: pass
sys.stderr.write("Cannot find function " + func + "\n")
return lambda *args: None
# In Python3, all Python strings are unicode so we have to convert to
# UTF8 byte strings before passing to Allegro.
if sys.version_info[0] > 2:
class _AL_UTF8String:
def from_param(x):
return x.encode("utf8")
else:
_AL_UTF8String = c_char_p
""" % locals())
postpone = []
for name, val in sorted(al.constants.items()):
try:
if isinstance(val, str):
val = int(eval(val, globals(), al.constants))
f.write(name + " = " + str(val) + "\n")
except:
postpone.append((name, val))
for name, val in postpone:
f.write(name + " = " + val + "\n")
structs = set()
for name, x in sorted(al.types.items()):
if not name:
continue
base = x.__bases__[0]
if base != Structure and base != Union:
f.write(name + " = " + x.__name__ + "\n")
else:
structs.add(name)
structs_list = []
remaining = set(structs)
while remaining:
for name in sorted(remaining):
ok = True
x = al.types[name]
if hasattr(x, "my_fields"):
for fname, ftype in x.my_fields:
if " " in ftype:
ftype = ftype.split()[0]
if ftype in structs and ftype in remaining:
ok = False
break
if ok:
structs_list.append(name)
remaining.remove(name)
for name in structs_list:
x = al.types[name]
base = x.__bases__[0]
f.write("class " + name + "(" + base.__name__ + "):\n")
if hasattr(x, "my_fields"):
f.write(" _fields_ = [\n")
for fname, ftype in x.my_fields:
f.write(" (\"" + fname + "\", " + ftype + "),\n")
f.write(" ]\n")
else:
f.write(" pass\n")
pt = POINTER(x)
f.write("%s = POINTER(%s)\n" % (pt.__name__, name))
for name, x in sorted(al.functions.items()):
try:
line = name + " = _dll(\"" + name + "\", "
line += x.restype.__name__ + ", "
line += "[" + (", ".join([a.__name__ for a in x.argtypes])) +\
"])\n"
f.write(line)
except AttributeError as e:
print("Ignoring " + name + " because of errors (" + str(e) + ").")
f.write(r"""
ALLEGRO_VERSION_INT = \
((ALLEGRO_VERSION << 24) | (ALLEGRO_SUB_VERSION << 16) | \
(ALLEGRO_WIP_VERSION << 8) | ALLEGRO_RELEASE_NUMBER)
""")
f.write(r"""
# work around bug http://gcc.gnu.org/bugzilla/show_bug.cgi?id=36834
if os.name == "nt":
def al_map_rgba_f(r, g, b, a): return ALLEGRO_COLOR(r, g, b, a)
def al_map_rgb_f(r, g, b): return ALLEGRO_COLOR(r, g, b, 1)
def al_map_rgba(r, g, b, a):
return ALLEGRO_COLOR(r / 255.0, g / 255.0, b / 255.0, a / 255.0)
def al_map_rgb(r, g, b):
return ALLEGRO_COLOR(r / 255.0, g / 255.0, b / 255.0, 1)
""")
f.write("""
def al_main(real_main, *args):
def python_callback(argc, argv):
real_main(*args)
return 0
cb = CFUNCTYPE(c_int, c_int, c_void_p)(python_callback)
al_run_main(0, 0, cb);
""")
f.close()
main()