Ejemplo n.º 1
0
def emit_union(m: Module, item: Item, *, resolver: Resolver) -> Definition:
    typename = goname(item.type_.__name__)
    kind_typename = typename + "Kind"

    # type <typename> {
    #     Kind string `json:"$kind"`
    # ...
    # }
    m.stmt(f"type {typename} struct {{")
    with m.scope():
        m.stmt(f'Kind {kind_typename} `json:"$kind"` // discriminator')
        for subtype in item.args:
            gotype: str = resolver.resolve_gotype(subtype)
            m.append(f"{gotype} *{gotype}")
            m.stmt(
                f' `json:"{untitleize(str(gotype)).rstrip("_")},omitempty"`')

    m.stmt("}")
    m.sep()

    # UnmarshalJSON
    discriminator_field = ("$kind", typeinfo.typeinfo(str), metadata())
    discriminator_field[-1]["_override_type"] = kind_typename

    pseudo_fields = [(sub_type.__name__, typeinfo.typeinfo(sub_type),
                      metadata(required=False)) for sub_type in item.args]
    pseudo_item = Item(
        type_=item.type_,
        fields=[discriminator_field] + pseudo_fields,
        args=[],
    )

    unmarshalJSON_definition = emit_unmarshalJSON(m,
                                                  pseudo_item,
                                                  resolver=resolver)
    m.sep()

    # one-of validation
    assert unmarshalJSON_definition.code_module is not None
    this = m.symbol(f"{item.type_.__name__[0].lower()}")
    maperr_pkg = m.import_("github.com/podhmo/maperr")

    sm = unmarshalJSON_definition.code_module
    sm.stmt("// one-of?")
    sm.stmt("{")
    with sm.scope():
        for go_name, info, _ in pseudo_item.fields[1:]:
            with sm.if_(
                    f'{this}.Kind == "{go_name}" && {this}.{go_name} == nil'):
                sm.stmt(
                    f'err = err.Add("{go_name}", {maperr_pkg}.Message{{Text: "treated as {go_name}, but no data"}})'
                )
    sm.stmt("}")

    # enums
    emit_enums(m, item.type_, resolver=resolver, name=kind_typename)

    definition = Definition(name=typename, code_module=None)
    return definition
Ejemplo n.º 2
0
def emit(g: Graph) -> Module:
    i = 0
    m = Module()
    variables: t.Dict[int, Symbol] = {}

    # TODO: with type
    # TODO: name
    original_args = [
        f"{node.name} string" for node in g.nodes if node.is_primitive
    ]
    with m.func("run", *original_args, return_="error"):
        for node in topological_sorted(g):
            if node.is_primitive:
                variables[node.uid] = m.symbol(node.name)
                continue

            args = []
            for dep in node.depends:
                args.append(variables[dep.uid])
            m.letN
            return_type = node.metadata.get("return_type", "")
            if return_type == "":
                [variables[node.uid]] = m.letN([f"v{i}"],
                                               m.symbol(node.name)(*args))
            elif return_type == "with-err":
                variables[node.uid], err = m.letN((f"v{i}", "err"),
                                                  m.symbol(node.name)(*args))
                with m.if_("err != nil"):
                    m.return_("err")
            elif return_type == "with-cleanup":
                variables[node.uid], cleanup = m.letN(
                    (f"v{i}", "cleanup"),
                    m.symbol(node.name)(*args))
                m.stmt("defer cleanup()")
            elif return_type == "with-cleanup-err":
                variables[node.uid], cleanup, err = m.letN(
                    (f"v{i}", "cleanup", "err"),
                    m.symbol(node.name)(*args))
                with m.if_("err != nil"):
                    m.return_("err")
                m.stmt("defer cleanup()")
            else:
                raise ValueError(f"unexpected return_type {return_type}")
            i += 1
        m.return_("nil")
    return m
Ejemplo n.º 3
0
def emit(m: Module, g: Graph) -> Symbol:
    # TODO: name
    # TODO: import_
    i = 0
    variables: t.Dict[int, Symbol] = {}

    for node in topological_sorted(g):
        if node.is_primitive:
            variables[node.uid] = m.symbol(node.name)
            continue

        metadata = t.cast(Metadata, node.metadata)
        return_type = metadata.get("return_type", "")

        return_types = list(t.get_args(return_type) or [return_type])
        var_names = [
            f"v{i}",
            *[getattr(typ, "name", typ.__name__) for typ in return_types[1:]],
        ]

        spec: Fnspec = metadata.get("fnspec")
        provider_callable: t.Optional[Symbol] = None
        if spec is not None:
            pkg_prefix = m.import_(metadata["component_type"].gopackage)
            provider_callable = m.symbol(f"{pkg_prefix}.{spec.name}")
        if provider_callable is None:
            provider_callable = m.symbol(spec.name if spec else node.name)

        args = [variables[dep.uid] for dep in node.depends]
        variables[node.uid], *extra_vars = m.letN(var_names,
                                                  provider_callable(*args))

        if extra_vars:
            for sym, typ in sorted(
                    zip(extra_vars, return_types[1:]),
                    key=lambda pair: getattr(pair[1], "priority", 5),
                    reverse=True,
            ):
                if hasattr(typ, "emit"):
                    typ.emit(m, sym)

        i += 1
    return variables[node.uid]
Ejemplo n.º 4
0
def emit(g: Graph) -> Module:
    # TODO: name
    # TODO: import_
    i = 0
    m = Module()
    variables: t.Dict[int, Symbol] = {}

    with m.func("run", *_get_args(g), return_="error"):
        for node in topological_sorted(g):
            if node.is_primitive:
                variables[node.uid] = m.symbol(node.name)
                continue

            metadata = t.cast(Metadata, node.metadata)
            return_type = metadata.get("return_type", "")

            return_types = list(t.get_args(return_type) or [return_type])
            var_names = [
                f"v{i}",
                *[
                    getattr(typ, "name", typ.__name__)
                    for typ in return_types[1:]
                ],
            ]
            provider_callable = m.symbol(metadata.get("provider") or node.name)

            args = [variables[dep.uid] for dep in node.depends]
            variables[node.uid], *extra_vars = m.letN(var_names,
                                                      provider_callable(*args))
            if extra_vars:
                for sym, typ in sorted(
                        zip(extra_vars, return_types[1:]),
                        key=lambda pair: getattr(pair[1], "priority", 5),
                        reverse=True,
                ):
                    if hasattr(typ, "emit"):
                        typ.emit(m, sym)

            i += 1
        m.return_("nil")
    return m
Ejemplo n.º 5
0
def emit(g: Graph) -> Module:
    i = 0
    m = Module()
    variables: t.Dict[int, Symbol] = {}

    # TODO: with type
    # TODO: name
    root_args = [
        f"{node.name} string" for node in g.nodes if node.is_primitive
    ]
    with m.func("run", *root_args, return_="error"):
        for node in topological_sorted(g):
            if node.is_primitive:
                variables[node.uid] = m.symbol(node.name)
                continue

            metadata = t.cast(Metadata, node.metadata)
            return_type = metadata.get("return_type", "")

            return_types = list(t.get_args(return_type) or [return_type])
            var_names = [
                f"v{i}",
                *[
                    getattr(typ, "name", typ.__name__)
                    for typ in return_types[1:]
                ],
            ]
            provider_callable = m.symbol(metadata.get("provider") or node.name)

            args = [variables[dep.uid] for dep in node.depends]
            variables[node.uid], *extra_vars = m.letN(var_names,
                                                      provider_callable(*args))
            if extra_vars:
                for sym, typ in zip(extra_vars, return_types[1:]):
                    if hasattr(typ, "emit"):
                        typ.emit(m, sym)

            i += 1
        m.return_("nil")
    return m
