diff --git a/hy/compiler.py b/hy/compiler.py index 36dd12a..7608553 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -1278,26 +1278,77 @@ class HyASTCompiler(object): @checkargs(min=1, max=3) def compile_apply_expression(self, expr): expr.pop(0) # apply - call = self.compile(expr.pop(0)) - call = ast.Call(func=call.expr, - args=[], - keywords=[], - starargs=None, - kwargs=None, - lineno=expr.start_line, - col_offset=expr.start_column) - ret = call + + ret = Result() + + fun = expr.pop(0) + + # We actually defer the compilation of the function call to + # @builds(HyExpression), allowing us to work on method calls + call = HyExpression([fun]).replace(fun) + + if isinstance(fun, HySymbol) and fun.startswith("."): + # (apply .foo lst) needs to work as lst[0].foo(*lst[1:]) + if not expr: + raise HyTypeError( + expr, "apply of a method needs to have an argument" + ) + + # We need to grab the arguments, and split them. + + # Assign them to a variable if they're not one already + if type(expr[0]) == HyList: + if len(expr[0]) == 0: + raise HyTypeError( + expr, "apply of a method needs to have an argument" + ) + call.append(expr[0].pop(0)) + else: + if isinstance(expr[0], HySymbol): + tempvar = expr[0] + else: + tempvar = HySymbol(self.get_anon_var()).replace(expr[0]) + assignment = HyExpression( + [HySymbol("setv"), tempvar, expr[0]] + ).replace(expr[0]) + + # and add the assignment to our result + ret += self.compile(assignment) + + # The first argument is the object on which to call the method + # So we translate (apply .foo args) to (.foo (get args 0)) + call.append(HyExpression( + [HySymbol("get"), tempvar, HyInteger(0)] + ).replace(tempvar)) + + # We then pass the other arguments to the function + expr[0] = HyExpression( + [HySymbol("slice"), tempvar, HyInteger(1)] + ).replace(expr[0]) + + ret += self.compile(call) + + if not isinstance(ret.expr, ast.Call): + raise HyTypeError( + fun, "compiling the application of `{}' didn't return a " + "function call, but `{}'".format(fun, type(ret.expr).__name__) + ) + if ret.expr.starargs or ret.expr.kwargs: + raise HyTypeError( + expr, "compiling the function application returned a function " + "call with arguments" + ) if expr: stargs = expr.pop(0) if stargs is not None: stargs = self.compile(stargs) - call.starargs = stargs.force_expr + ret.expr.starargs = stargs.force_expr ret = stargs + ret if expr: kwargs = self.compile(expr.pop(0)) - call.kwargs = kwargs.force_expr + ret.expr.kwargs = kwargs.force_expr ret = kwargs + ret return ret diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index 6a0e8dd..4efe768 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -164,6 +164,7 @@ {"y" 5 "z" 3}) {"x" 1 "y" 5 "z" 3 "one" "three"}))) + (defn test-apply [] "NATIVE: test working with args and functions" (defn sumit [a b c] (+ a b c)) @@ -174,6 +175,18 @@ (defn noargs [] [1 2 3]) (assert (= (apply noargs) [1 2 3]))) + +(defn test-apply-with-methods [] + "NATIVE: test apply to call a method" + (setv str "foo {bar}") + (assert (= (apply .format [str] {"bar" "baz"}) + (apply .format ["foo {0}" "baz"]) + "foo baz")) + (setv lst ["a {0} {1} {foo} {bar}" "b" "c"]) + (assert (= (apply .format lst {"foo" "d" "bar" "e"}) + "a b c d e"))) + + (defn test-dotted [] "NATIVE: test dotted invocation" (assert (= (.join " " ["one" "two"]) "one two")))