def _gen_write_complex(t: UxsdComplex, name: str, container: str) -> str: """Partial function to generate code which writes out a simple type.""" out = "" if t.attrs: out += "os << \"<%s\";\n" % name for a in t.attrs: out += _gen_write_attr(a, container) else: out += "os << \"<%s\";\n" % name if isinstance(t.content, (UxsdDfa, UxsdAll)): out += "os << \">\";\n" for e in t.content.children: if e.many: out += "for(auto &%s: %s.%s){\n" % (utils.checked( e.name), container, utils.pluralize(e.name)) out += utils.indent( _gen_write_element(e, utils.checked(e.name))) out += "}\n" else: new_container = "%s.%s" % (container, utils.checked(e.name)) if e.optional: out += "if(%s.has_%s){\n" % (container, e.name) out += utils.indent(_gen_write_element(e, new_container)) out += "}\n" else: out += _gen_write_element(e, new_container) out += "os << \"</%s>\";\n" % name elif isinstance(t.content, UxsdLeaf): out += "os << \">\";\n" out += _gen_write_simple(t.content.type, container + ".value") out += "os << \"</%s>\";\n" % name else: out += "os << \"/>\";\n" return out
def load_fn_from_complex_type(t: UxsdComplex) -> str: """Generate a full C++ function load_foo(&root, *out) which can load an XSD complex type from DOM &root into C++ type *out. """ out = "" out += "void load_%s(const pugi::xml_node &root, %s *out){\n" % (t.name, t.cpp) if isinstance(t.content, UxsdDfa): out = _gen_dfa_table(t) + out out += utils.indent(_gen_load_dfa(t)) elif isinstance(t.content, UxsdAll): out += utils.indent(_gen_load_all(t)) elif isinstance(t.content, UxsdLeaf): out += utils.indent( _gen_load_simple(t.content.type, "out->value", "root.child_value()")) else: out += "\tif(root.first_child().type() == pugi::node_element)\n" out += "\t\tthrow std::runtime_error(\"Unexpected child element in <%s>.\");\n" % t.name out += "\n" if t.attrs: out += utils.indent(_gen_load_attrs(t)) else: out += "\tif(root.first_attribute())\n" out += "\t\tthrow std::runtime_error(\"Unexpected attribute in <%s>.\");\n" % t.name out += "}\n" return out
def lexer_from_complex_type(t: UxsdComplex) -> str: """Generate one or two C++ functions to convert const char *s to enum values generated from an UxsdComplex. It's in the form of (a|g)tok_foo lex_(attr|node)_foo(const char *in) and currently uses a trie to lex the string. a or g indicates if the token is an attribute token or a group (child element) token. """ out = "" if isinstance(t.content, (UxsdDfa, UxsdAll)): out += "inline gtok_%s lex_node_%s(const char *in){\n" % (t.cpp, t.cpp) triehash_alph = [(e.name, "gtok_%s::%s" % (t.cpp, utils.to_token(e.name))) for e in t.content.children] out += utils.indent(triehash.gen_lexer_body(triehash_alph)) out += "\tthrow std::runtime_error(\"Found unrecognized child \" + std::string(in) + \" of <%s>.\");\n" % t.name out += "}\n" if t.attrs: out += "inline atok_%s lex_attr_%s(const char *in){\n" % (t.cpp, t.cpp) triehash_alph = [(x.name, "atok_%s::%s" % (t.cpp, utils.to_token(x.name))) for x in t.attrs] out += utils.indent(triehash.gen_lexer_body(triehash_alph)) out += "\tthrow std::runtime_error(\"Found unrecognized attribute \" + std::string(in) + \" of <%s>.\");\n" % t.name out += "}\n" return out
def _gen_write_complex_element(e: UxsdElement, parent: str) -> str: """Function to generate partial code which writes out an element with a complex type.""" assert isinstance(e.type, UxsdComplex) out = "" def _gen_write_element_body(root: str) -> str: assert isinstance(e.type, UxsdComplex) ouv = "" if e.type.attrs: for a in e.type.attrs: ouv += _gen_write_attr(a, e.type.name, root, "child_context") if e.type.content: ouv += "write_{name}_capnp_type(in, {root}, child_context);\n".format( root=root, name=e.type.name) else: if e.type.content: ouv += "write_{name}_capnp_type(in, {root}, child_context);\n".format( root=root, name=e.type.name) return ouv name = cpp._gen_stub_suffix(e, parent) if e.many: plural_name = utils.pluralize(cpp._gen_stub_suffix(e, parent)) out += "size_t num_{pname} = in.num_{name}(context);\n".format( name=name, pname=plural_name) out += "auto {name} = root.init{pname}(num_{name});\n".format( name=plural_name, pname=utils.pluralize(utils.to_pascalcase(e.name))) out += "for(size_t i = 0; i < num_{name}; i++) {{\n".format( name=plural_name) out += "\tauto {name} = {plural_name}[i];\n".format( name=name, plural_name=plural_name, ) out += "\tauto child_context = in.get_%s(i, context);\n" % cpp._gen_stub_suffix( e, parent) out += utils.indent(_gen_write_element_body(name)) out += "}\n" elif e.optional: out += "if(in.has_%s(context)){\n" % cpp._gen_stub_suffix(e, parent) out += "\tauto {name} = root.init{pname}();\n".format( name=name, pname=utils.to_pascalcase(e.name)) out += "\tauto child_context = in.get_%s(context);\n" % cpp._gen_stub_suffix( e, parent) out += utils.indent(_gen_write_element_body(name)) out += "}\n" else: out += "{\n" out += "\tauto child_context = in.get_%s(context);\n" % cpp._gen_stub_suffix( e, parent) out += "\tauto {name} = root.init{pname}();\n".format( name=name, pname=utils.to_pascalcase(e.name)) out += utils.indent(_gen_write_element_body(name)) out += "}\n" return out
def _gen_write_simple(t: UxsdSimple, container: str, attr_name: str = "") -> str: """Partial function to generate code which writes out a simple type. The attr_name parameter is passed by _gen_write_attr so that we can generate squashed code like `os << "index=\"" << y_list.index << "\"";`. """ out = "" if isinstance(t, UxsdAtomic): if attr_name: out += "os << \" %s=\\\"\" << %s << \"\\\"\";\n" % (attr_name, container) else: out += "os << %s;\n" % container elif isinstance(t, UxsdEnum): if attr_name: out += "os << \" %s=\\\"\" << lookup_%s[(int)%s] << \"\\\"\";\n" % ( attr_name, t.name, container) else: out += "os << lookup_%s[(int)%s];\n" % (t.name, container) elif isinstance(t, UxsdUnion): for m in t.member_types: out += "if(%s.tag == type_tag::%s)" % (container, utils.to_token(m.cpp)) out += utils.indent( _gen_write_simple( t, container + "." + utils.to_union_field_name(m.cpp), attr_name)) else: raise NotImplementedError("I don't know how to write out %s." % t) return out
def _gen_load_attrs(t: UxsdComplex) -> str: """Partial function to generate the attribute loading portion of a C++ function load_foo. See _gen_load_all to see how attributes are validated. """ assert len(t.attrs) > 0 N = len(t.attrs) out = "" out += "std::bitset<%d> astate = 0;\n" % N out += "for(pugi::xml_attribute attr = root.first_attribute(); attr; attr = attr.next_attribute()){\n" out += "\tatok_%s in = lex_attr_%s(attr.name());\n" % (t.cpp, t.cpp) out += "\tif(astate[(int)in] == 0) astate[(int)in] = 1;\n" out += "\telse throw std::runtime_error(\"Duplicate attribute \" + std::string(attr.name()) + \" in <%s>.\");\n" % t.name out += "\tswitch(in){\n" for attr in t.attrs: out += "\tcase atok_%s::%s:\n" % (t.cpp, utils.to_token(attr.name)) out += utils.indent( _gen_load_simple(attr.type, "out->%s" % utils.checked(attr.name), "attr.value()"), 2) out += "\t\tbreak;\n" out += "\tdefault: break; /* Not possible. */\n" out += "\t}\n" out += "}\n" mask = "".join(["1" if x.optional else "0" for x in t.attrs][::-1]) out += "std::bitset<%d> test_astate = astate | std::bitset<%d>(0b%s);\n" % ( N, N, mask) out += "if(!test_astate.all()) attr_error(test_astate, atok_lookup_%s);\n" % t.cpp return out
def _gen_write_attr(a: UxsdAttribute, container: str) -> str: """Partial function to generate code which writes out a single XML attribute.""" out = "" new_container = "%s.%s" % (container, a.name) if not a.optional or a.default_value: out += _gen_write_simple(a.type, new_container, a.name) else: out += "if((bool)%s)\n" % new_container out += utils.indent(_gen_write_simple(a.type, new_container, a.name)) return out
def write_fn_from_complex_type(t: UxsdComplex) -> str: assert isinstance(t.content, (UxsdDfa, UxsdAll, UxsdLeaf)) out = "" out += "template<class T, typename Context>\n" out += "inline void write_{name}_capnp_type(T &in, ucap::{cname}::Builder &root, Context &context) {{\n".format( name=t.name, cname=utils.to_pascalcase(t.name)) out += "\t(void)in;\n" out += "\t(void)root;\n" if isinstance(t.content, (UxsdDfa, UxsdAll)): for e in t.content.children: out += utils.indent(_gen_write_element(e, t.name)) elif isinstance(t.content, UxsdLeaf): out += "\troot.setValue(in.get_%s_value(context));\n" % t.name else: out += "\treturn;\n" out += "}\n" return out
def write_fn_from_root_element(e: UxsdElement) -> str: assert isinstance(e.type, UxsdComplex) out = "" out += "template <class T, typename Context>\n" out += "inline void write_{name}_capnp(T &in, Context &context, ucap::{cname}::Builder &root) {{\n".format( name=e.name, cname=utils.to_pascalcase(e.name)) out += "\tin.start_write();\n" if e.type.attrs: for a in e.type.attrs: out += utils.indent(_gen_write_attr(a, e.name)) out += "\twrite_{name}_capnp_type(in, root, context);\n".format( name=e.name) out += "\tin.finish_write();\n" out += "}\n" return out
def typedefn_from_complex_type(t: UxsdComplex) -> str: """Generate a C++ struct definition corresponding to a UxsdComplex. Example: /** * Generated from: * <xs:complexType... /> */ struct t_foo { int bar; bool has_my_baz; t_baz my_baz; collapsed_vec<t_quux, quux_pool> my_quuxes; } """ fields = [] for attr in t.attrs: fields.append("%s %s;" % (attr.type.cpp, utils.checked(attr.name))) if isinstance(t.content, (UxsdDfa, UxsdAll)): for el in t.content.children: if el.many: fields.append( "collapsed_vec<%s, %s_pool> %s;" % (el.type.cpp, el.type.name, utils.pluralize(el.name))) elif el.optional: fields.append("bool has_%s;" % el.name) fields.append("%s %s;" % (el.type.cpp, utils.checked(el.name))) else: fields.append("%s %s;" % (el.type.cpp, utils.checked(el.name))) elif isinstance(t.content, UxsdLeaf): fields.append("%s value;" % t.content.type.cpp) out = "" out += "/** Generated from:\n" out += utils.to_comment_body(t.source) out += "\n*/\n" out += "struct %s {\n" % t.cpp out += utils.indent("\n".join(fields)) out += "\n};" return out
def _gen_load_dfa(t: UxsdComplex) -> str: """Partial function to generate the child element validation&loading portion of a C++ function load_foo, if the model group is an xs:sequence or xs:choice. xs:sequence/xs:choice groups can be compiled down to a finite automaton. This is done in dfa.py. C++ state table is generated in _gen_dfa_table and the stream of child elements are validated according to the table here. The C++ table has -1s in place of invalid state transitions. If we step into a -1, we call dfa_error. We check again at the end of input. If we aren't in an accepted state, we again call dfa_error. """ assert isinstance(t.content, UxsdDfa) dfa = t.content.dfa out = "" out += "int next, state=%d;\n" % dfa.start out += "for(pugi::xml_node node = root.first_child(); node; node = node.next_sibling()){\n" out += "\tgtok_%s in = lex_node_%s(node.name());\n" % (t.cpp, t.cpp) out += "\tnext = gstate_%s[state][(int)in];\n" % t.cpp out += "\tif(next == -1)\n" out += "\t\tdfa_error(gtok_lookup_%s[(int)in], gstate_%s[state], gtok_lookup_%s, %d);\n"\ % (t.cpp, t.cpp, t.cpp, len(dfa.alphabet)) out += "\tstate = next;\n" out += "\tswitch(in){\n" for el in t.content.children: out += "\tcase gtok_%s::%s:\n" % (t.cpp, utils.to_token(el.name)) out += utils.indent(_gen_load_element(el, "out"), 2) out += "\t\tbreak;\n" out += "\tdefault: break; /* Not possible. */\n" out += "\t}\n" reject_cond = " && ".join(["state != %d" % x for x in dfa.accepts]) out += "}\n" out += "if(%s) dfa_error(\"end of input\", gstate_%s[state], gtok_lookup_%s, %d);\n"\ % (reject_cond, t.cpp, t.cpp, len(dfa.alphabet)) return out
def lexer_from_enum(t: UxsdEnum) -> str: """Generate a C++ function to convert const char *s to enum values generated from an UxsdEnum. It's in the form of enum_foo lex_enum_foo(const char *in, bool throw_on_invalid) and currently uses a trie to parse the string. throw_on_invalid is a hacky parameter to determine if we should throw on an invalid value. It's currently necessary to read unions - we don't need to throw on an invalid value if we are trying to read into an union but we need to throw otherwise. """ out = "" out += "inline %s lex_%s(const char *in, bool throw_on_invalid){\n" % ( t.cpp, t.cpp) triehash_alph = [(x, "%s::%s" % (t.cpp, utils.to_token(x))) for x in t.enumeration] out += utils.indent(triehash.gen_lexer_body(triehash_alph)) out += "\tif(throw_on_invalid)\n" out += "\t\tthrow std::runtime_error(\"Found unrecognized enum value \" + std::string(in) + \" of %s.\");\n" % t.cpp out += "\treturn %s::UXSD_INVALID;\n" % t.cpp out += "}\n" return out
def _gen_load_all(t: UxsdComplex) -> str: """Partial function to generate the child element validation&loading portion of a C++ function load_foo, if the model group is an xs:all. xs:alls can be validated in a similar fashion to xs:attributes. We maintain a bitset of which elements are found. At the end, we OR our bitset with the value corresponding to the optional elements and check if all bits in it are set. If not, we call attr_error with the token lookup table and the OR'd bitset. """ assert isinstance(t.content, UxsdAll) N = len(t.content.children) out = "" out += "std::bitset<%d> gstate = 0;\n" % N out += "for(pugi::xml_node node = root.first_child(); node; node = node.next_sibling()){\n" out += "\tgtok_%s in = lex_node_%s(node.name());\n" % (t.cpp, t.cpp) out += "\tif(gstate[(int)in] == 0) gstate[(int)in] = 1;\n" out += "\telse throw std::runtime_error(\"Duplicate element \" + std::string(node.name()) + \" in <%s>.\");\n" % t.name out += "\tswitch(in){\n" for el in t.content.children: out += "\tcase gtok_%s::%s:\n" % (t.cpp, utils.to_token(el.name)) out += utils.indent(_gen_load_element(el, "out"), 2) out += "\t\tbreak;\n" out += "\tdefault: break; /* Not possible. */\n" out += "\t}\n" out += "}\n" mask = "".join(["1" if x.optional else "0" for x in t.content.children][::-1]) out += "std::bitset<%d> test_gstate = gstate | std::bitset<%d>(0b%s);\n" % ( N, N, mask) out += "if(!test_gstate.all()) all_error(test_gstate, gtok_lookup_%s);\n" % t.cpp return out
def render_impl_header_file(schema: UxsdSchema, cmdline: str, capnp_file_name: str, interface_file_name: str, input_file: str) -> str: out = "" x = { "version": __version__, "cmdline": cmdline, "input_file": input_file, "md5": utils.md5(input_file) } out += cpp_templates.header_comment.substitute(x) out += '#include <stdexcept>\n' out += '#include <vector>\n' out += '#include "capnp/serialize.h"\n' out += '#include "{}.h"\n'.format(capnp_file_name) out += '#include "{}"\n'.format(interface_file_name) out += "\n/* All uxsdcxx functions and structs live in this namespace. */\n" out += "namespace uxsd {\n" if schema.enums: enum_converters = [_gen_conv_enum(t) for t in schema.enums] out += "\n\n/* Enum conversions from uxsd to ucap */\n" out += "\n".join(enum_converters) pname = utils.to_pascalcase(schema.root_element.name) out += "struct Capnp{pname}ContextTypes : public Default{pname}ContextTypes {{\n\t".format( pname=pname) out += "\n\t".join( "using {pname}ReadContext = ucap::{pname}::Reader;".format( pname=utils.to_pascalcase(t.name)) for t in schema.complex_types) out += "\n\t" out += "\n\t".join( "using {pname}WriteContext = ucap::{pname}::Builder;".format( pname=utils.to_pascalcase(t.name)) for t in schema.complex_types) out += "\n};\n" out += "\n" out += "class Capnp{pname} : public {pname}Base<Capnp{pname}ContextTypes> {{\n\t".format( pname=pname) out += "public:\n" out += "\tCapnp{pname}() {{}}\n\n".format(pname=pname) out += "\tvoid start_load(const std::function<void(const char *)> *report_error_in) override {\n" out += "\t\treport_error = report_error_in;\n" out += "\t}\n" out += "\tvoid finish_load() override {}\n" out += "\tvoid start_write() override {}\n" out += "\tvoid finish_write() override {}\n" out += "\tvoid error_encountered(const char * file, int line, const char *message) override {\n" out += "\t\tstd::stringstream msg;" out += "\t\tmsg << message << \" occured at file: \" << file << \" line: \" << line;\n" out += "\t\tthrow std::runtime_error(msg.str());\n" out += "\t}\n" for t in schema.complex_types: out += utils.indent( _gen_capnp_impl(t, t.name == schema.root_element.name)) out += "private:\n" out += "\tconst std::function<void(const char *)> *report_error;\n" for t in schema.complex_types: if isinstance(t.content, (UxsdDfa, UxsdAll)): for el in t.content.children: if el.many: out += "\tstd::vector<capnp::Orphan<ucap::{pname}>> {name}_;\n".format( pname=utils.to_pascalcase(el.type.name), name=utils.pluralize(el.type.name)) out += "};\n" out += "\n\n} /* namespace uxsd */\n" return out
def _add_field(ret: str, verb: str, what: str, args: str, impl: str): fields.append("inline %s %s_%s_%s(%s) override {\n%s}\n" % (ret, verb, t.name, what, args, utils.indent(impl)))
def write_fn_from_element(t: UxsdElement) -> str: out = "" out += "void %s::write(std::ostream &os){\n" % utils.checked(t.name) out += utils.indent(_gen_write_element(t, "(*this)")) out += "}\n" return out