How to identify an AST node as a function call?

I’m trying to identify function calls. My current solution seems to be working, but I’m not sure what are the edge cases I should test for and I imagine there’s a better solution.

So far I determine a node is a function call if it is a triple, it’s third element is a list and it is not a special form.
So this {:foo, [], []} would be a function call, but this {:{}, [], []} wouldn’t and neither would this {:foo, [], nil}.

The code:

  # for functions calls or special forms with the same structure
  defmacro foo({call, _meta, args}) when is_list(args) do
    if Macro.special_form?(call, length(args)) do
      # do stuff if it ISN'T a function call
    else
      # do stuff if it IS a function call
    end
  end

  # for literals and variables
  defmacro foo(node) do
    # do stuff if it ISN'T a function call
  end

If you see any errors or know a better solution your help would be greatly appreciated!

Hey @CarlosHSF to my knowledge this is roughly correct, but it’s also worth noting that it can’t distinguish between a macro call and a function call as, at an ast level at least, they are identical. You’ll also have to decide how you want to handle things like apply/3.

2 Likes

That was fast! Thank you for the clarification, seems like apply will indeed be something to consider… this might be more complicated than I initially thought.

What is your end goal, what are you trying to accomplish? How do you expect your macro to be used?

I’m trying to create a recursive block to allow for explicit recursion without the need to define a function or use ugly “recursive” anonymous functions. It is not meant to define a recursive function, only use recursion, just like a for loop for iteration in an imperative language. This is for a package I’m writing, Recurse.

Here’s an example of what I’m trying to do:

# factorial function expressed with a named recurse block
recurse fact(3) do
  0 -> 1
  n -> n * fact(n - 1)
end

# factorial function expressed with an unnamade recurse block
recurse 3 do
  0 -> 1
  n -> n * recurse(n - 1)
end

Both evaluate to 6.

Achieving something that does this was quite simple and that’s why I choose to keep the question simple instead of giving the whole context. But there many nuances that complicate making it robust and actually correct, that I’ve only now started to realize and make me doubt that what I’m trying to achieve is actually a good idea…

Currently, with what is in the package, it would be done like this:

recurse on 3 do
  0 -> 1
  n -> n * recurse(n - 1)
end

on is a second keyword to allow for multiple arguments to be given to recurse, but I would like to remove it as it doesn’t really make sense to have two keywords doing one job. There’s also the option of passing a tuple to give multiple arguments without a second keyword, but I’m trying to achieve something a bit more elegant.

So I thought I would allow the user to choose a name for the recursive call instead of just having on there, so instead of removing the second keyword, I would give it a purpose. Or the user could not choose any name and just use recurse as the recursive call, knowing that then they could only pass one argument or multiple in a tuple.

My problem then, is distinguishing if what follows recurse is a function call or not. There’s also the problem of it possibly being a function call, but one that should be evaluated and not used to create a named recursive call, but I’m ignoring that fact for the moment and once this is working and I’ve experimented with it I would decide if it is a decent idea or not.