Exausitve list of Expr's heads

As the title says, is there a place where I can find an exaustive list of the symbols that are
admissible (that is they lead to valid code upon evaluation) as Expr.head values? I was looking at the following https://docs.julialang.org/en/v1/devdocs/ast/#Surface-syntax-AST-1, which list some of theme is sparse order: how can I be sure that there are not any others?
My aim is to automatically decide whether or not the head of an Expr should be Symbol call, as in

Expr(:call, :foo)
Expr(:call, :(==), :a, :b)

as opposed to

Expr(:+=, :x, 2)

By the way, should I trust the fact the exposed julia AST won’t change in any radical way? I ask this because by taking advantage of this fact I wrote a very tiny (21 lines, comprising boilerplate to handle common lisp strings) common lisp function that parse a common lisp list representing julia AST into a julia Expr: calling julia’s eval twice let me execute julia code written in s-expression syntax, inside common lisp. Of course this cannot be more that a toy if I cannot rely upon the julia AST not to change drastically.

In this context, my initial question stem from the next step I wish to take, which is to get closer to lisp syntax by getting rid of the need of writing call everywere, as in

(call foo (if (call == 1 2)
              (call bar 1 2)
              (call zot 3 4)))

to get to a nicer

(foo (if (== 1 2)
         (bar 1 2)
         (zot 3 4)))

Thank you in advance.

3 Likes

We decided to format the AST devdocs as a mapping from surface syntax to representation, so as a result there is no table indexed by expression head listing all of them. Another good resource is the deparse function in src/ast.scm. That should have all expression heads in a fairly concise (albeit executable) format. It would be great to check whether it is truly exhaustive, and then add anything missing to the devdocs.

Yes, I think it’s fair to say the AST won’t change radically. Only tiny changes until 2.0. And even then I’m not sure whether it will change significantly.

This isn’t so easy to do. For example, (ref a b) is ambiguous — does it correspond to ref(a, b) or a[b]? This is a bit of a mismatch between the languages. In (various) lisps, macros and functions effectively share a namespace, so if you have a function called ref you accept that using it means you can’t easily access the ref special form for indexing. In julia these are distinguished by syntax, so you can have both in the same code.

3 Likes

Thanks for the quick reply.

After a quick look, this seems exactly what I was looking for.

When I’ve got some time to look into it, I’ll let you know if I find something not listed in the AST devdocs.

Good point there. I think I can get away with letting call be optional, and use it to dispatch in case of name clashes between “user defined” funtions and special forms, something like this

(call foo)           means      foo()
(foo)           also means      foo()
(ref a b)            means      a[b]
(call ref a b)       means      ref(a, b)  

In any case, by looking at the special forms found listed in the AST devdocs, it seams to me that the names they carry, being very concise, are perfectly good for built-in stuff, but not so good for naming my own stuff. Typically if I find myself naming a function something like ref or curly it is only because I’m being too lazy to find a proper more meaningful name.

2 Likes