and hands back its deciding operand too, and both locs get sharper
(and a b) desugared to (if a b false), so it answered the last operand only when every operand was truthy; a falsey one came back as a bare false, where Clojure answers the falsey operand itself. It now uses the same expansion or got in ad0f1fb -- (let [t a] (if t b t)) against or's (let [t a] (if t t b)) -- so the operand that decided the form is the answer, and the test is still evaluated exactly once. The temp binding and its if now carry the operand's own loc rather than the whole form's, which the or fix had lost: (or (vec-new i32) v) blamed the enclosing form at 3:13 and now points at 3:18, the operand, and and's second operand gained the same precision. The parse pins in test_flan.ml now tie the bound name to the temp the if tests and the bound value to the first operand, so a desugaring that dropped the temp and wrote the operand into the arm twice no longer passes; and has its own pin. dyn-if-truthy.flan grows the falsey-nil and falsey-false answers, 0 and "" as truthy operands, one- and zero-operand forms, and a printing operand that proves both the short circuit and the single evaluation. One behaviour that used to compile changed: with both arms of the desugared if now holding real values, (and dyn-value typed-bool) unifies on the typed arm and a non-bool dyn decider traps at the strict bool boundary -- (and (box nil) some-bool) printed false and now traps, the mirror of what (or false (box "s")) already did on dev-loop. Recorded in FIX.org as the author's call on how check_if should join a bool arm and a dyn arm.
This commit is contained in:
parent
f61f83f796
commit
bddc8fc5dd
46
FIX.org
46
FIX.org
@ -444,16 +444,42 @@ rename. typed-flan branch freezes the static language pre-dyn.
|
|||||||
decided going in, one fixed on review and one left as the author's
|
decided going in, one fixed on review and one left as the author's
|
||||||
call:
|
call:
|
||||||
|
|
||||||
- or's answer used to stay a strict bool where and's already carried a
|
- Neither and nor or handed back the operand that decided it.
|
||||||
non-bool dyn value through, Clojure-style — and's short-circuit
|
Clojure's rule is that both do; each answered a bare bool sentinel
|
||||||
sentinel sits in the else arm, so the real value's type wins there,
|
on its deciding path instead. and's "false" sat in the else arm, so
|
||||||
but or's sat in the then arm, the one check_if types first, so it
|
check_if typed the real branch first and boxed the sentinel to
|
||||||
decided the whole expression's type and a later non-bool dyn answer
|
match: an all-truthy and did carry its last dyn operand through,
|
||||||
hit the strict bool boundary and trapped. (or nil "x"), the
|
but a falsey one answered false where Clojure answers the falsey
|
||||||
canonical (or x default) idiom, crashed rather than answering "x".
|
operand — (and (box 1) (box nil) x) printed false, not nil. or's
|
||||||
FIXED, ad0f1fb: or now binds its test to a temp and answers the
|
"true" sat in the then arm, the one check_if types first, so the
|
||||||
temp, Clojure's own expansion, evaluating the test once and handing
|
sentinel decided the whole expression's type and a later non-bool
|
||||||
back whichever operand actually decided it.
|
dyn answer hit the strict bool boundary and trapped: (or nil "x"),
|
||||||
|
the canonical (or x default) idiom, crashed rather than answering
|
||||||
|
"x". FIXED for or in ad0f1fb and for and in this pass: both now
|
||||||
|
bind the test to a temp and answer the temp on the deciding path,
|
||||||
|
Clojure's own expansion — (let [t a] (if t t b)) for or and (let [t
|
||||||
|
a] (if t b t)) for and — evaluating each test exactly once. The
|
||||||
|
asymmetry between the two forms is fully closed; the survey program
|
||||||
|
(test/programs/dyn-if-truthy.flan) pins both, short-circuit and
|
||||||
|
single-evaluation included, and test_flan.ml pins both desugarings
|
||||||
|
down to the bound name and the bound value.
|
||||||
|
|
||||||
|
Two things came with that pass. The temp binding and the if it
|
||||||
|
feeds now carry the *operand's* loc rather than the whole form's,
|
||||||
|
which ad0f1fb had lost for or: (or (vec-new i32) v) blamed the
|
||||||
|
enclosing form at 3:13 and now points at the operand at 3:18, and
|
||||||
|
and's second operand gained the same precision. And, noted and not
|
||||||
|
acted on: with both arms of the desugared if now holding real
|
||||||
|
values, a dyn operand mixed with a typed bool one makes check_if
|
||||||
|
unify them by the then arm, so a non-bool dyn value on the losing
|
||||||
|
side traps at the strict bool boundary — (or false (box "s")) and
|
||||||
|
(and (box nil) some-bool) both do. Each form used to be safe in
|
||||||
|
exactly one of those directions, because the sentinel it answered
|
||||||
|
was a bool literal that boxed to fit the real branch; neither is
|
||||||
|
now, and they are at least symmetric about it. (and (box nil)
|
||||||
|
some-bool) printing false is the one previously-compiling behaviour
|
||||||
|
this pass changed. Making a bool arm and a dyn arm join as dyn is a
|
||||||
|
check_if question and the author's call, not settled here.
|
||||||
- A bare keyword condition used to be checked with want:Bool from the
|
- A bare keyword condition used to be checked with want:Bool from the
|
||||||
start and refused by the keyword arm's enum-or-refuse case: ":kw is
|
start and refused by the keyword arm's enum-or-refuse case: ":kw is
|
||||||
an enum member where an enum is expected and a dyn keyword
|
an enum member where an enum is expected and a dyn keyword
|
||||||
|
|||||||
@ -3179,8 +3179,9 @@ and check_recur ctx ~tail loc args =
|
|||||||
sugar built out of [Ast.If] in parse.ml, so they get this for free
|
sugar built out of [Ast.If] in parse.ml, so they get this for free
|
||||||
without a separate case. [and] and [or] are sugar too, and both their
|
without a separate case. [and] and [or] are sugar too, and both their
|
||||||
tests and their answers get this for free the same way — see
|
tests and their answers get this for free the same way — see
|
||||||
[shortcircuit] in parse.ml for how [or] carries its deciding operand
|
[shortcircuit] in parse.ml, where each binds its test to a temp and
|
||||||
through, the same way [and] always has.)
|
answers that temp on the path it decides, so the deciding operand
|
||||||
|
itself comes back rather than a bare bool.)
|
||||||
|
|
||||||
A dyn scrutinee is tested for truthiness, Clojure's rule: nil and false
|
A dyn scrutinee is tested for truthiness, Clojure's rule: nil and false
|
||||||
are the only falsey values, and everything else — 0, "", an empty vec, an
|
are the only falsey values, and everything else — 0, "", an empty vec, an
|
||||||
|
|||||||
60
lib/parse.ml
60
lib/parse.ml
@ -883,35 +883,55 @@ and cond f (args : Form.t list) : Ast.expr =
|
|||||||
|
|
||||||
(* Every test here is an [if]'s condition, so a dyn operand is truthy-tested
|
(* Every test here is an [if]'s condition, so a dyn operand is truthy-tested
|
||||||
(check.ml's check_truthy) exactly the way a bare [if]'s is, for both
|
(check.ml's check_truthy) exactly the way a bare [if]'s is, for both
|
||||||
[and] and [or]. The *answer* used to be asymmetric between them: [and]'s
|
[and] and [or]. The *answer* is the operand that decided the form, which
|
||||||
"false" sentinel sits in the else arm, so check_if picks the real
|
is Clojure's rule and needs the operand a second time: [(or a b)] is
|
||||||
branch's type first and boxes "false" to match it, which lets [and] hand
|
[(let [t a] (if t t b))] and [(and a b)] is [(let [t a] (if t b t))].
|
||||||
back the actual last dyn value, Clojure-style. [or] used to put its
|
The temp is what makes that a single evaluation — writing the operand
|
||||||
"true" sentinel in the then arm instead — the one check_if types first —
|
itself into the arm, as [(if a a b)] would, evaluates it twice.
|
||||||
so that sentinel decided the whole expression's type, and a later
|
|
||||||
|
Both used to answer a bare bool sentinel on the deciding path instead.
|
||||||
|
[and]'s "false" sentinel sat in the else arm, so check_if typed the real
|
||||||
|
branch first and boxed the sentinel to match: an all-truthy [and] did
|
||||||
|
hand back its last operand, but a falsey one answered [false] where
|
||||||
|
Clojure answers the falsey operand itself — (and 1 nil) said false, not
|
||||||
|
nil. [or]'s "true" sentinel sat in the then arm, the one check_if types
|
||||||
|
first, so the sentinel decided the whole expression's type and a later
|
||||||
non-bool dyn answer hit the strict bool boundary instead of surviving as
|
non-bool dyn answer hit the strict bool boundary instead of surviving as
|
||||||
itself: (or nil "x") traps rather than answering "x", exactly the
|
itself: (or nil "x") trapped rather than answering "x", exactly the
|
||||||
canonical (or x default) idiom Clojure is reached for.
|
canonical (or x default) idiom Clojure is reached for.
|
||||||
|
|
||||||
[or] now binds its test to a temp and asks the temp itself, the way
|
The locs are the operand's own, not the whole form's, because the temp's
|
||||||
Clojure's own or expands: [(or a b)] is [(let [t a] (if t t b))], not
|
[Var] node is what lands in the [if] condition and check_truthy reports
|
||||||
[(if a true b)]. The temp is what lets the answer be [a] itself without
|
the condition's loc when a typed operand is not a bool. Pointing that at
|
||||||
writing [a] a second time as the then arm — [(if a a b)] would evaluate
|
[f.loc] would blame the enclosing (and ...) for whichever operand is
|
||||||
it twice, once for the test and again for the answer — and it is the
|
actually wrong.
|
||||||
temp's own type check_if sees first, so [or] hands back the actual
|
|
||||||
truthy value the same way [and] hands back its own. *)
|
What answering the operand costs, for both forms alike: the two arms are
|
||||||
|
now both real values, so mixing a dyn operand with a typed bool one makes
|
||||||
|
check_if unify them, and the then arm decides. A non-bool dyn value on
|
||||||
|
the losing side then meets the strict bool boundary at run time —
|
||||||
|
(or false (box "s")) and (and (box nil) some-bool) both trap, verified on
|
||||||
|
this tree. Each form used to be safe in exactly one of those directions,
|
||||||
|
because the sentinel it answered was a bool literal that boxed to fit
|
||||||
|
whatever the real branch was; neither is now, and they are at least
|
||||||
|
symmetric about it. Making bool and dyn arms join as dyn is a check_if
|
||||||
|
question, noted in FIX.org under item 7 and not decided here. *)
|
||||||
and shortcircuit f (args : Form.t list) ~is_and : Ast.expr =
|
and shortcircuit f (args : Form.t list) ~is_and : Ast.expr =
|
||||||
let mk e = { Ast.e; loc = f.loc } in
|
let mk e = { Ast.e; loc = f.loc } in
|
||||||
let rec go = function
|
let rec go = function
|
||||||
| [] -> mk (Ast.Var (if is_and then "true" else "false"))
|
| [] -> mk (Ast.Var (if is_and then "true" else "false"))
|
||||||
| [ last ] -> expr last
|
| [ last ] -> expr last
|
||||||
| x :: rest ->
|
| x :: rest ->
|
||||||
if is_and then mk (Ast.If (expr x, go rest, Some (mk (Ast.Var "false"))))
|
let ex = expr x in
|
||||||
else
|
let t = fresh_temp () in
|
||||||
let t = fresh_temp () in
|
let tvar = { Ast.e = Ast.Var t; loc = ex.Ast.loc } in
|
||||||
let tvar = mk (Ast.Var t) in
|
let bind = { Ast.bname = t; bty = None; bval = ex; bloc = ex.Ast.loc } in
|
||||||
let bind = { Ast.bname = t; bty = None; bval = expr x; bloc = f.loc } in
|
let rest = go rest in
|
||||||
mk (Ast.Let ([ bind ], [ mk (Ast.If (tvar, tvar, Some (go rest))) ]))
|
let body =
|
||||||
|
if is_and then mk (Ast.If (tvar, rest, Some tvar))
|
||||||
|
else mk (Ast.If (tvar, tvar, Some rest))
|
||||||
|
in
|
||||||
|
mk (Ast.Let ([ bind ], [ body ]))
|
||||||
in
|
in
|
||||||
go args
|
go args
|
||||||
|
|
||||||
|
|||||||
@ -7,18 +7,21 @@
|
|||||||
;;;; The rule reaches every form built out of [if] under the hood -- [when],
|
;;;; The rule reaches every form built out of [if] under the hood -- [when],
|
||||||
;;;; [cond], [and] and [or] all desugar to it in parse.ml -- so their *tests*
|
;;;; [cond], [and] and [or] all desugar to it in parse.ml -- so their *tests*
|
||||||
;;;; need no separate case in check.ml and get exercised below through their
|
;;;; need no separate case in check.ml and get exercised below through their
|
||||||
;;;; own syntax rather than by inspecting the desugaring. [and]'s *answer*
|
;;;; own syntax rather than by inspecting the desugaring. Their *answers*
|
||||||
;;;; carries a non-bool dyn value through too, Clojure-style, because its
|
;;;; are covered here too: both [and] and [or] hand back the operand that
|
||||||
;;;; short-circuit sentinel is the else arm and the real value's type wins.
|
;;;; decided them, Clojure-style, rather than a bare bool. Each used to
|
||||||
;;;; [or] used to put its sentinel in the then arm instead, so a non-bool
|
;;;; answer a sentinel on its deciding path instead -- [and]'s "false" sat
|
||||||
;;;; dyn answer hit the strict bool boundary and traps -- (or nil "x"), the
|
;;;; in the else arm, so an all-truthy [and] did carry its last dyn operand
|
||||||
;;;; canonical Clojure (or x default) idiom, used to crash. [or] now binds
|
;;;; through but a falsey one answered [false] where Clojure answers the
|
||||||
;;;; its test to a temp and answers the temp itself, Clojure's own
|
;;;; falsey operand itself, and [or]'s "true" sat in the then arm, the one
|
||||||
;;;; expansion, so its answer carries a non-bool dyn value through exactly
|
;;;; the checker types first, so a non-bool dyn answer hit the strict bool
|
||||||
;;;; the way [and]'s does; both are exercised below, including the case
|
;;;; boundary and trapped: (or nil "x"), the canonical Clojure (or x
|
||||||
;;;; that used to be excluded here for being unsafe. [not] and [while] are
|
;;;; default) idiom, used to crash. Both now bind the test to a temp and
|
||||||
;;;; not [if] in disguise, so check_truthy is called at their own sites by
|
;;;; answer the temp on the deciding path, Clojure's own expansion, which
|
||||||
;;;; hand, and get their own coverage too.
|
;;;; also evaluates each test exactly once -- pinned below, including the
|
||||||
|
;;;; case that used to be excluded here for being unsafe. [not] and [while]
|
||||||
|
;;;; are not [if] in disguise, so check_truthy is called at their own sites
|
||||||
|
;;;; by hand, and get their own coverage too.
|
||||||
;;;;
|
;;;;
|
||||||
;;;; A typed if keeps needing a strict bool -- that refusal, and its message,
|
;;;; A typed if keeps needing a strict bool -- that refusal, and its message,
|
||||||
;;;; is a checker test in test_flan.ml, not a row here, since a program that
|
;;;; is a checker test in test_flan.ml, not a row here, since a program that
|
||||||
@ -33,6 +36,11 @@
|
|||||||
|
|
||||||
(defn truthy? [x] dyn (if x "truthy" "falsey"))
|
(defn truthy? [x] dyn (if x "truthy" "falsey"))
|
||||||
|
|
||||||
|
;; Prints its tag, then answers its value unchanged. An operand written this
|
||||||
|
;; way leaves a mark when it is evaluated, which is how the short-circuit
|
||||||
|
;; rows below prove that a skipped operand really was skipped.
|
||||||
|
(defn loud [tag x] dyn (println tag) x)
|
||||||
|
|
||||||
(defn main [] i32
|
(defn main [] i32
|
||||||
;; nil and false: the only two falsey dyn values. Everything else Clojure
|
;; nil and false: the only two falsey dyn values. Everything else Clojure
|
||||||
;; calls truthy that C or Python would not: 0, "", an empty vec, an empty
|
;; calls truthy that C or Python would not: 0, "", an empty vec, an empty
|
||||||
@ -68,11 +76,15 @@
|
|||||||
;; and/or: also if in disguise, so each test along the chain is
|
;; and/or: also if in disguise, so each test along the chain is
|
||||||
;; truthy-tested the same way if's own is -- 0 and "" do not stop and,
|
;; truthy-tested the same way if's own is -- 0 and "" do not stop and,
|
||||||
;; only nil and false do; 0 does stop or, the way any truthy value does.
|
;; only nil and false do; 0 does stop or, the way any truthy value does.
|
||||||
;; Both hand back the actual operand that decided them, Clojure-style --
|
;; Both hand back the actual operand that decided them, Clojure-style:
|
||||||
;; and's answer is the last truthy operand itself (:kw here); or's is the
|
;; and's is the falsey one that stopped it, or the last operand if none
|
||||||
;; first truthy one (0, then "x") rather than a bare true.
|
;; did; or's is the first truthy one, or the last operand if none was.
|
||||||
(println (and (box 1) (box "") (box :kw)))
|
(println (and (box 1) (box "") (box :kw)))
|
||||||
(println (and (box 1) (box false) (box "unreached")))
|
(println (and (box 1) (box false) (box "unreached")))
|
||||||
|
;; The falsey operand itself, not a bare false: nil comes back as nil.
|
||||||
|
(println (and (box 1) (box nil) (box "unreached")))
|
||||||
|
;; 0 and "" are truthy, so neither stops and -- the last operand answers.
|
||||||
|
(println (and (box 0) (box "")))
|
||||||
(println (or (box 0) (box false)))
|
(println (or (box 0) (box false)))
|
||||||
(println (or (box nil) (box false)))
|
(println (or (box nil) (box false)))
|
||||||
;; The case excluded before the fix: a non-bool value stopping or and
|
;; The case excluded before the fix: a non-bool value stopping or and
|
||||||
@ -81,6 +93,31 @@
|
|||||||
(println (or (box nil) (box "x")))
|
(println (or (box nil) (box "x")))
|
||||||
(println (or (box 5) (box "unreached")))
|
(println (or (box 5) (box "unreached")))
|
||||||
|
|
||||||
|
;; One operand is that operand, whatever it is -- no test, no sentinel.
|
||||||
|
(println (and (box nil)))
|
||||||
|
(println (and (box "")))
|
||||||
|
(println (or (box nil)))
|
||||||
|
(println (or (box 0)))
|
||||||
|
;; No operands at all: the identity of each, and the one place a bare
|
||||||
|
;; sentinel is still the answer -- (and) is true, (or) is false.
|
||||||
|
(println (and))
|
||||||
|
(println (or))
|
||||||
|
|
||||||
|
;; Short-circuit proof. [loud] prints its tag before answering, so an
|
||||||
|
;; operand that is never evaluated is an absent line, not a wrong value.
|
||||||
|
;; and stops at the first falsey operand and or at the first truthy one,
|
||||||
|
;; so the "unreached" tags below must never appear.
|
||||||
|
(println (and (box nil) (loud "and-unreached" 1)))
|
||||||
|
(println (and (loud "and-reached" 1) (box 2)))
|
||||||
|
(println (or (box 7) (loud "or-unreached" 1)))
|
||||||
|
(println (or (box nil) (loud "or-reached" 1)))
|
||||||
|
;; The test is evaluated once, not twice. Here the loud operand is the one
|
||||||
|
;; that decides the form, so it is both the test and the answer -- and it
|
||||||
|
;; still prints exactly one line, which is what the temp binding buys over
|
||||||
|
;; writing the operand into the arm a second time.
|
||||||
|
(println (and (loud "and-decider" nil) (box 3)))
|
||||||
|
(println (or (loud "or-decider" 9) (box 4)))
|
||||||
|
|
||||||
;; not: truthiness, negated -- true only for nil and false.
|
;; not: truthiness, negated -- true only for nil and false.
|
||||||
(println (not (box nil)))
|
(println (not (box nil)))
|
||||||
(println (not (box false)))
|
(println (not (box false)))
|
||||||
|
|||||||
@ -1256,16 +1256,30 @@ let () =
|
|||||||
along for free because their tests desugar to [if] in parse.ml;
|
along for free because their tests desugar to [if] in parse.ml;
|
||||||
[not] and [while] get the same treatment by hand in check.ml's
|
[not] and [while] get the same treatment by hand in check.ml's
|
||||||
check_truthy. [and] and [or] both hand back the actual operand that
|
check_truthy. [and] and [or] both hand back the actual operand that
|
||||||
decided them, Clojure-style -- [or] used to answer a bare bool
|
decided them, Clojure-style. Each used to answer a bare bool on its
|
||||||
instead, its short-circuit sentinel sitting in the arm check_if
|
deciding path instead: [and]'s "false" sentinel sat in the else arm,
|
||||||
types first, and a non-bool dyn value reaching that position (the
|
so an all-truthy [and] carried its last dyn operand through but a
|
||||||
canonical (or x default) idiom) used to trap rather than survive as
|
falsey one answered [false] rather than the falsey operand, and
|
||||||
itself; [or] now binds its test to a temp and answers the temp,
|
[or]'s "true" sentinel sat in the then arm, the one check_if types
|
||||||
fixing that. *)
|
first, so a non-bool dyn value reaching that position (the canonical
|
||||||
|
(or x default) idiom) trapped rather than surviving as itself. Both
|
||||||
|
now bind the test to a temp and answer the temp.
|
||||||
|
|
||||||
|
The empty lines in the middle are answers, not spacing: "" is a
|
||||||
|
truthy dyn value that [and] can hand back, and println prints it as
|
||||||
|
an empty line. The "loud" rows print a tag when an operand is
|
||||||
|
evaluated, so the tags that are absent here -- and-unreached,
|
||||||
|
or-unreached -- are the short-circuit proof, and each tag that is
|
||||||
|
present appears exactly once, which is the test-evaluated-once
|
||||||
|
proof. *)
|
||||||
let dyn_if_truthy_out =
|
let dyn_if_truthy_out =
|
||||||
"falsey\nfalsey\ntruthy\ntruthy\ntruthy\ntruthy\ntruthy\ntruthy\ntruthy\n\
|
"falsey\nfalsey\ntruthy\ntruthy\ntruthy\ntruthy\ntruthy\ntruthy\ntruthy\n\
|
||||||
truthy\ntruthy\ntruthy\nwhen 0 ran\nwhen empty-string ran\nb\nb\n:kw\n\
|
truthy\ntruthy\ntruthy\nwhen 0 ran\nwhen empty-string ran\nb\nb\n\
|
||||||
false\n0\nfalse\nx\n5\ntrue\ntrue\nfalse\nfalse\nfalse\n3\n2\n1\n"
|
:kw\nfalse\nnil\n\n0\nfalse\nx\n5\n\
|
||||||
|
nil\n\nnil\n0\ntrue\nfalse\n\
|
||||||
|
nil\nand-reached\n2\n7\nor-reached\n1\n\
|
||||||
|
and-decider\nnil\nor-decider\n9\n\
|
||||||
|
true\ntrue\nfalse\nfalse\nfalse\n3\n2\n1\n"
|
||||||
in
|
in
|
||||||
outputs "dyn if truthiness" "programs/dyn-if-truthy.flan" dyn_if_truthy_out;
|
outputs "dyn if truthiness" "programs/dyn-if-truthy.flan" dyn_if_truthy_out;
|
||||||
outputs ~opt:"-O0" "dyn if truthiness, -O0" "programs/dyn-if-truthy.flan"
|
outputs ~opt:"-O0" "dyn if truthiness, -O0" "programs/dyn-if-truthy.flan"
|
||||||
|
|||||||
@ -350,17 +350,30 @@ let () =
|
|||||||
| If (_, _, Some { e = If (_, _, Some { e = Int 3L; _ }); _ }) -> ()
|
| If (_, _, Some { e = If (_, _, Some { e = Int 3L; _ }); _ }) -> ()
|
||||||
| _ -> check "cond -> nested if with :else last" false);
|
| _ -> check "cond -> nested if with :else last" false);
|
||||||
|
|
||||||
(* and/or short-circuit, so they must not become calls *)
|
(* and/or short-circuit, so they must not become calls. Both bind the test
|
||||||
|
to a temp and answer the temp on the deciding path -- Clojure's own
|
||||||
|
expansion, (let [t a] (if t b t)) for and and (let [t a] (if t t b))
|
||||||
|
for or -- which is what hands back the actual deciding operand rather
|
||||||
|
than a bare bool, and what evaluates the test exactly once (M2 queue
|
||||||
|
item 7 and its review pass).
|
||||||
|
|
||||||
|
The bound name, the bound value and the arm are all pinned, not just
|
||||||
|
the shape: a desugaring that dropped the temp and wrote the operand
|
||||||
|
into the arm twice, (if a b a), would still match a pattern that left
|
||||||
|
the binding as [_]. *)
|
||||||
(match (parse1 "(and a b)").e with
|
(match (parse1 "(and a b)").e with
|
||||||
| If (_, _, Some { e = Var "false"; _ }) -> ()
|
| Let ([ { bname; bval = { e = Var "a"; _ }; _ } ],
|
||||||
|
[ { e = If ({ e = Var t1; _ },
|
||||||
|
{ e = Var "b"; _ },
|
||||||
|
Some { e = Var t2; _ }); _ } ])
|
||||||
|
when bname = t1 && t1 = t2 -> ()
|
||||||
| _ -> check "and short-circuits" false);
|
| _ -> check "and short-circuits" false);
|
||||||
(* or binds its test to a temp and answers the temp itself -- Clojure's
|
|
||||||
own expansion, and what lets or hand back the actual truthy operand
|
|
||||||
rather than a bare true (M2 queue item 7's review pass). *)
|
|
||||||
(match (parse1 "(or a b)").e with
|
(match (parse1 "(or a b)").e with
|
||||||
| Let ([ _ ],
|
| Let ([ { bname; bval = { e = Var "a"; _ }; _ } ],
|
||||||
[ { e = If ({ e = Var t1; _ }, { e = Var t2; _ }, Some _); _ } ])
|
[ { e = If ({ e = Var t1; _ },
|
||||||
when t1 = t2 -> ()
|
{ e = Var t2; _ },
|
||||||
|
Some { e = Var "b"; _ }); _ } ])
|
||||||
|
when bname = t1 && t1 = t2 -> ()
|
||||||
| _ -> check "or short-circuits" false);
|
| _ -> check "or short-circuits" false);
|
||||||
|
|
||||||
(* ── Forms that bind or alter control are never calls ──────────── *)
|
(* ── Forms that bind or alter control are never calls ──────────── *)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user