def METHOD_NAME(self, node, env=None):
    if env is None:
        env = {}
    if isinstance(node, lkl.Number):
        return float(node.text)
    elif isinstance(node, lkl.Identifier):
        try:
            return env[node.text]
        except KeyError:
            raise ExecutionError(
                node.sloc_range,
                'Unknown identifier: {}'.format(node.text)
            )
    elif isinstance(node, lkl.BinaryExpr):
        lhs = self.METHOD_NAME(node.f_lhs, env)
        rhs = self.METHOD_NAME(node.f_rhs, env)
        return self.BINOPS[type(node.f_op)](lhs, rhs)
    elif isinstance(node, lkl.CallExpr):
        name = node.f_callee.text
        try:
            func = self.functions[name]
        except KeyError:
            raise ExecutionError(
                node.f_callee.sloc_range,
                'No such function: "{}"'.format(name)
            )
        formals = func.f_proto.f_args
        actuals = node.f_args
        # Check that the call is consistent with the function prototype
        if len(formals) != len(actuals):
            raise ExecutionError(
                node.sloc_range,
                '"{}" expects {} arguments, but got {} ones'.format(
                    node.f_callee.f_name.text,
                    len(formals), len(actuals)
                )
            )
        # Evaluate arguments and then evaluate the call itself
        new_env = {f.text: self.METHOD_NAME(a, env)
                   for f, a in zip(formals, actuals)}
        result = self.METHOD_NAME(func.f_body, new_env)
        return result
    else:
        # There should be no other kind of node in expressions
        assert False