GithubHelp home page GithubHelp logo

Comments (63)

tsbohc avatar tsbohc commented on June 1, 2024 1

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.

tsbohc avatar tsbohc commented on June 1, 2024 1

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.

tsbohc avatar tsbohc commented on June 1, 2024 1

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.

UdayvirThind avatar UdayvirThind commented on June 1, 2024

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.

UdayvirThind avatar UdayvirThind commented on June 1, 2024

My reasoning is having inline function gives more native feeling than having to use many different macros.

from zest.nvim.

tsbohc avatar tsbohc commented on June 1, 2024

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.

UdayvirThind avatar UdayvirThind commented on June 1, 2024

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.

UdayvirThind avatar UdayvirThind commented on June 1, 2024

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.

UdayvirThind avatar UdayvirThind commented on June 1, 2024

Btw, merge #2 pull request to add syntax highlighting in github. It becomes easier to read code in browser that way.

from zest.nvim.

UdayvirThind avatar UdayvirThind commented on June 1, 2024

Looking forward to it.

from zest.nvim.

UdayvirThind avatar UdayvirThind commented on June 1, 2024

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.

UdayvirThind avatar UdayvirThind commented on June 1, 2024

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) :: scoped
  • unquote :: scoped
  • quote :: scoped
  • symbol
  • 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.

UdayvirThind avatar UdayvirThind commented on June 1, 2024

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.

UdayvirThind avatar UdayvirThind commented on June 1, 2024

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.

UdayvirThind avatar UdayvirThind commented on June 1, 2024

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.

UdayvirThind avatar UdayvirThind commented on June 1, 2024

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.

tsbohc avatar tsbohc commented on June 1, 2024

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.

tsbohc avatar tsbohc commented on June 1, 2024

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.

UdayvirThind avatar UdayvirThind commented on June 1, 2024

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.

UdayvirThind avatar UdayvirThind commented on June 1, 2024

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:

  1. check if it is (list?).
  2. check if (. list 1) is sym?
  3. if (length "list") is 1 then return first item. ie: directly bind it.
  4. else wrap it around (fn [] list).

This removes the need to use unquote and make them easier to read.

from zest.nvim.

UdayvirThind avatar UdayvirThind commented on June 1, 2024

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.

UdayvirThind avatar UdayvirThind commented on June 1, 2024

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.

tsbohc avatar tsbohc commented on June 1, 2024

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:

(fn def-keymap [mod opt lhs rhs]
(let [rhs (if opt.expr
(bind "%s()" rhs)
(bind ":call %s()<cr>" rhs))]
(each [m (mod:gmatch ".") ]
(vim.api.nvim_set_keymap m lhs rhs opt))))

And pointed some macros to them:

(fn def-keymap [args lhs rhs]
(let [(mod opt) (_keymap-options args)]
(if rhs
(call :def-keymap mod opt lhs (seq-to-fn rhs))
(call :def-keymap-pairs mod opt lhs))))

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.

UdayvirThind avatar UdayvirThind commented on June 1, 2024

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.

UdayvirThind avatar UdayvirThind commented on June 1, 2024

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.

tsbohc avatar tsbohc commented on June 1, 2024

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.

tsbohc avatar tsbohc commented on June 1, 2024

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.

UdayvirThind avatar UdayvirThind commented on June 1, 2024

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.

UdayvirThind avatar UdayvirThind commented on June 1, 2024

that's just a personal preference I like have more clean layout but everyone has different tastes I guess.

from zest.nvim.

UdayvirThind avatar UdayvirThind commented on June 1, 2024

from zest.nvim.

tsbohc avatar tsbohc commented on June 1, 2024

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.

UdayvirThind avatar UdayvirThind commented on June 1, 2024

But that can just be done with:(do (print :foo) (print :bar))

from zest.nvim.

UdayvirThind avatar UdayvirThind commented on June 1, 2024

Sorry I just keep clicking on close with comment.

