~technomancy/fennel

176c90b0695a5e3880b2fc856d6a445119a7e7ee — Phil Hagelberg 2 months ago 283ede6 implicit-else-nil
Treat else branches in if as nil, not zero values.

Previously when you had an even number of arguments in an if, there was
no else branch emitted, which means that if none of the conditions were
truthy, zero values were returned.

In Fennel we can't avoid some cases where zero values are distinct
from nil, simply because we must be compatible with Lua, and Lua makes
such a distinction. But there are some cases where we can avoid
zero-values, and this is one of them. If you really want zero values,
you can put (values) in your else clause.
3 files changed, 12 insertions(+), 16 deletions(-)

M changelog.md
M src/fennel/specials.fnl
M test/core.fnl
M changelog.md => changelog.md +1 -0
@@ 17,6 17,7 @@ Changes are **marked in bold** which could result in backwards-incompatibility.
### Changes and Removals
* **Enforce compiler sandbox** by default instead of warning
* **Disallow &** in identifiers
* Implicit else branches in `if` are treated as nil, **not zero values**


## 0.10.0 / 2021-08-07

M src/fennel/specials.fnl => src/fennel/specials.fnl +9 -15
@@ 465,6 465,7 @@ nested values, but all parents must contain an existing table.")

;; TODO: refactor; too long!
(fn if* [ast scope parent opts]
  (compiler.assert (< 2 (length ast)) "expected condition and body" ast)
  (let [do-scope (compiler.make-scope scope)
        branches []
        (wrapper inner-tail inner-target target-exprs) (calculate-target scope


@@ 478,6 479,10 @@ nested values, but all parents must contain an existing table.")
                                    chunk nil (. ast i))
        {: chunk :scope cscope}))

    ;; Implicit else becomes nil
    (when (= 1 (% (length ast) 2))
      (table.insert ast (utils.sym :nil)))

    (for [i 2 (- (length ast) 1) 2]
      (let [condchunk []
            res (compiler.compile1 (. ast i) do-scope condchunk {:nval 1})


@@ 488,8 493,7 @@ nested values, but all parents must contain an existing table.")
        (set branch.nested (and (not= i 2) (= (next condchunk nil) nil)))
        (table.insert branches branch)))
    ;; Emit code
    (let [has-else? (and (> (length ast) 3) (= (% (length ast) 2) 0))
          else-branch (and has-else? (compile-body (length ast)))
    (let [else-branch (compile-body (length ast))
          s (compiler.gensym scope)
          buffer []]
      (var last-buffer buffer)


@@ 497,9 501,7 @@ nested values, but all parents must contain an existing table.")
        (let [branch (. branches i)
              fstr (if (not branch.nested) "if %s then" "elseif %s then")
              cond (tostring branch.cond)
              cond-line (if (and (= cond :true) branch.nested
                                 (= i (length branches)) (not has-else?))
                            :else (: fstr :format cond))]
              cond-line (: fstr :format cond)]
          (if branch.nested
              (compiler.emit last-buffer branch.condchunk ast)
              (each [_ v (ipairs branch.condchunk)]


@@ 508,16 510,8 @@ nested values, but all parents must contain an existing table.")
          (compiler.emit last-buffer branch.chunk ast)
          (if (= i (length branches))
              (do
                (if has-else?
                    (do
                      (compiler.emit last-buffer :else ast)
                      (compiler.emit last-buffer else-branch.chunk ast))
                    ;; TODO: Consolidate use of cond-line ~= "else" with has-else
                    (and inner-target (not= cond-line :else))
                    (do
                      (compiler.emit last-buffer :else ast)
                      (compiler.emit last-buffer
                                     (: "%s = nil" :format inner-target) ast)))
                (compiler.emit last-buffer :else ast)
                (compiler.emit last-buffer else-branch.chunk ast)
                (compiler.emit last-buffer :end ast))
              (not (. (. branches (+ i 1)) :nested))
              (let [next-buffer []]

M test/core.fnl => test/core.fnl +2 -1
@@ 142,7 142,8 @@
               ["(var x 12) (if true (set x 22) 0) x" 22]
               ["(when (= 12 88) (os.exit 1)) false" false]
               ["(while (let [f false] f) (lua :break))" nil]
               ["(if _G.abc :abc true 55 :else)" 55]]]
               ["(if _G.abc :abc true 55 :else)" 55]
               ["(select :# (if false 3))" 1]]]
    (each [_ [code expected] (ipairs cases)]
      (l.assertEquals (fennel.eval code {:correlate true}) expected code))))