HPInc / HP-Digital-Microfluidics

HP Digital Microfluidics Software Platform and Libraries
MIT License
3 stars 1 forks source link

Better printing for DML interpreter #272

Open EvanKirshenbaum opened 10 months ago

EvanKirshenbaum commented 10 months ago

Currently, DMLInterpreter.eval_and_print() is defined as

    def eval_and_print(self, expr: str) -> Delayed[tuple[dmf_lang.Type, Any]]:
        print(f"Interactive cmd: {expr}")
        def on_val(pair: tuple[dmf_lang.Type, Any]) -> None:
            ret_type, val = pair
            if ret_type is dmf_lang.Type.ERROR:
                print(f"  Caught exception ({type(val).__name__}): {val}")
            elif ret_type is dmf_lang.Type.NO_VALUE:
                print(f"  Interactive command returned without value.")
            else:
                print(f"  Interactive cmd val ({ret_type.name}): {val}")
        return self.evaluate(expr).then_call(on_val)

If all goes well, it prints out the return type and value, but it does so using the Python conversion routines, so you get things like

Interactive cmd: (1,1)'s drop
  Interactive cmd val (DROP): Drop[Pad(1,1), 0.6300541919999999 μl of unknown]

This is nice as a trace for the Python developer, but it would be nice if both the type and the value were printed in a way that matched the syntax of DML, something more like

  Interactive cmd val (a drop): 0.630 μl of unknown at (1,1)

This would require two changes. First, we would need to have a way to map types to strings. This could be done as part of Type, but I'm a little leery of doing that, as the types are really still somewhat independent of the language. So it might make more sense to put it in DMFInterpreter or even DMLInterpreter. (Yeah, like that isn't confusing.) But if I'm going to do this, it would be nice if Func.type_error() and possibly even the exceptions TypeMismatchError and ConversionError use them rather than just using type.name.

Since Type also has the notion of the compiler registering conversions (and thereby breaking language independence), I guess we can use the same notion to allow the language to register print forms or provide a mapping, with the notion that if the print form is unspecified, you fall back to the Python conversion.

The second part is changing the way values are printed. Currently, this is simply done by a str conversion, as shown above, and in the print expression, it's done by calling print(). There's also a str built-in that's currently defined as

BuiltIns = {
    # ...
    "str": Functions["STRING"],
    # ...
    }

class DMFCompiler(DMFVisitor):
    @classmethod
    def setup_function_table(cls) -> None:
        # ...
        fn = Functions["STRING"]
        fn.register_immediate((Type.ANY,), Type.STRING, str)

In theory, we should just be able to add extra methods onto that and the closest one to the actual type would be used. If we register that Func with Type, then it could have a pretty_print(val) method on Type that looked there to see how to format, and since it had a default of str, it would be guaranteed to succeed.

We could then modify

    def visitPrint_expr(self, ctx:DMFParser.Print_exprContext) -> Executable:
        arg_text = (self.text_of(c) for c in ctx.vals)
        def print_vals(*vals: Sequence[Any]) -> Delayed[BoundImmediateAction]:
            def do_print() -> None:
                print(*vals)
            name = f"print({', '.join(arg_text)})"
            return Delayed.complete(BoundImmediateAction(do_print, name = name))
        vals = tuple(self.visit(c) for c in ctx.vals)
        sig = Signature.of(tuple(v.return_type for v in vals), Type.ACTION)
        return self.use_callable(print_vals, vals, sig)

to add a call to Functions["STRING"] into each arg path in vals, meaning that what comes to print_vals would be a Sequence[str]. (The alternative would be to do the work in print_vals(), but then (1) we'd need to allow for a conversion potentially not being immediate and (2) we'd need to squirrel away a list of types for the arguments. It's probably easier just to let them happen as the values are produced.)

Migrated from internal repository. Originally created by @EvanKirshenbaum on Jun 03, 2023 at 3:06 PM PDT.
EvanKirshenbaum commented 10 months ago

This issue was referenced by the following commits before migration: