Fennel wiki: parser

Parse a file and determine the length of all the functions inside it.

(local fennel (require :fennel))
(local v (require :fennelview))

(fn walk-tree [root f custom-iterator] ; stolen from fennel.utils
  (fn walk [iterfn parent idx node]
    (when (f idx node parent)
      (each [k v (iterfn node)]
        (walk iterfn node k v))))
  (walk (or custom-iterator pairs) nil nil root)

(fn find-functions [read results]
  (fn walk-node [results idx node parent]
    (when (and (= :table (type node)) (not (fennel.sym? node)))
      (when (= :fn (tostring (. node 1)))
        (table.insert results node))

  (let [(ok? parsed) (read)]
    (when ok?
      (if (= :table (type parsed))
          (walk-tree parsed (partial walk-node results))
      (find-functions read results))))

(fn find-max-line [max-line idx node parent]
  (when (and (= :table (type node))
             (= :number (type node.line))
             (< (. max-line 1) node.line))
    (tset max-line 1 node.line))
  (and (= :table (type node)) (not (fennel.sym? node))))

(fn find-lengths [filename]
  (with-open [f (assert (io.open filename :r))]
    (let [contents (assert (f:read :*all))
          read (fennel.parser (fennel.string-stream contents))
          results {}]
      (find-functions read results)
      (each [_ f (ipairs results)]
        (let [first-line (. f 1 :line)
              second (. f 2)
              max-line [first-line]
              name (if (fennel.sym? second)
                       (tostring second)
          ;; TODO: subtract docstrings
          (walk-tree f (partial find-max-line max-line))
          (print name (- (. max-line 1) first-line -1)))))))

(match arg
  [filename] (find-lengths filename)
  _ (print "Usage: function-length.fnl FILENAME"))