@@ 1,65 1,42 @@
;; A pretty-printer that outputs tables in Fennel syntax.
-;; Loosely based on inspect.lua: http://github.com/kikito/inspect.lua
-
-(fn view-quote [str] (.. "\"" (: str :gsub "\"" "\\\"") "\""))
-
-(local short-control-char-escapes
- {"\a" "\\a" "\b" "\\b" "\f" "\\f" "\n" "\\n"
- "\r" "\\r" "\t" "\\t" "\v" "\\v"})
-
-(local long-control-char-escapes
- (let [long {}]
- (for [i 0 31]
- (let [ch (string.char i)]
- (when (not (. short-control-char-escapes ch))
- (tset short-control-char-escapes ch (.. "\\" i))
- (tset long ch (: "\\%03d" :format i)))))
- long))
-
-(fn escape [str]
- (-> str
- (: :gsub "\\" "\\\\")
- (: :gsub "(%c)%f[0-9]" long-control-char-escapes)
- (: :gsub "%c" short-control-char-escapes)))
-
-(fn sequence-key? [k len]
- (and (= (type k) "number")
- (<= 1 k)
- (<= k len)
- (= (math.floor k) k)))
(local type-order {:number 1 :boolean 2 :string 3 :table 4
:function 5 :userdata 6 :thread 7})
(fn sort-keys [[a] [b]]
+ ;; Sort keys depending on the `type-order`.
(let [ta (type a) tb (type b)]
(if (and (= ta tb)
(or (= ta "string") (= ta "number")))
(< a b)
(let [dta (. type-order ta)
dtb (. type-order tb)]
- (if (and dta dtb)
- (< dta dtb)
+ (if (and dta dtb) (< dta dtb)
dta true
dtb false
- :else (< ta tb))))))
+ false)))))
-(fn get-sequence-length [t]
- (var len 0)
- (each [i (ipairs t)] (set len i))
- len)
+(fn table-kv-pairs [t]
+ ;; 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.
-(fn get-nonsequential-keys [t]
- (let [keys []
- sequence-length (get-sequence-length t)]
+ ;; [:a :b :c] => [[1 :a] [2 :b] [3 :c]]
+ ;; {:a 1 :b 2} => [[:a 1] [:b 2]]
+ (var assoc? false)
+ (let [kv []
+ insert table.insert]
(each [k v (pairs t)]
- (when (not (sequence-key? k sequence-length))
- (table.insert keys [k v])))
- (table.sort keys sort-keys)
- (values keys sequence-length)))
+ (when (not= (type k) :number)
+ (set assoc? true))
+ (insert kv [k v]))
+ (table.sort kv sort-keys)
+ (if (= (length kv) 0)
+ (values kv :empty)
+ (values kv (if assoc? :table :seq)))))
(fn count-table-appearances [t appearances]
- (when (= (type t) "table")
+ (when (= (type t) :table)
(if (not (. appearances t))
(do (tset appearances t 1)
(each [k v (pairs t)]
@@ 68,145 45,282 @@
(tset appearances t (+ (or (. appearances t) 0) 1))))
appearances)
+(fn save-table [t seen]
+ ;; Save table `t` in `seen` storing `t` as key, and its index as an id.
+ (let [seen (or seen {:len 0})
+ id (+ seen.len 1)]
+ (when (not (. seen t))
+ (tset seen t id)
+ (set seen.len id))
+ seen))
+
+(fn detect-cycle [t seen]
+ ;; Return `true` if table `t` appears in itself.
+ (let [seen (or seen {})]
+ (tset seen t true)
+ (each [k v (pairs t)]
+ (when (and (= (type k) :table)
+ (or (. seen k) (detect-cycle k seen)))
+ (lua "return true"))
+ (when (and (= (type v) :table)
+ (or (. seen v) (detect-cycle v seen)))
+ (lua "return true")))))
+
+(fn visible-cycle? [t options]
+ ;; Detect cycle, save table's ID in seen tables, and determine if
+ ;; cycle is visible. Exposed via options table to use in
+ ;; __fennelview metamethod implementations
+ (and options.detect-cycles?
+ (detect-cycle t)
+ (save-table t options.seen)
+ (< 1 (or (. options.appearances t) 0))))
+
+(fn table-indent [t indent id]
+ ;; When table contains cycles, it is printed with a prefix before opening
+ ;; delimiter. Prefix has a variable length, as it contains `id` of the table
+ ;; and fixed part of `2` characters `@` and either `[` or `{` depending on
+ ;; `t`type. If `t` has visible cycles, we need to increase indent by the size
+ ;; of the prefix.
+ (let [opener-length (if id
+ (+ (length (tostring id)) 2)
+ 1)]
+ (+ indent opener-length)))
+
+(local pp {})
+
+(fn concat-table-lines
+ [elements options multiline? indent table-type prefix]
+ (.. (or prefix "")
+ (if (= :seq table-type) "[" "{")
+ (table.concat
+ elements
+ (if (and (not options.one-line?)
+ (or multiline?
+ (> (length elements) (if (= table-type :seq)
+ options.sequential-length
+ options.associative-length))
+ (> indent 40)))
+ (.. "\n" (string.rep " " indent))
+ " "))
+ (if (= :seq table-type) "]" "}")))
+
+(fn pp-associative [t kv options indent key?]
+ (var multiline? false)
+ (let [elements []
+ id (. options.seen t)]
+ (if (>= options.level options.depth) "{...}"
+ (and id options.detect-cycles?) (.. "@" id "{...}")
+ (let [visible-cycle? (visible-cycle? t options)
+ id (and visible-cycle? (. options.seen t))
+ indent (table-indent t indent id)
+ slength (or (and options.utf8? (-?> (rawget _G :utf8) (. :len)))
+ #(length $))
+ prefix (if visible-cycle? (.. "@" id) "")]
+ (each [i [k v] (pairs kv)]
+ (when (or (= (type k) :table) (= (type v) :table))
+ (set multiline? true))
+ (let [k (pp.pp k options (+ indent 1) true)
+ v (pp.pp v options (+ indent (slength k) 1))]
+ (table.insert elements (.. k " " v))))
+ (concat-table-lines
+ elements options multiline? indent :table prefix)))))
+
+(fn pp-sequence [t kv options indent]
+ (var multiline? false)
+ (let [elements []
+ id (. options.seen t)]
+ (if (>= options.level options.depth) "[...]"
+ (and id options.detect-cycles?) (.. "@" id "[...]")
+ (let [visible-cycle? (visible-cycle? t options)
+ id (and visible-cycle? (. options.seen t))
+ indent (table-indent t indent id)
+ prefix (if visible-cycle? (.. "@" id) "")]
+ (each [_ [_ v] (pairs kv)]
+ (when (= (type v) :table)
+ (set multiline? true))
+ (table.insert elements (pp.pp v options indent)))
+ (concat-table-lines
+ elements options multiline? indent :seq prefix)))))
+
+(fn concat-lines [lines options indent one-line?]
+ (if (= (length lines) 0)
+ (if options.empty-as-sequence? "[]" "{}")
+ (if (and (not options.one-line?)
+ (not one-line?))
+ (table.concat lines (.. "\n" (string.rep " " indent)))
+ (-> (icollect [_ line (ipairs lines)]
+ (line:gsub "^%s+" " "))
+ table.concat))))
+
+(fn pp-metamethod [t metamethod options indent]
+ (if (>= options.level options.depth)
+ (if options.empty-as-sequence? "[...]" "{...}")
+ (let [_ (set options.visible-cycle? #(visible-cycle? $ options))
+ (lines force-one-line?) (metamethod t pp.pp options indent)]
+ (set options.visible-cycle? nil)
+ (match (type lines)
+ :string lines ;; assuming that it is already single line
+ :table (concat-lines lines options indent force-one-line?)
+ _ (error "Error: __fennelview metamethod must return a table of lines")))))
+
+(fn pp-table [x options indent]
+ ;; Generic table pretty-printer. Supports associative and
+ ;; sequential tables, as well as tables, that contain __fennelview
+ ;; metamethod.
+ (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)
+ (_ :empty) (if options.empty-as-sequence? "[]" "{}")
+ (kv :table) (pp-associative x kv options indent)
+ (kv :seq) (pp-sequence x kv options indent)))]
+ (set options.level (- options.level 1))
+ x))
+
-(var put-value nil) ; mutual recursion going on; defined below
-
-(fn puts [self ...]
- (each [_ v (ipairs [...])]
- (table.insert self.buffer v)))
-
-(fn tabify [self] (puts self "\n" (: self.indent :rep self.level)))
-
-(fn already-visited? [self v] (not= (. self.ids v) nil))
-
-(fn get-id [self v]
- (var id (. self.ids v))
- (when (not id)
- (let [tv (type v)]
- (set id (+ (or (. self.max-ids tv) 0) 1))
- (tset self.max-ids tv id)
- (tset self.ids v id)))
- (tostring id))
-
-(fn put-sequential-table [self t len]
- (puts self "[")
- (set self.level (+ self.level 1))
- (each [k v (ipairs t)]
- (when (< 1 k (+ 1 len))
- (puts self " "))
- (put-value self v))
- (set self.level (- self.level 1))
- (puts self "]"))
-
-(fn put-key [self k]
- (if (and (= (type k) "string")
- (: k :find "^[-%w?\\^_!$%&*+./@:|<=>]+$"))
- (puts self ":" k)
- (put-value self k)))
-
-(fn put-kv-table [self t ordered-keys]
- (puts self "{")
- (set self.level (+ self.level 1))
- ;; first, output sorted nonsequential keys
- (each [i [k v] (ipairs ordered-keys)]
- (when (or self.table-edges (not= i 1))
- (tabify self))
- (put-key self k)
- (puts self " ")
- (put-value self v))
- ;; next, output any sequential keys
- (each [i v (ipairs t)]
- (tabify self)
- (put-key self i)
- (puts self " ")
- (put-value self v))
- (set self.level (- self.level 1))
- (when self.table-edges
- (tabify self))
- (puts self "}"))
-
-(fn put-table [self t]
- (let [metamethod (and self.metamethod? (-?> t getmetatable (. :__fennelview)))]
- (if (and (already-visited? self t) self.detect-cycles?)
- (puts self "#<table @" (get-id self t) ">")
- (>= self.level self.depth)
- (puts self "{...}")
- metamethod
- (puts self (metamethod t self.fennelview))
- :else
- (let [(non-seq-keys len) (get-nonsequential-keys t)
- id (get-id self t)]
- ;; fancy metatable stuff can result in self.appearances not including
- ;; a table, so if it's not found, assume we haven't seen it; we can't
- ;; do cycle detection in that case.
- (when (and (< 1 (or (. self.appearances t) 0)) self.detect-cycles?)
- (puts self "@" id))
- (if (and (= (length non-seq-keys) 0) (= (length t) 0))
- (puts self (if self.empty-as-square "[]" "{}"))
- (= (length non-seq-keys) 0)
- (put-sequential-table self t len)
- :else
- (put-kv-table self t non-seq-keys))))))
-
-(fn put-number [self n]
- (puts self (match (math.modf n)
- (int 0) (tostring int)
- ((0 frac) ? (< frac 0)) (.. "-0." (: (tostring frac) :gsub "^-?0." ""))
- (int frac) (.. int "." (: (tostring frac) :gsub "^-?0." "")))))
-
-(set put-value
- (fn [self v]
- (let [tv (type v)]
- (if (= tv :string)
- (puts self (view-quote (escape v)))
- (= tv :number)
- (put-number self v)
- (or (= tv :boolean) (= tv :nil))
- (puts self (tostring v))
- (or (= tv :table)
- (and (= tv :userdata)
- (not= nil (-?> (getmetatable v) (. :__fennelview)))))
- (put-table self v)
- (puts self "#<" (tostring v) ">")))))
+(fn number->string [n]
+ ;; Transform number to a string without depending on correct `os.locale`
+ (match (math.modf n)
+ (int 0) (tostring int)
+ ((0 frac) ? (< frac 0)) (.. "-0." (: (tostring frac) :gsub "^-?0." ""))
+ (int frac) (.. int "." (: (tostring frac) :gsub "^-?0." ""))))
+
+(fn colon-string? [s]
+ ;; Test if given string is valid colon string.
+ (s:find "^[-%w?\\^_!$%&*+./@:|<=>]+$"))
-(fn one-line [str]
- ;; save return value as local to ignore gsub's extra return value
- (let [ret (-> str
- (: :gsub "\n" " ")
- (: :gsub "%[ " "[") (: :gsub " %]" "]")
- (: :gsub "%{ " "{") (: :gsub " %}" "}")
- (: :gsub "%( " "(") (: :gsub " %)" ")"))]
- ret))
+(fn make-options [t options]
+ (let [;; defaults are used when options are not provided
+ defaults {:sequential-length 10
+ :associative-length 4
+ :one-line? false
+ :depth 128
+ :detect-cycles? true
+ :empty-as-sequence? false
+ :metamethod? true
+ :utf8? true}
+ ;; overrides can't be accessed via options
+ overrides {:level 0
+ :appearances (count-table-appearances t {})
+ :seen {:len 0}}]
+ (each [k v (pairs (or options {}))]
+ (tset defaults k v))
+ (each [k v (pairs overrides)]
+ (tset defaults k v))
+ defaults))
+
+(fn pp.pp [x options indent key?]
+ ;; main serialization loop, entry point is defined below
+ (let [indent (or indent 0)
+ options (or options (make-options x))
+ tv (type x)]
+ (if (or (= tv :table)
+ (and (= tv :userdata)
+ (-?> (getmetatable x) (. :__fennelview))))
+ (pp-table x options indent)
+ (= tv :number)
+ (number->string x)
+ (and (= tv :string) key? (colon-string? x))
+ (.. ":" x)
+ (= tv :string)
+ (string.format "%q" x)
+ (or (= tv :boolean) (= tv :nil))
+ (tostring x)
+ (.. "#<" (tostring x) ">"))))
(fn fennelview [x options]
"Return a string representation of x.
Can take an options table with these keys:
-* :one-line (boolean: default: false) keep the output string as a one-liner
+* :one-line? (boolean: default: false) keep the output string as a one-liner
* :depth (number, default: 128) limit how many levels to go (default: 128)
-* :indent (string, default: \" \") use this string to indent each level
* :detect-cycles? (boolean, default: true) don't try to traverse a looping table
* :metamethod? (boolean: default: true) use the __fennelview metamethod if found
-* :table-edges (boolean: default: true) put {} table brackets on their own line
-* :empty-as-square (boolean: default: false) render empty tables as [], not {}
+* :empty-as-sequence? (boolean, default: false) render empty tables as []
+* :sequential-length (number, default: 10) amount of elements at which
+ multi-line sequence ouptut is produced.
+* :associative-length (number, default: 4) amount of elements at which
+ multi-line table ouptut is produced.
+* :utf8? (boolean, default true) whether to use utf8 module to compute string
+ lengths
The __fennelview metamethod should take the table being serialized as its first
-argument and a function as its second arg which can be used on table elements to
-continue the fennelview process on them.
+argument, a function as its second argument, options table as third argument,
+and current amount of indentation as its last argument:
+
+(fn [t view inspector indent] ...)
+
+`view` function contains pretty printer, that can be used to serialize elements
+stored within the table being serialized. If your metamethod produces indented
+representation, you should pass `indent` parameter to `view` increased by the
+amount of addition indentation you've introduced.
+
+`inspector` table contains options described above, and also `visible-cycle?`
+function, that takes a table being serialized, detects and saves information
+about possible reachable cycle. Should be used in __fennelview to implement
+cycle detection.
+
+`__fennelview` metamethod should always return a table of correctly indented
+lines when producing multi-line output, or a string when returning single-line
+item. If single-line representation is needed in some cases, there's no need to
+concatenate table manually, instead `__fennelview` should return two values - a
+table of lines, and a boolean indicating if one-line representation should be
+forced.
+
+There's no need to incorporate indentation beyond needed to correctly align
+elements within the printed representation of your data structure. For example,
+if you want to print a multi-line table, like this:
+
+@my-table[1
+ 2
+ 3]
+
+__fennelview should return a sequence of lines:
+
+[\"@my-table[1\"
+ \" 2\"
+ \" 3]\"]
+
+Note, since we've introduced inner indent string of length 10, when calling
+`view` function from within __fennelview metamethod, in order to keep inner
+tables indented correctly, `indent` must be increased by this amount of extra
+indentation.
+
+`view` function also accepts additional boolean argument, which controls if
+strings should be printed as a colon-strings when possible. Set it to `true`
+when `view` is being called on the key of a table.
+
+Here's an implementation of such pretty-printer for an arbitrary sequential
+table:
+
+(fn pp-doc-example [t view inspector indent]
+ (let [lines (icollect [i v (ipairs t)]
+ (let [v (view v inspector (+ 10 indent))]
+ (if (= i 1) v
+ (.. \" \" v))))]
+ (doto lines
+ (tset 1 (.. \"@my-table[\" (or (. lines 1) \"\")))
+ (tset (length lines) (.. (. lines (length lines)) \"]\")))))
+
+Setting table's __fennelview metamethod to this function will provide correct
+results regardless of nesting:
+
+>> {:my-table (setmetatable [{:a {} :b [[1] [2]]} 3]
+ {:__fennelview pp-doc-example})
+ :normal-table [{:c [1 2 3] :d :some-data} 4]}
+{:my-table @my-table[{:a {}
+ :b [[1]
+ [2]]}
+ 3]
+ :normal-table [{:c [1 2 3]
+ :d \"some-data\"}
+ 4]}
+
+Note that even though we've only indented inner elements of our table with 10
+spaces, the result is correctly indented in terms of outer table, and inner
+tables also remain indented correctly.
"
- (let [options (or options {})
- inspector {:appearances (count-table-appearances x {})
- :depth (or options.depth 128)
- :level 0 :buffer {} :ids {} :max-ids {}
- :indent (or options.indent (if options.one-line "" " "))
- :detect-cycles? (not (= false options.detect-cycles?))
- :metamethod? (not (= false options.metamethod?))
- :fennelview #(fennelview $1 options)
- :table-edges (not= options.table-edges false)
- :empty-as-square options.empty-as-square}]
- (put-value inspector x)
- (let [str (table.concat inspector.buffer)]
- (if options.one-line (one-line str) str))))
+ (pp.pp x (make-options x options) 0))
@@ 1,25 1,3 @@
-local function view_quote(str)
- return ("\"" .. str:gsub("\"", "\\\"") .. "\"")
-end
-local short_control_char_escapes = {["\11"] = "\\v", ["\12"] = "\\f", ["\13"] = "\\r", ["\7"] = "\\a", ["\8"] = "\\b", ["\9"] = "\\t", ["\n"] = "\\n"}
-local long_control_char_escapes = nil
-do
- local long = {}
- for i = 0, 31 do
- local ch = string.char(i)
- if not short_control_char_escapes[ch] then
- short_control_char_escapes[ch] = ("\\" .. i)
- long[ch] = ("\\%03d"):format(i)
- end
- end
- long_control_char_escapes = long
-end
-local function escape(str)
- return str:gsub("\\", "\\\\"):gsub("(%c)%f[0-9]", long_control_char_escapes):gsub("%c", short_control_char_escapes)
-end
-local function sequence_key_3f(k, len)
- return ((type(k) == "number") and (1 <= k) and (k <= len) and (math.floor(k) == k))
-end
local type_order = {["function"] = 5, boolean = 2, number = 1, string = 3, table = 4, thread = 7, userdata = 6}
local function sort_keys(_0_0, _1_0)
local _1_ = _0_0
@@ 39,28 17,34 @@ local function sort_keys(_0_0, _1_0)
return true
elseif dtb then
return false
- elseif "else" then
- return (ta < tb)
+ else
+ return false
end
end
end
-local function get_sequence_length(t)
- local len = 0
- for i in ipairs(t) do
- len = i
- end
- return len
-end
-local function get_nonsequential_keys(t)
- local keys = {}
- local sequence_length = get_sequence_length(t)
+local function table_kv_pairs(t)
+ local assoc_3f = false
+ local kv = {}
+ local insert = table.insert
for k, v in pairs(t) do
- if not sequence_key_3f(k, sequence_length) then
- table.insert(keys, {k, v})
+ if (type(k) ~= "number") then
+ assoc_3f = true
end
+ insert(kv, {k, v})
+ end
+ table.sort(kv, sort_keys)
+ if (#kv == 0) then
+ return kv, "empty"
+ else
+ local function _2_()
+ if assoc_3f then
+ return "table"
+ else
+ return "seq"
+ end
+ end
+ return kv, _2_()
end
- table.sort(keys, sort_keys)
- return keys, sequence_length
end
local function count_table_appearances(t, appearances)
if (type(t) == "table") then
@@ 76,193 60,304 @@ local function count_table_appearances(t, appearances)
end
return appearances
end
-local put_value = nil
-local function puts(self, ...)
- for _, v in ipairs({...}) do
- table.insert(self.buffer, v)
+local function save_table(t, seen)
+ local seen0 = (seen or {len = 0})
+ local id = (seen0.len + 1)
+ if not seen0[t] then
+ seen0[t] = id
+ seen0.len = id
end
- return nil
-end
-local function tabify(self)
- return puts(self, "\n", (self.indent):rep(self.level))
-end
-local function already_visited_3f(self, v)
- return (self.ids[v] ~= nil)
+ return seen0
end
-local function get_id(self, v)
- local id = self.ids[v]
- if not id then
- local tv = type(v)
- id = ((self["max-ids"][tv] or 0) + 1)
- self["max-ids"][tv] = id
- self.ids[v] = id
- end
- return tostring(id)
-end
-local function put_sequential_table(self, t, len)
- puts(self, "[")
- self.level = (self.level + 1)
- for k, v in ipairs(t) do
- local _2_ = (1 + len)
- if ((1 < k) and (k < _2_)) then
- puts(self, " ")
+local function detect_cycle(t, seen)
+ local seen0 = (seen or {})
+ seen0[t] = true
+ for k, v in pairs(t) do
+ if ((type(k) == "table") and (seen0[k] or detect_cycle(k, seen0))) then
+ return true
+ end
+ if ((type(v) == "table") and (seen0[v] or detect_cycle(v, seen0))) then
+ return true
end
- put_value(self, v)
end
- self.level = (self.level - 1)
- return puts(self, "]")
+ return nil
+end
+local function visible_cycle_3f(t, options)
+ return (options["detect-cycles?"] and detect_cycle(t) and save_table(t, options.seen) and (1 < (options.appearances[t] or 0)))
end
-local function put_key(self, k)
- if ((type(k) == "string") and k:find("^[-%w?\\^_!$%&*+./@:|<=>]+$")) then
- return puts(self, ":", k)
+local function table_indent(t, indent, id)
+ local opener_length = nil
+ if id then
+ opener_length = (#tostring(id) + 2)
else
- return put_value(self, k)
+ opener_length = 1
end
+ return (indent + opener_length)
end
-local function put_kv_table(self, t, ordered_keys)
- puts(self, "{")
- self.level = (self.level + 1)
- for i, _2_0 in ipairs(ordered_keys) do
- local _3_ = _2_0
- local k = _3_[1]
- local v = _3_[2]
- if (self["table-edges"] or (i ~= 1)) then
- tabify(self)
+local pp = {}
+local function concat_table_lines(elements, options, multiline_3f, indent, table_type, prefix)
+ local function _2_()
+ if ("seq" == table_type) then
+ return "["
+ else
+ return "{"
end
- put_key(self, k)
- puts(self, " ")
- put_value(self, v)
end
- for i, v in ipairs(t) do
- tabify(self)
- put_key(self, i)
- puts(self, " ")
- put_value(self, v)
+ local function _3_()
+ local _3_
+ if (table_type == "seq") then
+ _3_ = options["sequential-length"]
+ else
+ _3_ = options["associative-length"]
+ end
+ if (not options["one-line?"] and (multiline_3f or (#elements > _3_) or (indent > 40))) then
+ return ("\n" .. string.rep(" ", indent))
+ else
+ return " "
+ end
end
- self.level = (self.level - 1)
- if self["table-edges"] then
- tabify(self)
+ local function _4_()
+ if ("seq" == table_type) then
+ return "]"
+ else
+ return "}"
+ end
end
- return puts(self, "}")
+ return ((prefix or "") .. _2_() .. table.concat(elements, _3_()) .. _4_())
end
-local function put_table(self, t)
- local metamethod = nil
- local function _3_()
- local _2_0 = t
- if _2_0 then
- local _4_0 = getmetatable(_2_0)
- if _4_0 then
- return _4_0.__fennelview
+local function pp_associative(t, kv, options, indent, key_3f)
+ local multiline_3f = false
+ local elements = {}
+ local id = options.seen[t]
+ if (options.level >= options.depth) then
+ return "{...}"
+ elseif (id and options["detect-cycles?"]) then
+ return ("@" .. id .. "{...}")
+ else
+ local visible_cycle_3f0 = visible_cycle_3f(t, options)
+ local id0 = (visible_cycle_3f0 and options.seen[t])
+ local indent0 = table_indent(t, indent, id0)
+ local slength = nil
+ local function _3_()
+ local _2_0 = rawget(_G, "utf8")
+ if _2_0 then
+ return _2_0.len
else
- return _4_0
+ return _2_0
end
+ end
+ local function _4_(_241)
+ return #_241
+ end
+ slength = ((options["utf8?"] and _3_()) or _4_)
+ local prefix = nil
+ if visible_cycle_3f0 then
+ prefix = ("@" .. id0)
else
- return _2_0
+ prefix = ""
+ end
+ for i, _6_0 in pairs(kv) do
+ local _7_ = _6_0
+ local k = _7_[1]
+ local v = _7_[2]
+ if ((type(k) == "table") or (type(v) == "table")) then
+ multiline_3f = true
+ end
+ local k0 = pp.pp(k, options, (indent0 + 1), true)
+ local v0 = pp.pp(v, options, (indent0 + slength(k0) + 1))
+ table.insert(elements, (k0 .. " " .. v0))
end
+ return concat_table_lines(elements, options, multiline_3f, indent0, "table", prefix)
end
- metamethod = (self["metamethod?"] and _3_())
- if (already_visited_3f(self, t) and self["detect-cycles?"]) then
- return puts(self, "#<table @", get_id(self, t), ">")
- elseif (self.level >= self.depth) then
- return puts(self, "{...}")
- elseif metamethod then
- return puts(self, metamethod(t, self.fennelview))
- elseif "else" then
- local non_seq_keys, len = get_nonsequential_keys(t)
- local id = get_id(self, t)
- if ((1 < (self.appearances[t] or 0)) and self["detect-cycles?"]) then
- puts(self, "@", id)
+end
+local function pp_sequence(t, kv, options, indent)
+ local multiline_3f = false
+ local elements = {}
+ local id = options.seen[t]
+ if (options.level >= options.depth) then
+ return "[...]"
+ elseif (id and options["detect-cycles?"]) then
+ return ("@" .. id .. "[...]")
+ else
+ local visible_cycle_3f0 = visible_cycle_3f(t, options)
+ local id0 = (visible_cycle_3f0 and options.seen[t])
+ local indent0 = table_indent(t, indent, id0)
+ local prefix = nil
+ if visible_cycle_3f0 then
+ prefix = ("@" .. id0)
+ else
+ prefix = ""
end
- if ((#non_seq_keys == 0) and (#t == 0)) then
- local function _5_()
- if self["empty-as-square"] then
- return "[]"
- else
- return "{}"
- end
+ for _, _3_0 in pairs(kv) do
+ local _4_ = _3_0
+ local _0 = _4_[1]
+ local v = _4_[2]
+ if (type(v) == "table") then
+ multiline_3f = true
end
- return puts(self, _5_())
- elseif (#non_seq_keys == 0) then
- return put_sequential_table(self, t, len)
- elseif "else" then
- return put_kv_table(self, t, non_seq_keys)
+ table.insert(elements, pp.pp(v, options, indent0))
end
+ return concat_table_lines(elements, options, multiline_3f, indent0, "seq", prefix)
end
end
-local function put_number(self, n)
- local function _5_()
- local _2_0, _3_0, _4_0 = math.modf(n)
- if ((nil ~= _2_0) and (_3_0 == 0)) then
- local int = _2_0
- return tostring(int)
+local function concat_lines(lines, options, indent, one_line_3f)
+ if (#lines == 0) then
+ if options["empty-as-sequence?"] then
+ return "[]"
else
- local _6_
- do
- local frac = _3_0
- _6_ = (((_2_0 == 0) and (nil ~= _3_0)) and (frac < 0))
- end
- if _6_ then
- local frac = _3_0
- return ("-0." .. tostring(frac):gsub("^-?0.", ""))
- elseif ((nil ~= _2_0) and (nil ~= _3_0)) then
- local int = _2_0
- local frac = _3_0
- return (int .. "." .. tostring(frac):gsub("^-?0.", ""))
+ return "{}"
+ end
+ else
+ if (not options["one-line?"] and not one_line_3f) then
+ return table.concat(lines, ("\n" .. string.rep(" ", indent)))
+ else
+ local function _2_()
+ local tbl_0_ = {}
+ for _, line in ipairs(lines) do
+ tbl_0_[(#tbl_0_ + 1)] = line:gsub("^%s+", " ")
+ end
+ return tbl_0_
end
+ return table.concat(_2_())
end
end
- return puts(self, _5_())
end
-local function _2_(self, v)
- local tv = type(v)
- if (tv == "string") then
- return puts(self, view_quote(escape(v)))
- elseif (tv == "number") then
- return put_number(self, v)
- elseif ((tv == "boolean") or (tv == "nil")) then
- return puts(self, tostring(v))
+local function pp_metamethod(t, metamethod, options, indent)
+ if (options.level >= options.depth) then
+ if options["empty-as-sequence?"] then
+ return "[...]"
+ else
+ return "{...}"
+ end
else
- local _4_
- do
- local _3_0 = getmetatable(v)
+ local _ = nil
+ local function _2_(_241)
+ return visible_cycle_3f(_241, options)
+ end
+ options["visible-cycle?"] = _2_
+ _ = nil
+ local lines, force_one_line_3f = metamethod(t, pp.pp, options, indent)
+ options["visible-cycle?"] = nil
+ local _3_0 = type(lines)
+ if (_3_0 == "string") then
+ return lines
+ elseif (_3_0 == "table") then
+ return concat_lines(lines, options, indent, force_one_line_3f)
+ else
+ local _0 = _3_0
+ return error("Error: __fennelview metamethod must return a table of lines")
+ end
+ end
+end
+local function pp_table(x, options, indent)
+ options.level = (options.level + 1)
+ local x0 = nil
+ do
+ local _2_0 = nil
+ if options["metamethod?"] then
+ local _3_0 = x
if _3_0 then
- _4_ = _3_0.__fennelview
+ local _4_0 = getmetatable(_3_0)
+ if _4_0 then
+ _2_0 = _4_0.__fennelview
+ else
+ _2_0 = _4_0
+ end
else
- _4_ = _3_0
+ _2_0 = _3_0
end
+ else
+ _2_0 = nil
end
- if ((tv == "table") or ((tv == "userdata") and (nil ~= _4_))) then
- return put_table(self, v)
+ if (nil ~= _2_0) then
+ local metamethod = _2_0
+ x0 = pp_metamethod(x, metamethod, options, indent)
else
- return puts(self, "#<", tostring(v), ">")
+ local _ = _2_0
+ local _4_0, _5_0 = table_kv_pairs(x)
+ if (true and (_5_0 == "empty")) then
+ local _0 = _4_0
+ if options["empty-as-sequence?"] then
+ x0 = "[]"
+ else
+ x0 = "{}"
+ end
+ elseif ((nil ~= _4_0) and (_5_0 == "table")) then
+ local kv = _4_0
+ x0 = pp_associative(x, kv, options, indent)
+ elseif ((nil ~= _4_0) and (_5_0 == "seq")) then
+ local kv = _4_0
+ x0 = pp_sequence(x, kv, options, indent)
+ else
+ x0 = nil
+ end
end
end
+ options.level = (options.level - 1)
+ return x0
end
-put_value = _2_
-local function one_line(str)
- local ret = str:gsub("\n", " "):gsub("%[ ", "["):gsub(" %]", "]"):gsub("%{ ", "{"):gsub(" %}", "}"):gsub("%( ", "("):gsub(" %)", ")")
- return ret
+local function number__3estring(n)
+ local _2_0, _3_0, _4_0 = math.modf(n)
+ if ((nil ~= _2_0) and (_3_0 == 0)) then
+ local int = _2_0
+ return tostring(int)
+ else
+ local _5_
+ do
+ local frac = _3_0
+ _5_ = (((_2_0 == 0) and (nil ~= _3_0)) and (frac < 0))
+ end
+ if _5_ then
+ local frac = _3_0
+ return ("-0." .. tostring(frac):gsub("^-?0.", ""))
+ elseif ((nil ~= _2_0) and (nil ~= _3_0)) then
+ local int = _2_0
+ local frac = _3_0
+ return (int .. "." .. tostring(frac):gsub("^-?0.", ""))
+ end
+ end
end
-local function fennelview(x, options)
- local options0 = (options or {})
- local inspector = nil
- local function _3_(_241)
- return fennelview(_241, options0)
+local function colon_string_3f(s)
+ return s:find("^[-%w?\\^_!$%&*+./@:|<=>]+$")
+end
+local function make_options(t, options)
+ local defaults = {["associative-length"] = 4, ["detect-cycles?"] = true, ["empty-as-sequence?"] = false, ["metamethod?"] = true, ["one-line?"] = false, ["sequential-length"] = 10, ["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
end
- local function _4_()
- if options0["one-line"] then
- return ""
+ for k, v in pairs(overrides) do
+ defaults[k] = v
+ end
+ return defaults
+end
+pp.pp = function(x, options, indent, key_3f)
+ local indent0 = (indent or 0)
+ local options0 = (options or make_options(x))
+ local tv = type(x)
+ local function _3_()
+ local _2_0 = getmetatable(x)
+ if _2_0 then
+ return _2_0.__fennelview
else
- return " "
+ return _2_0
end
end
- inspector = {["detect-cycles?"] = not (false == options0["detect-cycles?"]), ["empty-as-square"] = options0["empty-as-square"], ["max-ids"] = {}, ["metamethod?"] = not (false == options0["metamethod?"]), ["table-edges"] = (options0["table-edges"] ~= false), appearances = count_table_appearances(x, {}), buffer = {}, depth = (options0.depth or 128), fennelview = _3_, ids = {}, indent = (options0.indent or _4_()), level = 0}
- put_value(inspector, x)
- local str = table.concat(inspector.buffer)
- if options0["one-line"] then
- return one_line(str)
+ if ((tv == "table") or ((tv == "userdata") and _3_())) then
+ return pp_table(x, options0, indent0)
+ elseif (tv == "number") then
+ return number__3estring(x)
+ elseif ((tv == "string") and key_3f and colon_string_3f(x)) then
+ return (":" .. x)
+ elseif (tv == "string") then
+ return string.format("%q", x)
+ elseif ((tv == "boolean") or (tv == "nil")) then
+ return tostring(x)
else
- return str
+ return ("#<" .. tostring(x) .. ">")
end
end
+local function fennelview(x, options)
+ return pp.pp(x, make_options(x, options), 0)
+end
return fennelview