from zest.nvim.

UdayvirThind avatar UdayvirThind commented on June 1, 2024

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.

tsbohc avatar tsbohc commented on June 1, 2024

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.

UdayvirThind avatar UdayvirThind commented on June 1, 2024

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:

(fn seq-to-fn [f]
(if (sequence? f)
'(fn [] ,(unpack f))
f))

from zest.nvim.

UdayvirThind avatar UdayvirThind commented on June 1, 2024

Something like this:

(fn parse-fn [f]
  (if 
    (sequence? f)
      '(fn [] ,(unpack f))
    (function-call? f)
       (zfc f)
    f))

from zest.nvim.

tsbohc avatar tsbohc commented on June 1, 2024

Do you mean checking if a function was passed?

from zest.nvim.

UdayvirThind avatar UdayvirThind commented on June 1, 2024

I meant check if a function-call ie: (do something) was passed.

from zest.nvim.

tsbohc avatar tsbohc commented on June 1, 2024

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.

tsbohc avatar tsbohc commented on June 1, 2024

Or even:

(fn my-cmd-gen [param]
  (.. cmd " " param))

(ki- [x] :<c-m> (my-cmd-gen :foo))

from zest.nvim.

UdayvirThind avatar UdayvirThind commented on June 1, 2024

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.

UdayvirThind avatar UdayvirThind commented on June 1, 2024

So, yeah *SEQUENCE macro can be kept for now.

from zest.nvim.

UdayvirThind avatar UdayvirThind commented on June 1, 2024
(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.

UdayvirThind avatar UdayvirThind commented on June 1, 2024

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.

UdayvirThind avatar UdayvirThind commented on June 1, 2024

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.

tsbohc avatar tsbohc commented on June 1, 2024

What fennel code causes this currently?

from zest.nvim.

UdayvirThind avatar UdayvirThind commented on June 1, 2024

This only happens in developing test-macros so I doesn't cause much problem now but may cause when goes main.

from zest.nvim.

tsbohc avatar tsbohc commented on June 1, 2024

Could you give a specific example?

from zest.nvim.

UdayvirThind avatar UdayvirThind commented on June 1, 2024

It is 10:30 pm here so I will find the bug in morning. Sorry for the wait.

from zest.nvim.

tsbohc avatar tsbohc commented on June 1, 2024

It's all right, leave a message when you can!

from zest.nvim.

UdayvirThind avatar UdayvirThind commented on June 1, 2024

Thanks

from zest.nvim.

UdayvirThind avatar UdayvirThind commented on June 1, 2024

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:

(fn seq-to-fn [f]
(if (sequence? f)
'(fn [] ,(unpack f))
f))

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:

vlua (_vlua `(fn [] ,...) :keymap (M.smart-concat [fs modes]))

can be fixed by zfn but not needed as these will get deprecated soon.

from zest.nvim.

tsbohc avatar tsbohc commented on June 1, 2024

Got it, thanks a bunch. Yeah, I'll add a check to prevent unnecessary wrapping.

from zest.nvim.

UdayvirThind avatar UdayvirThind commented on June 1, 2024

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.

UdayvirThind avatar UdayvirThind commented on June 1, 2024

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.

UdayvirThind avatar UdayvirThind commented on June 1, 2024

Deleted that fork, wrote a separate plugin for just that tangerine.nvim.
What are your thoughts about the plugin.

from zest.nvim.

tsbohc avatar tsbohc commented on June 1, 2024

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.

UdayvirThind avatar UdayvirThind commented on June 1, 2024

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.

tsbohc avatar tsbohc commented on June 1, 2024

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.

tsbohc avatar tsbohc commented on June 1, 2024

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.

UdayvirThind avatar UdayvirThind commented on June 1, 2024

Why not use GitHub Discussions as this issue is more a conversation rather than a code issue.

from zest.nvim.

UdayvirThind avatar UdayvirThind commented on June 1, 2024

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 photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.