~technomancy/fennel

77f8a57beb68c215b7e6dc07e92f6ebdab984646 — Andrey Listopadov 2 months ago eb8124f
better sparse table printing in fennel.view

Tables that now have missing numeric keys that are smaller than the
max-sparse-gap option will be printed as sequential tables with nils
in place of missing keys
5 files changed, 110 insertions(+), 19 deletions(-)

M changelog.md
M fennelview.lua
M src/fennel/view.fnl
M test/core.fnl
M test/fennelview.fnl
M changelog.md => changelog.md +2 -0
@@ 13,6 13,8 @@ Backwards-incompatible changes are **marked in bold**.
* Add `,complete foo` repl command
* Add `fennel.syntax` function describing built-ins
* Add `accumulate` macro
* Keep gaps when printing sparse sequences; see `max-sparse-gap`
  option in `fennel.view`

## 0.9.2 / 2021-05-02


M fennelview.lua => fennelview.lua +51 -8
@@ 22,30 22,73 @@ local function sort_keys(_0_0, _1_0)
    end
  end
end
local function table_kv_pairs(t)
local function max_index_gap(kv)
  local gap = 0
  if (#kv > 0) then
    local _2_ = kv
    local _3_ = _2_[1]
    local i = _3_[1]
    local rest = {(table.unpack or unpack)(_2_, 2)}
    for _, _4_0 in ipairs(rest) do
      local _5_ = _4_0
      local k = _5_[1]
      if ((k - i) > gap) then
        gap = (k - i)
      end
      i = k
    end
  end
  return gap
end
local function fill_gaps(kv)
  do
    local missing_indexes = {}
    local i = 0
    for _, _2_0 in ipairs(kv) do
      local _3_ = _2_0
      local j = _3_[1]
      i = (i + 1)
      while (i < j) do
        table.insert(missing_indexes, i)
        i = (i + 1)
      end
    end
    for _, k in ipairs(missing_indexes) do
      table.insert(kv, k, {k})
    end
  end
  return kv
end
local function table_kv_pairs(t, options)
  local assoc_3f = false
  local i = 1
  local kv = {}
  local insert = table.insert
  for k, v in pairs(t) do
    if ((type(k) ~= "number") or (k ~= i)) then
    if (type(k) ~= "number") then
      assoc_3f = true
    end
    i = (i + 1)
    insert(kv, {k, v})
  end
  table.sort(kv, sort_keys)
  if not assoc_3f then
    local gap = max_index_gap(kv)
    if (max_index_gap(kv) > options["max-sparse-gap"]) then
      assoc_3f = true
    else
      fill_gaps(kv)
    end
  end
  if (#kv == 0) then
    return kv, "empty"
  else
    local function _2_()
    local function _3_()
      if assoc_3f then
        return "table"
      else
        return "seq"
      end
    end
    return kv, _2_()
    return kv, _3_()
  end
end
local function count_table_appearances(t, appearances)


@@ 284,7 327,7 @@ local function pp_table(x, options, indent)
      x0 = pp_metamethod(x, metamethod, options, indent)
    else
      local _ = _2_0
      local _4_0, _5_0 = table_kv_pairs(x)
      local _4_0, _5_0 = table_kv_pairs(x, options)
      if (true and (_5_0 == "empty")) then
        local _0 = _4_0
        if options["empty-as-sequence?"] then


@@ 328,7 371,7 @@ local function pp_string(str, options, indent)
  return ("\"" .. str:gsub("[%c\\\"]", escs) .. "\"")
end
local function make_options(t, options)
  local defaults = {["detect-cycles?"] = true, ["empty-as-sequence?"] = false, ["escape-newlines?"] = false, ["line-length"] = 80, ["metamethod?"] = true, ["one-line?"] = false, ["prefer-colon?"] = false, ["utf8?"] = true, depth = 128}
  local defaults = {["detect-cycles?"] = true, ["empty-as-sequence?"] = false, ["escape-newlines?"] = false, ["line-length"] = 80, ["max-sparse-gap"] = 10, ["metamethod?"] = true, ["one-line?"] = false, ["prefer-colon?"] = false, ["utf8?"] = true, depth = 128}
  local overrides = {appearances = count_table_appearances(t, {}), level = 0, seen = {len = 0}}
  for k, v in pairs((options or {})) do
    defaults[k] = v

M src/fennel/view.fnl => src/fennel/view.fnl +37 -6
@@ 21,22 21,50 @@
              dtb false
              (< ta tb))))))

