raghavnautiyal / Dip

Dip
http://dip-lang.org
MIT License
42 stars 0 forks source link

Change interpreter.py dispatch methodology to use `functools.singledispatchmethod` #4

Open kleinesfilmroellchen opened 4 years ago

kleinesfilmroellchen commented 4 years ago

The interpreter needs a multitude of different methods to visit the different node types. This problem of single dynamic dispatch (choosing a method overload based on the argument type not at compile time, like in C, but at runtime) is currently solved with the type name serving as a lookup with getattr into the list of methods provided in the interpreter.

This approach is slow and error-prone. Typos in the method name are caught only at runtime and may not be tracked down immediately. Also, getattr in conjunction with the string formatting (involving concatenation) seems like an expensive operation, especially when considering it is done on every node of the syntax tree recursively.

Python has a standard library solution for this: the functools.singledispatchmethod decorator. This decorator uses Python's support for type annotations and types themselves to check all dispatch methods and their argument types at compile time (or module load time, for that matter) and automatically perform the dispatch via optimized code at runtime. It may be used in the following manner:

from functools import singledispatchmethod

# ...

    @singledispatchmethod
    def visit(self, node, context):
        # code from old "no_visit_method"
        raise Exception(f'No visit_{type(node).__name__} method defined')

    @visit.register
    def visit_StringNode_name_does_not_matter(self, node: StringNode, context):
        return RTResult().success(
            dt.String(node.tok.value).set_context(context).set_pos(node.pos_start, node.pos_end)
        )

    # formerly visit_ContinueNode
    @visit.register
    def _(self, node: ContinueNode, context):
        return RTResult().success_continue()

The specific methods will not appear independently in the class (the annotation removes them from the class dir() immediately), so they can be given any placeholder name, like _. Note the use of the StringNode type annotation in its formal arguments. Although type annotations are optional and not checked at runtime, they can reflectively be queried, which is done by the singledispatchmethod logic to determine which type of argument is handled by this "overload". The default method that was annotated in the first place is only called when no other overload with appropriate argument type was found, so it can do the error handling, which currently resides in no_visit_method. The @visit.register command can be repeated any number of times for all methods.

More information on how singledispatchmethod can be used and how it works internally can be found in the Python documentation, which I have linked above.

I don't have the skill working with Dip to think that I could implement this properly, however, @raghavnautiyal , this would be an easy change to make which does not alter the language's properties, but makes it faster, safer and more extensible. A small bonus is the fact that all the visit overloads and the error handler are not vebosely accessible outside of Interpreter.

raghavnautiyal commented 4 years ago

Thanks a lot @kleinesfilmroellchen! I'll definitely look into it!