def visit_core(m: Module, *, name: str) -> None: accessor_name, name = name.split(".", 2) a = globals()[accessor_name] m.stmt(f"func {name}() error {{") with m.scope(): m.stmt(a.new(m, name=name)) m.stmt("}")
def visit_wrap(m: Module, *, name: str, prev: str) -> None: accessor_name, name = name.split(".", 2) a = globals()[accessor_name] m.stmt(f"func {name}() error {{") with m.scope(): m.stmt(a.wrap(m, name=name, prev=prev)) m.stmt("}")
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]
def visit(m: Module, *, names: t.List[str]) -> None: m.stmt("func run() error {") with m.scope(): m.stmt(f"return {names[0]}()") m.stmt("}") for name, prev in itertools.zip_longest(names, names[1:]): if prev is None: visit_core(m, name=name) else: visit_wrap(m, name=name, prev=prev)
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
def gen(*, m: t.Optional[Module] = None, names: t.List[str]) -> Module: m = m or Module() m.stmt("package main") m.sep() m.toplevel = m.submodule() m.sep() m.stmt("func main() {") with m.scope(): m.stmt("if err := run(); err != nil {") with m.scope(): log = m.toplevel.import_("log") m.stmt(f'{log.Fatalf}("!!%+v", err)') m.stmt("}") m.stmt("}") m.sep() visit(m, names=names) return m
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
def gen(*, m=None): m = m or Module() m.stmt("package main") m.sep() m.stmt("import (") with m.scope(): m.stmt('"fmt"') m.stmt('"log"') m.sep() m.stmt('"github.com/pkg/errors"') m.stmt(")") m.sep() m.stmt("func main() {") with m.scope(): m.stmt("if err := run(); err != nil {") with m.scope(): m.stmt('log.Fatalf("!!%+v", err)') m.stmt("}") m.stmt("}") m.sep() visit(m, names=["f", "g"]) return m
def visit_wrap(m: Module, *, name: str, prev: str) -> None: m.stmt(f"func {name}() error {{") with m.scope(): m.stmt(f'return errors.Wrap({prev}(), "on {name}")') m.stmt("}")
def visit_core(m: Module, *, name: str) -> None: m.stmt(f"func {name}() error {{") with m.scope(): m.stmt('return fmt.Errorf("xxx")') m.stmt("}")
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]
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 args = [variables[dep.uid] for dep in node.depends] metadata = t.cast(Metadata, node.metadata) return_type = metadata.get("return_type", "") provider = m.symbol(metadata.get("provider") or node.name) if return_type == "": (variables[node.uid],) = m.letN([f"v{i}"], provider(*args)) elif return_type == "with-err": variables[node.uid], err = m.letN([f"v{i}", "err"], provider(*args)) with m.if_("err != nil"): m.return_("err") elif return_type == "with-cleanup": variables[node.uid], cleanup = m.letN( [f"v{i}", "cleanup"], provider(*args) ) m.stmt("defer cleanup()") elif return_type == "with-cleanup-err": variables[node.uid], cleanup, err = m.letN( [f"v{i}", "cleanup", "err"], provider(*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
def emit(self, m: Module, cleanup: Symbol) -> None: m.stmt(f"defer {cleanup}()")
def emit(self, m: Module, err: Symbol) -> None: with m.if_(f"{err} != nil"): m.return_(err)
returns=return_type, ): emit_fn(m.symbol(self.receiver), *args) return m.symbol(methodname) def _literal(s: str) -> t.Any: import json from prestring.utils import UnRepr return UnRepr(json.dumps(s)) gomethod = staticmethod class Greeter: @gomethod def emitHello(self, message: str) -> str: global m fmt = m.import_("fmt") m.return_(fmt.Printf(_literal("Hello %s\n"), message)) m = Module() m.package("greeter") m.import_("") emitter = MethodEmitter(Greeter) emitter.emit(Greeter.emitHello) print(m)
from prestring.go.codeobject import Module from prestring import codeobject import json # print("**switch") # codeobject.default_as_value = json.dumps m = Module() fmt = m.import_("fmt") m.stmt("fmt.Println({})", "foo") m.stmt("fmt.Println({!r})", "foo") m.stmt(fmt.Println("foo")) m.stmt("foo") print(m)
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
def emit_struct(m: Module, item: Item, *, resolver: Resolver) -> Definition: gopackage = get_gopackage(item.type_) if gopackage is not None: return "" typename = str(resolver.resolve_gotype(item.type_)) # // <typename> ... doc = inspect.getdoc(item.type_) if doc: lines = doc.split("\n") m.stmt(f"// {typename} {lines[0]}") for line in lines[1:]: m.stmt(f"// {line}") # type <typename> struct { # ... # } m.stmt(f"type {typename} struct {{") with m.scope(): for name, info, metadata in item.fields: gotype: str = resolver.resolve_gotype(info.raw) # handling field (private field?, embedded?) if metadata.get("inline", False): m.append(gotype) elif name.startswith("_"): m.append(f"{untitleize(goname(name))} {gotype}") else: m.append(f"{goname(name)} {gotype}") # todo: handling tags if "tags" not in metadata: metadata["tags"] = {} metadata["tags"] = {"json": [name.rstrip("_")]} m.append(f" `{build_gotags(metadata['tags'])}`") # handling comments if metadata.get("inline", False): m.stmt(f" // {metadata}") else: comment = metadata.get("comment", "") m.stmt( f" // {comment.split(_NEWLINE, 1)[0]}" if comment else "") definition = Definition(name=typename, code_module=None) m.stmt("}") return definition
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)
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
methodname = goname(spec.name) self = m.symbol(receiver.lstrip("* ")[0].lower()) # todo: type -> gotype args = [m.symbol(name) for name, _, _, in spec.arguments] return_type = "string" with m.method( f"{self} *{receiver}", methodname, *[f"{x} string" for x in args], returns=return_type, ): emit_fn(self, *args) return m.symbol(methodname) return _emit @codeobject def emitHello(self, name: str) -> str: global m fmt = m.import_("fmt") m.return_(fmt.Printf(_literal("Hello %s\n"), name)) m = Module() m.import_("") emitHello(receiver="Greeter") print(m)
def emit(self, m: Module, teardown: Symbol) -> None: m.stmt(f"defer {teardown}()")