Ejemplo n.º 6
0
def inject(
    m: Module,
    g: Graph,
    *,
    variables: t.Dict[int, Symbol],
    levels: t.Optional[t.Dict[int, int]] = None,
    nodes: t.Optional[t.List[Node]] = None,
    seen: t.Optional[t.Set[int]] = None,
    strict: bool = True,
) -> Symbol:
    # TODO: name
    i = len(variables)
    if levels is None:
        levels = defaultdict(int)
    if nodes:
        node = nodes[0]

    nodes = topological_sorted(g, seen=seen, nodes=nodes)
    for node in nodes:
        if node.is_primitive:
            if strict:
                assert node.uid in variables
            else:
                variables[node.uid] = m.symbol(node.name)
            continue

        metadata = t.cast(Metadata, node.metadata)
        return_type = metadata.get("return_type", "")

        # handling provider callable
        return_types = list(typing_get_args(return_type) or [return_type])
        var_names = [
            f"v{i}",
            *[getattr(typ, "name", typ.__name__) for typ in return_types[1:]],
        ]

        spec: t.Optional[Fnspec] = metadata.get("fnspec")
        provider_callable: t.Optional[Symbol] = None
        if spec is not None:
            provider = spec.name
            pkg = get_gopackage(metadata["fnspec"].body)
            if pkg is not None:
                pkg_prefix = m.import_(pkg)
                provider = f"{pkg_prefix}.{provider}"
            provider_callable = m.symbol(provider)
        if provider_callable is None:
            provider_callable = m.symbol(spec.name if spec else node.name)

        # handling arguments (pointer)
        if spec is None:
            args = [variables[dep.uid] for dep in node.depends]  # todo: remove
        else:
            args = []
            assert len(node.depends) == len(spec.arguments), (
                len(node.depends),
                len(spec.arguments),
            )
            for dep, (name, typ, _) in zip(node.depends,
                                           spec.arguments):  # parameters?
                sym = variables[dep.uid]

                current_level = levels[dep.uid]
                need_level = metadata["levels"].get(name, 0)  # more strict?
                level_diff = need_level - current_level

                if level_diff == 0:
                    pass
                elif level_diff > 0:
                    sym = Symbol("&" * level_diff + str(sym))
                else:
                    sym = Symbol("*" * -level_diff + str(sym))
                args.append(sym)
            levels[node.uid] = metadata["levels"]["return"]

        variables[node.uid], *extra_vars = m.letN(var_names,
                                                  provider_callable(*args))

        # handling error and cleanup:
        if extra_vars:
            for sym, typ in sorted(
                    zip(extra_vars, return_types[1:]),
                    key=lambda pair: getattr(  # type:ignore
                        pair[1], "priority", priority.NORMAL),
                    reverse=True,
            ):
                if hasattr(typ, "emit"):
                    typ.emit(m, sym)

        i += 1
    return variables[node.uid]
