Eolian: Add first version of the generator script
Doesn't really produce anything working yet
This commit is contained in:
parent
abefe9eb89
commit
3ef8626ba4
|
@ -0,0 +1,533 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
import os
|
||||
import textwrap
|
||||
import keyword
|
||||
|
||||
from argparse import ArgumentParser
|
||||
parser = ArgumentParser(description="Python generator for eolian")
|
||||
parser.add_argument(
|
||||
'--header-file', required=True,
|
||||
help="Filename of the library header")
|
||||
parser.add_argument('-o', '--output', default="/tmp")
|
||||
parser.add_argument('path')
|
||||
args = parser.parse_args()
|
||||
|
||||
import logging
|
||||
handler = logging.StreamHandler()
|
||||
formatter = logging.Formatter("%(name)s %(levelname)s: %(message)s")
|
||||
handler.setFormatter(formatter)
|
||||
log = logging.getLogger("efl.eolian")
|
||||
log.setLevel(logging.DEBUG)
|
||||
log.addHandler(handler)
|
||||
|
||||
from efl import eolian
|
||||
eolian.init()
|
||||
|
||||
DOCSTRINGS_ENABLED = True
|
||||
|
||||
complex_types = (
|
||||
"Eina_List"
|
||||
)
|
||||
|
||||
param_type_mapping = {
|
||||
"Eina_Bool": ("bint", None),
|
||||
"Evas_Coord": ("int", None),
|
||||
"char *": (
|
||||
"",
|
||||
"if isinstance({0}, unicode): {0} = PyUnicode_AsUTF8String({0})",
|
||||
),
|
||||
"Eo *": ("_Eo_Base", None)
|
||||
}
|
||||
|
||||
return_type_mapping = {
|
||||
"Eina_Bool": ("bint", None),
|
||||
"char *": ("unicode", '{0}.decode("utf-8")'),
|
||||
"Elm_Object_Item *": (
|
||||
"_ObjectItem", 'object_item_to_python({0})'
|
||||
),
|
||||
"Evas_Object *": ("_Eo", 'object_from_instance({0})'),
|
||||
"Eo *": ("_Eo", 'object_from_instance({0})'),
|
||||
}
|
||||
|
||||
|
||||
class Generator(object):
|
||||
|
||||
tab = " "
|
||||
|
||||
def __init__(self):
|
||||
self.result = []
|
||||
self.level = 0
|
||||
|
||||
def indent(self):
|
||||
self.level += 1
|
||||
|
||||
def dedent(self):
|
||||
self.level -= 1
|
||||
|
||||
def write(self, text=None, wrapped=False, i_ind=None, s_ind=None):
|
||||
if not i_ind:
|
||||
i_ind = self.level * self.tab
|
||||
if not s_ind:
|
||||
s_ind = self.level * self.tab
|
||||
if text:
|
||||
if wrapped:
|
||||
self.result.append(textwrap.fill(
|
||||
text,
|
||||
initial_indent=i_ind,
|
||||
subsequent_indent=s_ind,
|
||||
width=79
|
||||
))
|
||||
else:
|
||||
self.result.append(i_ind + text)
|
||||
else:
|
||||
self.result.append("")
|
||||
|
||||
def docstring_write(self, docstrings):
|
||||
if not DOCSTRINGS_ENABLED or not docstrings:
|
||||
return
|
||||
elif len(docstrings) == 1:
|
||||
self.write('"""' + docstrings[0] + '"""')
|
||||
else:
|
||||
self.write('"""')
|
||||
for docs in docstrings:
|
||||
if docs.startswith("- "):
|
||||
self.write(
|
||||
docs, wrapped=True,
|
||||
#i_ind=self.level * self.tab + "- ",
|
||||
s_ind=self.level * self.tab + " "
|
||||
)
|
||||
elif docs.startswith(" - "):
|
||||
self.write(
|
||||
docs, wrapped=True,
|
||||
#i_ind=self.level * self.tab + " - ",
|
||||
s_ind=self.level * self.tab + " "
|
||||
)
|
||||
elif docs.startswith(":"):
|
||||
self.write(
|
||||
docs, wrapped=True,
|
||||
s_ind=self.level * self.tab + " "
|
||||
)
|
||||
else:
|
||||
self.write(docs, wrapped=True)
|
||||
self.write('"""')
|
||||
|
||||
def printout(self):
|
||||
return "\n".join(self.result)
|
||||
|
||||
|
||||
class PyxGenerator(Generator):
|
||||
def method_header_write(self, name, params):
|
||||
params2 = []
|
||||
for t, n in params:
|
||||
t.replace("const ", "")
|
||||
t = param_type_mapping.get(t, ("", None))[0]
|
||||
params2.append(" ".join((t, n)).strip())
|
||||
params2 = ", ".join(params2)
|
||||
define = "def %s(self" % (name)
|
||||
if params2:
|
||||
define += ", "
|
||||
define += "%s" % (params2)
|
||||
define += "):"
|
||||
self.write(define)
|
||||
|
||||
def cdefs_write(self, cdefs):
|
||||
self.write("cdef:")
|
||||
self.indent()
|
||||
types = {}
|
||||
for t, n in cdefs:
|
||||
types.setdefault(t, []).append(n)
|
||||
for t, names in types.items():
|
||||
if "*" in t:
|
||||
# complex, write one per line
|
||||
for n in names:
|
||||
self.write("%s %s" % (t, n))
|
||||
else:
|
||||
# simple, all on one line
|
||||
self.write("%s %s" % (t, ", ".join(names)))
|
||||
self.dedent()
|
||||
|
||||
|
||||
class Method(object):
|
||||
|
||||
def __init__(self, eo_prefix):
|
||||
super(type(self), self).__init__()
|
||||
self.params = []
|
||||
self.py_params = []
|
||||
self.c_params = []
|
||||
self.cdefs = []
|
||||
self.returns = []
|
||||
self.docs = []
|
||||
self.ret_type = None
|
||||
self.py_name = None
|
||||
self.eo_prefix = eo_prefix
|
||||
|
||||
@classmethod
|
||||
def parse(cls, func, eo_prefix):
|
||||
self = cls.__new__(cls)
|
||||
self.__init__(eo_prefix)
|
||||
self.func = func
|
||||
|
||||
self.py_name = func.name
|
||||
if keyword.iskeyword(self.py_name):
|
||||
self.py_name += "_"
|
||||
self.c_name = "_".join((self.eo_prefix, func.name))
|
||||
|
||||
for p in func.parameters_list:
|
||||
pdir, ptype, name, desc = p.information
|
||||
self.params.append((ptype, name))
|
||||
if pdir == eolian.ParameterDir.IN:
|
||||
self.py_params.append((ptype, name))
|
||||
self.c_params.append((ptype, name))
|
||||
self.docs.append(":param %s: %s" % (name, desc))
|
||||
self.docs.append(":type %s: %s" % (name, ptype))
|
||||
elif pdir == eolian.ParameterDir.OUT:
|
||||
self.cdefs.append((ptype, name))
|
||||
self.c_params.append((ptype, "&%s" % (name)))
|
||||
self.returns.append((ptype, name))
|
||||
else:
|
||||
self.py_params.append((ptype, name))
|
||||
self.c_params.append((ptype, name))
|
||||
self.returns.append((ptype, name))
|
||||
|
||||
self.ret_type = func.return_type_get(func.type)
|
||||
ret_desc = func.return_comment_get(func.type)
|
||||
|
||||
if self.returns:
|
||||
self.docs.append(
|
||||
":return: " + ", ".join([r[1] for r in self.returns]))
|
||||
self.docs.append(
|
||||
":rtype: " + ", ".join([r[0] for r in self.returns]))
|
||||
elif self.ret_type:
|
||||
if ret_desc:
|
||||
self.docs.append(":return: " + ret_desc)
|
||||
self.docs.append(":rtype: " + self.ret_type)
|
||||
|
||||
return self
|
||||
|
||||
def pyx_generate(self, gen):
|
||||
gen.method_header_write(self.py_name, self.py_params)
|
||||
gen.indent()
|
||||
|
||||
RET_PARAM = "py_efl_ret"
|
||||
|
||||
if self.docs:
|
||||
gen.docstring_write(self.docs)
|
||||
|
||||
if self.ret_type:
|
||||
r_type = self.ret_type
|
||||
if r_type in return_type_mapping:
|
||||
r_type = return_type_mapping[r_type][0]
|
||||
self.cdefs.append((r_type, RET_PARAM))
|
||||
|
||||
if self.cdefs:
|
||||
gen.cdefs_write(self.cdefs)
|
||||
|
||||
for t, n in self.py_params:
|
||||
t = t.replace("const ", "")
|
||||
if t in param_type_mapping:
|
||||
conv = param_type_mapping[t][1]
|
||||
if conv:
|
||||
gen.write(conv.format(n))
|
||||
|
||||
c_params = ", ".join([c[1] for c in self.c_params])
|
||||
c_call = "eo_do(self.obj, %s(%s))" % (self.c_name, c_params)
|
||||
if self.ret_type:
|
||||
r_type = self.ret_type
|
||||
if r_type in return_type_mapping:
|
||||
conv = return_type_mapping[r_type][1]
|
||||
if conv:
|
||||
c_call = conv.format(c_call)
|
||||
c_call = RET_PARAM + " = " + c_call
|
||||
gen.write(c_call)
|
||||
|
||||
if self.returns:
|
||||
ret = self.returns[:]
|
||||
for i, (t, n) in enumerate(self.returns):
|
||||
t = t.replace("const ", "")
|
||||
if t in return_type_mapping:
|
||||
conv = return_type_mapping[t][1]
|
||||
if conv:
|
||||
gen.write("conv_%s = %s" % (n, conv.format(n)))
|
||||
ret[i] = t, "conv_%s" % (n)
|
||||
gen.write("return %s" % (", ".join([r[1] for r in ret])))
|
||||
elif self.ret_type:
|
||||
ret = RET_PARAM
|
||||
r_type = self.ret_type
|
||||
if r_type in return_type_mapping:
|
||||
conv = return_type_mapping[r_type][1]
|
||||
if conv:
|
||||
ret = conv.format(ret)
|
||||
gen.write("return %s" % (ret))
|
||||
|
||||
gen.dedent()
|
||||
gen.write()
|
||||
|
||||
def cdef_generate(self, gen):
|
||||
rtype = self.ret_type
|
||||
if not rtype:
|
||||
rtype = "void"
|
||||
line = " ".join((rtype, self.c_name))
|
||||
line += "("
|
||||
line += ", ".join([" ".join((t, n)) for t, n in self.params])
|
||||
line += ")"
|
||||
|
||||
gen.write(line)
|
||||
|
||||
|
||||
class Property(object):
|
||||
def __init__(self, eo_prefix):
|
||||
super(type(self), self).__init__()
|
||||
self.docs = []
|
||||
self.eo_prefix = eo_prefix
|
||||
|
||||
@classmethod
|
||||
def parse(cls, func, eo_prefix):
|
||||
self = cls.__new__(cls)
|
||||
self.__init__(eo_prefix)
|
||||
self.func = func
|
||||
|
||||
self.getter = None
|
||||
self.setter = None
|
||||
|
||||
for param in func.property_values_list:
|
||||
self.docs.append(":type %s: %s" % (param.name, param.type))
|
||||
|
||||
if func.type == eolian.FunctionType.PROP_SET or \
|
||||
func.type == eolian.FunctionType.PROPERTY:
|
||||
# Create setter
|
||||
m = self.setter = Method(eo_prefix)
|
||||
m.py_name = "__set__"
|
||||
m.c_name = "_".join((self.eo_prefix, func.name, "set"))
|
||||
for p in func.property_values_list:
|
||||
pdir, ptype, name, desc = p.information
|
||||
assert pdir == eolian.ParameterDir.IN, "prop has other than IN"
|
||||
m.params.append((ptype, name))
|
||||
m.py_params.append((ptype, name))
|
||||
m.c_params.append((ptype, name))
|
||||
|
||||
m.ret_type = func.return_type_get(eolian.FunctionType.PROP_SET)
|
||||
|
||||
if func.type == eolian.FunctionType.PROP_GET or \
|
||||
func.type == eolian.FunctionType.PROPERTY:
|
||||
# Create getter
|
||||
m = self.getter = Method(eo_prefix)
|
||||
m.py_name = "__get__"
|
||||
m.c_name = "_".join((self.eo_prefix, func.name, "get"))
|
||||
for p in func.property_values_list:
|
||||
pdir, ptype, name, desc = p.information
|
||||
assert pdir == eolian.ParameterDir.IN, "prop has other than IN"
|
||||
m.params.append((ptype, name))
|
||||
m.cdefs.append((ptype, name))
|
||||
m.c_params.append((ptype, "&%s" % (name)))
|
||||
m.returns.append((ptype, name))
|
||||
|
||||
m.ret_type = func.return_type_get(eolian.FunctionType.PROP_GET)
|
||||
|
||||
return self
|
||||
|
||||
def pyx_generate(self, gen):
|
||||
gen.write("property %s:" % (self.func.name))
|
||||
|
||||
gen.indent()
|
||||
gen.docstring_write(self.docs)
|
||||
|
||||
if self.setter:
|
||||
self.setter.pyx_generate(gen)
|
||||
if self.getter:
|
||||
self.getter.pyx_generate(gen)
|
||||
|
||||
gen.dedent()
|
||||
|
||||
def cdef_generate(self, gen):
|
||||
if self.setter:
|
||||
self.setter.cdef_generate(gen)
|
||||
if self.getter:
|
||||
self.getter.cdef_generate(gen)
|
||||
|
||||
|
||||
class Constructor(Method):
|
||||
|
||||
def __init__(self, eo_prefix):
|
||||
self.c_params = []
|
||||
self.py_params = []
|
||||
self.method = None
|
||||
self.eo_prefix = eo_prefix
|
||||
|
||||
@classmethod
|
||||
def parse(cls, eo_prefix, method):
|
||||
self = cls.__new__(cls)
|
||||
self.__init__(eo_prefix)
|
||||
self.method = method
|
||||
|
||||
self.c_name = "_".join((self.eo_prefix, method.name))
|
||||
|
||||
for p in self.method.parameters_list:
|
||||
pdir, ptype, name, desc = p.information
|
||||
self.c_params.append("%s" % (name))
|
||||
self.py_params.append("%s %s" % (ptype, name))
|
||||
self.c_params = ", ".join(self.c_params)
|
||||
self.py_params.insert(0, "parent")
|
||||
self.py_params.insert(0, "cls")
|
||||
self.py_params = ", ".join(self.py_params)
|
||||
|
||||
return self
|
||||
|
||||
def pyx_generate(self, gen):
|
||||
cls_get = self.eo_prefix + "_class_get()"
|
||||
gen.write("@classmethod")
|
||||
gen.write("def %s(%s):" % (self.method.name, self.py_params))
|
||||
gen.indent()
|
||||
gen.write("self = cls.__new__(cls)")
|
||||
gen.write(
|
||||
"eo_add_custom(%s, parent.obj, %s(%s))" % (
|
||||
cls_get, self.method.name, self.c_params
|
||||
)
|
||||
)
|
||||
gen.write("return self")
|
||||
gen.dedent()
|
||||
|
||||
gen.write()
|
||||
|
||||
def cdef_generate(self, gen):
|
||||
pass
|
||||
|
||||
|
||||
class DefaultConstructor(Constructor):
|
||||
def pyx_generate(self, gen):
|
||||
cls_get = self.eo_prefix + "_class_get()"
|
||||
gen.write("def __init__(self, parent=None):")
|
||||
gen.indent()
|
||||
gen.write(
|
||||
"eo_add(%s, parent.obj if parent is not None else NULL)" % (
|
||||
cls_get
|
||||
)
|
||||
)
|
||||
gen.dedent()
|
||||
gen.write()
|
||||
|
||||
|
||||
class Class(object):
|
||||
def __init__(self):
|
||||
super(type(self), self).__init__()
|
||||
self.docs = []
|
||||
self.inherits = []
|
||||
|
||||
self.ctors = []
|
||||
self.methods = []
|
||||
self.props = []
|
||||
self.default_ctor = False
|
||||
|
||||
@classmethod
|
||||
def parse(klass, cls):
|
||||
self = klass.__new__(klass)
|
||||
self.__init__()
|
||||
self.cls = cls
|
||||
|
||||
for i in cls.inherits_list:
|
||||
i_cls = eolian.class_find_by_name(i)
|
||||
if i_cls and (
|
||||
i_cls.type == eolian.ClassType.INTERFACE or
|
||||
i_cls.type == eolian.ClassType.MIXIN):
|
||||
continue
|
||||
self.inherits.append("_" + i)
|
||||
|
||||
if not self.inherits:
|
||||
self.inherits = ("object", )
|
||||
self.inherits = ", ".join(self.inherits)
|
||||
|
||||
desc = self.cls.description
|
||||
if desc:
|
||||
desc = desc.replace("\n", " ").strip()
|
||||
self.docs.append(desc)
|
||||
self.docs.append("")
|
||||
|
||||
for k, v in (
|
||||
type(self.cls).__dict__.items()
|
||||
):
|
||||
if k.startswith("__") or hasattr(v, "__call__") or \
|
||||
k == "description":
|
||||
continue
|
||||
|
||||
if k == "events" or k == "implements" or k == "inherits_list":
|
||||
objs = v.__get__(self.cls)
|
||||
if not objs:
|
||||
continue
|
||||
self.docs.append(
|
||||
"- %s: " % (k)
|
||||
)
|
||||
for obj in objs:
|
||||
self.docs.append(
|
||||
" - %s" % (obj)
|
||||
)
|
||||
else:
|
||||
self.docs.append(
|
||||
"- %s: %s" % (k, v.__get__(self.cls))
|
||||
)
|
||||
|
||||
ctors = None
|
||||
prefix = self.prefix = cls.eo_prefix
|
||||
|
||||
if not prefix:
|
||||
return self
|
||||
|
||||
if cls.type == eolian.ClassType.REGULAR: # \
|
||||
#or cls.type == eolian.ClassType.MIXIN:
|
||||
ctors = cls.functions_list_get(eolian.FunctionType.CTOR)
|
||||
if ctors:
|
||||
for ctor in ctors:
|
||||
self.ctors.append(Constructor.parse(prefix, ctor))
|
||||
self.ctors.append(DefaultConstructor(prefix))
|
||||
|
||||
props = cls.functions_list_get(eolian.FunctionType.PROPERTY)
|
||||
for prop in props:
|
||||
self.props.append(Property.parse(prop, prefix))
|
||||
|
||||
methods = cls.functions_list_get(eolian.FunctionType.METHOD)
|
||||
for method in methods:
|
||||
self.methods.append(Method.parse(method, prefix))
|
||||
|
||||
return self
|
||||
|
||||
def pyx_generate(self, gen):
|
||||
if self.ctors or self.methods or self.props:
|
||||
gen.write('cdef extern from "%s":' % (args.header_file))
|
||||
gen.indent()
|
||||
for o in self.ctors + self.methods + self.props:
|
||||
o.cdef_generate(gen)
|
||||
gen.dedent()
|
||||
gen.write()
|
||||
|
||||
gen.write("cdef class _%s(%s):" % (self.cls.name, self.inherits))
|
||||
gen.indent()
|
||||
|
||||
gen.docstring_write(self.docs)
|
||||
|
||||
if not self.ctors and not self.props and not self.methods:
|
||||
gen.write("pass")
|
||||
else:
|
||||
for o in self.ctors + self.methods + self.props:
|
||||
o.pyx_generate(gen)
|
||||
|
||||
gen.dedent()
|
||||
gen.write()
|
||||
|
||||
|
||||
for dirpath, dirnames, filenames in os.walk(args.path):
|
||||
for filename in filenames:
|
||||
if filename.endswith(".eo"):
|
||||
f = os.path.join(dirpath, filename)
|
||||
eolian.eo_file_parse(f)
|
||||
cls = eolian.class_find_by_file(f)
|
||||
|
||||
pyxgen = PyxGenerator()
|
||||
gencls = Class.parse(cls)
|
||||
gencls.pyx_generate(pyxgen)
|
||||
|
||||
f_base = os.path.splitext(filename)[0]
|
||||
pxi_path = os.path.join(args.output, f_base + ".pxi")
|
||||
with open(pxi_path, "w") as f:
|
||||
f.write(pyxgen.printout() + "\n")
|
||||
log.info(pxi_path + " written")
|
||||
|
||||
eolian.shutdown()
|
Loading…
Reference in New Issue