(fn table-kv-pairs [t]
(fn max-index-gap [kv]
  ;; Find the largest gap between neighbor items
  (var gap 0)
  (when (> (length kv) 0)
    (var [[i] & rest] kv)
    (each [_ [k] (ipairs rest)]
      (when (> (- k i) gap)
        (set gap (- k i)))
      (set i k)))
  gap)

(fn fill-gaps [kv]
  ;; Fill gaps in sequential kv-table
  ;; [[1 "a"] [4 "d"]] => [[1 "a"] [2] [3] [4 "d"]]
  (let [missing-indexes []]
    (var i 0)
    (each [_ [j] (ipairs kv)]
      (set i (+ i 1))
      (while (< i j)
        (table.insert missing-indexes i)
        (set i (+ i 1))))
    (each [_ k (ipairs missing-indexes)]
      (table.insert kv k [k])))
  kv)

(fn table-kv-pairs [t options]
  ;; Return table of tables with first element representing key and second
  ;; element representing value.  Second value indicates table type, which is
  ;; either sequential or associative.
  ;; [:a :b :c] => [[1 :a] [2 :b] [3 :c]]
  ;; {:a 1 :b 2} => [[:a 1] [:b 2]]
  (var assoc? false)
  (var i 1)
  (let [kv []
        insert table.insert]
    (each [k v (pairs t)]
      (when (or (not= (type k) :number) (not= k i))
      (when (or (not= (type k) :number))
        (set assoc? true))
      (set i (+ i 1))
      (insert kv [k v]))
    (table.sort kv sort-keys)
    (when (not assoc?)
      (let [gap (max-index-gap kv)]
        (if (> (max-index-gap kv) options.max-sparse-gap)
            (set assoc? true)
            (fill-gaps kv))))
    (if (= (length kv) 0)
        (values kv :empty)
        (values kv (if assoc? :table :seq)))))


@@ 165,7 193,7 @@
  (set options.level (+ options.level 1))
  (let [x (match (if options.metamethod? (-?> x getmetatable (. :__fennelview)))
            metamethod (pp-metamethod x metamethod options indent)
            _ (match (table-kv-pairs x)
            _ (match (table-kv-pairs x options)
                (_ :empty) (if options.empty-as-sequence? "[]" "{}")
                (kv :table) (pp-associative x kv options indent)
                (kv :seq) (pp-sequence x kv options indent)))]


@@ 211,7 239,8 @@ as numeric escapes rather than letter-based escapes, which is ugly."
                  :metamethod? true
                  :prefer-colon? false
                  :escape-newlines? false
                  :utf8? true}
                  :utf8? true
                  :max-sparse-gap 10}
        ;; overrides can't be accessed via options
        overrides {:level 0
                   :appearances (count-table-appearances t {})


@@ 259,6 288,8 @@ Can take an options table with these keys:
* :prefer-colon? (default: false) emit strings in colon notation when possible
* :utf8? (boolean, default true) whether to use utf8 module to compute string
  lengths
* :max-sparse-gap (integer, default 10) maximum gap to fill in with nils in
  sparse sequential tables.

The `__fennelview` metamethod should take the table being serialized as its
first argument, a function as its second argument, options table as third

M test/core.fnl => test/core.fnl +19 -4
@@ 147,7 147,7 @@
               "(: {:foo (fn [self] (.. self.bar 2)) :bar :baz} :foo)" "baz2"
               "(do (tset {} :a 1) 1)" 1
               "(do (var a nil) (var b nil) (local ret (fn [] a)) (set (a b) (values 4 5)) (ret))" 4
               "(fn b [] (each [e {}] (e))) (let [(_ e) (pcall b)] (e:match \"a .*\"))" "a table value"
               "(pcall #(each [e {}] nil))" false
               "(global a_b :global) (local a-b :local) a_b" "global"
               "(global x 1) (global x 284) x" 284
               "(let [k 5 t {: k}] t.k)" 5


@@ 314,7 314,7 @@
               "((require :fennel.view) [1 2 [3 4]] {:line-length 7})"
               "[1\n 2\n [3 4]]"
               "((require :fennel.view) {[1] [2 [3]] :data {4 {:data 5} 6 [0 1 2 3]}} {:line-length 15})"
               "{:data {4 {:data 5}\n        6 [0\n           1\n           2\n           3]}\n [1] [2 [3]]}"
               "{:data [nil\n        nil\n        nil\n        {:data 5}\n        nil\n        [0\n         1\n         2\n         3]]\n [1] [2 [3]]}"
               "((require :fennel.view) {{:a 1} {:b 2 :c 3}})"
               "{{:a 1} {:b 2 :c 3}}"
               "((require :fennel.view) [{:aaa [1 2 3]}] {:line-length 0})"


@@ 325,13 325,28 @@
               "{:a [1\n     2]\n :b [1\n     2]\n :c [1\n     2]\n :d [1\n     2]}"
               "((require :fennel.view)  {:a [1 2 3 4 5 6 7 8] :b [1 2 3 4 5 6 7 8] :c [1 2 3 4 5 6 7 8] :d [1 2 3 4 5 6 7 8]})"
               "{:a [1 2 3 4 5 6 7 8]\n :b [1 2 3 4 5 6 7 8]\n :c [1 2 3 4 5 6 7 8]\n :d [1 2 3 4 5 6 7 8]}"
               ;; sparse tables
               "((require :fennel.view) {1 1 5 5})"
               "[1 nil nil nil 5]"
               "((require :fennel.view) {1 1 15 5})"
               "{1 1 15 5}"
               "((require :fennel.view) {1 1 15 15} {:one-line? true :max-sparse-gap 1000})"
               "[1 nil nil nil nil nil nil nil nil nil nil nil nil nil 15]"
               "((require :fennel.view) {1 1 3 3} {:max-sparse-gap 1})"
               "{1 1 3 3}"
               "((require :fennel.view) {1 1 3 3} {:max-sparse-gap 0})"
               "{1 1 3 3}"
               "((require :fennel.view) {1 1 5 5 :n 5})"
               "{1 1 5 5 :n 5}"
               "((require :fennel.view) [1 nil 2 nil nil 3 nil nil nil])"
               "[1 nil 2 nil nil 3]"
               ;; Unicode
               "((require :fennel.view) \"ваыв\")"
               "\"ваыв\""
               "((require :fennel.view) {[1] [2 [3]] :ваыв {4 {:ваыв 5} 6 [0 1 2 3]}} {:line-length 15})"
               (if _G.utf8
                   "{\"ваыв\" {4 {\"ваыв\" 5}\n         6 [0\n            1\n            2\n            3]}\n [1] [2 [3]]}"
                   "{\"ваыв\" {4 {\"ваыв\" 5}\n             6 [0\n                1\n                2\n                3]}\n [1] [2 [3]]}")
                   "{\"ваыв\" [nil\n         nil\n         nil\n         {\"ваыв\" 5}\n         nil\n         [0\n          1\n          2\n          3]]\n [1] [2 [3]]}"
                   "{\"ваыв\" [nil\n             nil\n             nil\n             {\"ваыв\" 5}\n             nil\n             [0\n              1\n              2\n              3]]\n [1] [2 [3]]}")
               ;; the next one may look incorrect in some editors, but is actually correct
               "((require :fennel.view) {:ǍǍǍ {} :ƁƁƁ {:ǍǍǍ {} :ƁƁƁ {}}} {:line-length 1})"
               (if _G.utf8 ; older versions of Lua can't indent this correctly

M test/fennelview.fnl => test/fennelview.fnl +1 -1
@@ 59,7 59,7 @@
    (tset sparse 4 sparse)
    (l.assertEquals (view t) "@1{:a 1 :b 2 :t @1{...}}")
    (l.assertEquals (view t2) "@1{:foo 19 :tbl [1 \"b\" @1{...}]}")
    (l.assertEquals (view sparse) "@1{1 \"abc\" 4 @1{...}}")))
    (l.assertEquals (view sparse) "@1[\"abc\" nil nil @1[...]]")))

(fn test-newline []
  (let [s "hello\nworld!\n"]