-
Notifications
You must be signed in to change notification settings - Fork 126
CompilerPluginHooks
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.
Here are some things that don't have good hooks.
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
.
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.
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.
This has been discussed as a possibility in the irc. Not sure if the "destructure" is sufficient, or if more hooks are needed.