Comments (63)
Hey,
I've thought adding a check like this, but the caveat you mentioned causes a behavior that's very unintuitive: inline functions work, functions passed as variables do not.
Right now, although it isn't perfect, I feel it's better to keep the distinction between def-keymap
and def-keymap-fn
. The first takes a string (or something that evaluates into a string, like a variable or a function call), the latter takes a function body. At the same time, passing a function as a variable can be accomplished with vlua
.
from zest.nvim.
Hello,
This has been a good discussion and it has given me quite a few ideas. I will be deprecating the -fn
macros like you suggested, but I don't have an ETA yet. Sooner than later, though.
Thanks!
from zest.nvim.
To be clear, this is the behavior I want to achieve:
(ki- "foo") ; => bind "foo" as a str
(ki- foo) ; => bind foo which can be a str of a fn
(ki- (foo)) ; => bind the resulf of evaluating (foo), which can be a str or a fn
(ki- [(foo) (bar)]) ; => bind (fn [] (foo) (bar))
(ki- (fn [] (foo) (bar))) ; => bind (fn [] (foo) (bar))
(ki- #((foo) (bar))) ; => bind (fn [] (foo) (bar))
The brackets are really just another notation for creating a function.
from zest.nvim.
I have another solution:
(fn function [])
(mac- ,function) ; pass it as (unquote (function))
;; Implementation
(fn unquote? [sb]
"checks if sym 'sb' is unquote"
(let [ref (?. sb 1 1)]
(= ref :unquote)))
; inside macro
(if (unquote? variable)
(call-fn-macro (. variable 1 2))
(call-macro variable))
from zest.nvim.
My reasoning is having inline function gives more native feeling than having to use many different macros.
from zest.nvim.
Okay, so I was playing around with the unquote
hack, and it's very clever!
(fn M.unquote? [sy]
(let [ref (?. sy 1 1)]
(= ref :unquote)))
(fn M.test [f]
(if (M.unquote? f)
`(print "function" ,(. f 2))
`(print "string" ,f)))
; functions
(test ,(fn [] (print)))
(test ,my_function)
; strings
(test my_string)
(test (.. "_" my_string))
This will produce:
local function _0_() end
print("function", _0_)
print("function", my_function)
print("string", my_string)
print("string", ("_" .. my_string))
This gave me an idea, what if by default zest converted symbols to strings, unless they're unquoted?
(def-keymap <c-m> [n] j) ; => vim.api.nvim_set_keymap("n", "<c-m>", "j", {noremap = true})
(def-keymap ,from [n] ,to) ; => vim.api.nvim_set_keymap("n", from, to, {noremap = true})
This could be used for autocmd events and selectors, augroup and other names, etc. This would reduce the amount of :
and ""
across the board. I think it makes sense to use unquote
to differentiate between literals and variables.
Now, about functions vs strings.
Experiment 1: define a new wrapper macro
(fn M._ [f]
f)
(fn M.test [data]
(print data)
(if (= (. data 1 1) :_)
`(print "function" ,(. data 2))
`(print "string" ,data)))
(test (_ x))) ; => print("function", x)
(test y) ; => print("string", y)
Caveat: the wrapper macro, _
in this case, cannot be aliased. That breaks everything. Also, the extra set of parens looks bad. Dismissing this idea.
Experiment 2: use tables to wrap functions
So, here's a thought: there's no use case I can think of when one would want to bind a table to a keymap or autocmd or whatever else. Which means:
(fn M.test [data]
(let [f (require :zest.fennel)]
(if (f.sequence? data)
`(print "function" ,(unpack data))
`(print "string" ,data))))
(test [x]) ; => print("function", x)
(test y) ; => print("string", y)
This essentially means that we can do this:
(def-keymap :<c-m> [nvo]
my_string)
(def-keymap :<c-m> [nvo]
[my_function])
(def-keymap :<c-m> [nvo]
[(fn [] (print "fn"))])
What do you think?
from zest.nvim.
Using table to wrap string.
Looks good to me but you can also use:
(local str :str)
(mac- 'str) ;; pass it as (quote str)
using unquote
for function and quote
for string keeps syntax more cleaner.
from zest.nvim.
Here is an example of it on action:
(local pattern "*.fnl")
(fn print-hello []
(print :Hello!))
(au- [:BufRead] 'pattern ,print-hello)
; and some normal code down here
(map- [n] <leader>ba <cmd>bn<CR>)
from zest.nvim.
Btw, merge #2 pull request to add syntax highlighting in github. It becomes easier to read code in browser that way.
from zest.nvim.
Looking forward to it.
from zest.nvim.
Hey,
Just wrote a macro for Emacs-like (use-package)
;; Usage
(use-package :package/source
:run ":commands"
:branch "master"
:config #(require :config))
(use-package :kevinhwang91/rnvimr
:config #(require :plugins.ranger))
;; Implementation
(macro use-package [name ...]
(let [rest [...]
conf {1 name}]
(each [idx val (ipairs rest)]
(when (= 1 (% idx 2)) ;; odd? [idx]
; set val -> (next item in array)
(tset conf val (. rest (+ idx 1)))))
`(use ,conf)))
another example using quote wrappers:
(use-package package/source
run :commands
branch master
config #(require :config))
from zest.nvim.
Helper to find fennel type easily :
;; utils
(fn function? [f]
"checks if a 'f' is function."
(let [ref (?. f 1 1)]
(or (= ref :hashfn)
(= ref :fn))))
(fn function-call? [f]
(let [ref (?. f 1)]
(and (list? f)
(sym? ref)
(in-scope? ref))))
(fn is-sym? [name x]
(let [ref (?. x 1)
ref-name (?. ref 1)
sb (?. x 2)]
(and (= ref-name name)
(sym? ref)
(in-scope? sb))))
; main helper
(fn fnl-type [x]
"returns fennel type of 'x'"
(if
(not x) "nil"
(= (type x) :string) "string"
(is-sym? :unquote x) "unquote"
(is-sym? :quote x) "quote"
(function-call? x) "function-call"
(function? x) "function"
(sym? x) "symbol"
"invalid"))
(fnl-type) returns following types and also checks if a variable is in scope.
string
function
-> # or (fn [])function-call
-> (fnc arg) :: scopedunquote
:: scopedquote
:: scopedsymbol
invalid
-> 'x' not in scope
usage:
(match (fnl-type variable)
"string" (mac variable)
"quote" (mac (. variable 2))
"symbol" (mac (tostring variable))
; handel functions
"function" (fn-mac variable)
"unquote" (fn-mac (. variable 2))
"function-call" (fn-mac '(fn [] ,variable))
; error handling
"invalid" (print (.. "[macro] variable: " variable " is not in scope."))
_ (print (.. "[macro] invalid input: " variable)))
from zest.nvim.
an easy to use wrapper over "fnl-type"
(fn switch-macro [str-macro fn-macro fx ...]
"switchs macros based on typeof 'fx'."
(match (fnl-type fx)
"string" (str-macro fx ...)
"quote" (str-macro (. fx 2) ...)
"symbol" (str-macro (tostring fx) ...)
; handel functions
"function" (fn-macro fx ...)
"unquote" (fn-macro (. fx 2) ...)
"function-call" (fn-macro '(fn [] ,fx) ...)
; error handling
"nil" (print "[macro] can't pass nil.")
"invalid" (print "[macro] variable: " fx " is not in scope.")
_ (print "[macro] invalid input: " fx)))
usage:
; inside macro
(switch-macro def-cmd def-fn-cmd fx ; main
...opts)
;; cavet: can't accept 'fx' as arg at last, like-
(fn def-cmd [opts fx] ; will not work
...)
from zest.nvim.
here is a working macro:
; (fn def-map [ts modes fs opts]
; "define a normal vim mapping."
; (let [out []]
; (each [m (string.gmatch modes ".")]
; (table.insert out `(vim.api.nvim_set_keymap ,m ,fs ,ts ,opts)))
; (if (> (length out) 1)
; `(do ,(unpack out))
; `,(unpack out))))
; (fn def-fn-map [fx modes LHS opts]
; "define vim map with function 'f' binded to [v:lua]."
; (let [maps []]
; (each [m (string.gmatch modes ".")]
; (table.insert maps `(vim.api.nvim_set_keymap ,m ,LHS RHS# ,opts)))
; `(do
; (local RHS# (string.format
; ,(if opts.expr "%s()" ":call %s()<cr>")
; ,(vlua fx)))
; ,(unpack maps))))
(fn M.map [args lhs rhs]
(let [(modes opts) (keymap-options args)
fs (tostring fs)]
; only this part has to be changed
(switch-macro def-map def-fn-map rhs modes lhs opts)))
Usage:
; directly calling function
(map [n] :h (print :hello))
; using a local variable
(local lhs (vim.api.use__something))
(map [n :expr] 'lhs
(if (> vim.v.count 0) "k" "gk"))
; using local function
(fn tab-complete []
(tc- "<C-n"))
(map [is] <Tab> ,tab-complete)
It will also throw error if local var/function is not in scope or doesn't exist.
from zest.nvim.
Simple uuid v4 generator
(fn uuid []
(math.randomseed (.. (os.time) (math.random 100000)))
(let [template :uxxxxxxx_xxxx_4xxx_xxxx_xxxxxxxxxxxx]
(string.gsub template "x"
#(let [v (math.random 0 15)]
(string.format "%x" v)))))
from zest.nvim.
You should use uuid to generate v:lua id at compile time rather than doing it at runtime.
It will make output more cleaner to read.
right now, my outputted lua is a mess to read.
Here is comparison:
at runtime:
do
local CMD_0_
local function _2_(...)
local ID_NUM_0_ = (_G._fnl["#"] or 1)
local ID_0_ = ("id_" .. ID_NUM_0_)
_G._fnl[ID_0_] = cmd_eval
_G._fnl["#"] = (1 + ID_NUM_0_)
return ("v:lua._fnl." .. ID_0_)
end
CMD_0_ = string.format(":call %s()", _2_(...))
vim.cmd(("autocmd FileType fennel " .. CMD_0_))
end
at compile-time:
do
_G.fnl['u8c70001_e9d0_4033_9dc0_25e058d6db9d'] = cmd_eval
vim.cmd("autocmd FileType fennel :call v:lua._fnl.u8c70001_e9d0_4033_9dc0_25e058d6db9d()")
end
If you are worried about collision the collision rate of v4 uuid id extremely low:
50% probability of at least one collision after 2.71 quintillion generations.
from zest.nvim.
Compile time ids make it impossible to use the macro in a loop or a function, as each subsequent call will replace the previously stored function. A more concise run time solution would be to use metatables in _G.zest
, but I haven't experimented with it much yet. Although, I prefer the output to be completely transparent (albeit more convoluted) instead of shorter.
from zest.nvim.
Hmm, I could leverage this through a call such as local zest_id = _G.zest.store(f)
. Then again, it's not immediately apparent what store()
does.
I might simply wrap the current implementation in fold comments:
do
local zest_id
-- {{{ zest.id
<code>
-- }}}
end
So that it's hidden by default.
from zest.nvim.
Just tested it inside a loop.
It won't cause error if same function is referenced.
(each [_ k (ipairs [:h :l])]
(map [n] k (print :works)))
; here <rhs> shadows <rhs> so no problem.
If for some wierd reason 'rhs' is looped as a function. It will cause bugs.
(fn h-func [])
(fn j-func [])
(each [k v (pairs [{:h h-func :j j-func}])]
(map [n] k (vlua-format ":call %s()<cr>" v)))
But then again I had to use (vlua)
to make it loop through function,
we can expose them as (generated-vlua)
and (generated-vlua-format)
,
and use <uuid>(vlua)
for rest as this edge case was really not practical.
from zest.nvim.
Here is my current setup for now when checking functions.
First case:
(fn tab-complete [])
(mac- (tab-complete)) ;; we can directly call it here
Here because the function accepts no args we can directly bind v:lua
.
second case
(lambda reusable [x y])
(mac- (reusable 2 4))
(mac- (if (< something 2)
(print :yes)
(print :no)))
Here it requires args to work so we have to wrap it around (fn [] ...).
so here it how it goes:
- check if it is (list?).
- check if (. list 1) is sym?
- if (length "list") is 1 then return first item. ie: directly bind it.
- else wrap it around (fn [] list).
This removes the need to use unquote
and make them easier to read.
from zest.nvim.
Here it is in practice:
(fn function? [f]
"checks if 'f' is function."
(let [ref (?. f 1 1)]
(or (= ref :hashfn)
(= ref :fn)
(= ref :partial))))
; can also be written as
; (vim.tbl_contains [:partial :fn :hashfn] ref)
; with the downside that it can be compiled outside of vim.
(fn function-call? [f]
(let [ref (?. f 1)]
(and (list? f)
(sym? ref))))
(fn zfc [fc]
"treat function-call for binding."
(let [single? (= (length fc) 1)]
(if single?
(. fc 1)
'(fn [] ,fc))))
(fn zfn [fx]
(if (function? fx) fx
(function-call? fx) (zfc fx)))
here is more consise version:
(fn zfc [fc]
"treat function-call for binding."
(let [single? (= (length fc) 1)]
(if single?
(. fc 1)
'(fn [] ,fc))))
(fn zfn [fx]
(let [ref (?. fx 1)
refn (?. ref 1)]
(if (or (= refn :hashfn)
(= refn :fn)
(= refn :partial)) fx
(and (list? fx)
(sym? ref)) (zfc fx))))
from zest.nvim.
Discovered a fun thing while experimenting:
Lispy arrays
(mac- (:lisp :in :fennel) ...)
; It can be simply converted to normal array by:
[(unpack @lisp)]
why whywhy
(Just (fun) (((to)) use (-> complete
((((lispy))))((((code))))
(((((((((((((without :brackets)))))))))))))
;; Some of current macros without [] goodness.
(opt-append completeopt ("menuone" "noselect"))
(def-keymap :H (:nv :slient) "0")
(map- (:is :silent) <TAB> (tab-complete))
But I won't recommend actually doing it.
from zest.nvim.
I've reached the point where I'm happy with the new zest.pure
macros, so I decided to experiment with run time.
I basically rewrote def-keymap
and def-autocmd
as run time functions:
Lines 59 to 64 in ec6c365
And pointed some macros to them:
zest.nvim/fnl/zest/test-macros.fnl
Lines 20 to 24 in ec6c365
The call
macro outputs something like this: require("zest")["def-keymap"](...)
After benchmarking the two compiled configs a few times, I can't really say there's a noticeable difference. I am running a good machine, but yeah.
It was fun solving so many problems while being limited to compile time (which was my main reason for doing so), but speaking pragmatically, I don't think it's worth it. The lua output is very neat, but apart of that... I don't know.
And so I might me converting everything to run time in the end, while using macros just for syntactic sugar. Let the Curb Your Enthusiasm theme play, I guess!
And yeah, that is pretty cool. It seems fennel stays very true to being a lisp.
from zest.nvim.
Yeah, my only concern with runtime vlua was my lua output become dirty and debugging became a pain.
Here it is:
show lua
local fennel = require("compiler")
local fnl_files = "~/cool/dotfiles/nvim/fnl/**.fnl,~/cool/dotfiles/nvim/init.fnl"
local cmd_compile
local function _0_()
local CMD_0_
local function _1_()
local ID_NUM_0_ = (_G._fnl["#"] or 1)
local ID_0_ = ("id_" .. ID_NUM_0_)
local function _2_()
return fennel.compileBuffer()
end
_G._fnl[ID_0_] = _2_
_G._fnl["#"] = (1 + ID_NUM_0_)
return ("v:lua._fnl." .. ID_0_)
end
CMD_0_ = string.format(":call %s()", _1_())
return vim.cmd(("command!" .. " -buffer " .. "FnlCompileBuffer" .. " " .. CMD_0_))
end
cmd_compile = _0_
local cmd_eval
local function _1_()
local CMD_0_
local function _2_()
local ID_NUM_0_ = (_G._fnl["#"] or 1)
local ID_0_ = ("id_" .. ID_NUM_0_)
local function _3_()
return fennel.evalBuffer()
end
_G._fnl[ID_0_] = _3_
_G._fnl["#"] = (1 + ID_NUM_0_)
return ("v:lua._fnl." .. ID_0_)
end
CMD_0_ = string.format(":call %s()", _2_())
return vim.cmd(("command!" .. " -buffer " .. "FnlEvalBuffer" .. " " .. CMD_0_))
end
cmd_eval = _1_
do
vim.cmd("augroup fennel-commands")
vim.cmd("autocmd!")
do
do
local CMD_0_
local function _2_(...)
local ID_NUM_0_ = (_G._fnl["#"] or 1)
local ID_0_ = ("id_" .. ID_NUM_0_)
_G._fnl[ID_0_] = cmd_compile
_G._fnl["#"] = (1 + ID_NUM_0_)
return ("v:lua._fnl." .. ID_0_)
end
CMD_0_ = string.format(":call %s()", _2_(...))
vim.cmd(("autocmd " .. "BufRead,BufNewFile" .. " " .. fnl_files .. " " .. CMD_0_))
end
local CMD_0_
local function _2_(...)
local ID_NUM_0_ = (_G._fnl["#"] or 1)
local ID_0_ = ("id_" .. ID_NUM_0_)
_G._fnl[ID_0_] = cmd_eval
_G._fnl["#"] = (1 + ID_NUM_0_)
return ("v:lua._fnl." .. ID_0_)
end
CMD_0_ = string.format(":call %s()", _2_(...))
vim.cmd(("autocmd " .. "FileType" .. " " .. "fennel" .. " " .. CMD_0_))
end
vim.cmd("augroup END")
end
do
local CMD_0_
local function _2_(...)
local ID_NUM_0_ = (_G._fnl["#"] or 1)
local ID_0_ = ("id_" .. ID_NUM_0_)
local function _3_()
return fennel.compileAll()
end
_G._fnl[ID_0_] = _3_
_G._fnl["#"] = (1 + ID_NUM_0_)
return ("v:lua._fnl." .. ID_0_)
end
CMD_0_ = string.format(":call %s()", _2_(...))
vim.cmd(("command!" .. " " .. "FnlCompileAll" .. " " .. CMD_0_))
end
do
local CMD_0_
local function _2_(...)
local ID_NUM_0_ = (_G._fnl["#"] or 1)
local ID_0_ = ("id_" .. ID_NUM_0_)
local function _3_()
return fennel.sync()
end
_G._fnl[ID_0_] = _3_
_G._fnl["#"] = (1 + ID_NUM_0_)
return ("v:lua._fnl." .. ID_0_)
end
CMD_0_ = string.format(":call %s()", _2_(...))
vim.cmd(("command!" .. " " .. "FnlSync" .. " " .. CMD_0_))
end
vim.cmd("augroup fennel-save")
vim.cmd("autocmd!")
do
local CMD_0_
local function _2_(...)
local ID_NUM_0_ = (_G._fnl["#"] or 1)
local ID_0_ = ("id_" .. ID_NUM_0_)
local function _3_()
return fennel.compileBuffer()
end
_G._fnl[ID_0_] = _3_
_G._fnl["#"] = (1 + ID_NUM_0_)
return ("v:lua._fnl." .. ID_0_)
end
CMD_0_ = string.format(":call %s()", _2_(...))
vim.cmd(("autocmd " .. "BufWritePost" .. " " .. fnl_files .. " " .. CMD_0_))
end
return vim.cmd("augroup END")
As far as performance is concerned it doesn't really matter as lua is really simple (compiler wise).
from zest.nvim.
Just wanted to ask why not use "LIST" instead of "SEQUENCE" for passing function.
with current method "LIST" is still passed inside "SEQUENCE".
ie:
(mac- [(some-function :x)])
it is passed as:
{
{
{
"some-function",
<metatable> = { "SYMBOL" }
},
<metatable> = { "LIST" }
},
<metatable> = { "SEQUENCE" }
}
while it can be directly passed without interference with other types:
{
{
"some-function",
<metatable> = { "SYMBOL" }
},
<metatable> = { "LIST" }
},
Here is language docs for list:
lists are compile-time concepts that don’t exist at runtime; they are implemented as tables which have a special metatable to distinguish them from regular tables
(list) - return a list, which is a special kind of table used for code
from zest.nvim.
It's just something I like visually, e.g:
(ki- [x] :*
[(let [p (vim.fn.getpos ".")]
(vim.cmd "norm! gvy")
(vim.cmd (.. "/" (vim.api.nvim_eval "@\"")))
(vim.fn.setpos "." p))])
The #()
notation messes up treesitter highlighting, and the (fn [])
one introduces an extra level of indentation.
from zest.nvim.
I think I'll move most of the processing to run time and store tables containing some data about the binding, like so:
_G.zest.keymap.nvo_23c43m54 = { mod = "nvo", lhs = "<c-m>", rhs = function <...>, opt = <...> }
And also add some error handling for keymaps and etc
from zest.nvim.
But, the same example looks much more natural without []'s
(ki- [x] :*
(let [p (vim.fn.getpos ".")]
(vim.cmd "norm! gvy")
(vim.cmd (.. "/" (vim.api.nvim_eval "@\"")))
(vim.fn.setpos "." p)))
from zest.nvim.
that's just a personal preference I like have more clean layout but everyone has different tastes I guess.
from zest.nvim.
from zest.nvim.
Normally I'm evaluating the rhs and binding the result, while the contents of a seq, e.g ([(print :foo) (print :bar)]
become
(fn [] (print :foo) (print :bar))
And are bound as a function.
from zest.nvim.
But that can just be done with:(do (print :foo) (print :bar))
from zest.nvim.
Sorry I just keep clicking on close with comment
.
from zest.nvim.
In majority of my use cases I write function outside of macro and then pass it.
I like to do separations of concern but with your [seq] example it seems fine use case for me too.
So, Ok.
from zest.nvim.
That introduces an indentation level and there's no way to tell how it should be bound:
This evaluates foo
, binding the result:
(ki- [x] :<c-m>
(foo))
This creates a function (fn [] (foo))
and binds it:
(ki- [x] :<c-m>
[(foo)])
That is, it's synonymous to
(ki- [x] :<c-m>
(fn []
(foo)))
Or #()
from zest.nvim.
Emacs users have been using function call inside of configuration for long time so it doesn't seem like much of problem to me, but we can still keep sequence macro and add few line for parsing LIST here if you like:
zest.nvim/fnl/zest/test-macros.fnl
Lines 15 to 18 in ec6c365
from zest.nvim.
Something like this:
(fn parse-fn [f]
(if
(sequence? f)
'(fn [] ,(unpack f))
(function-call? f)
(zfc f)
f))
from zest.nvim.
Do you mean checking if a function was passed?
from zest.nvim.
I meant check if a function-call ie: (do something)
was passed.
from zest.nvim.
Why though?
This evaluates foo, binding the result:
(ki- [x] :<c-m> (foo))
This allows binding strings like so:
(ki- [x] :<c-m> (.. "!" my-shell-executable " %:p"))
I think that's a good expected behavior.
from zest.nvim.
Or even:
(fn my-cmd-gen [param]
(.. cmd " " param))
(ki- [x] :<c-m> (my-cmd-gen :foo))
from zest.nvim.
Or list macro can just be kept I just don't like the extra binding it does to already made functions.
(fn example [])
(mac- [example])
; It does not tell that the function is being called.
(mac- [(example)])
; this wrappes it inside another function.
(mac- (example))
; this is more explicit that function is being called and also does not double wrap.
from zest.nvim.
So, yeah *SEQUENCE macro can be kept for now.
from zest.nvim.
(fn my-cmd-gen [param] (.. cmd " " param)) (ki- [x] :<c-m> (my-cmd-gen :foo))
Now that I think about it,
This is really useful like you can use:
(fn do-completion [type]
; checks
(termcode type))
(map [is] :<TAB> [(do-completion "<TAB")])
(map [is] :<S-TAB> [(do-completion "<S-TAB")])
from zest.nvim.
Yeah this is what I also realized now after this conversation.
(xxx) can compile down to
some_api_fn(args, xxx())
And [(xxx)] to:
some_api_fn(args, ":call v:lua.id_xxxx")
from zest.nvim.
The only thing that I don't like is wrapping I mentioned above:
Like this is what output looks like:
function example()
print "works"
end
do
function _0_()
return example(); -- this part annoys me
end
bind = _0_ -- here example should have been present.
...rest
end
-- it should directly bind the pre defined function. Like:
bind = example
it can easily be fixed as mentioned above:
(fn seq-to-fn [f] (let [ref (?. f 1) single? (= (length ref) 1)] (if (sequence? f) (if single? f '(fn [] ,(unpack f))) f)))
from zest.nvim.
What fennel code causes this currently?
from zest.nvim.
This only happens in developing test-macros so I doesn't cause much problem now but may cause when goes main.
from zest.nvim.
Could you give a specific example?
from zest.nvim.
It is 10:30 pm here so I will find the bug in morning. Sorry for the wait.
from zest.nvim.
It's all right, leave a message when you can!
from zest.nvim.
Thanks
from zest.nvim.
Here is it:
test-macros.fnl
Input:
(fn example [] (yoo))
(def-keymap [n] :n [(example)])
Output:
local function example()
return print("yoo")
end
local function _0_() -- wrap
return example()
end
do end (require("zest"))["def-keymap"]("n", {expr = false, noremap = true}, "n", _0_)
Problem:
zest.nvim/fnl/zest/test-macros.fnl
Lines 15 to 18 in ec6c365
Fix:
(fn seq-to-fn [f]
(let [ref (?. f 1)
single? (= (length ref) 1)]
(if (sequence? f)
(if single?
(. ref 1)
'(fn [] ,(unpack f)))
f)))
Fixed-Output
local function example()
return print("yoo")
end
do end (require("zest"))["def-keymap"]("n", {expr = false, noremap = true}, "n", example) -- directly binds example
macros.fnl
all of the -fn
macros wrap any input they get into (fn [] ...)
that cause anything that you pass to be wrapped.
example:
inline
(def-keymap-fn :a [n] (fn [] (print :yoo)))
; rhs -> (fn [] (fn [] (print :yoo)))
same case for hashfn
.
external
(fn example [] (yoo))
(def-keymap-fn :a [n] (example))
; out -> `same as test-macros`
; (fn example [])
; (fn [] (example))
Bug:
this line in let statement of every macro:
Line 114 in ec6c365
can be fixed by zfn
but not needed as these will get deprecated soon.
from zest.nvim.
Got it, thanks a bunch. Yeah, I'll add a check to prevent unnecessary wrapping.
from zest.nvim.
I just wrote a new feature-full compiler for zest.
It still has some rough edges but still way better than the current implementation.
Available in this fork.
features
- compile and sources init.fnl in root directory.
- package.path is patched so you can now put .lua files in /fnl dir and require it.
- diffs all /fnl files and compile the stale one, does this entire process in 0.2 sec.
- it achieves this by marking all compiled files.
- marker looks like this
-- :fennel:<source-ftime>
- it matches source ftime with target.
setup
local compiler = require"zest"
zest.setup({
global_macros={
['let-g']="g-",
['def-keymap']="map",
}, -- can pass "all" string to require all zest macros globally.
compile={
onsave={
type='diff', -- can pass 'current' to only compile current.
verbose=true,
reload_macros=true, -- reloads macro every save so you don't have to quit vim.
},
onload=false -- under development
},
init_source="~/.config/nvim/init.fnl",
init_module="init", -- module that is required.
source="~/.config/nvim/fnl/",
target="~/.config/nvim/lua/"
})
zest.compiler
provides the api table:
{
; provides way to get the config.
:env _getter_
; patched package.path
:path "abc/?.lua"
:eval {
:string eval.eval-string
:file eval.eval-file
:buffer eval.eval-buffer
:range eval.eval-range
}
:compile {
:string comp.compile-string
:file comp.compile-file
:buffer comp.compile-buffer
:all comp.compile-all
:diff comp.compile-diff
}
}
Here are some highlights of api:
eval
eval.range
-> select some lines in V-Line mode and have them evaluated.eval.buffer
-> really useful used it like 100 - 200 times while making this, does what it says evaluates current buffer.
compile
compile.buffer
-> compiles current buffer.compile.diff
-> default function that runs on save.
Example vim config:
command! FnlEnv :lua compiler.api.env()
command! FnlCompileBuffer :lua compiler.api.compile.buffer()
command! FnlCompileDiff :lua compiler.api.compile.diff()
command! FnlCompileAll :lua compiler.api.compile.all()
command! -nargs=* Fnl :lua compiler.api.eval.string(<q-args>)
command! -range=0 FnlEvalRange :lua compiler.api.eval.string(<line1>, <line2>, <count>)
command! -complete=file -nargs=1 FnlFile :lua compiler.api.eval.file(<q-args>)
command! FnlEvalBuffer :lua compiler.api.eval.buffer()
from zest.nvim.
Just added ability to put compiled lua files in local dir so you don't have to see them.
option can be set by:
-- inside setup function
zest.setup {
target="~/.local/share/nvim/zest",
}
it will also source init.fnl automatically.
Also added command FnlGotoOutput
to open file containing compiled lua for current buffer.
from zest.nvim.
Deleted that fork, wrote a separate plugin for just that tangerine.nvim.
What are your thoughts about the plugin.
from zest.nvim.
I'll have to take a more thorough look at it later, but I can tell it's very cool. I like the eval
stuff!
To be honest, I did not intend the included compiler to be featureful, as most people are already using aniseed or something else. In fact, I'll most likely be removing it entirely. I intend the next iteration to be runtime based (+ macros, of course) and compatible with lua. I have some of it done already. I will be keeping the pure
macros around. I should really start using branches.
Myself, I now run a bash script (see https://github.com/tsbohc/.garden/blob/master/bin/bayleaf). It recompiles the whole config into a separate directory, launches as headless instance of neovim in the background, and checks the output. That way it's impossible to introduce a breaking change. It was a experiment I tried on a whim, but it ended up working out fairly well.
I was going to suggest you turning it into a separate plugin, so I'm glad you've arrived at the same conclusion. I'll include the link to tangerine in the readme. A very cute name, by the way!
Also, I want you to know, I really appreciate your continued interest in zest! I didn't think anyone would be so engaged with my work!
from zest.nvim.
Thanks,
I have been thinking for while now rather than using macro for runtime just provide function that users can require directly, having macros that relies on runtime just beats purpose of having them. Something like this:
(local {: def-keymap} (require :zest.utils))
It will do the same thing without any side effects.
My entire purpose of using macros was that it did all the stuff on compile-time so It didn't had to re-calculated on every load.
Having function for runtime also keeps lua clean like:
(def-keymap [:n] :H some_var)
def_keymap({"n"}, "H", some_var)
But then again you can't just do [n]
, [:n]
has to be passed or lua will think it is an variable.
and here also string has to be passed:
(g- :some_global_var "string")
(set-opt :nonumber)
It is kinda going full circle.
from zest.nvim.
I see it something like this. Not final by any means.
Being lua compatible is neat:
zest.def_keymap('n', { noremap = true }, '<c-m>', function()
print('foo')
end)
Fennel makes it nicer:
(zest.def-keymap "n" {:noremap true} "<c-m>"
(fn [] (print "foo")))
Macros make it nicer yet:
(def-keymap [n :noremap] "<c-m>"
(fn [] (print "foo")))
The best part of exposing runtime stuff is that it ensures things are actually predictable. It's very difficult to support variables in macros, as there are just too many checks for compile time and the code grows ever uglier. If going pure macros was the crazy idea, doing it this way is a sensible (and easily maintainable) solution.
from zest.nvim.
Also, some things aren't going into the runtime. There's no point in wrapping vim.opt
at runtime as the api is simple and easy to use already. Macros make it (in my opinion) even better:
(set-option [g :append] shortmess :I) ; => vim.opt_global.shortmess:append("I")
from zest.nvim.
Why not use GitHub Discussions as this issue is more a conversation rather than a code issue.
from zest.nvim.
Just wrote template string macro for fennel.
(lambda f [str]
(let [xs []]
(each [v (str:gmatch "${(.-)}")]
(table.insert xs (sym v)))
'(string.format
,(str:gsub "${.-}" "%%s")
,(unpack xs))))
Usage:
(let [name "World!!!"]
(print (f "Hello ${name}")))
output:
local name = "World!!!"
print(
string.format("Hello %s", name)
)
from zest.nvim.
Related Issues (4)
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from zest.nvim.