diff --git a/misc/py_wrap_generator.py b/misc/py_wrap_generator.py index 4ce8e947e..d8649e9ce 100644 --- a/misc/py_wrap_generator.py +++ b/misc/py_wrap_generator.py @@ -253,6 +253,8 @@ class WContainer: candidate = WType.from_string(arg.strip(), containing_file, line_number) if candidate == None: return None + if candidate.name == "void": + return None cont.args.append(candidate) return cont @@ -880,11 +882,8 @@ class WClass: text += fun.gen_def_virtual() return text - def gen_boost_py(self): - text = "\n\t\tclass_<" + self.name - if self.link_type == link_types.derive: - text += "Wrap, boost::noncopyable" - text += ">(\"" + self.name + "\"" + def gen_boost_py_body(self): + text = "" if self.printable_constrs() == 0 or not self.contains_default_constr(): text += ", no_init" text += ")" @@ -907,6 +906,21 @@ class WClass: text += "\n\t\t\t;\n" return text + def gen_boost_py(self): + body = self.gen_boost_py_body() + if self.link_type == link_types.derive: + text = "\n\t\tclass_<" + self.name + ">(\"" + self.name + "\"" + text += body + text += "\n\t\tclass_<" + self.name + text += "Wrap, boost::noncopyable" + text += ">(\"" + self.name + "\"" + text += body + else: + text = "\n\t\tclass_<" + self.name + ">(\"Cpp" + self.name + "\"" + text += body + return text + + def contains_default_constr(self): for c in self.found_constrs: if len(c.args) == 0: @@ -974,6 +988,7 @@ blacklist_methods = ["YOSYS_NAMESPACE::Pass::run_register", "YOSYS_NAMESPACE::Mo enum_names = ["State","SyncType","ConstFlags"] enums = [] #Do not edit +glbls = [] unowned_functions = [] @@ -1723,6 +1738,159 @@ class WMember: text += ")" return text +class WGlobal: + orig_text = None + wtype = attr_types.default + name = None + containing_file = None + namespace = "" + is_const = False + + def from_string(str_def, containing_file, line_number, namespace): + glbl = WGlobal() + glbl.orig_text = str_def + glbl.wtype = None + glbl.name = "" + glbl.containing_file = containing_file + glbl.namespace = namespace + glbl.is_const = False + + if not str.startswith(str_def, "extern"): + return None + str_def = str_def[7:] + + if str.startswith(str_def, "const "): + glbl.is_const = True + str_def = str_def[6:] + + if str_def.count(" ") == 0: + return None + + parts = split_list(str_def.strip(), " ") + + prefix = "" + i = 0 + for part in parts: + if part in ["unsigned", "long", "short"]: + prefix += part + " " + i += 1 + else: + break + parts = parts[i:] + + if len(parts) <= 1: + return None + + glbl.wtype = WType.from_string(prefix + parts[0], containing_file, line_number) + + if glbl.wtype == None: + return None + + str_def = parts[1] + for part in parts[2:]: + str_def = str_def + " " + part + + if str_def.find("(") != -1 or str_def.find(")") != -1 or str_def.find("{") != -1 or str_def.find("}") != -1: + return None + + found = str_def.find(";") + if found == -1: + return None + + found_eq = str_def.find("=") + if found_eq != -1: + found = found_eq + + glbl.name = str_def[:found] + str_def = str_def[found+1:] + if glbl.name.find("*") == 0: + glbl.name = glbl.name.replace("*", "") + glbl.wtype.attr_type = attr_types.star + if glbl.name.find("&&") == 0: + glbl.name = glbl.name.replace("&&", "") + glbl.wtype.attr_type = attr_types.ampamp + if glbl.name.find("&") == 0: + glbl.name = glbl.name.replace("&", "") + glbl.wtype.attr_type = attr_types.amp + + if(len(str_def.strip()) != 0): + return None + + if len(glbl.name.split(",")) > 1: + glbl_list = [] + for name in glbl.name.split(","): + name = name.strip(); + glbl_list.append(WGlobal()) + glbl_list[-1].orig_text = glbl.orig_text + glbl_list[-1].wtype = glbl.wtype + glbl_list[-1].name = name + glbl_list[-1].containing_file = glbl.containing_file + glbl_list[-1].namespace = glbl.namespace + glbl_list[-1].is_const = glbl.is_const + return glbl_list + + return glbl + + def gen_def(self): + text = "\n\t" + if self.is_const: + text += "const " + text += self.wtype.gen_text() + " get_var_py_" + self.name + "()" + text += "\n\t{\n\t\t" + if self.wtype.attr_type == attr_types.star: + text += "if(" + self.namespace + "::" + self.name + " == NULL)\n\t\t\t" + text += "throw std::runtime_error(\"" + self.namespace + "::" + self.name + " is NULL\");\n\t\t" + if self.wtype.name in known_containers: + text += self.wtype.gen_text_cpp() + else: + if self.is_const: + text += "const " + text += self.wtype.gen_text() + + if self.wtype.name in classnames or (self.wtype.name in known_containers and self.wtype.attr_type == attr_types.star): + text += "*" + text += " ret_ = " + if self.wtype.name in classnames: + text += self.wtype.name + "::get_py_obj(" + if self.wtype.attr_type != attr_types.star: + text += "&" + text += self.namespace + "::" + self.name + if self.wtype.name in classnames: + text += ")" + text += ";" + + if self.wtype.name in classnames: + text += "\n\t\treturn *ret_;" + elif self.wtype.name in known_containers: + text += known_containers[self.wtype.name].translate_cpp("ret_", self.wtype.cont.args, "\n\t\t", self.wtype.attr_type == attr_types.star) + text += "\n\t\treturn ret____tmp;" + else: + text += "\n\t\treturn ret_;" + text += "\n\t}\n" + + if self.is_const: + return text + + ret = Attribute(self.wtype, "rhs"); + + if self.wtype.name in classnames: + text += "\n\tvoid set_var_py_" + self.name + "(" + self.wtype.gen_text() + " *rhs)" + else: + text += "\n\tvoid set_var_py_" + self.name + "(" + self.wtype.gen_text() + " rhs)" + text += "\n\t{" + text += ret.gen_translation() + text += "\n\t\t" + self.namespace + "::" + self.name + " = " + ret.gen_call() + ";" + text += "\n\t}\n" + + return text; + + def gen_boost_py(self): + text = "\n\t\t\t.add_static_property(\"" + self.name + "\", &" + "YOSYS_PYTHON::get_var_py_" + self.name + if not self.is_const: + text += ", &YOSYS_PYTHON::set_var_py_" + self.name + text += ")" + return text + def concat_namespace(tuple_list): if len(tuple_list) == 0: return "" @@ -1859,6 +2027,16 @@ def parse_header(source): else: debug("\t\tFound member \"" + candidate.name + "\" of class \"" + class_[0].name + "\" of type \"" + candidate.wtype.name + "\"", 2) class_[0].found_vars.append(candidate) + if candidate == None and class_ == None: + candidate = WGlobal.from_string(ugly_line, source.name, i, concat_namespace(namespaces)) + if candidate != None: + if type(candidate) == list: + for c in candidate: + glbls.append(c) + debug("\tFound global \"" + c.name + "\" in namespace " + concat_namespace(namespaces), 2) + else: + glbls.append(candidate) + debug("\tFound global \"" + candidate.name + "\" in namespace " + concat_namespace(namespaces), 2) j = i line = unpretty_string(line) @@ -1888,6 +2066,17 @@ def parse_header(source): debug("\t\tFound constructor of class \"" + class_[0].name + "\" in namespace " + concat_namespace(namespaces),2) class_[0].found_constrs.append(candidate) continue + if class_ == None: + candidate = WGlobal.from_string(line, source.name, i, concat_namespace(namespaces)) + if candidate != None: + if type(candidate) == list: + for c in candidate: + glbls.append(c) + debug("\tFound global \"" + c.name + "\" in namespace " + concat_namespace(namespaces), 2) + else: + glbls.append(candidate) + debug("\tFound global \"" + candidate.name + "\" in namespace " + concat_namespace(namespaces), 2) + continue if candidate != None: while i < j: i += 1 @@ -1990,6 +2179,7 @@ def gen_wrappers(filename, debug_level_ = 0): if len(class_.found_constrs) == 0: class_.found_constrs.append(WConstructor(source.name, class_)) debug(str(len(unowned_functions)) + " functions are unowned", 1) + debug(str(len(unowned_functions)) + " global variables", 1) for enum in enums: debug("Enum " + assure_length(enum.name, len(max(enum_names, key=len)), True) + " contains " + assure_length(str(len(enum.values)), 2, False) + " values", 1) debug("-"*col, 1) @@ -2025,10 +2215,15 @@ def gen_wrappers(filename, debug_level_ = 0): #include #include #include - +#include // std::streamsize +#include +#include // boost::iostreams::sink +#include USING_YOSYS_NAMESPACE namespace YOSYS_PYTHON { + + struct YosysStatics{}; """) for source in sources: @@ -2050,6 +2245,9 @@ namespace YOSYS_PYTHON { for fun in unowned_functions: wrapper_file.write(fun.gen_def()) + for glbl in glbls: + wrapper_file.write(glbl.gen_def()) + wrapper_file.write(""" struct Initializer { Initializer() { @@ -2068,12 +2266,89 @@ namespace YOSYS_PYTHON { } }; + + /// source: https://stackoverflow.com/questions/26033781/converting-python-io-object-to-stdostream-when-using-boostpython?noredirect=1&lq=1 + /// @brief Type that implements the Boost.IOStream's Sink and Flushable + /// concept for writing data to Python object that support: + /// n = object.write(str) # n = None or bytes written + /// object.flush() # if flush exists, then it is callable + class PythonOutputDevice + { + public: + + // This class models both the Sink and Flushable concepts. + struct category + : boost::iostreams::sink_tag, + boost::iostreams::flushable_tag + {}; + + explicit + PythonOutputDevice(boost::python::object object) + : object_(object) + {} + + // Sink concept. + public: + + typedef char char_type; + + std::streamsize write(const char* buffer, std::streamsize buffer_size) + { + namespace python = boost::python; + // Copy the buffer to a python string. + python::str data(buffer, buffer_size); + + // Invoke write on the python object, passing in the data. The following + // is equivalent to: + // n = object_.write(data) + python::extract bytes_written( + object_.attr("write")(data)); + + // Per the Sink concept, return the number of bytes written. If the + // Python return value provides a numeric result, then use it. Otherwise, + // such as the case of a File object, use the buffer_size. + return bytes_written.check() + ? bytes_written + : buffer_size; + } + + // Flushable concept. + public: + + bool flush() + { + // If flush exists, then call it. + boost::python::object flush = object_.attr("flush"); + if (!flush.is_none()) + { + flush(); + } + + // Always return true. If an error occurs, an exception should be thrown. + return true; + } + + private: + boost::python::object object_; + }; + + /// @brief Use an auxiliary function to adapt the legacy function. + void log_to_stream(boost::python::object object) + { + // Create an ostream that delegates to the python object. + boost::iostreams::stream* output = new boost::iostreams::stream(object); + Yosys::log_streams.insert(Yosys::log_streams.begin(), output); + }; + + BOOST_PYTHON_MODULE(libyosys) { using namespace boost::python; class_("Initializer"); scope().attr("_hidden") = new Initializer(); + + def("log_to_stream", &log_to_stream); """) for enum in enums: @@ -2086,6 +2361,11 @@ namespace YOSYS_PYTHON { for fun in unowned_functions: wrapper_file.write(fun.gen_boost_py()) + wrapper_file.write("\n\n\t\tclass_(\"Yosys\")\n") + for glbl in glbls: + wrapper_file.write(glbl.gen_boost_py()) + wrapper_file.write("\t\t;\n") + wrapper_file.write("\n\t}\n}\n#endif") def print_includes():