Ejemplo n.º 7
0
def emit_unmarshalJSON(m: Module, item: Item, *,
                       resolver: Resolver) -> Definition:
    this = m.symbol(f"{item.type_.__name__[0].lower()}")
    this_type = f"{resolver.resolve_gotype(item.type_)}"
    this_type_pointer = f"*{this_type}"

    # func (ob *Ob) UnmarshalJSON(b []byte) error {
    b = m.symbol("b")
    m.stmt(
        f"func ({this} {this_type_pointer}) UnmarshalJSON({b} []byte) error {{"
    )
    with m.scope():

        # var err *maperr.Error
        err = m.symbol("err")
        maperr_pkg = m.import_("github.com/podhmo/maperr")
        m.stmt(f"var {err} *{maperr_pkg}.Error")
        m.sep()

        # var inner struct {
        #   ...
        # }
        m.stmt("// loading internal data")
        inner = m.symbol("inner")
        m.stmt(f"var {inner} struct {{")
        with m.scope():
            for name, info, metadata in item.fields:
                if name.startswith("_"):
                    continue  # xxx:

                if "_override_type" in metadata:
                    gotype: str = metadata["_override_type"]
                elif has_class_object(info):
                    json_pkg = m.import_("encoding/json")
                    gotype: str = str(json_pkg.RawMessage)
                else:
                    gotype: str = resolver.resolve_gotype(info.raw)

                m.append(f'{goname(name)} *{gotype} `json:"{name}"`')
                m.stmt("// required" if metadata["required"] else "")
        m.stmt("}")

        # if rawErr := json.Unmarshal(b, &inner); rawErr != nil {
        # ...
        # }
        json_pkg = m.import_("encoding/json")
        raw_err = m.symbol("rawErr")
        with m.if_(
                f"{raw_err} := {json_pkg}.Unmarshal(b, &{inner}); {raw_err} != nil"
        ):
            m.return_(err.AddSummary(raw_err.Error()))
        m.sep()

        # if <field> != nil {
        #     ob.<field> = *<field>
        # } else {
        #     m.add(<field>, "required")
        # }
        rawerr = m.symbol("rawerr")
        m.stmt("// binding field value and required check")
        with m.block():
            for name, info, metadata in item.fields:
                field = m.symbol(goname(name))
                with m.if_(f"{inner}.{field} != nil"):
                    if has_class_object(info):
                        # pointer
                        if info.is_optional:
                            gotype: str = resolver.resolve_gotype(
                                info.normalized)
                            m.stmt(f"{this}.{goname(name)} = &{gotype}{{}}")
                            ref = f"{this}.{field}"
                        elif hasattr(info, "args"):  # xxx
                            gotype: str = resolver.resolve_gotype(
                                info.normalized)
                            m.stmt(f"{this}.{goname(name)} = {gotype}{{}}")
                            ref = f"&{this}.{field}"
                        else:
                            ref = f"&{this}.{field}"

                        with m.if_(
                                f"{rawerr} := json.Unmarshal(*{inner}.{field}, {ref}); {rawerr} != nil"
                        ):
                            m.stmt(
                                f'{err} = {err}.Add("{name}", {maperr_pkg}.Message{{Error: {rawerr}}})'
                            )
                    else:
                        m.stmt(f"{this}.{field} = *{inner}.{field}")
                if metadata["required"]:
                    with m.else_():
                        m.stmt(
                            f'{err} = err.Add("{name}", {maperr_pkg}.Message{{Text: "required"}})'
                        )
        m.sep()

        # NOTE: for injecting code from extrnal area
        code_module = m.submodule("", newline=False)

        # return err.Untyped()
        m.return_(err.Untyped())

    m.stmt("}")
    return Definition(name="UnmarshalJSON", code_module=code_module)
Ejemplo n.º 8
0
def emit_enums(
    m: Module,
    literal_type: t.Type[t.Any],
    *,
    resolver: Resolver,
    name: t.Optional[str] = None,
) -> str:
    # literal_type or union_type
    go_type = name or f"{resolver.resolve_gotype(literal_type)}"

    first_of_args = t.get_args(literal_type)[0]
    base_go_type = resolver.resolve_gotype(
        type(getattr(first_of_args, "__name__", first_of_args)))

    const_names = [getattr(x, "__name__", x) for x in t.get_args(literal_type)]
    const_members = {name: f"{go_type}{goname(name)}" for name in const_names}
    this = m.symbol("v")
    as_literal = resolver.resolve_default

    # type <enum> string
    m.stmt(f"type {go_type} {base_go_type}")
    m.sep()

    # const (
    #     <enum>xxx <enum> = "xxx"
    # ...
    # )
    with m.const_group() as cg:
        for name in const_names:
            cg(f"{const_members[name]} {go_type} = {as_literal(type(name), name)}"
               )
    m.sep()

    # func (v <enum>) Valid() error {
    # ...
    # }
    with m.method(f"{this} {go_type}", "Valid", returns="error"):
        with m.switch(this) as sm:
            with sm.case(", ".join(const_members.values())):
                sm.return_("nil")
            with sm.default() as sm:
                fmt_pkg = m.import_("fmt")
                candidates = ", ".join([str(x) for x in const_names])
                sm.return_(
                    fmt_pkg.Errorf(
                        as_literal(
                            str,
                            f"%q is invalid enum value of ({candidates})"),
                        this,
                    ))
        sm.unnewline()

    # func (v <enum>) UnmarshalJSON(b []byte) error {
    # ...
    # }
    with m.method(f"{this} *{go_type}",
                  "UnmarshalJSON",
                  f"b []byte",
                  returns="error"):
        strings_pkg = m.import_("strings")
        m.stmt(f'*{this} = {go_type}({strings_pkg}.Trim(string(b), `"`))')
        m.return_(this.Valid())

    definition = Definition(name=go_type, code_module=None)
    return definition