Skip to content

CompilerPluginHooks

XeroOl edited this page Jun 27, 2023 · 2 revisions

The fennel compiler has a plugin system, which lets extra code hook into the compiler to affect the way things are compiled. To do this, fennel exposes an experimental API of "hooks", which the compiler calls at various points in the compilation process.

Read more about the existing plugin hooks here. The rest of this document is a place to brainstorm ideas for new hooks.

Ideas for new hooks:

Here are some things that don't have good hooks.

Every time a scope is made.

In the program fennel-ls, I need to offer completions of variables that are in scope. Imagine the user has typed the code (let [identifier 10] iden into their editor. Ideally, fennel-ls would offer a completion for identifier, but in order to do that, it needs to know that the scope applies to the let expression. Importantly, with the current hooks, a plugin only knows that identifier is inside a scope, but it doesn't know "where" the scope is.

I'd like to propose a new hook that gets called every time a scope is introduced. The arguments would be [ast scope], where ast is the part of the ast where the scope is introduced, and the scope would be the fresh new scope that is introduced.

In the let example above, the new hook would be called before any hooks involving identifier.

Every time something is declared/bound

This one is a bit complicated. As far as I have found, there are 4 ways to introduce variables:

;; 1: declaring variable
(local x 10)
(local [x y] [10 10])
(let [[x y] [10 10]] ...)

;; 2: for/each iterator
(for [x 1 1 1] ...)
(each [[x y] (pairs ...)] ...)

;; 3 & 4: `fn` names, and `fn` arguments
(fn this-goes-in-the-outer-scope [this-goes-in-the-inner-scope] ...)
(fn [[x y]] ...)

I propose a hook or set of hooks that cleanly covers these ways (and possibly others if I've missed some) to introduce variables. We currently have destructure, which covers some, but not all of these cases, (and also covers some cases like set, which I would like to treat separately), but I would like to see hooks that specifically align with things coming into scope.

Perhaps the signature of this hook could look something like [ast-l ast-r scope binding-type] where binding-type is one of :local, :let, :for, :each, :fn-arg, or fn-name.

It would also be nice to see something similar for when macros come into scope.

Every time a variable is referenced

We currently have the hook symbol-to-expression, which feels very spiritually similar to when every variable is referenced, but behaves slightly differently.

(local x 10)

;; written (but not read)
(set x [])

;; read
(print x)
(x)

;; read (but it's __index)
(print x.foo)
(x:foo)

;; mutate (__newindex)
(set x.foo 10)
(tset x :foo 10)
(fn x.foo [])

Right now, symbol-to-expression is called on all of these lines (with the exception of x:foo sort of), but we only get one boolean variable (?reference?) to distinguish between these 4 cases, and it doesn't even distinguish between these very clearly. Also, symbol-to-expression gets called in other cases which don't correspond with my intuition about "referencing" a variabel.

I would like a hook to cover every time a lexical variable is referenced. Ideally this hook would give clear indication on whether something is being assigned/overwritten, vs read, vs mutated.

__fennelrest

This has been discussed as a possibility in the irc. Not sure if the "destructure" is sufficient, or if more hooks are needed.

Clone this wiki locally