variadic if
The `if` form now supports elif clauses. It works like `cond` but without the implicit `do`. The old `if` is now `if*` variadic lif now supports "ellif" clauses. Update if-no-waste compiler to use `if*` properly. (Sometimes one character is all it takes.) document if reword truthiness
This commit is contained in:
parent
9a334c83bb
commit
f4afb0ca7e
@ -823,26 +823,47 @@ keyword, the second function would have raised a ``NameError``.
|
|||||||
(set-a 5)
|
(set-a 5)
|
||||||
(print-a)
|
(print-a)
|
||||||
|
|
||||||
if / if-not
|
if / if* / if-not
|
||||||
-----------
|
-----------------
|
||||||
|
|
||||||
.. versionadded:: 0.10.0
|
.. versionadded:: 0.10.0
|
||||||
if-not
|
if-not
|
||||||
|
|
||||||
``if`` is used to conditionally select code to be executed. It has to contain a
|
``if / if* / if-not`` respect Python *truthiness*, that is, a *test* fails if it
|
||||||
condition block and the block to be executed if the condition block evaluates
|
evaluates to a "zero" (including values of ``len`` zero, ``nil``, and
|
||||||
to ``True``. Optionally, it may contain a final block that is executed in case
|
``false``), and passes otherwise, but values with a ``__bool__`` method
|
||||||
the evaluation of the condition is ``False``.
|
(``__nonzero__`` in Python 2) can overrides this.
|
||||||
|
|
||||||
``if-not`` is similar, but the second block will be executed when the condition
|
The ``if`` macro is for conditionally selecting an expression for evaluation.
|
||||||
fails while the third and final block is executed when the test succeeds -- the
|
The result of the selected expression becomes the result of the entire ``if``
|
||||||
opposite order of ``if``.
|
form. ``if`` can select a group of expressions with the help of a ``do`` block.
|
||||||
|
|
||||||
|
``if`` takes any number of alternating *test* and *then* expressions, plus an
|
||||||
|
optional *else* expression at the end, which defaults to ``nil``. ``if`` checks
|
||||||
|
each *test* in turn, and selects the *then* corresponding to the first passed
|
||||||
|
test. ``if`` does not evaluate any expressions following its selection, similar
|
||||||
|
to the ``if/elif/else`` control structure from Python. If no tests pass, ``if``
|
||||||
|
selects *else*.
|
||||||
|
|
||||||
|
The ``if*`` special form is restricted to 2 or 3 arguments, but otherwise works
|
||||||
|
exactly like ``if`` (which expands to nested ``if*`` forms), so there is
|
||||||
|
generally no reason to use it directly.
|
||||||
|
|
||||||
|
``if-not`` is similar to ``if*`` but the second expression will be executed
|
||||||
|
when the condition fails while the third and final expression is executed when
|
||||||
|
the test succeeds -- the opposite order of ``if*``. The final expression is
|
||||||
|
again optional and defaults to ``nil``.
|
||||||
|
|
||||||
Example usage:
|
Example usage:
|
||||||
|
|
||||||
.. code-block:: clj
|
.. code-block:: clj
|
||||||
|
|
||||||
(if (money-left? account)
|
(print (if (< n 0.0) "negative"
|
||||||
|
(= n 0.0) "zero"
|
||||||
|
(> n 0.0) "positive"
|
||||||
|
"not a number"))
|
||||||
|
|
||||||
|
(if* (money-left? account)
|
||||||
(print "let's go shopping")
|
(print "let's go shopping")
|
||||||
(print "let's go and work"))
|
(print "let's go and work"))
|
||||||
|
|
||||||
@ -850,9 +871,6 @@ Example usage:
|
|||||||
(print "let's go and work")
|
(print "let's go and work")
|
||||||
(print "let's go shopping"))
|
(print "let's go shopping"))
|
||||||
|
|
||||||
Python truthiness is respected. ``None``, ``False``, zero of any numeric type,
|
|
||||||
an empty sequence, and an empty dictionary are considered ``False``; everything
|
|
||||||
else is considered ``True``.
|
|
||||||
|
|
||||||
|
|
||||||
lif and lif-not
|
lif and lif-not
|
||||||
|
@ -999,7 +999,7 @@ class HyASTCompiler(object):
|
|||||||
name=name,
|
name=name,
|
||||||
body=body)
|
body=body)
|
||||||
|
|
||||||
@builds("if")
|
@builds("if*")
|
||||||
@checkargs(min=2, max=3)
|
@checkargs(min=2, max=3)
|
||||||
def compile_if(self, expression):
|
def compile_if(self, expression):
|
||||||
expression.pop(0)
|
expression.pop(0)
|
||||||
@ -1011,7 +1011,7 @@ class HyASTCompiler(object):
|
|||||||
if expression:
|
if expression:
|
||||||
orel_expr = expression.pop(0)
|
orel_expr = expression.pop(0)
|
||||||
if isinstance(orel_expr, HyExpression) and isinstance(orel_expr[0],
|
if isinstance(orel_expr, HyExpression) and isinstance(orel_expr[0],
|
||||||
HySymbol) and orel_expr[0] == 'if':
|
HySymbol) and orel_expr[0] == 'if*':
|
||||||
# Nested ifs: don't waste temporaries
|
# Nested ifs: don't waste temporaries
|
||||||
root = self.temp_if is None
|
root = self.temp_if is None
|
||||||
nested = True
|
nested = True
|
||||||
|
@ -25,12 +25,20 @@
|
|||||||
;;; These macros are the essential hy macros.
|
;;; These macros are the essential hy macros.
|
||||||
;;; They are automatically required everywhere, even inside hy.core modules.
|
;;; They are automatically required everywhere, even inside hy.core modules.
|
||||||
|
|
||||||
|
(defmacro if [&rest args]
|
||||||
|
"if with elif"
|
||||||
|
(setv n (len args))
|
||||||
|
(if* n
|
||||||
|
(if* (= n 1)
|
||||||
|
(get args 0)
|
||||||
|
`(if* ~(get args 0)
|
||||||
|
~(get args 1)
|
||||||
|
(if ~@(cut args 2))))))
|
||||||
|
|
||||||
(defmacro macro-error [location reason]
|
(defmacro macro-error [location reason]
|
||||||
"error out properly within a macro"
|
"error out properly within a macro"
|
||||||
`(raise (hy.errors.HyMacroExpansionError ~location ~reason)))
|
`(raise (hy.errors.HyMacroExpansionError ~location ~reason)))
|
||||||
|
|
||||||
|
|
||||||
(defmacro defn [name lambda-list &rest body]
|
(defmacro defn [name lambda-list &rest body]
|
||||||
"define a function `name` with signature `lambda-list` and body `body`"
|
"define a function `name` with signature `lambda-list` and body `body`"
|
||||||
(if (not (= (type name) HySymbol))
|
(if (not (= (type name) HySymbol))
|
||||||
@ -39,7 +47,6 @@
|
|||||||
(macro-error name "defn takes a parameter list as second argument"))
|
(macro-error name "defn takes a parameter list as second argument"))
|
||||||
`(setv ~name (fn ~lambda-list ~@body)))
|
`(setv ~name (fn ~lambda-list ~@body)))
|
||||||
|
|
||||||
|
|
||||||
(defmacro let [variables &rest body]
|
(defmacro let [variables &rest body]
|
||||||
"Execute `body` in the lexical context of `variables`"
|
"Execute `body` in the lexical context of `variables`"
|
||||||
(if (not (isinstance variables HyList))
|
(if (not (isinstance variables HyList))
|
||||||
|
@ -159,20 +159,25 @@
|
|||||||
ret)
|
ret)
|
||||||
|
|
||||||
|
|
||||||
(defmacro if-not [test not-branch &optional [yes-branch nil]]
|
(defmacro if-not [test not-branch &optional yes-branch]
|
||||||
"Like `if`, but execute the first branch when the test fails"
|
"Like `if`, but execute the first branch when the test fails"
|
||||||
(if (nil? yes-branch)
|
`(if* (not ~test) ~not-branch ~yes-branch))
|
||||||
`(if (not ~test) ~not-branch)
|
|
||||||
`(if (not ~test) ~not-branch ~yes-branch)))
|
|
||||||
|
|
||||||
|
|
||||||
(defmacro lif [test &rest branches]
|
(defmacro lif [&rest args]
|
||||||
"Like `if`, but anything that is not None/nil is considered true."
|
"Like `if`, but anything that is not None/nil is considered true."
|
||||||
`(if (is-not ~test nil) ~@branches))
|
(setv n (len args))
|
||||||
|
(if* n
|
||||||
|
(if* (= n 1)
|
||||||
|
(get args 0)
|
||||||
|
`(if* (is-not ~(get args 0) nil)
|
||||||
|
~(get args 1)
|
||||||
|
(lif ~@(cut args 2))))))
|
||||||
|
|
||||||
(defmacro lif-not [test &rest branches]
|
|
||||||
|
(defmacro lif-not [test not-branch &optional yes-branch]
|
||||||
"Like `if-not`, but anything that is not None/nil is considered true."
|
"Like `if-not`, but anything that is not None/nil is considered true."
|
||||||
`(if (is ~test nil) ~@branches))
|
`(if* (is ~test nil) ~not-branch ~yes-branch))
|
||||||
|
|
||||||
|
|
||||||
(defmacro when [test &rest body]
|
(defmacro when [test &rest body]
|
||||||
|
@ -73,15 +73,15 @@ def test_ast_bad_type():
|
|||||||
|
|
||||||
|
|
||||||
def test_ast_bad_if():
|
def test_ast_bad_if():
|
||||||
"Make sure AST can't compile invalid if"
|
"Make sure AST can't compile invalid if*"
|
||||||
cant_compile("(if)")
|
cant_compile("(if*)")
|
||||||
cant_compile("(if foobar)")
|
cant_compile("(if* foobar)")
|
||||||
cant_compile("(if 1 2 3 4 5)")
|
cant_compile("(if* 1 2 3 4 5)")
|
||||||
|
|
||||||
|
|
||||||
def test_ast_valid_if():
|
def test_ast_valid_if():
|
||||||
"Make sure AST can't compile invalid if"
|
"Make sure AST can compile valid if*"
|
||||||
can_compile("(if foo bar)")
|
can_compile("(if* foo bar)")
|
||||||
|
|
||||||
|
|
||||||
def test_ast_valid_unary_op():
|
def test_ast_valid_unary_op():
|
||||||
@ -539,13 +539,13 @@ def test_invalid_list_comprehension():
|
|||||||
|
|
||||||
def test_bad_setv():
|
def test_bad_setv():
|
||||||
"""Ensure setv handles error cases"""
|
"""Ensure setv handles error cases"""
|
||||||
cant_compile("(setv if 1)")
|
cant_compile("(setv if* 1)")
|
||||||
cant_compile("(setv (a b) [1 2])")
|
cant_compile("(setv (a b) [1 2])")
|
||||||
|
|
||||||
|
|
||||||
def test_defn():
|
def test_defn():
|
||||||
"""Ensure that defn works correctly in various corner cases"""
|
"""Ensure that defn works correctly in various corner cases"""
|
||||||
cant_compile("(defn if [] 1)")
|
cant_compile("(defn if* [] 1)")
|
||||||
cant_compile("(defn \"hy\" [] 1)")
|
cant_compile("(defn \"hy\" [] 1)")
|
||||||
cant_compile("(defn :hy [] 1)")
|
cant_compile("(defn :hy [] 1)")
|
||||||
can_compile("(defn &hy [] 1)")
|
can_compile("(defn &hy [] 1)")
|
||||||
@ -561,5 +561,5 @@ def test_setv_builtins():
|
|||||||
(defn get [self] 42)
|
(defn get [self] 42)
|
||||||
(defclass B []
|
(defclass B []
|
||||||
(defn get [self] 42))
|
(defn get [self] 42))
|
||||||
(defn if [self] 0))
|
(defn if* [self] 0))
|
||||||
""")
|
""")
|
||||||
|
@ -274,6 +274,32 @@
|
|||||||
(assert (= (cond) nil)))
|
(assert (= (cond) nil)))
|
||||||
|
|
||||||
|
|
||||||
|
(defn test-if []
|
||||||
|
"NATIVE: test if if works."
|
||||||
|
;; with an odd number of args, the last argument is the default case
|
||||||
|
(assert (= 1 (if 1)))
|
||||||
|
(assert (= 1 (if 0 -1
|
||||||
|
1)))
|
||||||
|
;; with an even number of args, the default is nil
|
||||||
|
(assert (is nil (if)))
|
||||||
|
(assert (is nil (if 0 1)))
|
||||||
|
;; test deeper nesting
|
||||||
|
(assert (= 42
|
||||||
|
(if 0 0
|
||||||
|
nil 1
|
||||||
|
"" 2
|
||||||
|
1 42
|
||||||
|
1 43)))
|
||||||
|
;; test shortcutting
|
||||||
|
(setv x nil)
|
||||||
|
(if 0 (setv x 0)
|
||||||
|
"" (setv x "")
|
||||||
|
42 (setv x 42)
|
||||||
|
43 (setv x 43)
|
||||||
|
(setv x "default"))
|
||||||
|
(assert (= x 42)))
|
||||||
|
|
||||||
|
|
||||||
(defn test-index []
|
(defn test-index []
|
||||||
"NATIVE: Test that dict access works"
|
"NATIVE: Test that dict access works"
|
||||||
(assert (= (get {"one" "two"} "one") "two"))
|
(assert (= (get {"one" "two"} "one") "two"))
|
||||||
|
@ -190,11 +190,11 @@
|
|||||||
|
|
||||||
(defn test-lif []
|
(defn test-lif []
|
||||||
"test that lif works as expected"
|
"test that lif works as expected"
|
||||||
; nil is false
|
;; nil is false
|
||||||
(assert (= (lif None "true" "false") "false"))
|
(assert (= (lif None "true" "false") "false"))
|
||||||
(assert (= (lif nil "true" "false") "false"))
|
(assert (= (lif nil "true" "false") "false"))
|
||||||
|
|
||||||
; But everything else is True! Even falsey things.
|
;; But everything else is True! Even falsey things.
|
||||||
(assert (= (lif True "true" "false") "true"))
|
(assert (= (lif True "true" "false") "true"))
|
||||||
(assert (= (lif False "true" "false") "true"))
|
(assert (= (lif False "true" "false") "true"))
|
||||||
(assert (= (lif 0 "true" "false") "true"))
|
(assert (= (lif 0 "true" "false") "true"))
|
||||||
@ -202,7 +202,14 @@
|
|||||||
(assert (= (lif "" "true" "false") "true"))
|
(assert (= (lif "" "true" "false") "true"))
|
||||||
(assert (= (lif (+ 1 2 3) "true" "false") "true"))
|
(assert (= (lif (+ 1 2 3) "true" "false") "true"))
|
||||||
(assert (= (lif nil "true" "false") "false"))
|
(assert (= (lif nil "true" "false") "false"))
|
||||||
(assert (= (lif 0 "true" "false") "true")))
|
(assert (= (lif 0 "true" "false") "true"))
|
||||||
|
|
||||||
|
;; Test ellif [sic]
|
||||||
|
(assert (= (lif nil 0
|
||||||
|
nil 1
|
||||||
|
0 2
|
||||||
|
3)
|
||||||
|
2)))
|
||||||
|
|
||||||
(defn test-lif-not []
|
(defn test-lif-not []
|
||||||
"test that lif-not works as expected"
|
"test that lif-not works as expected"
|
||||||
|
Loading…
Reference in New Issue
Block a user