Eolian: WIP

This commit is contained in:
Kai Huuhko 2014-09-24 18:55:18 +03:00
parent 6de7432e28
commit 0faee7c36e
2 changed files with 250 additions and 173 deletions

View File

@ -19,42 +19,50 @@ docstring_replacements = (
("NULL", "None"),
)
builtin_types = ( # automatically converted by cython
"short",
"ushort",
#"char",
"int",
"uint",
"double",
"Evas_Coord",
"Evas_Real"
)
complex_types = (
"Eina_List"
)
mapping_in = {
# c_type: pyx_type
"Eina_Bool": "bint",
"char *": "",
"bool": "bint",
"char": "",
"Evas_Object *": "_Eo_Base",
"Eo *": "_Eo_Base",
}
conversions_in = {
# c_type: (conversion, c_param_convert)
"Eina_Bool": (None, None),
"char *": (
"if isinstance({0}, unicode): {0} = PyUnicode_AsUTF8String({0})",
"<{1}>if {0} is not None else NULL"
),
"Evas_Object *": (None, "{0}.obj"),
"Eo *": (None, "{0}.obj"),
# c_type: conversion
"bool": None,
"char": "if isinstance({0}, unicode): {0} = PyUnicode_AsUTF8String({0})",
"Evas_Object": None,
"Eo": None,
}
mapping_out = {
# c_type: pyx_type
"bool": "bint",
"char *": "unicode",
"Elm_Object_Item *": "_ObjectItem",
"Evas_Object *": "_Eo_Base",
"Eo *": "_Eo_Base",
"char": "unicode",
"Elm_Object_Item": "_ObjectItem",
"Evas_Object": "_Eo_Base",
"Eo": "_Eo_Base",
}
conversions_out = {
# c_type: conversion
"bool": "bool({0})",
"char *": '{0}.decode("utf-8")',
"char": '{0}.decode("utf-8")',
"Elm_Object_Item *": 'object_item_to_python({0})',
"Evas_Object *": 'object_from_instance({0})',
"Eo": 'object_from_instance({0})',
@ -76,34 +84,43 @@ def remove_type_prefixes(ctype):
def convert_in_param(tp, name, is_nonull=None):
c_type = tp.c_type
if tp.type == eolian.TypeType.POINTER:
c_type = tp.c_type
tp = tp.base_type
else:
c_type = tp.c_type
if tp.type == eolian.TypeType.CLASS:
return None, c_type, "{0}.obj".format(tp.name)
key = tp.name if tp.name else c_type
conv_expr = conversions_in.get(key)
if conv_expr is not None:
conv_expr = conv_expr.format(name)
c_type = conversions_in.get(key, c_type)
if tp.type == eolian.TypeType.CLASS:
key = "{0}.obj".format(key)
conv_t = mapping_in.get(key)
if conv_t is None:
if key not in builtin_types:
log.warn("Unknown in param %s (type=%s, c_type=%s)", tp.name, tp.type, c_type)
conv_t = ""
if not is_nonull:
key = "<{0}>if {1} is not None else NULL".format(c_type, name)
return conv_expr, c_type, key
return conv_expr, conv_t, key
def convert_out_param(tp, name):
if tp.type == eolian.TypeType.POINTER:
tp = tp.base_type
if tp.type == eolian.TypeType.CLASS:
# TODO: set obj
return tp.c_type, name
key = tp.name if tp.name else tp.c_type
conv = conversions_out.get(key)
if not conv:
if not key in builtin_types:
log.warn("Unknown out param %s (type=%s, c_type=%s)", tp.name, tp.type, tp.c_type)
return tp.c_type, name
name = conv.format(name)
@ -114,17 +131,21 @@ def conv_type_ret(tp):
if not tp:
return
if tp.type == eolian.TypeType.POINTER:
tp = tp.base_type
c_type = tp.c_type
key = tp.name if tp.name else c_type
#c_t = remove_type_prefixes(tp)
py_t = mapping_out.get(key)
if py_t is None:
log.warn("Unknown ret type: %s", key)
if key not in builtin_types:
log.warn("Unknown ret type: %s, %s", key, tp.type)
return c_type, c_type
py_ret_type = py_t[0]
c_ret_type = remove_type_prefixes(c_type)
#c_ret_type = remove_type_prefixes(c_type)
return py_ret_type, c_ret_type
return py_ret_type, c_type

View File

@ -139,11 +139,15 @@ class PyxGenerator(Generator):
self.dedent()
def c_call_write(self, c_name, c_params, ret_type=None, returns=None):
# TODO: Eina_Bool ret value, useful?
self.write("_eo_do_start(self.obj, NULL, EINA_FALSE, eina_main_loop_is(), NULL, NULL, 0)")
c_params = ", ".join(c_params)
c_call = "eo_do(self.obj, %s(%s))" % (c_name, c_params)
#c_call = "eo_do(self.obj, %s(%s))" % (c_name, c_params)
c_call = "%s(%s)" % (c_name, c_params)
if ret_type:
c_call = "cdef " + ret_type + " " + self.RET_PARAM + " = " + "<{0}>".format(ret_type) + c_call
self.write(c_call)
self.write("_eo_do_end(self.obj)")
class Function(object):
@ -155,25 +159,31 @@ class Function(object):
self.prefix = prefix
self.isget = isget
ftypes = eolian.FunctionType
if func.type == ftypes.PROP_SET or func.type == ftypes.PROPERTY and not isget:
ftype = eolian.FunctionType.PROP_SET
if func.type == ftypes.PROP_GET or func.type == ftypes.PROPERTY and isget:
ftype = eolian.FunctionType.PROP_GET
else:
ftype = eolian.FunctionType.METHOD
ret_type = func.return_type_get(ftype)
if ret_type.type == eolian.TypeType.UNKNOWN:
self.c_ret_type = "void"
else:
self.c_ret_type = ret_type.c_type
self.c_name = func.full_c_name_get(self.prefix)
ftypes = eolian.FunctionType
if (func.type == ftypes.PROP_SET or func.type == ftypes.PROPERTY) and not isget:
ret_type = func.return_type_get(eolian.FunctionType.PROP_SET)
self.c_name += "_set"
if (func.type == ftypes.PROP_GET or func.type == ftypes.PROPERTY) and isget:
ret_type = func.return_type_get(eolian.FunctionType.PROP_GET)
self.c_name += "_get"
else:
ret_type = func.return_type_get(eolian.FunctionType.METHOD)
self.c_ret_type = ret_type.c_type if ret_type.type != eolian.TypeType.UNKNOWN else "void"
self.c_params = []
for p in func.parameters:
self.c_params.append((p.type.c_type, p.name))
def cdefs_generate(self, gen):
def cdef_generate(self, gen):
func = self.func
ftypes = eolian.FunctionType
if (func.type == ftypes.PROP_SET or func.type == ftypes.PROPERTY) and self.isget:
return
if (func.type == ftypes.PROP_GET or func.type == ftypes.PROPERTY) and not self.isget:
return
rtype = self.c_ret_type
if not rtype:
rtype = "void"
@ -193,6 +203,7 @@ class Function(object):
c_call_params = []
return_params = []
conv_params = []
conv_in_exps = []
out_cdefs = []
@ -215,11 +226,12 @@ class Function(object):
if di == eolian.ParameterDir.IN:
conv_expr, c_type, name = convert_in_param(p.type, p.name, p.is_nonull)
conv_in_exps.append(conv_expr)
header_params.append((c_type, p.name))
c_call_params.append(name)
elif di == eolian.ParameterDir.OUT:
out_cdefs.append((c_type, "&"+name))
c_type, name = convert_out_param(p.type, name)
out_cdefs.append((c_type, "&"+p.name))
c_type, name = convert_out_param(p.type, p.name)
return_params.append((p.type, name))
elif di == eolian.ParameterDir.INOUT:
conv_expr, c_type, name = convert_in_param(p.type, p.name, p.is_nonull)
@ -243,7 +255,8 @@ class Function(object):
conv_expr, c_type, name = convert_in_param(p.type, p.name, p.is_nonull)
conv_params.append(name)
conv_in_exps.append(conv_expr)
conv_params.append(p.name)
c_call_params.append(name)
assert conv_params, "params should not be empty for setter of: %s" % (func.name)
@ -281,6 +294,10 @@ class Function(object):
conv_params = ", ".join(conv_params)
gen.write("%s = value" % (conv_params))
for e in conv_in_exps:
if e:
gen.write(e)
gen.out_cdefs_write(out_cdefs)
if ret_type.type != eolian.TypeType.UNKNOWN:
@ -340,9 +357,9 @@ class Property(object):
self.getter = Function(prop, prefix, isget=True)
self.setter = Function(prop, prefix, isget=False)
def cdefs_generate(self, gen):
self.getter.cdefs_generate(gen)
self.setter.cdefs_generate(gen)
def cdef_generate(self, gen):
self.getter.cdef_generate(gen)
self.setter.cdef_generate(gen)
def generate(self, gen):
gen.write("property %s:" % (self.py_name))
@ -355,62 +372,71 @@ class Property(object):
gen.dedent()
class EoConstructor(Function):
class Constructor(object):
def pyx_generate(self, gen):
cls_get = self.eo_prefix + "_class_get()"
gen.method_header_write(
self.py_name, [("", "parent")] + self.py_params, clsm=True)
gen.indent()
#gen.docstring_write(self.docs)
c_params = ", ".join([c[1] for c in self.c_params])
gen.write("self = cls.__new__(cls)")
gen.write(
"cdef Eo *obj = eo_add_custom"
"(%s, parent.obj if parent is not None else NULL, %s(%s))" % (
cls_get, self.c_name, c_params
)
)
gen.write("self._set_obj(obj)")
gen.write("return self")
gen.dedent()
gen.write()
class EoDefaultConstructor(EoConstructor):
def __init__(self, prefix):
def __init__(self, ctors, prefix):
self.prefix = prefix
self.ctors = ctors
def cdef_generate(self, gen):
pass
def generate(self, gen):
cls_get = self.prefix + "_class_get()"
gen.write("def __init__(self, parent=None):")
header_params = [("_Base", "parent=None")]
c_ctors = []
for ctor in self.ctors:
func = ctor.function
c_name = func.full_c_name_get(self.prefix)
c_call_params = []
py_name = func.name
if keyword.iskeyword(py_name): # Check if name is python reserved
py_name += "_"
ftypes = eolian.FunctionType
if (func.type == ftypes.PROP_SET or func.type == ftypes.PROPERTY):
c_name = c_name + "_set"
for p in func.parameters:
di = p.direction
assert di == eolian.ParameterDir.IN, "other than IN param for constructor"
c_type = p.type.c_type
conv_expr, c_type, name = convert_in_param(p.type, p.name, p.is_nonull)
header_params.append((c_type, p.name))
c_call_params.append(name)
c_call_params = ", ".join(c_call_params)
c_ctors.append((c_name, c_call_params))
gen.write("def __init__(self, %s):" % (", ".join([" ".join((t, n)).strip() for t, n in header_params])))
gen.indent()
gen.write(
"cdef Eo *obj = eo_add"
"(%s, parent.obj if parent is not None else NULL)" % (
cls_get
)
)
gen.write("cdef Eo *obj = eo_add(")
gen.indent()
gen.write("%s, parent.obj if parent is not None else NULL," % (cls_get))
for ctor in c_ctors:
gen.write("%s(%s)" % (ctor[0], ctor[1]))
gen.write(")")
gen.dedent()
gen.write("self._set_obj(obj)")
gen.dedent()
gen.write()
def cdefs_generate(self, gen):
pass
class Class(object):
def __init__(self):
self.docs = []
self.inherits = []
self.ctors = []
self.ctor = []
self.methods = []
self.props = []
self.default_ctor = None
@ -435,8 +461,6 @@ class Class(object):
self.inherits = list(cls.inherits)
ctors = None
prefix = cls.eo_prefix
if not prefix:
@ -445,29 +469,8 @@ class Class(object):
self.prefix = prefix
if cls.type == eolian.ClassType.REGULAR: # or cls.type == eolian.ClassType.MIXIN:
ctors = cls.constructors
if ctors:
for ctor in ctors:
function_counter[ctor.full_name] += 1
if not args.with_legacy_api:
try:
o = EoConstructor.parse(ctor, prefix)
except Exception as e:
log.warn(
"Skipping %s.%s because of an exception",
cls.name, ctor.full_name
)
log.warn(e)
log.debug(format_exc())
continue
else:
log.error("legacy custom constructors not supported")
continue
self.ctors.append(o)
generated_function_counter[
"_".join((prefix, ctor.name))] += 1
self.default_ctor = EoDefaultConstructor(prefix)
#if cls.type == eolian.ClassType.REGULAR: # or cls.type == eolian.ClassType.MIXIN:
self.ctor.append(Constructor(cls.constructors, prefix))
props = cls.functions_get(eolian.FunctionType.PROPERTY)
for prop in props:
@ -480,7 +483,7 @@ class Class(object):
function_counter["_".join((prefix, prop.name))] += 1
try:
o = Property(prop, prefix)
except Exception as e:
except Exception:
log.exception(
"Skipping %s.%s because of an exception",
cls.name, prop.name
@ -500,7 +503,7 @@ class Class(object):
function_counter["_".join((prefix, method.name))] += 1
try:
o = Function(method, prefix)
except Exception as e:
except Exception:
log.exception(
"Skipping %s.%s because of an exception"
% (cls.name, method.name))
@ -512,6 +515,14 @@ class Class(object):
return self
def cdefs_generate(self, gen):
if self.ctor or self.methods or self.props:
for o in self.ctor + self.methods + self.props:
try:
o.cdef_generate(gen)
except Exception:
log.exception("Error generating cdefs for %r", o)
def pyx_generate(self, gen):
inherits = []
for i in self.inherits:
@ -526,19 +537,10 @@ class Class(object):
if len(inherits) > 1:
log.error(
"Multiple inheritance is not supported in extension classes.\n"
"Class: %r" % (cls)
"Class: %r" % (self.c_name)
)
return
if self.ctors or self.methods or self.props:
gen.write('cdef extern from "%s":' % (self.header_path))
gen.indent()
for o in self.ctors + self.methods + self.props:
o.cdefs_generate(gen)
gen.dedent()
gen.write()
if not inherits:
inherits = ("object", )
inherits = ", ".join(inherits)
@ -546,18 +548,19 @@ class Class(object):
gen.write("cdef class _%s(%s):" % (self.name, inherits))
gen.indent()
if not self.ctors and not self.props and not self.methods and \
not self.default_ctor:
if not self.ctor and not self.props and not self.methods:
gen.write("pass")
else:
for o in (
[self.default_ctor] +
list(self.ctors) +
self.ctor +
list(self.methods) +
list(self.props)
):
if o:
o.generate(gen)
try:
o.generate(gen)
except Exception:
log.exception("Error while generating %r", o)
gen.dedent()
gen.write()
@ -600,65 +603,118 @@ class Class(object):
gen.write()
py_path = os.path.join(args.output, "__init__" + ".py")
if os.path.exists(py_path):
os.remove(py_path)
class File(object):
def __init__(self, filepath):
self.filepath = filepath
self.pyxgen = PyxGenerator()
self.pygen = Generator()
self.cls = eolian.Class.get_by_file(filepath)
if not self.cls:
raise RuntimeError("Could not get class")
self.gencls = Class.parse(self.cls)
def cdefs_generate(self):
gen = self.pyxgen
gen.write('cdef extern from "Eina.h":')
gen.indent()
gen.write('Eina_Bool eina_main_loop_is(void)')
gen.dedent()
gen.write()
gen.write('cdef extern from "Eo.h":')
gen.indent()
gen.write('Eina_Bool _eo_do_start(const Eo *obj, const Eo_Class *cur_klass, Eina_Bool is_super, Eina_Bool is_main_loop, const char *file, const char *func, int line)')
gen.write('void _eo_do_end(const Eo **ojb)')
gen.dedent()
gen.write()
gen.write('cdef extern from "%s":' % (self.gencls.header_path))
gen.indent()
for i in eolian.type_aliases_get_by_file(self.filepath):
print(i)
for i in eolian.type_structs_get_by_file(self.filepath):
print(i)
for i in eolian.type_enums_get_by_file(self.filepath):
print(i)
self.gencls.cdefs_generate(gen)
gen.dedent()
gen.write()
def pyx_generate(self):
self.gencls.pyx_generate(self.pyxgen)
#f_base = os.path.splitext(py_path)[0]
path = [args.output]
namespaces = []
for ns in self.cls.namespaces:
namespaces.append(ns.lower())
if not namespaces:
log.error("Class %s has no namespaces defined" % (self.cls.name))
filename = self.cls.name.lower() + ".pxi"
namespace = ".".join(namespaces)
filename = ".".join((namespace, filename))
path.append(filename)
pxi_path = os.path.join(*path)
o = self.pyxgen.printout()
if o:
with open(pxi_path, "w") as f:
f.write(o.encode("utf-8"))
log.info(pxi_path + " written")
def py_generate(self):
py_path = os.path.join(args.output, "__init__" + ".py")
if os.path.exists(py_path):
os.remove(py_path)
with open(py_path, "a") as py_f:
self.gencls.py_generate(self.pygen)
o = self.pygen.printout()
if o:
py_f.write(o.encode("utf-8"))
py_f.write("\n")
log.info(py_path + " appended")
class Alias(object):
def cdef_generate(self, gen):
pass
class Struct(object):
def cdef_generate(self, gen):
pass
class Enum(object):
def cdef_generate(self, gen):
pass
class_counter = Counter()
generated_class_counter = Counter()
function_counter = Counter()
generated_function_counter = Counter()
for path in args.paths:
for dirpath, dirnames, filenames in os.walk(path):
eolian.directory_scan(dirpath)
#eolian.all_eo_files_parse()
#eolian.all_eot_files_parse()
for filename in filenames:
if filename.endswith(".eo"):
f = os.path.join(dirpath, filename)
if not eolian.eo_file_parse(f):
log.warn("Errors in parsing %s" % (f))
with open(py_path, "a") as py_f:
for cls in eolian.all_classes_get():
try:
gencls = Class.parse(cls)
except Exception:
log.exception(
"Skipping %s because of an exception" % (cls.name))
continue
pyxgen = PyxGenerator()
gencls.pyx_generate(pyxgen)
#f_base = os.path.splitext(py_path)[0]
path = [args.output]
namespaces = []
for ns in cls.namespaces:
namespaces.append(ns.lower())
filename = cls.name.lower() + ".pxi"
namespace = ".".join(namespaces)
filename = ".".join((namespace, filename))
path.append(filename)
pxi_path = os.path.join(*path)
o = pyxgen.printout()
if o:
with open(pxi_path, "w") as f:
f.write(o.encode("utf-8"))
log.info(pxi_path + " written")
pygen = Generator()
gencls.py_generate(pygen)
o = pygen.printout()
if o:
py_f.write(o.encode("utf-8"))
py_f.write("\n")
log.info(py_path + " appended")
eolf = File(filename)
eolf.cdefs_generate()
eolf.pyx_generate()
def report():